Skip to content
This repository was archived by the owner on Jul 26, 2025. It is now read-only.

Commit ffca331

Browse files
feat: add multiply/divide functions (#459)
* feat: add multiply and divide functions * feat: add divide function * test: add testing cases * test: add tests close: #457 * refactor: change phrasing of tests and error * fix: resolve conversations * fix: default number of channels takes alpha into account * test: refactor testing cases * test: add testing of out and channels options * feat: add functions as image methods * docs: add comments about methods * docs: minor fixes * refactor: move divide and multiply functions to compare folder * fix: add exports to pass typedoc errors * refactor: refactor interfaces * docs: fix typo
1 parent f3ca64c commit ffca331

File tree

6 files changed

+258
-1
lines changed

6 files changed

+258
-1
lines changed

src/Image.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { RgbColor } from 'colord';
22

33
import { Mask } from './Mask';
44
import { add, subtract, SubtractImageOptions } from './compare';
5+
import { divide, DivideOptions } from './compare/divide';
6+
import { multiply, MultiplyOptions } from './compare/multiply';
57
import { median } from './compute';
68
import { variance } from './compute/variance';
79
import { correctColor } from './correctColor';
@@ -679,7 +681,24 @@ export class Image {
679681
public add(other: Image): Image {
680682
return add(this, other);
681683
}
682-
684+
/**
685+
* Multiply image pixels by a constant.
686+
* @param value - Value which pixels will be multiplied to.
687+
* @param options - Multiply options.
688+
* @returns Multiplied image.
689+
*/
690+
public multiply(value: number, options: MultiplyOptions = {}): Image {
691+
return multiply(this, value, options);
692+
}
693+
/**
694+
* Divide image pixels by a constant.
695+
* @param value - Value which pixels will be divided to.
696+
* @param options - Divide options.
697+
* @returns Divided image.
698+
*/
699+
public divide(value: number, options: DivideOptions = {}): Image {
700+
return divide(this, value, options);
701+
}
683702
// COMPUTE
684703

685704
public histogram(options?: HistogramOptions): Uint32Array {

src/compare/__tests__/divide.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Image } from '../../Image';
2+
import { divide } from '../divide';
3+
4+
test('divide by 2', () => {
5+
let image = testUtils.createRgbaImage([
6+
[230, 80, 120, 255],
7+
[100, 140, 13, 1],
8+
]);
9+
image = divide(image, 2);
10+
const result = testUtils.createRgbaImage([
11+
[115, 40, 60, 127],
12+
[50, 70, 6, 0],
13+
]);
14+
expect(image).toStrictEqual(result);
15+
});
16+
17+
test('error when dividing by 0', () => {
18+
const image = testUtils.createRgbaImage([
19+
[230, 80, 120, 255],
20+
[100, 140, 13, 1],
21+
]);
22+
expect(() => {
23+
divide(image, 0);
24+
}).toThrow('Cannot divide by 0');
25+
});
26+
test('divide by decimal', () => {
27+
let image = testUtils.createRgbaImage([
28+
[230, 80, 120, 255],
29+
[100, 140, 13, 4],
30+
]);
31+
image = image.divide(0.25);
32+
const result = testUtils.createRgbaImage([
33+
[255, 255, 255, 255],
34+
[255, 255, 52, 16],
35+
]);
36+
expect(image).toStrictEqual(result);
37+
});
38+
test('divide by prime number', () => {
39+
let image = testUtils.createRgbaImage([
40+
[230, 80, 120, 255],
41+
[100, 140, 13, 1],
42+
]);
43+
image = image.divide(7);
44+
const result = testUtils.createRgbaImage([
45+
[32, 11, 17, 36],
46+
[14, 20, 1, 0],
47+
]);
48+
expect(image).toStrictEqual(result);
49+
});
50+
test('testing channels option', () => {
51+
let image = testUtils.createRgbaImage([
52+
[230, 80, 120, 255],
53+
[100, 140, 13, 1],
54+
]);
55+
image = image.divide(7, { channels: [0, 1, 3] });
56+
const result = testUtils.createRgbaImage([
57+
[32, 11, 120, 36],
58+
[14, 20, 13, 0],
59+
]);
60+
expect(image).toStrictEqual(result);
61+
});
62+
test('testing out option', () => {
63+
const image = testUtils.createRgbaImage([
64+
[230, 80, 120, 255],
65+
[100, 140, 13, 1],
66+
]);
67+
const out = new Image(image.width, image.height, { colorModel: 'RGBA' });
68+
image.divide(7, { out });
69+
const result = testUtils.createRgbaImage([
70+
[32, 11, 17, 36],
71+
[14, 20, 1, 0],
72+
]);
73+
expect(out).toStrictEqual(result);
74+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Image } from '../../Image';
2+
3+
test('multiply by 2', () => {
4+
let image = testUtils.createRgbaImage([
5+
[230, 80, 120, 255],
6+
[100, 140, 13, 1],
7+
]);
8+
image = image.multiply(2);
9+
const result = testUtils.createRgbaImage([
10+
[255, 160, 240, 255],
11+
[200, 255, 26, 2],
12+
]);
13+
expect(image).toStrictEqual(result);
14+
});
15+
16+
test('mulitply by 100', () => {
17+
let image = testUtils.createRgbaImage([
18+
[230, 80, 120, 255],
19+
[100, 140, 13, 1],
20+
]);
21+
image = image.multiply(100);
22+
const result = testUtils.createRgbaImage([
23+
[255, 255, 255, 255],
24+
[255, 255, 255, 100],
25+
]);
26+
expect(image).toStrictEqual(result);
27+
});
28+
test('multiply by decimal', () => {
29+
let image = testUtils.createRgbaImage([
30+
[230, 80, 120, 255],
31+
[100, 140, 13, 1],
32+
]);
33+
image = image.multiply(0.5);
34+
const result = testUtils.createRgbaImage([
35+
[115, 40, 60, 127],
36+
[50, 70, 6, 0],
37+
]);
38+
expect(image).toStrictEqual(result);
39+
});
40+
test('testing channels option', () => {
41+
let image = testUtils.createRgbaImage([
42+
[230, 80, 120, 255],
43+
[100, 140, 13, 1],
44+
]);
45+
image = image.multiply(0.5, { channels: [0, 1] });
46+
const result = testUtils.createRgbaImage([
47+
[115, 40, 120, 255],
48+
[50, 70, 13, 1],
49+
]);
50+
expect(image).toStrictEqual(result);
51+
});
52+
test('testing out option', () => {
53+
let image = testUtils.createRgbaImage([
54+
[230, 80, 120, 255],
55+
[100, 140, 13, 1],
56+
]);
57+
const out = new Image(image.width, image.height, { colorModel: 'RGBA' });
58+
image = image.multiply(0.5, { channels: [0, 1], out });
59+
const result = testUtils.createRgbaImage([
60+
[115, 40, 120, 255],
61+
[50, 70, 13, 1],
62+
]);
63+
expect(out).toStrictEqual(result);
64+
});

src/compare/divide.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Image } from '../Image';
2+
import { getOutputImage } from '../utils/getOutputImage';
3+
import { validateChannels } from '../utils/validators/validators';
4+
5+
export interface DivideOptions {
6+
/**
7+
* Channels where value will be divided.
8+
* @default all channels
9+
*/
10+
channels?: number[];
11+
/**
12+
* Image to which the resulting image has to be put.
13+
*/
14+
out?: Image;
15+
}
16+
17+
/**
18+
*
19+
* Divides image pixels by a certain value.
20+
* @param image - image to which division will be applied.
21+
* @param value - Value by which each pixel will be divided.
22+
* @param options - Divide options
23+
* @returns image.
24+
*/
25+
export function divide(
26+
image: Image,
27+
value: number,
28+
options: DivideOptions = {},
29+
) {
30+
const {
31+
channels = new Array(image.channels).fill(0).map((value, index) => index),
32+
} = options;
33+
validateChannels(channels, image);
34+
if (value === 0) {
35+
throw new TypeError(`Cannot divide by 0`);
36+
}
37+
const newImage = getOutputImage(image, options, { clone: true });
38+
if (channels.length === 0) {
39+
return newImage;
40+
}
41+
for (const channel of channels) {
42+
for (let row = 0; row < newImage.height; row++) {
43+
for (let column = 0; column < newImage.width; column++) {
44+
const newIntensity = newImage.getValue(column, row, channel) / value;
45+
newImage.setClampedValue(column, row, channel, newIntensity);
46+
}
47+
}
48+
}
49+
return newImage;
50+
}

src/compare/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export * from './computeRmse';
44
export * from './computeSsim';
55
export * from './subtract';
66
export * from './add';
7+
export * from './divide';
8+
export * from './multiply';

src/compare/multiply.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Image } from '../Image';
2+
import { getOutputImage } from '../utils/getOutputImage';
3+
import { validateChannels } from '../utils/validators/validators';
4+
5+
export interface MultiplyOptions {
6+
/**
7+
* Channels where value will be multiplied.
8+
* @default all channels
9+
*/
10+
channels?: number[];
11+
/**
12+
* Image to which the resulting image has to be put.
13+
*/
14+
out?: Image;
15+
}
16+
17+
/**
18+
*
19+
* Multiplies points by a certain value.
20+
* @param image - image to which multiplication will be applied.
21+
* @param value - Value by which each pixel will be multiplied.
22+
* @param options - Multiply options
23+
* @returns image.
24+
*/
25+
export function multiply(
26+
image: Image,
27+
value: number,
28+
options: MultiplyOptions = {},
29+
) {
30+
const {
31+
channels = new Array(image.channels).fill(0).map((value, index) => index),
32+
} = options;
33+
validateChannels(channels, image);
34+
35+
const newImage = getOutputImage(image, options, { clone: true });
36+
if (channels.length === 0) {
37+
return newImage;
38+
}
39+
for (const channel of channels) {
40+
for (let row = 0; row < newImage.height; row++) {
41+
for (let column = 0; column < newImage.width; column++) {
42+
const newIntensity = newImage.getValue(column, row, channel) * value;
43+
newImage.setClampedValue(column, row, channel, newIntensity);
44+
}
45+
}
46+
}
47+
return newImage;
48+
}

0 commit comments

Comments
 (0)