Skip to content

Commit 4129214

Browse files
authored
feat: Generative Replace Background (#167)
# Description Adds the generative replace background feature https://cloudinary.com/documentation/transformation_reference#e_gen_background_replace ## Usage * Boolean ``` replaceBackground ``` * String ``` replaceBackground="prompt" ``` * Object ``` replaceBackground={{ prompt: 'prompt', seed: 2 // the ability to regenerate if you don't like the first one }} ```
1 parent 69ecc1c commit 4129214

5 files changed

Lines changed: 133 additions & 0 deletions

File tree

packages/url-loader/src/lib/cloudinary.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { recolorPlugin } from "../plugins/recolor.js";
2020
import { removeBackgroundPlugin } from "../plugins/remove-background.js";
2121
import { removePlugin } from "../plugins/remove.js";
2222
import { replacePlugin } from "../plugins/replace.js";
23+
import { replaceBackgroundPlugin } from "../plugins/replace-background.js";
2324
import { restorePlugin } from "../plugins/restore.js";
2425
import { sanitizePlugin } from "../plugins/sanitize.js";
2526
import { seoPlugin } from "../plugins/seo.js";
@@ -48,6 +49,7 @@ export const transformationPlugins = [
4849
removeBackgroundPlugin,
4950
removePlugin,
5051
replacePlugin,
52+
replaceBackgroundPlugin,
5153
restorePlugin,
5254

5355
// Cropping needs to be before any other general transformations
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { z } from "zod";
2+
import type { ImageOptions } from "../types/image.js";
3+
import type { TransformationPlugin } from "../types/plugins.js";
4+
5+
export const replaceBackgroundProps = {
6+
replaceBackground: z
7+
.union([
8+
z.boolean(),
9+
z.string(),
10+
z.object({
11+
seed: z.number().optional(),
12+
prompt: z.string().optional(),
13+
}),
14+
])
15+
.describe(
16+
JSON.stringify({
17+
text: "Replaces the background of an image with an AI-generated background.",
18+
url: "https://cloudinary.com/documentation/transformation_reference#e_gen_background_replace",
19+
})
20+
)
21+
.optional(),
22+
};
23+
24+
export const replaceBackgroundPlugin = {
25+
props: replaceBackgroundProps,
26+
assetTypes: ["image", "images"],
27+
plugin: (settings) => {
28+
const { cldAsset, options } = settings;
29+
const { replaceBackground } = options;
30+
31+
if (replaceBackground === false || typeof replaceBackground === "undefined") return {};
32+
33+
const properties = [];
34+
35+
if ( typeof replaceBackground === 'object' ) {
36+
37+
if ( typeof replaceBackground.prompt !== 'undefined' ) {
38+
properties.push(`prompt_${replaceBackground.prompt}`);
39+
}
40+
41+
if ( typeof replaceBackground.seed === 'number' ) {
42+
properties.push(`seed_${replaceBackground.seed}`);
43+
}
44+
} else if ( typeof replaceBackground === 'string' ) {
45+
properties.push(`prompt_${replaceBackground}`);
46+
}
47+
48+
let transformation = 'e_gen_background_replace';
49+
50+
if ( properties.length > 0 ) {
51+
transformation = `${transformation}:${properties.join(';')}`;
52+
}
53+
54+
cldAsset.addTransformation(transformation);
55+
56+
return {};
57+
},
58+
} satisfies TransformationPlugin<ImageOptions>;

packages/url-loader/src/types/image.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { fillBackgroundProps } from "../plugins/fill-background.js";
55
import { recolorProps } from "../plugins/recolor.js";
66
import { removeProps } from "../plugins/remove.js";
77
import { replaceProps } from "../plugins/replace.js";
8+
import { replaceBackgroundProps } from "../plugins/replace-background.js";
89
import { restoreProps } from "../plugins/restore.js";
910
import { zoompanProps } from "../plugins/zoompan.js";
1011
import { assetOptionsSchema } from "./asset.js";
@@ -19,6 +20,7 @@ export const imageOptionsSchema = assetOptionsSchema.merge(
1920
...recolorProps,
2021
...removeProps,
2122
...replaceProps,
23+
...replaceBackgroundProps,
2224
...restoreProps,
2325
...zoompanProps,
2426
})

packages/url-loader/tests/lib/cloudinary.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,10 @@ describe('Cloudinary', () => {
883883
removeShadow: true
884884
},
885885
removeBackground: true,
886+
replaceBackground: {
887+
prompt: 'space jellyfish',
888+
seed: 2
889+
},
886890
restore: true,
887891
zoompan: true,
888892
},
@@ -900,6 +904,7 @@ describe('Cloudinary', () => {
900904
'e_gen_recolor:prompt_duck;to-color_blue;multiple_true',
901905
`e_background_removal`,
902906
`e_gen_remove:prompt_apple;multiple_true;remove-shadow_true`,
907+
`e_gen_background_replace:prompt_space%20jellyfish;seed_2`,
903908
`e_gen_restore`,
904909
`c_${crop},w_${width},h_${height},g_auto,z_${zoom}`,
905910
`d_${defaultImage}`,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Cloudinary } from "@cloudinary/url-gen";
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { replaceBackgroundPlugin } from "../../src/plugins/replace-background";
5+
6+
const { plugin } = replaceBackgroundPlugin;
7+
8+
const cld = new Cloudinary({
9+
cloud: {
10+
cloudName: "test-cloud-name",
11+
},
12+
});
13+
14+
const TEST_PUBLIC_ID = "test-public-id";
15+
16+
describe("Plugins", () => {
17+
describe("Generative Replace Background", () => {
18+
it("should replace the background", () => {
19+
const cldImage = cld.image(TEST_PUBLIC_ID);
20+
21+
const options = {
22+
replaceBackground: true,
23+
};
24+
25+
plugin({
26+
cldAsset: cldImage,
27+
options,
28+
});
29+
30+
expect(cldImage.toURL()).toContain('/e_gen_background_replace/');
31+
});
32+
33+
it("should replace the background with a prompt", () => {
34+
const cldImage = cld.image(TEST_PUBLIC_ID);
35+
36+
const options = {
37+
replaceBackground: 'space jellyfish in space',
38+
};
39+
40+
plugin({
41+
cldAsset: cldImage,
42+
options,
43+
});
44+
45+
expect(cldImage.toURL()).toContain(`/e_gen_background_replace:prompt_${encodeURIComponent(options.replaceBackground)}/`);
46+
});
47+
48+
it("should replace the background with a prompt", () => {
49+
const cldImage = cld.image(TEST_PUBLIC_ID);
50+
51+
const options = {
52+
replaceBackground: {
53+
prompt: 'space jellyfish in outer space',
54+
seed: 2
55+
}
56+
};
57+
58+
plugin({
59+
cldAsset: cldImage,
60+
options,
61+
});
62+
63+
expect(cldImage.toURL()).toContain(`/e_gen_background_replace:prompt_${encodeURIComponent(options.replaceBackground.prompt)};seed_${options.replaceBackground.seed}/`);
64+
});
65+
});
66+
});

0 commit comments

Comments
 (0)