Skip to content

Commit e16c19e

Browse files
authored
Add monochrome and duotone options (#41)
* Add monochrome and duotone options * Add changesets and mark options PRO
1 parent 1aca7d7 commit e16c19e

File tree

9 files changed

+221
-0
lines changed

9 files changed

+221
-0
lines changed

.changeset/hot-owls-pick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@imgproxy/imgproxy-js-core": minor
3+
---
4+
5+
Add [monochrome](https://docs.imgproxy.net/usage/processing#monochrome) and [duotone](https://docs.imgproxy.net/usage/processing#duotone) options

src/options/duotone.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Duotone, DuotoneOptionsPartial } from "../types/duotone";
2+
import { guardIsNotNum, guardIsUndef } from "../utils";
3+
4+
const getOpt = (options: DuotoneOptionsPartial): Duotone | undefined =>
5+
options.duotone ?? options.dt;
6+
7+
const test = (options: DuotoneOptionsPartial): boolean => {
8+
return Boolean(getOpt(options));
9+
};
10+
11+
const build = (options: DuotoneOptionsPartial): string => {
12+
const duotoneOpt = getOpt(options);
13+
14+
guardIsUndef(duotoneOpt, "duotone");
15+
16+
const { intensity, color1 = "", color2 = "" } = duotoneOpt;
17+
18+
guardIsNotNum(intensity, "duotone.intensity", {
19+
addParam: {
20+
min: 0,
21+
max: 1,
22+
},
23+
});
24+
25+
if (color2) {
26+
return `dt:${intensity}:${color1}:${color2}`;
27+
} else if (color1) {
28+
return `dt:${intensity}:${color1}`;
29+
} else {
30+
return `dt:${intensity}`;
31+
}
32+
};
33+
34+
export { test, build };

src/options/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * as cacheBuster from "../optionsShared/cacheBuster";
1010
export * as contrast from "./contrast";
1111
export * as crop from "../optionsShared/crop";
1212
export * as disableAnimation from "./disableAnimation";
13+
export * as duotone from "./duotone";
1314
export * as dpi from "./dpi";
1415
export * as dpr from "./dpr";
1516
export * as drawDetections from "./drawDetections";
@@ -35,6 +36,7 @@ export * as maxSrcFileSize from "../optionsShared/maxSrcFileSize";
3536
export * as maxSrcResolution from "../optionsShared/maxSrcResolution";
3637
export * as minHeight from "./minHeight";
3738
export * as minWidth from "./minWidth";
39+
export * as monochrome from "./monochrome";
3840
export * as padding from "./padding";
3941
export * as page from "../optionsShared/page";
4042
export * as pages from "./pages";

src/options/monochrome.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Monochrome, MonochromeOptionsPartial } from "../types/monochrome";
2+
import { guardIsNotNum, guardIsUndef } from "../utils";
3+
4+
const getOpt = (options: MonochromeOptionsPartial): Monochrome | undefined =>
5+
options.monochrome ?? options.mc;
6+
7+
const test = (options: MonochromeOptionsPartial): boolean => {
8+
return Boolean(getOpt(options));
9+
};
10+
11+
const build = (options: MonochromeOptionsPartial): string => {
12+
const monochromeOpt = getOpt(options);
13+
14+
guardIsUndef(monochromeOpt, "monochrome");
15+
16+
const { intensity, color } = monochromeOpt;
17+
18+
guardIsNotNum(intensity, "monochrome.intensity", {
19+
addParam: {
20+
min: 0,
21+
max: 1,
22+
},
23+
});
24+
25+
if (color) {
26+
return `mc:${intensity}:${color}`;
27+
}
28+
29+
return `mc:${intensity}`;
30+
};
31+
32+
export { test, build };

src/types/duotone.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* *Duotone option*. **PRO feature**
3+
*
4+
* When intensity is greater than zero, imgproxy will convert the resulting image to duotone.
5+
*/
6+
export interface Duotone {
7+
/** Floating-point number between 0-1 defining effect strength */
8+
intensity: number;
9+
/** Hex color for dark areas */
10+
color1?: string;
11+
/** Hex color for light areas */
12+
color2?: string;
13+
}
14+
15+
export interface DuotoneOptionsPartial {
16+
duotone?: Duotone;
17+
dt?: Duotone;
18+
}

src/types/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { CacheBusterOptionsPartial } from "../typesShared/cacheBuster";
1010
import type { ContrastOptionsPartial } from "./contrast";
1111
import type { CropOptionsPartial } from "../typesShared/crop";
1212
import type { DisableAnimationOptionsPartial } from "./disableAnimation";
13+
import type { DuotoneOptionsPartial } from "./duotone";
1314
import type { DPIOptionsPartial } from "./dpi";
1415
import type { DPROptionsPartial } from "./dpr";
1516
import type { DrawDetectionsOptionsPartial } from "./drawDetections";
@@ -34,6 +35,7 @@ import type { MaxSrcFileSizeOptionsPartial } from "../typesShared/maxSrcFileSize
3435
import type { MaxSrcResolutionOptionsPartial } from "../typesShared/maxSrcResolution";
3536
import type { MinHeightOptionsPartial } from "./minHeight";
3637
import type { MinWidthOptionsPartial } from "./minWidth";
38+
import type { MonochromeOptionsPartial } from "./monochrome";
3739
import type { ObjectsPositionOptionsPartial } from "./objectsPosition";
3840
import type { PaddingOptionsPartial } from "./padding";
3941
import type { PageOptionsPartial } from "../typesShared/page";
@@ -82,6 +84,7 @@ export type Options = AdjustOptionsPartial &
8284
ContrastOptionsPartial &
8385
CropOptionsPartial &
8486
DisableAnimationOptionsPartial &
87+
DuotoneOptionsPartial &
8588
DPIOptionsPartial &
8689
DPROptionsPartial &
8790
DrawDetectionsOptionsPartial &
@@ -106,6 +109,7 @@ export type Options = AdjustOptionsPartial &
106109
MaxSrcResolutionOptionsPartial &
107110
MinHeightOptionsPartial &
108111
MinWidthOptionsPartial &
112+
MonochromeOptionsPartial &
109113
ObjectsPositionOptionsPartial &
110114
PaddingOptionsPartial &
111115
PageOptionsPartial &

src/types/monochrome.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* *Monochrome option*. **PRO feature**
3+
*
4+
* When `intensity` is greater than zero, imgproxy will convert the resulting image to monochrome.
5+
*/
6+
export interface Monochrome {
7+
/** Floating-point number between 0-1 defining effect strength */
8+
intensity: number;
9+
/** Optional hex color for monochrome palette base */
10+
color?: string;
11+
}
12+
13+
export interface MonochromeOptionsPartial {
14+
monochrome?: Monochrome;
15+
mc?: Monochrome;
16+
}

tests/optionsBasic/duotone.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { it, expect, describe, assertType } from "vitest";
2+
import { test, build } from "../../src/options/duotone";
3+
import { Options } from "../../src/types";
4+
5+
describe("duotone", () => {
6+
describe("test", () => {
7+
it("should test true if duotone is set", () => {
8+
expect(test({ duotone: { intensity: 1 } })).toBe(true);
9+
});
10+
11+
it("should test true if shorthand is set", () => {
12+
expect(test({ dt: { intensity: 1 } })).toBe(true);
13+
});
14+
15+
it("should test false if duotone is false", () => {
16+
expect(test({})).toBe(false);
17+
});
18+
});
19+
20+
describe("build", () => {
21+
it("should work with various parameters", () => {
22+
expect(build({ duotone: { intensity: 0 } })).toBe("dt:0");
23+
expect(build({ duotone: { intensity: 1 } })).toBe("dt:1");
24+
expect(build({ duotone: { intensity: 0.4 } })).toBe("dt:0.4");
25+
expect(build({ duotone: { intensity: 0.4, color1: "b3b3b3" } })).toBe(
26+
"dt:0.4:b3b3b3"
27+
);
28+
expect(build({ duotone: { intensity: 0.4, color2: "b3b3b3" } })).toBe(
29+
"dt:0.4::b3b3b3"
30+
);
31+
expect(
32+
build({
33+
duotone: { intensity: 0.4, color1: "000000", color2: "b3b3b3" },
34+
})
35+
).toBe("dt:0.4:000000:b3b3b3");
36+
});
37+
38+
it("should validate intensity", () => {
39+
expect(() => build({ duotone: { intensity: -1 } })).toThrow();
40+
expect(() => build({ duotone: { intensity: 2 } })).toThrow();
41+
});
42+
});
43+
44+
describe("Check types", () => {
45+
it("check TS type declaration", () => {
46+
assertType<Options>({
47+
duotone: {
48+
intensity: 0.5,
49+
color1: "000000",
50+
color2: "000000",
51+
},
52+
dt: {
53+
intensity: 0.5,
54+
color1: "000000",
55+
color2: "000000",
56+
},
57+
});
58+
});
59+
});
60+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { it, expect, describe, assertType } from "vitest";
2+
import { test, build } from "../../src/options/monochrome";
3+
import { Options } from "../../src/types";
4+
5+
describe("monochrome", () => {
6+
describe("test", () => {
7+
it("should test true if monochrome is set", () => {
8+
expect(test({ monochrome: { intensity: 1 } })).toBe(true);
9+
});
10+
11+
it("should test true if mc is set", () => {
12+
expect(test({ mc: { intensity: 1 } })).toBe(true);
13+
});
14+
15+
it("should test false if monochrome is false", () => {
16+
expect(test({})).toBe(false);
17+
});
18+
});
19+
20+
describe("build", () => {
21+
it("should work with various parameters", () => {
22+
expect(build({ monochrome: { intensity: 0 } })).toBe("mc:0");
23+
expect(build({ monochrome: { intensity: 1 } })).toBe("mc:1");
24+
expect(build({ monochrome: { intensity: 0.4 } })).toBe("mc:0.4");
25+
expect(build({ monochrome: { intensity: 0.4, color: "b3b3b3" } })).toBe(
26+
"mc:0.4:b3b3b3"
27+
);
28+
});
29+
30+
it("should validate intensity", () => {
31+
expect(() => build({ monochrome: { intensity: -1 } })).toThrow();
32+
expect(() => build({ monochrome: { intensity: 2 } })).toThrow();
33+
});
34+
});
35+
36+
describe("Check types", () => {
37+
it("check TS type declaration", () => {
38+
assertType<Options>({
39+
monochrome: {
40+
intensity: 0.5,
41+
color: "000000",
42+
},
43+
mc: {
44+
intensity: 0.5,
45+
color: "000000",
46+
},
47+
});
48+
});
49+
});
50+
});

0 commit comments

Comments
 (0)