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

Commit 0952643

Browse files
committed
feat(level): parameters can now be arrays
Closes: #307
1 parent 3b92425 commit 0952643

File tree

2 files changed

+95
-17
lines changed

2 files changed

+95
-17
lines changed

src/filters/__tests__/level.test.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,42 @@ test('3x1 rgba image, custom channels', () => {
7777
[30, 40, 50, 50],
7878
[60, 70, 80, 50],
7979
]);
80-
expect(
81-
image.level({
82-
inputMin: 10,
83-
inputMax: 60,
84-
outputMin: 0,
85-
outputMax: 100,
86-
channels: [1],
87-
}),
88-
).toMatchImageData([
80+
81+
const result = image.level({
82+
inputMin: 10,
83+
inputMax: 60,
84+
outputMin: 0,
85+
outputMax: 100,
86+
channels: [1],
87+
});
88+
expect(result).toMatchImageData([
8989
[0, 0, 20, 50],
9090
[30, 60, 50, 50],
9191
[60, 100, 80, 50],
9292
]);
9393
});
9494

95+
test('3x1 rgba image, other custom channels', () => {
96+
const image = testUtils.createRgbaImage([
97+
[0, 10, 20, 50],
98+
[30, 40, 50, 50],
99+
[60, 70, 80, 50],
100+
]);
101+
102+
const result = image.level({
103+
inputMin: 10,
104+
inputMax: 60,
105+
outputMin: 0,
106+
outputMax: 100,
107+
channels: [0, 3],
108+
});
109+
expect(result).toMatchImageData([
110+
[0, 10, 20, 80],
111+
[40, 40, 50, 80],
112+
[100, 70, 80, 80],
113+
]);
114+
});
115+
95116
test('3x1 rgba image, modify alpha', () => {
96117
const image = testUtils.createRgbaImage([
97118
[0, 10, 20, 50],
@@ -112,3 +133,24 @@ test('3x1 rgba image, modify alpha', () => {
112133
[100, 100, 100, 80],
113134
]);
114135
});
136+
137+
test('3x1 rgba image, arrays as input', () => {
138+
const image = testUtils.createRgbaImage([
139+
[0, 10, 20, 50],
140+
[30, 40, 50, 50],
141+
[60, 70, 80, 50],
142+
]);
143+
144+
const result = image.level({
145+
inputMin: [0, 0, 0, 0],
146+
inputMax: [50, 50, 100, 100],
147+
outputMin: 0,
148+
outputMax: 100,
149+
});
150+
151+
expect(result).toMatchImageData([
152+
[0, 20, 20, 50],
153+
[60, 80, 50, 50],
154+
[100, 100, 80, 50],
155+
]);
156+
});

src/filters/level.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,31 @@ export interface LevelOptions {
1717
*
1818
* @default 0
1919
*/
20-
inputMin?: number;
20+
inputMin?: number | number[];
2121
/**
2222
* Maximal input value.
2323
*
2424
* @default image.maxValue
2525
*/
26-
inputMax?: number;
26+
inputMax?: number | number[];
2727
/**
2828
* Minimal output value.
2929
*
3030
* @default 0
3131
*/
32-
outputMin?: number;
32+
outputMin?: number | number[];
3333
/**
3434
* Maximal output value.
3535
*
3636
* @default image.maxValue
3737
*/
38-
outputMax?: number;
38+
outputMax?: number | number[];
3939
/**
4040
* Specifies the shape of the curve connecting the two points.
4141
*
4242
* @default 1
4343
*/
44-
gamma?: number;
44+
gamma?: number | number[];
4545
/**
4646
* Image to which to output.
4747
*/
@@ -75,17 +75,31 @@ export function level(image: Image, options: LevelOptions = {}) {
7575

7676
const clamp = getClamp(image);
7777

78+
inputMin = getValueArray(inputMin, image.channels);
79+
inputMax = getValueArray(inputMax, image.channels);
80+
outputMin = getValueArray(outputMin, image.channels);
81+
outputMax = getValueArray(outputMax, image.channels);
82+
gamma = getValueArray(gamma, image.channels);
83+
7884
for (let row = 0; row < image.height; row++) {
7985
for (let column = 0; column < image.width; column++) {
8086
for (let channel of channels) {
8187
let currentValue = image.getValue(column, row, channel);
8288

83-
let clamped = Math.max(Math.min(currentValue, inputMax), inputMin);
89+
let clamped = Math.max(
90+
Math.min(currentValue, inputMax[channel]),
91+
inputMin[channel],
92+
);
8493

85-
const ratio = clamp((clamped - inputMin) / (inputMax - inputMin));
94+
const ratio = clamp(
95+
(clamped - inputMin[channel]) /
96+
(inputMax[channel] - inputMin[channel]),
97+
);
8698

8799
const result = clamp(
88-
ratio ** (1 / gamma) * (outputMax - outputMin) + outputMin,
100+
ratio ** (1 / gamma[channel]) *
101+
(outputMax[channel] - outputMin[channel]) +
102+
outputMin[channel],
89103
);
90104

91105
newImage.setValue(column, row, channel, result);
@@ -94,3 +108,25 @@ export function level(image: Image, options: LevelOptions = {}) {
94108
}
95109
return newImage;
96110
}
111+
112+
/**
113+
* Get an array with correct values for each channel to process.
114+
*
115+
* @param value - Number or array to transform to the final array.
116+
* @param imageChannels - Number of channels processed in the level function.
117+
* @returns Array of values for each channel.
118+
*/
119+
function getValueArray(
120+
value: number | number[],
121+
imageChannels: number,
122+
): number[] {
123+
if (Array.isArray(value)) {
124+
if (value.length === imageChannels) {
125+
return value;
126+
} else {
127+
throw new Error('array length is not compatible with channel option');
128+
}
129+
} else {
130+
return new Array(imageChannels).fill(value);
131+
}
132+
}

0 commit comments

Comments
 (0)