diff --git a/.changeset/small-views-battle.md b/.changeset/small-views-battle.md new file mode 100644 index 00000000..337a8fdb --- /dev/null +++ b/.changeset/small-views-battle.md @@ -0,0 +1,5 @@ +--- +"@imgproxy/imgproxy-js-core": minor +--- + +Add [avifo](https://docs.imgproxy.net/latest/usage/processing#avif-options) option support diff --git a/src/options/avifOptions.ts b/src/options/avifOptions.ts new file mode 100644 index 00000000..b464642b --- /dev/null +++ b/src/options/avifOptions.ts @@ -0,0 +1,32 @@ +import type { AvifOptions, AvifOptionsPartial } from "../types/avifOptions"; +import { guardIsUndef, guardIsValidVal } from "../utils"; + +const correctOptions = { + auto: true, + on: true, + off: true, +}; + +const getOpt = (options: AvifOptionsPartial): AvifOptions | undefined => + options.avif_options || options.avifo; + +const test = (options: AvifOptionsPartial): boolean => Boolean(getOpt(options)); + +const build = (options: AvifOptionsPartial): string => { + const avifOptions = getOpt(options); + let subsample: string; + + guardIsUndef(avifOptions, "avif_options"); + + if (typeof avifOptions === "string") { + subsample = avifOptions; + } else { + subsample = avifOptions.subsample; + } + + guardIsValidVal(correctOptions, subsample, "avif_options"); + + return `avifo:${subsample}`; +}; + +export { test, build }; diff --git a/src/options/index.ts b/src/options/index.ts index 08a16a31..e33f036e 100644 --- a/src/options/index.ts +++ b/src/options/index.ts @@ -1,6 +1,7 @@ export * as adjust from "./adjust"; export * as autoquality from "./autoquality"; export * as autoRotate from "./autoRotate"; +export * as avifOptions from "./avifOptions"; export * as background from "./background"; export * as backgroundAlpha from "./backgroundAlpha"; export * as blur from "./blur"; diff --git a/src/types/avifOptions.ts b/src/types/avifOptions.ts new file mode 100644 index 00000000..95c6f1f5 --- /dev/null +++ b/src/types/avifOptions.ts @@ -0,0 +1,39 @@ +/** + * Available AVIF subsample values: + * - `"auto"` - (default) use subsampling when the image is saved with quality less than 90 + * - `"on"` - always apply subsampling + * - `"off"` - never apply subsampling + */ +type AvifSubsampleOptions = "auto" | "on" | "off"; + +/** + * *AVIF options*. **PRO feature** + * + * Allows redefining AVIF saving options. + * + * @default "auto" + * + * @see {@link https://docs.imgproxy.net/generating_the_url?id=avif-options | AVIF options imgproxy docs} + */ +type AvifOptions = + | AvifSubsampleOptions + | { + /** + * Controls when chroma subsampling is used. Default: `"auto"` + */ + subsample: AvifSubsampleOptions; + }; + +/** + * *AVIF options option*. **PRO feature** + * + * To describe the AVIF options option, you can use the keyword `avif_options` or `avifo`. + * + * @see https://docs.imgproxy.net/generating_the_url?id=avif-options + */ +interface AvifOptionsPartial { + avif_options?: AvifOptions; + avifo?: AvifOptions; +} + +export { AvifOptions, AvifOptionsPartial }; diff --git a/src/types/index.ts b/src/types/index.ts index a4452369..0ea5ceb2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,7 @@ import type { AdjustOptionsPartial } from "./adjust"; import type { AutoqualityOptionsPartial } from "./autoquality"; import type { AutoRotateOptionsPartial } from "./autoRotate"; +import type { AvifOptionsPartial } from "./avifOptions"; import type { BackgroundOptionsPartial } from "./background"; import type { BackgroundAlphaOptionsPartial } from "./backgroundAlpha"; import type { BlurDetectionsOptionsPartial } from "./blurDetections"; @@ -80,6 +81,7 @@ import type { WildOptionsPartial } from "../typesShared/wildOptions"; export type Options = AdjustOptionsPartial & AutoqualityOptionsPartial & AutoRotateOptionsPartial & + AvifOptionsPartial & BackgroundOptionsPartial & BackgroundAlphaOptionsPartial & BlurDetectionsOptionsPartial & diff --git a/tests/optionsBasic/avifOptions.test.ts b/tests/optionsBasic/avifOptions.test.ts new file mode 100644 index 00000000..8162adf4 --- /dev/null +++ b/tests/optionsBasic/avifOptions.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import { test, build } from "../../src/options/avifOptions"; + +describe("avifOptions", () => { + describe("test", () => { + it("should return true if avif_options option is defined", () => { + expect(test({ avif_options: "auto" })).toEqual(true); + }); + + it("should return true if avifo option is defined", () => { + expect(test({ avifo: "on" })).toEqual(true); + }); + + it("should return false if avif_options option is undefined", () => { + expect(test({})).toEqual(false); + }); + }); + + describe("build", () => { + it("should throw an error if avif_options is undefined", () => { + expect(() => build({})).toThrow("avif_options option is undefined"); + }); + + it("should throw an error if avif_options is invalid", () => { + // @ts-expect-error: Let's ignore an error (check for users with vanilla js). + expect(() => build({ avif_options: "invalid" })).toThrow( + "avif_options option is invalid. Valid values are: auto, on, off" + ); + }); + + it("should throw an error if avif_options is a number", () => { + // @ts-expect-error: Let's ignore an error (check for users with vanilla js). + expect(() => build({ avif_options: 1 })).toThrow( + "avif_options option is invalid. Valid values are: auto, on, off" + ); + }); + + it("should return avifo:auto if avif_options is auto", () => { + expect(build({ avif_options: "auto" })).toEqual("avifo:auto"); + }); + + it("should return avifo:on if avifo option is on", () => { + expect(build({ avifo: "on" })).toEqual("avifo:on"); + }); + + it("should return avifo:off if avif_options is off", () => { + expect(build({ avif_options: "off" })).toEqual("avifo:off"); + }); + + it("should support object notation with subsample property", () => { + expect(build({ avif_options: { subsample: "auto" } })).toEqual( + "avifo:auto" + ); + + expect(build({ avif_options: { subsample: "on" } })).toEqual("avifo:on"); + + expect(build({ avif_options: { subsample: "off" } })).toEqual( + "avifo:off" + ); + }); + + it("should throw an error if subsample is invalid in object notation", () => { + expect(() => + // @ts-expect-error: Let's ignore an error (check for users with vanilla js). + build({ avif_options: { subsample: "invalid" } }) + ).toThrow( + "avif_options option is invalid. Valid values are: auto, on, off" + ); + }); + }); +});