Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/two-days-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@imgproxy/imgproxy-js-core": minor
---

Add support for objw mode for [gravity option](https://docs.imgproxy.net/usage/processing#gravity)
27 changes: 27 additions & 0 deletions src/options/gravity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
Gravity,
FPGravity,
ObjGravity,
ObjwGravity,
BaseGravity,
} from "../types/gravity";
import {
Expand Down Expand Up @@ -33,6 +34,7 @@ const currentAllTypes = {
sm: true,
fp: true,
obj: true,
objw: true,
};

const getOpt = (options: GravityOptionsPartial): Gravity | undefined =>
Expand Down Expand Up @@ -64,6 +66,9 @@ const build = (
if (gravityOpts.class_names && type !== "obj")
throw new Error("gravity.class_names can be used only with type obj");
// @ts-expect-error: Let's ignore an error.
if (gravityOpts.class_weights && type !== "objw")
throw new Error("gravity.class_weights can be used only with type objw");
// @ts-expect-error: Let's ignore an error.
if ((gravityOpts.x || gravityOpts.y) && type !== "fp")
throw new Error("gravity.x and gravity.y can be used only with type fp");

Expand All @@ -90,6 +95,28 @@ const build = (

const class_names = gravityObj.class_names;
return withHead(`${type}:${class_names.join(":")}`, headless);
}

if (type === "objw") {
const gravityObjw = gravityOpts as ObjwGravity;

guardIsUndef(gravityObjw.class_weights, "gravity.class_weights");
guardIsNotArray(gravityObjw.class_weights, "gravity.class_weights");

const weightPairs = gravityObjw.class_weights.map(item => {
if (
typeof item !== "object" ||
!item.class ||
typeof item.weight !== "number"
) {
throw new Error(
"Each item in gravity.class_weights must have 'class' and 'weight' properties"
);
}
return `${item.class}:${item.weight}`;
});

return withHead(`${type}:${weightPairs.join(":")}`, headless);
} else {
const gravityBase = gravityOpts as BaseGravity;
const x_offset =
Expand Down
45 changes: 43 additions & 2 deletions src/types/gravity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ interface ObjGravity {
class_names: string[];
}

/**
* **PRO feature.**
*
* Object-weighted gravity. imgproxy detects objects of provided classes on the image, calculates the resulting image center using their positions, and adds weights to these positions.
*
* If class weights are omited, imgproxy will use all the detected objects with equal weights.
*
* @param {string} type - Must be `objw`.
* @param {Array<{class: string, weight: number}>} class_weights - Array of objects with class names and their weights.
*
* @example
* {gravity: {type: "objw", class_weights: [{class: "face", weight: 1}, {class: "person", weight: 0.5}]}}
*
* @see https://docs.imgproxy.net/generating_the_url?id=gravity
*/
interface ObjwGravity {
type: "objw";
class_weights: Array<{ class: string; weight: number }>;
}

/**
* *Gravity option*
*
Expand Down Expand Up @@ -138,6 +158,12 @@ interface ObjGravity {
* If class names are omited, imgproxy will use all the detected objects.
* @param {string} type - Must be `obj`.
* @param {string[]} class_names - Array of class names.
*
* *Object-weighted gravity*. **PRO feature.**
* imgproxy detects objects of provided classes on the image, calculates the resulting image center using their positions, and adds weights to these positions.
* If class weights are omited, imgproxy will use all the detected objects with equal weights.
* @param {string} type - Must be `objw`.
* @param {Array<{class: string, weight: number}>} class_weights - Array of objects with class names and their weights.
*
* *FP gravity*.
* The gravity focus point.
Expand All @@ -164,13 +190,21 @@ interface ObjGravity {
*
* @example <caption>Object-oriented gravity</caption>
* {gravity: {type: "obj", class_names: ["face", "person"]}}
*
* @example <caption>Object-weighted gravity</caption>
* {gravity: {type: "objw", class_weights: [{class: "face", weight: 1}, {class: "person", weight: 0.5}]}}
*
* @example <caption>FP gravity</caption>
* {gravity: {type: "fp", x: 0.5, y: 0.5}}
*
* @see https://docs.imgproxy.net/generating_the_url?id=gravity
*/
type Gravity = BaseGravity | SmartGravity | ObjGravity | FPGravity;
type Gravity =
| BaseGravity
| SmartGravity
| ObjGravity
| ObjwGravity
| FPGravity;

/**
* *Gravity option*
Expand All @@ -184,4 +218,11 @@ interface GravityOptionsPartial {
g?: Gravity;
}

export { BaseGravity, FPGravity, ObjGravity, Gravity, GravityOptionsPartial };
export {
BaseGravity,
FPGravity,
ObjGravity,
ObjwGravity,
Gravity,
GravityOptionsPartial,
};
65 changes: 65 additions & 0 deletions tests/optionsBasic/gravity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,70 @@ describe("gravity", () => {
).toEqual("g:fp:0:0");
});
});

describe("ObjwGravity", () => {
it("should throw an error if gravity includes property class_weights but type is not 'objw'", () => {
expect(() =>
build({
gravity: {
type: "no",
// @ts-expect-error: Let's ignore an error (check for users with vanilla js).
class_weights: [{ class: "face", weight: 1 }],
},
})
).toThrow(`gravity.class_weights can be used only with type objw`);
});

it("should throw an error if class_weights is undefined", () => {
expect(() =>
build({
// @ts-expect-error: Let's ignore an error (check for users with vanilla js).
gravity: {
type: "objw",
},
})
).toThrow(`gravity.class_weights is undefined`);
});

it("should throw an error if class_weights is not an array", () => {
expect(() =>
build({
gravity: {
type: "objw",
// @ts-expect-error: Let's ignore an error (check for users with vanilla js).
class_weights: "face:1",
},
})
).toThrow(`gravity.class_weights is not an array`);
});

it("should throw an error if a class_weights item is missing class or weight", () => {
expect(() =>
build({
gravity: {
type: "objw",
// @ts-expect-error: Let's ignore an error (check for users with vanilla js).
class_weights: [{ class: "face" }],
},
})
).toThrow(
`Each item in gravity.class_weights must have 'class' and 'weight' properties`
);
});

it("should return g:objw:face:1:person:0.5 if gravity is {type: 'objw', class_weights: [{class: 'face', weight: 1}, {class: 'person', weight: 0.5}]} ", () => {
expect(
build({
gravity: {
type: "objw",
class_weights: [
{ class: "face", weight: 1 },
{ class: "person", weight: 0.5 },
],
},
})
).toEqual("g:objw:face:1:person:0.5");
});
});
});
});