@takazudo/zudo-image-tweaker 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/product-photo/index.js +103 -95
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- b4push: queue heavy steps through the machine-wide heavy-guard (4b15f51)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- product-photo: pass Buffer input to imgly as a MIME-typed Blob, so `removeBackground()` no longer fails with "Unsupported format" ([#126](https://github.com/zudolab/zudo-image-tweaker/pull/126), closes [#123](https://github.com/zudolab/zudo-image-tweaker/issues/123)) (140168c)
|
|
12
|
+
- deps: resolve root-workspace pnpm audit advisories ([#116](https://github.com/zudolab/zudo-image-tweaker/issues/116)) (993351d)
|
|
13
|
+
- deps: limit audit overrides to their current major/minor lines (87c3255)
|
|
14
|
+
|
|
15
|
+
### Other Changes
|
|
16
|
+
|
|
17
|
+
- ci: bump actions/checkout v7.0.1, actions/setup-node v7.1.0, pnpm/action-setup v6.1.0 (8998428)
|
|
18
|
+
- docs(doc): re-scaffold the doc site onto create-zudo-doc 8.1.0 / zfb 5.1.2 (f92c5d6, 7fde2a5, ff0c72d)
|
|
19
|
+
- ci: gate the doc build, links, wind audit and dependency audit; doc jobs move to Node 24 (4651d4e)
|
|
20
|
+
- build(doc): clear the high doc audit advisories (wrangler 4.152.0, zod 4.6.7, undici override) (aa5f7cf)
|
|
21
|
+
- chore: follow-ups from the code review (8bdd50e)
|
|
22
|
+
- chore(ci): record the NPM_TOKEN rotation on 2026-10-26 (760b1d1)
|
|
23
|
+
|
|
3
24
|
## 0.1.2
|
|
4
25
|
|
|
5
26
|
### Fixed
|
|
@@ -1,110 +1,110 @@
|
|
|
1
1
|
import P from "node:path";
|
|
2
2
|
import { pathToFileURL as L } from "node:url";
|
|
3
|
-
import
|
|
4
|
-
const Y = { r: 0, g: 0, b: 0, alpha: 0 },
|
|
3
|
+
import i from "sharp";
|
|
4
|
+
const Y = { r: 0, g: 0, b: 0, alpha: 0 }, j = { lightX: 0.3, lightY: 0.25, strength: 0.12, spread: 1.3 }, D = [
|
|
5
5
|
{ blur: 100, opacity: 0.25, offsetX: 10, offsetY: 8, fadeStartRatio: -0.1 },
|
|
6
6
|
{ blur: 40, opacity: 0.35, offsetX: 8, offsetY: 6, fadeStartRatio: 0 }
|
|
7
|
-
],
|
|
7
|
+
], F = { blur: 8, opacity: 0.55, offsetX: 4, offsetY: 3 }, G = {
|
|
8
8
|
squash: 0.25,
|
|
9
9
|
offsetX: 65,
|
|
10
10
|
offsetY: 35,
|
|
11
11
|
blur: 75,
|
|
12
12
|
opacity: 0.45
|
|
13
|
-
},
|
|
13
|
+
}, x = [
|
|
14
14
|
{ blur: 120, opacity: 0.3, offsetX: 62, offsetY: 55, fadeStartRatio: -0.1 },
|
|
15
15
|
{ blur: 52, opacity: 0.45, offsetX: 58, offsetY: 50, fadeStartRatio: 0 }
|
|
16
16
|
], X = { blur: 12, opacity: 0.5, offsetX: 55, offsetY: 45 };
|
|
17
|
-
async function
|
|
18
|
-
const { mode: n = "grounded", vignette: e, contactShadow: f, bottomShadows: s, projectedShadow: u } = t, { width:
|
|
19
|
-
if (!
|
|
17
|
+
async function U(o, t = {}) {
|
|
18
|
+
const { mode: n = "grounded", vignette: e, contactShadow: f, bottomShadows: s, projectedShadow: u } = t, { width: a, height: r } = await i(o).metadata();
|
|
19
|
+
if (!a || !r)
|
|
20
20
|
throw new Error("generateShadowLayers: could not read alphaImage dimensions.");
|
|
21
|
-
const c = await
|
|
21
|
+
const c = await i(o).extractChannel(3).toColourspace("b-w").raw().toBuffer(), l = q(c, a, r), b = { ...j, ...e }, d = W(a, r, b);
|
|
22
22
|
if (n === "floating") {
|
|
23
|
-
const
|
|
23
|
+
const m = { ...G, ...u }, [B, g] = _(x, s), p = { ...X, ...f };
|
|
24
24
|
return Promise.all([
|
|
25
25
|
d,
|
|
26
|
-
H(c,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
H(c, a, r, l, m),
|
|
27
|
+
A(c, a, r, l, B),
|
|
28
|
+
A(c, a, r, l, g),
|
|
29
|
+
N(c, a, r, p)
|
|
30
30
|
]);
|
|
31
31
|
}
|
|
32
|
-
const [y,
|
|
32
|
+
const [y, w] = _(D, s), S = { ...F, ...f };
|
|
33
33
|
return Promise.all([
|
|
34
34
|
d,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
A(c, a, r, l, y),
|
|
36
|
+
A(c, a, r, l, w),
|
|
37
|
+
N(c, a, r, S)
|
|
38
38
|
]);
|
|
39
39
|
}
|
|
40
40
|
function _(o, t) {
|
|
41
41
|
return o.map((n, e) => ({ ...n, ...t?.[e] }));
|
|
42
42
|
}
|
|
43
|
-
function
|
|
43
|
+
function h(o) {
|
|
44
44
|
return Number.isFinite(o) ? Math.min(1, Math.max(0, o)) : 0;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function q(o, t, n) {
|
|
47
47
|
let e = n, f = 0, s = t, u = 0;
|
|
48
|
-
for (let
|
|
49
|
-
for (let
|
|
50
|
-
o[
|
|
48
|
+
for (let a = 0; a < n; a++)
|
|
49
|
+
for (let r = 0; r < t; r++)
|
|
50
|
+
o[a * t + r] > 0 && (a < e && (e = a), a > f && (f = a), r < s && (s = r), r > u && (u = r));
|
|
51
51
|
return e > f ? null : { top: e, bottom: f + 1, left: s, right: u + 1 };
|
|
52
52
|
}
|
|
53
|
-
async function
|
|
54
|
-
const
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
async function H(o, t, n, e, { squash: f, offsetX: s, offsetY: u, blur:
|
|
58
|
-
if (!e) return
|
|
59
|
-
const c = e.right - e.left, l = e.bottom - e.top,
|
|
60
|
-
for (let
|
|
61
|
-
for (let
|
|
62
|
-
|
|
63
|
-
const d = Math.max(1, Math.round(l * f)), y = await
|
|
64
|
-
for (let
|
|
65
|
-
const
|
|
66
|
-
if (!(
|
|
53
|
+
async function N(o, t, n, { blur: e, opacity: f, offsetX: s, offsetY: u }) {
|
|
54
|
+
const a = I(o, t, n, s, u), r = await i(a, { raw: { width: t, height: n, channels: 1 } }).toColourspace("b-w").blur(Math.max(e, 0.3)).linear(h(f), 0).png().toBuffer();
|
|
55
|
+
return E(r, t, n);
|
|
56
|
+
}
|
|
57
|
+
async function H(o, t, n, e, { squash: f, offsetX: s, offsetY: u, blur: a, opacity: r }) {
|
|
58
|
+
if (!e) return k(t, n);
|
|
59
|
+
const c = e.right - e.left, l = e.bottom - e.top, b = Buffer.alloc(c * l);
|
|
60
|
+
for (let g = 0; g < l; g++)
|
|
61
|
+
for (let p = 0; p < c; p++)
|
|
62
|
+
b[g * c + p] = o[(e.top + g) * t + (e.left + p)];
|
|
63
|
+
const d = Math.max(1, Math.round(l * f)), y = await i(b, { raw: { width: c, height: l, channels: 1 } }).toColourspace("b-w").resize(c, d, { fit: "fill" }).raw().toBuffer(), w = Buffer.alloc(t * n, 0), S = e.left + s, m = e.bottom + u;
|
|
64
|
+
for (let g = 0; g < d; g++) {
|
|
65
|
+
const p = m + g;
|
|
66
|
+
if (!(p < 0 || p >= n))
|
|
67
67
|
for (let T = 0; T < c; T++) {
|
|
68
|
-
const
|
|
69
|
-
|
|
68
|
+
const C = S + T;
|
|
69
|
+
C < 0 || C >= t || (w[p * t + C] = y[g * c + T]);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
const
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
async function
|
|
76
|
-
if (!e) return
|
|
77
|
-
const c =
|
|
78
|
-
for (let
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
for (let
|
|
83
|
-
|
|
72
|
+
const B = await i(w, { raw: { width: t, height: n, channels: 1 } }).toColourspace("b-w").blur(Math.max(a, 0.3)).linear(h(r), 0).png().toBuffer();
|
|
73
|
+
return E(B, t, n);
|
|
74
|
+
}
|
|
75
|
+
async function A(o, t, n, e, { blur: f, opacity: s, offsetX: u, offsetY: a, fadeStartRatio: r }) {
|
|
76
|
+
if (!e) return k(t, n);
|
|
77
|
+
const c = h(s), l = I(o, t, n, u, a), b = await i(l, { raw: { width: t, height: n, channels: 1 } }).toColourspace("b-w").blur(Math.max(f, 0.3)).raw().toBuffer(), d = e.top + (e.bottom - e.top) * (0.5 + r), y = (e.bottom - d) * 0.5, w = Buffer.alloc(t * n, 0);
|
|
78
|
+
for (let m = 0; m < n; m++) {
|
|
79
|
+
let B;
|
|
80
|
+
m <= d ? B = 0 : y > 0 && m <= d + y ? B = (m - d) / y : B = 1;
|
|
81
|
+
const g = B * c;
|
|
82
|
+
for (let p = 0; p < t; p++)
|
|
83
|
+
w[m * t + p] = Math.round(b[m * t + p] * g);
|
|
84
84
|
}
|
|
85
|
-
const S = await
|
|
86
|
-
return
|
|
85
|
+
const S = await i(w, { raw: { width: t, height: n, channels: 1 } }).toColourspace("b-w").png().toBuffer();
|
|
86
|
+
return E(S, t, n);
|
|
87
87
|
}
|
|
88
88
|
async function W(o, t, { lightX: n, lightY: e, strength: f, spread: s }) {
|
|
89
|
-
const u =
|
|
89
|
+
const u = h(f), a = Buffer.alloc(o * t, 0), r = o * n, c = t * e, l = Math.min(o, t) / 2, b = s * s;
|
|
90
90
|
for (let y = 0; y < t; y++) {
|
|
91
|
-
const
|
|
92
|
-
for (let
|
|
93
|
-
const
|
|
94
|
-
|
|
91
|
+
const w = (y - c) / l, S = w * w;
|
|
92
|
+
for (let m = 0; m < o; m++) {
|
|
93
|
+
const B = (m - r) / l, g = B * B + S, p = Math.min(1, (g / b) ** 0.75) * u * 255;
|
|
94
|
+
a[y * o + m] = Math.round(p);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
const d = await
|
|
98
|
-
return
|
|
97
|
+
const d = await i(a, { raw: { width: o, height: t, channels: 1 } }).toColourspace("b-w").png().toBuffer();
|
|
98
|
+
return E(d, o, t);
|
|
99
99
|
}
|
|
100
|
-
function
|
|
100
|
+
function I(o, t, n, e, f) {
|
|
101
101
|
const s = Buffer.alloc(t * n, 0);
|
|
102
102
|
for (let u = 0; u < n; u++) {
|
|
103
|
-
const
|
|
104
|
-
if (!(
|
|
105
|
-
for (let
|
|
106
|
-
const c =
|
|
107
|
-
c < 0 || c >= t || (s[
|
|
103
|
+
const a = u + f;
|
|
104
|
+
if (!(a < 0 || a >= n))
|
|
105
|
+
for (let r = 0; r < t; r++) {
|
|
106
|
+
const c = r + e;
|
|
107
|
+
c < 0 || c >= t || (s[a * t + c] = o[u * t + r]);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
return s;
|
|
@@ -113,18 +113,18 @@ const R = /* @__PURE__ */ new Map();
|
|
|
113
113
|
function $(o, t) {
|
|
114
114
|
const n = `${o}x${t}`;
|
|
115
115
|
let e = R.get(n);
|
|
116
|
-
return e || (e =
|
|
116
|
+
return e || (e = i({ create: { width: o, height: t, channels: 3, background: { r: 0, g: 0, b: 0 } } }).png().toBuffer(), R.set(n, e)), e;
|
|
117
117
|
}
|
|
118
|
-
async function
|
|
118
|
+
async function E(o, t, n) {
|
|
119
119
|
const e = await $(t, n);
|
|
120
|
-
return { buffer: await
|
|
120
|
+
return { buffer: await i(e).joinChannel(o).png().toBuffer(), blend: "multiply", offset: { x: 0, y: 0 } };
|
|
121
121
|
}
|
|
122
|
-
async function
|
|
123
|
-
return { buffer: await
|
|
122
|
+
async function k(o, t) {
|
|
123
|
+
return { buffer: await i({ create: { width: o, height: t, channels: 4, background: Y } }).png().toBuffer(), blend: "multiply", offset: { x: 0, y: 0 } };
|
|
124
124
|
}
|
|
125
|
-
const v = 1600, V = 1440, Z = 90,
|
|
126
|
-
async function
|
|
127
|
-
const t = await z(), n =
|
|
125
|
+
const v = 1600, V = 1440, Z = 90, M = { r: 0, g: 0, b: 0, alpha: 0 }, O = "@imgly/background-removal-node";
|
|
126
|
+
async function ft(o) {
|
|
127
|
+
const t = await z(), n = await tt(o), f = await (await t.removeBackground(n)).arrayBuffer();
|
|
128
128
|
return Buffer.from(f);
|
|
129
129
|
}
|
|
130
130
|
async function z() {
|
|
@@ -145,45 +145,53 @@ function K(o) {
|
|
|
145
145
|
}
|
|
146
146
|
return !1;
|
|
147
147
|
}
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
const Q = {
|
|
149
|
+
png: "image/png",
|
|
150
|
+
jpeg: "image/jpeg",
|
|
151
|
+
webp: "image/webp"
|
|
152
|
+
};
|
|
153
|
+
async function tt(o) {
|
|
154
|
+
if (o instanceof URL) return o;
|
|
155
|
+
if (!Buffer.isBuffer(o)) return L(P.resolve(o)).href;
|
|
156
|
+
const { format: t } = await i(o).metadata(), n = t ? Q[t] : void 0;
|
|
157
|
+
return n ? new Blob([new Uint8Array(o)], { type: n }) : new Blob([new Uint8Array(await i(o).png().toBuffer())], { type: "image/png" });
|
|
150
158
|
}
|
|
151
|
-
async function
|
|
159
|
+
async function st(o, t = {}) {
|
|
152
160
|
const { threshold: n = 10 } = t;
|
|
153
|
-
return
|
|
161
|
+
return i(o).trim({ background: M, threshold: n }).png().toBuffer();
|
|
154
162
|
}
|
|
155
|
-
async function
|
|
163
|
+
async function ut(o, t) {
|
|
156
164
|
const { background: n, size: e = v, quality: f = Z, shadow: s = !1 } = t, u = t.fit ?? Math.round(e * (V / v));
|
|
157
165
|
if (n == null)
|
|
158
166
|
throw new Error(
|
|
159
167
|
"composeProductPhoto requires a `background`: an image path/Buffer, or a flat `{ color }` fill."
|
|
160
168
|
);
|
|
161
|
-
const
|
|
169
|
+
const a = Math.round((e - u) / 2), r = await i(o).resize(u, u, { fit: "contain", background: M }).png().toBuffer(), c = et(n, e);
|
|
162
170
|
let l;
|
|
163
171
|
if (s) {
|
|
164
|
-
const y = await
|
|
165
|
-
create: { width: e, height: e, channels: 4, background:
|
|
166
|
-
}).composite([{ input:
|
|
167
|
-
l = [...(await
|
|
172
|
+
const y = await i({
|
|
173
|
+
create: { width: e, height: e, channels: 4, background: M }
|
|
174
|
+
}).composite([{ input: r, left: a, top: a }]).png().toBuffer();
|
|
175
|
+
l = [...(await U(y, s === !0 ? {} : s)).map(ot), { input: y, left: 0, top: 0 }];
|
|
168
176
|
} else
|
|
169
|
-
l = [{ input:
|
|
170
|
-
const { data:
|
|
171
|
-
return { buffer:
|
|
177
|
+
l = [{ input: r, left: a, top: a }];
|
|
178
|
+
const { data: b, info: d } = await c.composite(l).jpeg({ quality: f }).toBuffer({ resolveWithObject: !0 });
|
|
179
|
+
return { buffer: b, width: d.width, height: d.height, format: "jpeg" };
|
|
172
180
|
}
|
|
173
|
-
function
|
|
181
|
+
function ot(o) {
|
|
174
182
|
return { input: o.buffer, left: o.offset.x, top: o.offset.y, blend: o.blend };
|
|
175
183
|
}
|
|
176
|
-
function
|
|
184
|
+
function nt(o) {
|
|
177
185
|
return typeof o == "object" && !Buffer.isBuffer(o) && "color" in o;
|
|
178
186
|
}
|
|
179
|
-
function
|
|
180
|
-
return
|
|
187
|
+
function et(o, t) {
|
|
188
|
+
return nt(o) ? i({
|
|
181
189
|
create: { width: t, height: t, channels: 3, background: o.color }
|
|
182
|
-
}) :
|
|
190
|
+
}) : i(o).resize(t, t, { fit: "cover" });
|
|
183
191
|
}
|
|
184
192
|
export {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
st as alphaTrim,
|
|
194
|
+
ut as composeProductPhoto,
|
|
195
|
+
U as generateShadowLayers,
|
|
196
|
+
ft as removeBackground
|
|
189
197
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takazudo/zudo-image-tweaker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "sharp-based image-tweaking toolkit: variants, HEIF conversion, OGP cards, byte-budget encoding, square crops, product-photo background removal, calibration, compositing, blurhash, EXIF, and browser upload prep.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|