imagescript 3.1.0 → 3.2.0
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/README.md +12 -4
- package/dist/animation.d.ts +92 -7
- package/dist/animation.js +49 -19
- package/dist/codecs/exif.js +62 -0
- package/dist/codecs/gif.js +32 -19
- package/dist/codecs/jpeg.js +13 -1
- package/dist/codecs/png.js +85 -21
- package/dist/codecs/quantize.js +2 -2
- package/dist/codecs/tga.js +47 -4
- package/dist/codecs/vp8l.js +27 -30
- package/dist/codecs/webp.js +23 -10
- package/dist/frame.d.ts +30 -2
- package/dist/frame.js +20 -9
- package/dist/gif.d.ts +10 -7
- package/dist/gif.js +7 -5
- package/dist/image-type.d.ts +1 -0
- package/dist/image-type.js +1 -0
- package/dist/image.d.ts +231 -31
- package/dist/image.js +111 -50
- package/dist/index.d.ts +14 -4
- package/dist/index.js +6 -4
- package/dist/metadata.d.ts +35 -0
- package/dist/metadata.js +43 -0
- package/dist/render/svg.js +39 -21
- package/dist/util/optimize.js +264 -0
- package/dist/util/options.js +3 -0
- package/dist/util/resample.js +44 -26
- package/dist/util/utf8.js +4 -51
- package/dist/version.js +1 -1
- package/package.json +9 -2
- package/dist/codecs/crc.d.ts +0 -9
- package/dist/codecs/exif.d.ts +0 -11
- package/dist/codecs/gif.d.ts +0 -44
- package/dist/codecs/jpeg.d.ts +0 -35
- package/dist/codecs/png.d.ts +0 -97
- package/dist/codecs/quantize.d.ts +0 -25
- package/dist/codecs/tga.d.ts +0 -16
- package/dist/codecs/tiff.d.ts +0 -6
- package/dist/codecs/vp8-tables.d.ts +0 -10
- package/dist/codecs/vp8.d.ts +0 -8
- package/dist/codecs/vp8l.d.ts +0 -62
- package/dist/codecs/webp.d.ts +0 -67
- package/dist/codecs/zlib.d.ts +0 -22
- package/dist/render/font.d.ts +0 -92
- package/dist/render/hinting.d.ts +0 -110
- package/dist/render/svg.d.ts +0 -11
- package/dist/render/text.d.ts +0 -63
- package/dist/util/blur.d.ts +0 -13
- package/dist/util/resample.d.ts +0 -12
- package/dist/util/utf8.d.ts +0 -5
- package/dist/version.d.ts +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const ALPHA = new Int32Array(new Uint8Array([0, 0, 0, 255]).buffer)[0];
|
|
2
|
+
const ALPHA_COST = 4 / 3;
|
|
3
|
+
export function optimize(frames, width, height, { replace, even, alpha, palette, cover }) {
|
|
4
|
+
const size = width * height * 4;
|
|
5
|
+
const canvas = new Uint8Array(size), canvas32 = new Int32Array(canvas.buffer);
|
|
6
|
+
const shown = new Uint8Array(size), shown32 = new Int32Array(shown.buffer);
|
|
7
|
+
const output = [];
|
|
8
|
+
const state = { canvas32, shown32, width, replace };
|
|
9
|
+
let opaque = true, denseTotal = 0, chosenTotal = 0;
|
|
10
|
+
const add = (duration) => {
|
|
11
|
+
let changed = bounds(state, { x: 0, y: 0, width, height });
|
|
12
|
+
if (!changed) {
|
|
13
|
+
if (output.length)
|
|
14
|
+
output[output.length - 1].duration += duration;
|
|
15
|
+
else {
|
|
16
|
+
output.push(empty(duration));
|
|
17
|
+
opaque = false;
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (even)
|
|
22
|
+
changed = evenArea(changed);
|
|
23
|
+
let area = sparse(state, changed);
|
|
24
|
+
let blend = true;
|
|
25
|
+
if (area.unblendable && replace)
|
|
26
|
+
blend = false;
|
|
27
|
+
else if (area.unblendable) {
|
|
28
|
+
const last = output[output.length - 1];
|
|
29
|
+
grow(last, union(last, area.unblendable));
|
|
30
|
+
last.dispose = true;
|
|
31
|
+
clear(shown, width, last);
|
|
32
|
+
changed = bounds(state, { x: 0, y: 0, width, height });
|
|
33
|
+
if (!changed)
|
|
34
|
+
return void output.push(empty(duration));
|
|
35
|
+
if (even)
|
|
36
|
+
changed = evenArea(changed);
|
|
37
|
+
area = sparse(state, changed);
|
|
38
|
+
}
|
|
39
|
+
const { x, y, width: w, height: h } = changed;
|
|
40
|
+
const pixels = area.pixels;
|
|
41
|
+
if (!output.length && (w !== width || h !== height))
|
|
42
|
+
opaque = false;
|
|
43
|
+
if (!area.denseOpaque)
|
|
44
|
+
opaque = false;
|
|
45
|
+
const sparseCost = alpha === 'frame' && area.denseOpaque ? area.sparseChanges * ALPHA_COST : area.sparseChanges;
|
|
46
|
+
const dense = !blend || area.denseChanges < sparseCost
|
|
47
|
+
|| (palette !== undefined && colors(new Int32Array(pixels.buffer), w, { x: 0, y: 0, width: w, height: h }, palette + 1) > palette
|
|
48
|
+
&& colors(canvas32, width, changed, palette + 1) <= palette);
|
|
49
|
+
denseTotal += area.denseChanges;
|
|
50
|
+
chosenTotal += dense ? area.denseChanges : area.sparseChanges;
|
|
51
|
+
if (dense) {
|
|
52
|
+
for (let row = 0; row < h; row++)
|
|
53
|
+
pixels.set(canvas.subarray(((y + row) * width + x) * 4, ((y + row) * width + x + w) * 4), row * w * 4);
|
|
54
|
+
if (replace)
|
|
55
|
+
blend = false;
|
|
56
|
+
}
|
|
57
|
+
for (let row = 0; row < h; row++) {
|
|
58
|
+
const start = ((y + row) * width + x) * 4;
|
|
59
|
+
shown.set(canvas.subarray(start, start + w * 4), start);
|
|
60
|
+
}
|
|
61
|
+
output.push({ x, y, width: w, height: h, duration, dispose: false, blend, pixels });
|
|
62
|
+
};
|
|
63
|
+
let cleared = { x: 0, y: 0, width, height };
|
|
64
|
+
for (const f of frames) {
|
|
65
|
+
const saved = f.dispose === 2 ? canvas.slice() : null;
|
|
66
|
+
const area = clip(f, width, height);
|
|
67
|
+
if (cleared && contains(cleared, area))
|
|
68
|
+
copy(canvas, width, f, area);
|
|
69
|
+
else
|
|
70
|
+
draw(canvas, width, height, f);
|
|
71
|
+
cleared = null;
|
|
72
|
+
add(f.duration);
|
|
73
|
+
if (f.dispose === 1) {
|
|
74
|
+
clear(canvas, width, area);
|
|
75
|
+
cleared = area;
|
|
76
|
+
}
|
|
77
|
+
else if (saved)
|
|
78
|
+
canvas.set(saved);
|
|
79
|
+
}
|
|
80
|
+
if (typeof alpha === 'function' && opaque && output.some(f => f.blend) && denseTotal < chosenTotal * alpha(output)) {
|
|
81
|
+
shown.fill(0);
|
|
82
|
+
for (const f of output) {
|
|
83
|
+
const pixels32 = new Int32Array(f.pixels.buffer, f.pixels.byteOffset, f.width * f.height);
|
|
84
|
+
for (let row = 0, k = 0; row < f.height; row++, k += f.width) {
|
|
85
|
+
const start = (f.y + row) * width + f.x;
|
|
86
|
+
if (!f.blend)
|
|
87
|
+
shown32.set(pixels32.subarray(k, k + f.width), start);
|
|
88
|
+
else
|
|
89
|
+
for (let i = k, p = start, end = k + f.width; i < end; i++, p++) {
|
|
90
|
+
if ((pixels32[i] & ALPHA) === 0)
|
|
91
|
+
pixels32[i] = shown32[p];
|
|
92
|
+
else
|
|
93
|
+
shown32[p] = pixels32[i];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
f.blend = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (cover && output.length)
|
|
100
|
+
grow(output[0], { x: 0, y: 0, width, height });
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
function colors(image32, width, { x, y, width: w, height: h }, limit) {
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
for (let row = y; row < y + h; row++) {
|
|
106
|
+
let previous;
|
|
107
|
+
for (let p = row * width + x, end = p + w; p < end; p++) {
|
|
108
|
+
const value = image32[p];
|
|
109
|
+
if (value === previous)
|
|
110
|
+
continue;
|
|
111
|
+
previous = value;
|
|
112
|
+
seen.add(value);
|
|
113
|
+
if (seen.size >= limit)
|
|
114
|
+
return seen.size;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return seen.size;
|
|
118
|
+
}
|
|
119
|
+
const empty = (duration) => ({ x: 0, y: 0, width: 1, height: 1, duration, dispose: false, blend: true, pixels: new Uint8Array(4) });
|
|
120
|
+
function clip(r, width, height) {
|
|
121
|
+
const x = Math.max(0, r.x), y = Math.max(0, r.y);
|
|
122
|
+
return { x, y, width: Math.max(0, Math.min(width, r.x + r.width) - x), height: Math.max(0, Math.min(height, r.y + r.height) - y) };
|
|
123
|
+
}
|
|
124
|
+
function contains(a, b) {
|
|
125
|
+
return b.x >= a.x && b.y >= a.y && b.x + b.width <= a.x + a.width && b.y + b.height <= a.y + a.height;
|
|
126
|
+
}
|
|
127
|
+
function union(a, b) {
|
|
128
|
+
const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
|
|
129
|
+
return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
130
|
+
}
|
|
131
|
+
function evenArea({ x, y, width, height }) {
|
|
132
|
+
return { x: x & ~1, y: y & ~1, width: width + (x & 1), height: height + (y & 1) };
|
|
133
|
+
}
|
|
134
|
+
function sparse({ canvas32, shown32, width, replace }, { x, y, width: w, height: h }) {
|
|
135
|
+
const pixels = new Uint8Array(w * h * 4), pixels32 = new Int32Array(pixels.buffer);
|
|
136
|
+
let sparseChanges = 0, denseChanges = 0, lastSparse = -1, lastDense = -1;
|
|
137
|
+
let left = w, right = -1, top = h, bottom = -1, translucent = 0;
|
|
138
|
+
for (let row = 0, k = 0; row < h; row++) {
|
|
139
|
+
const start = (y + row) * width + x;
|
|
140
|
+
for (let i = 0, p = start; i < w; i++, p++, k++) {
|
|
141
|
+
const value = canvas32[p], before = shown32[p];
|
|
142
|
+
if (value === before || ((value | before) & ALPHA) === 0) {
|
|
143
|
+
if (lastSparse !== 0)
|
|
144
|
+
sparseChanges++;
|
|
145
|
+
lastSparse = 0;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
pixels32[k] = value;
|
|
149
|
+
if (value !== lastSparse)
|
|
150
|
+
sparseChanges++;
|
|
151
|
+
lastSparse = value;
|
|
152
|
+
if ((value & ALPHA) !== ALPHA && (replace || (before & ALPHA) !== 0)) {
|
|
153
|
+
if (i < left)
|
|
154
|
+
left = i;
|
|
155
|
+
if (i > right)
|
|
156
|
+
right = i;
|
|
157
|
+
if (row < top)
|
|
158
|
+
top = row;
|
|
159
|
+
bottom = row;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (value !== lastDense) {
|
|
163
|
+
denseChanges++;
|
|
164
|
+
translucent |= ~value & ALPHA;
|
|
165
|
+
}
|
|
166
|
+
lastDense = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const unblendable = right < 0 ? null : { x: x + left, y: y + top, width: right - left + 1, height: bottom - top + 1 };
|
|
170
|
+
return { pixels, sparseChanges, denseChanges, unblendable, denseOpaque: translucent === 0 };
|
|
171
|
+
}
|
|
172
|
+
function bounds({ canvas32, shown32, width }, area) {
|
|
173
|
+
const x0 = area.x, x1 = area.x + area.width, y0 = area.y, y1 = area.y + area.height;
|
|
174
|
+
const test = (p) => {
|
|
175
|
+
const c = canvas32[p], s = shown32[p];
|
|
176
|
+
return c !== s && ((c | s) & ALPHA) !== 0;
|
|
177
|
+
};
|
|
178
|
+
const rowHas = (y, from, to) => {
|
|
179
|
+
for (let x = from, p = y * width + from; x < to; x++, p++)
|
|
180
|
+
if (test(p))
|
|
181
|
+
return x;
|
|
182
|
+
return -1;
|
|
183
|
+
};
|
|
184
|
+
let top = y0, first = -1;
|
|
185
|
+
for (; top < y1; top++)
|
|
186
|
+
if ((first = rowHas(top, x0, x1)) >= 0)
|
|
187
|
+
break;
|
|
188
|
+
if (top === y1)
|
|
189
|
+
return null;
|
|
190
|
+
let bottom = y1 - 1;
|
|
191
|
+
while (bottom > top && rowHas(bottom, x0, x1) < 0)
|
|
192
|
+
bottom--;
|
|
193
|
+
let left = first, right = first;
|
|
194
|
+
for (let y = top; y <= bottom; y++) {
|
|
195
|
+
if (left > x0) {
|
|
196
|
+
const x = rowHas(y, x0, left);
|
|
197
|
+
if (x >= 0)
|
|
198
|
+
left = x;
|
|
199
|
+
}
|
|
200
|
+
for (let x = x1 - 1, p = y * width + x; x > right; x--, p--)
|
|
201
|
+
if (test(p)) {
|
|
202
|
+
right = x;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { x: left, y: top, width: right - left + 1, height: bottom - top + 1 };
|
|
207
|
+
}
|
|
208
|
+
function draw(canvas, width, height, f) {
|
|
209
|
+
const { x: x0, y: y0, width: w, height: h } = clip(f, width, height);
|
|
210
|
+
const pixels = f.pixels.byteOffset % 4 ? f.pixels.slice() : f.pixels;
|
|
211
|
+
const src = new Int32Array(pixels.buffer, pixels.byteOffset, f.width * f.height);
|
|
212
|
+
const dst = new Int32Array(canvas.buffer, canvas.byteOffset, width * height);
|
|
213
|
+
for (let y = y0; y < y0 + h; y++)
|
|
214
|
+
for (let x = x0, s = (y - f.y) * f.width + x - f.x, d = y * width + x; x < x0 + w; x++, s++, d++) {
|
|
215
|
+
let n = 0;
|
|
216
|
+
while (n < x0 + w - x && (src[s + n] & ALPHA) === ALPHA)
|
|
217
|
+
n++;
|
|
218
|
+
if (n) {
|
|
219
|
+
if (n >= 16)
|
|
220
|
+
dst.set(src.subarray(s, s + n), d);
|
|
221
|
+
else
|
|
222
|
+
for (let i = 0; i < n; i++)
|
|
223
|
+
dst[d + i] = src[s + i];
|
|
224
|
+
x += n - 1;
|
|
225
|
+
s += n - 1;
|
|
226
|
+
d += n - 1;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const value = src[s], alpha = value & ALPHA;
|
|
230
|
+
if (alpha === 0)
|
|
231
|
+
continue;
|
|
232
|
+
if (alpha === ALPHA || (dst[d] & ALPHA) === 0)
|
|
233
|
+
dst[d] = value;
|
|
234
|
+
else {
|
|
235
|
+
const sa = pixels[s * 4 + 3], da = canvas[d * 4 + 3];
|
|
236
|
+
const u = sa * 255, v = (255 - sa) * da, al = u + v;
|
|
237
|
+
for (let c = 0; c < 3; c++)
|
|
238
|
+
canvas[d * 4 + c] = (pixels[s * 4 + c] * u + canvas[d * 4 + c] * v) / al | 0;
|
|
239
|
+
canvas[d * 4 + 3] = al / 255 | 0;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function copy(canvas, width, f, { x, y, width: w, height: h }) {
|
|
244
|
+
const canvas32 = new Int32Array(canvas.buffer, canvas.byteOffset, width * (y + h));
|
|
245
|
+
for (let row = y; row < y + h; row++) {
|
|
246
|
+
const s = ((row - f.y) * f.width + x - f.x) * 4, d = row * width + x;
|
|
247
|
+
canvas.set(f.pixels.subarray(s, s + w * 4), d * 4);
|
|
248
|
+
for (let p = d; p < d + w; p++)
|
|
249
|
+
if ((canvas32[p] & ALPHA) === 0)
|
|
250
|
+
canvas32[p] = 0;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function clear(canvas, width, r) {
|
|
254
|
+
for (let y = r.y; y < r.y + r.height; y++)
|
|
255
|
+
canvas.fill(0, (y * width + r.x) * 4, (y * width + r.x + r.width) * 4);
|
|
256
|
+
}
|
|
257
|
+
function grow(frame, area) {
|
|
258
|
+
if (area.x === frame.x && area.y === frame.y && area.width === frame.width && area.height === frame.height)
|
|
259
|
+
return;
|
|
260
|
+
const pixels = new Uint8Array(area.width * area.height * 4);
|
|
261
|
+
for (let row = 0; row < frame.height; row++)
|
|
262
|
+
pixels.set(frame.pixels.subarray(row * frame.width * 4, (row + 1) * frame.width * 4), ((frame.y - area.y + row) * area.width + frame.x - area.x) * 4);
|
|
263
|
+
Object.assign(frame, area, { pixels });
|
|
264
|
+
}
|
package/dist/util/resample.js
CHANGED
|
@@ -40,33 +40,33 @@ function coefficients(inSize, outSize, filter) {
|
|
|
40
40
|
}
|
|
41
41
|
export function resample(src, width, height, outWidth, outHeight, filter, out) {
|
|
42
42
|
let plane = new Float32Array(outWidth * height * 4);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const wa = weights[x * size + k] * src[s + 3];
|
|
43
|
+
if (outWidth !== width) {
|
|
44
|
+
const { start, count, weights, size } = coefficients(width, outWidth, filter);
|
|
45
|
+
for (let y = 0, o = 0; y < height; y++) {
|
|
46
|
+
const row = y * width * 4;
|
|
47
|
+
for (let x = 0; x < outWidth; x++, o += 4) {
|
|
48
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
49
|
+
for (let k = 0, s = row + start[x] * 4, w = x * size, n = count[x]; k < n; k++, s += 4) {
|
|
50
|
+
const wa = weights[w + k] * src[s + 3];
|
|
52
51
|
r += src[s] * wa;
|
|
53
52
|
g += src[s + 1] * wa;
|
|
54
53
|
b += src[s + 2] * wa;
|
|
55
54
|
a += wa;
|
|
56
55
|
}
|
|
56
|
+
plane[o] = r / 255;
|
|
57
|
+
plane[o + 1] = g / 255;
|
|
58
|
+
plane[o + 2] = b / 255;
|
|
59
|
+
plane[o + 3] = a;
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
plane[
|
|
67
|
-
plane[o + 1] = g / 255;
|
|
68
|
-
plane[o + 2] = b / 255;
|
|
69
|
-
plane[o + 3] = a;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
for (let s = 0; s < plane.length; s += 4) {
|
|
65
|
+
const a = src[s + 3];
|
|
66
|
+
plane[s] = src[s] * a / 255;
|
|
67
|
+
plane[s + 1] = src[s + 1] * a / 255;
|
|
68
|
+
plane[s + 2] = src[s + 2] * a / 255;
|
|
69
|
+
plane[s + 3] = a;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
if (outHeight !== height) {
|
|
@@ -74,12 +74,30 @@ export function resample(src, width, height, outWidth, outHeight, filter, out) {
|
|
|
74
74
|
const next = new Float32Array(outWidth * outHeight * 4);
|
|
75
75
|
const stride = outWidth * 4;
|
|
76
76
|
for (let y = 0; y < outHeight; y++) {
|
|
77
|
-
const o = y * stride;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
const o = y * stride, n = count[y], w = y * size;
|
|
78
|
+
const s0 = start[y] * stride, s1 = s0 + stride, s2 = s1 + stride, s3 = s2 + stride;
|
|
79
|
+
const w0 = weights[w], w1 = weights[w + 1], w2 = weights[w + 2], w3 = weights[w + 3];
|
|
80
|
+
if (n === 1)
|
|
81
|
+
for (let x = 0; x < stride; x++)
|
|
82
|
+
next[o + x] = 0 + plane[s0 + x] * w0;
|
|
83
|
+
else if (n === 2)
|
|
84
|
+
for (let x = 0; x < stride; x++)
|
|
85
|
+
next[o + x] = Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1;
|
|
86
|
+
else if (n === 3) {
|
|
81
87
|
for (let x = 0; x < stride; x++)
|
|
82
|
-
next[o + x]
|
|
88
|
+
next[o + x] = Math.fround(Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1) + plane[s2 + x] * w2;
|
|
89
|
+
}
|
|
90
|
+
else if (n === 4) {
|
|
91
|
+
for (let x = 0; x < stride; x++)
|
|
92
|
+
next[o + x] = Math.fround(Math.fround(Math.fround(0 + plane[s0 + x] * w0) + plane[s1 + x] * w1) + plane[s2 + x] * w2) + plane[s3 + x] * w3;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (let x = 0; x < stride; x++) {
|
|
96
|
+
let sum = 0;
|
|
97
|
+
for (let k = 0, s = s0 + x; k < n; k++, s += stride)
|
|
98
|
+
sum = Math.fround(sum + plane[s] * weights[w + k]);
|
|
99
|
+
next[o + x] = sum;
|
|
100
|
+
}
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
plane = next;
|
package/dist/util/utf8.js
CHANGED
|
@@ -1,55 +1,8 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
const decoder = new TextDecoder();
|
|
1
3
|
export function encode(text) {
|
|
2
|
-
|
|
3
|
-
for (const char of text) {
|
|
4
|
-
const c = char.codePointAt(0);
|
|
5
|
-
if (c < 0x80)
|
|
6
|
-
out.push(c);
|
|
7
|
-
else if (c < 0x800)
|
|
8
|
-
out.push(0xc0 | c >> 6, 0x80 | c & 63);
|
|
9
|
-
else if (c < 0x10000)
|
|
10
|
-
out.push(0xe0 | c >> 12, 0x80 | c >> 6 & 63, 0x80 | c & 63);
|
|
11
|
-
else
|
|
12
|
-
out.push(0xf0 | c >> 18, 0x80 | c >> 12 & 63, 0x80 | c >> 6 & 63, 0x80 | c & 63);
|
|
13
|
-
}
|
|
14
|
-
return Uint8Array.from(out);
|
|
4
|
+
return encoder.encode(text);
|
|
15
5
|
}
|
|
16
6
|
export function decode(bytes) {
|
|
17
|
-
|
|
18
|
-
let i = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
19
|
-
while (i < bytes.length) {
|
|
20
|
-
const b = bytes[i++];
|
|
21
|
-
let c, extra;
|
|
22
|
-
if (b < 0x80) {
|
|
23
|
-
c = b;
|
|
24
|
-
extra = 0;
|
|
25
|
-
}
|
|
26
|
-
else if (b >= 0xc2 && b < 0xe0) {
|
|
27
|
-
c = b & 31;
|
|
28
|
-
extra = 1;
|
|
29
|
-
}
|
|
30
|
-
else if (b >= 0xe0 && b < 0xf0) {
|
|
31
|
-
c = b & 15;
|
|
32
|
-
extra = 2;
|
|
33
|
-
}
|
|
34
|
-
else if (b >= 0xf0 && b < 0xf5) {
|
|
35
|
-
c = b & 7;
|
|
36
|
-
extra = 3;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
out += '�';
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
let valid = true;
|
|
43
|
-
for (let k = 0; k < extra; k++) {
|
|
44
|
-
const next = bytes[i];
|
|
45
|
-
if (next === undefined || (next & 0xc0) !== 0x80) {
|
|
46
|
-
valid = false;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
c = c << 6 | next & 63;
|
|
50
|
-
i++;
|
|
51
|
-
}
|
|
52
|
-
out += valid && c <= 0x10ffff ? String.fromCodePoint(c) : '�';
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
7
|
+
return decoder.decode(bytes);
|
|
55
8
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '3.
|
|
1
|
+
export const VERSION = '3.2.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imagescript",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "zero-dependency javascript image manipulation",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -47,7 +47,14 @@
|
|
|
47
47
|
"bitmap",
|
|
48
48
|
"gif",
|
|
49
49
|
"picture",
|
|
50
|
-
"thumbnail"
|
|
50
|
+
"thumbnail",
|
|
51
|
+
"webp",
|
|
52
|
+
"apng",
|
|
53
|
+
"tiff",
|
|
54
|
+
"tga",
|
|
55
|
+
"animation",
|
|
56
|
+
"text rendering",
|
|
57
|
+
"font"
|
|
51
58
|
],
|
|
52
59
|
"author": "Mathis Mensing <mathis@matmen.dev>",
|
|
53
60
|
"license": "(AGPL-3.0-or-later OR MIT)",
|
package/dist/codecs/crc.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Slice-by-16 CRC-32, derived from "Fast CRC32 in JavaScript" (https://gist.github.com/101arrowz/e58695f7ccfdf74f60ba22018093edea)
|
|
3
|
-
* Copyright (c) 2021 Arjun Barrett, MIT License (see THIRD_PARTY_NOTICES.md)
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* CRC-32 (ISO 3309), as used by PNG and zlib. Processes 16 bytes per step (slice-by-16, see #12), byte by byte so
|
|
7
|
-
* it doesn't depend on alignment or endianness
|
|
8
|
-
*/
|
|
9
|
-
export declare function crc32(buffer: Uint8Array): number;
|
package/dist/codecs/exif.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reads the orientation tag (0x0112) from the first IFD of Exif data
|
|
3
|
-
* @param data a TIFF header and IFDs, optionally preceded by "Exif\0\0" (as in JPEG's APP1)
|
|
4
|
-
* @return the orientation (1-8), 1 if it is missing or invalid
|
|
5
|
-
*/
|
|
6
|
-
export declare function orientation(data: Uint8Array): number;
|
|
7
|
-
/**
|
|
8
|
-
* Reads tiff:Orientation from an XMP packet, as attribute or element
|
|
9
|
-
* @return the orientation (1-8), 1 if it is missing or invalid
|
|
10
|
-
*/
|
|
11
|
-
export declare function xmpOrientation(xmp: string): number;
|
package/dist/codecs/gif.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export interface GIFFrame {
|
|
2
|
-
x: number;
|
|
3
|
-
y: number;
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
/** Frame delay in centiseconds */
|
|
7
|
-
delay: number;
|
|
8
|
-
/** ImageScript disposal mode (0 any, 1 keep, 2 previous, 3 background) */
|
|
9
|
-
dispose: number;
|
|
10
|
-
/** RGBA pixel data */
|
|
11
|
-
pixels: Uint8Array;
|
|
12
|
-
}
|
|
13
|
-
export interface GIFData {
|
|
14
|
-
width: number;
|
|
15
|
-
height: number;
|
|
16
|
-
/** -1 loops forever, 0 plays once, n repeats n times */
|
|
17
|
-
loopCount: number;
|
|
18
|
-
frames: GIFFrame[];
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Encodes frames into a GIF
|
|
22
|
-
* @param {{width: number, height: number, loopCount?: number, frames: GIFFrame[]}} gif loopCount -1 loops forever, 0 plays once, n repeats n times
|
|
23
|
-
* @param {number} [quality=95] 1-100, scales the palette size (1 = 8 colors, 50 = 55, 90 and above = 256)
|
|
24
|
-
* @return {Uint8Array}
|
|
25
|
-
*/
|
|
26
|
-
export declare function encode({ width, height, loopCount, frames }: Omit<GIFData, 'loopCount'> & {
|
|
27
|
-
loopCount?: number;
|
|
28
|
-
}, quality?: number): Uint8Array;
|
|
29
|
-
/**
|
|
30
|
-
* Decodes a GIF into its individual (uncomposited) frames, zero-sized images are skipped
|
|
31
|
-
* @param {Uint8Array} data
|
|
32
|
-
* @param {boolean} [onlyFirstFrame=false]
|
|
33
|
-
* @return {{width: number, height: number, loopCount: number, frames: GIFFrame[]}}
|
|
34
|
-
*/
|
|
35
|
-
export declare function decode(data: Uint8Array, onlyFirstFrame?: boolean): GIFData;
|
|
36
|
-
/**
|
|
37
|
-
* Composites decoded frames onto the logical screen, applying their disposal modes
|
|
38
|
-
* @param {{width: number, height: number, frames: GIFFrame[]}} gif output of {@link decode}
|
|
39
|
-
* @return {Array<{pixels: Uint8Array, delay: number}>} full screen RGBA frames
|
|
40
|
-
*/
|
|
41
|
-
export declare function composite({ width, height, frames }: GIFData): Array<{
|
|
42
|
-
pixels: Uint8Array;
|
|
43
|
-
delay: number;
|
|
44
|
-
}>;
|
package/dist/codecs/jpeg.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* JPEG codec. This software is based in part on the work of the Independent JPEG Group: the IDCT, FDCT, upsampling,
|
|
3
|
-
* color conversion, progressive refinement and optimal Huffman tables are translated from libjpeg/libjpeg-turbo
|
|
4
|
-
* (IJG license, see licenses/README.ijg). The scan decoder follows the structure of pdf.js' jpg.js
|
|
5
|
-
* (Copyright 2014 Mozilla Foundation, Apache License 2.0). See THIRD_PARTY_NOTICES.md
|
|
6
|
-
*/
|
|
7
|
-
export interface DecodeOptions {
|
|
8
|
-
/** overrides the color transform detected from JFIF/Adobe markers (used by TIFF) */
|
|
9
|
-
transform?: 'ycc' | 'rgb' | 'cmyk' | null;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Decodes a JPEG into 8-bit RGBA pixel data
|
|
13
|
-
* Supports baseline, extended and progressive huffman coded JPEGs with any
|
|
14
|
-
* sampling factors, restart intervals and greyscale, YCbCr, RGB, CMYK and YCCK color
|
|
15
|
-
* The output matches libjpeg's defaults (islow IDCT, fancy upsampling)
|
|
16
|
-
* @param {Uint8Array} data
|
|
17
|
-
* @param {{transform?: 'ycc'|'rgb'|'cmyk'}} [options] overrides the color transform detected from JFIF/Adobe markers (used by TIFF)
|
|
18
|
-
* @return {{width: number, height: number, pixels: Uint8Array}}
|
|
19
|
-
*/
|
|
20
|
-
export declare function decode(data: Uint8Array, { transform }?: DecodeOptions): {
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
pixels: Uint8Array;
|
|
24
|
-
orientation: number;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Encodes RGBA pixel data into a baseline JPEG (alpha is ignored)
|
|
28
|
-
* Uses 4:2:0 chroma subsampling up to quality 90 and 4:4:4 above, with optimized huffman tables
|
|
29
|
-
* @param {Uint8Array} pixels
|
|
30
|
-
* @param {number} width
|
|
31
|
-
* @param {number} height
|
|
32
|
-
* @param {number} [quality=90] 1-100
|
|
33
|
-
* @return {Uint8Array}
|
|
34
|
-
*/
|
|
35
|
-
export declare function encode(pixels: Uint8Array, width: number, height: number, quality?: number): Uint8Array;
|
package/dist/codecs/png.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export interface EncodeOptions {
|
|
2
|
-
/** tEXt chunks, empty values are skipped */
|
|
3
|
-
text?: Record<string, string | undefined>;
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
channels: number;
|
|
7
|
-
depth?: number;
|
|
8
|
-
level?: number;
|
|
9
|
-
/** packed RGB palette, makes the data palette indices (1 channel) */
|
|
10
|
-
palette?: Uint8Array;
|
|
11
|
-
/** alpha per palette entry */
|
|
12
|
-
transparency?: Uint8Array;
|
|
13
|
-
/** whether to paeth filter the rows (pays off for truecolor and greyscale images) */
|
|
14
|
-
filter?: boolean;
|
|
15
|
-
}
|
|
16
|
-
export interface Decoded {
|
|
17
|
-
width: number;
|
|
18
|
-
height: number;
|
|
19
|
-
/** 8-bit RGBA */
|
|
20
|
-
pixels: Uint8Array;
|
|
21
|
-
/** Exif orientation (1-8) */
|
|
22
|
-
orientation: number;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Encodes raw pixel data into a PNG
|
|
26
|
-
* @param data Raw pixel data, rows packed MSB first for depths below 8
|
|
27
|
-
*/
|
|
28
|
-
export declare function encode(data: Uint8Array, { text, width, height, channels, depth, level, palette, transparency, filter: filterRows }: EncodeOptions): Promise<Uint8Array>;
|
|
29
|
-
/**
|
|
30
|
-
* Encodes 8-bit RGBA pixels into a PNG with the smallest lossless color type (see {@link pixelFormat}).
|
|
31
|
-
* Rows of non-palette images are paeth filtered (unless the data isn't compressed), palette indices aren't
|
|
32
|
-
* filtered (like libpng recommends)
|
|
33
|
-
*/
|
|
34
|
-
export declare function encodeRGBA(rgba: Uint8Array, width: number, height: number, level: number, text?: EncodeOptions['text']): Promise<Uint8Array>;
|
|
35
|
-
export interface APNGEncodeFrame {
|
|
36
|
-
x: number;
|
|
37
|
-
y: number;
|
|
38
|
-
width: number;
|
|
39
|
-
height: number;
|
|
40
|
-
/** milliseconds (stored as a fraction of up to 65535/65535 seconds) */
|
|
41
|
-
duration: number;
|
|
42
|
-
/** 0 none, 1 background (transparent), 2 previous */
|
|
43
|
-
dispose: number;
|
|
44
|
-
/** RGBA */
|
|
45
|
-
pixels: Uint8Array;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Encodes frames into an APNG. Frames are alpha blended onto the canvas ("over"); the first frame is also the
|
|
49
|
-
* default image that viewers without APNG support show
|
|
50
|
-
* @param plays How often to play the animation, 0 = forever
|
|
51
|
-
*/
|
|
52
|
-
export declare function encodeAnimation(frames: APNGEncodeFrame[], width: number, height: number, plays: number, level: number): Promise<Uint8Array>;
|
|
53
|
-
/**
|
|
54
|
-
* Decodes a PNG into 8-bit RGBA pixel data (for APNGs, the default image)
|
|
55
|
-
* Supports all color types, bit depths, tRNS transparency and adam7 interlacing
|
|
56
|
-
* The orientation comes from the eXIf chunk, or, if there is none, from the Exif text ImageMagick wrote before eXIf
|
|
57
|
-
* existed ("Raw profile type exif") or from the XMP packet
|
|
58
|
-
*/
|
|
59
|
-
export declare function decode(array: Uint8Array): Promise<Decoded>;
|
|
60
|
-
export interface APNGFrame {
|
|
61
|
-
x: number;
|
|
62
|
-
y: number;
|
|
63
|
-
width: number;
|
|
64
|
-
height: number;
|
|
65
|
-
/** milliseconds */
|
|
66
|
-
duration: number;
|
|
67
|
-
/** 0 none, 1 background (transparent), 2 previous */
|
|
68
|
-
dispose: number;
|
|
69
|
-
/** 0 source (replace), 1 over */
|
|
70
|
-
blend: number;
|
|
71
|
-
pixels: Uint8Array;
|
|
72
|
-
}
|
|
73
|
-
/** Whether the PNG is an APNG (has an acTL chunk) */
|
|
74
|
-
export declare function isAnimated(array: Uint8Array): boolean;
|
|
75
|
-
/**
|
|
76
|
-
* Decodes an APNG (https://wiki.mozilla.org/APNG_Specification) into its (uncomposited) frames; a plain PNG becomes
|
|
77
|
-
* a single frame
|
|
78
|
-
* @return plays: 0 = forever
|
|
79
|
-
*/
|
|
80
|
-
export declare function decodeAnimation(array: Uint8Array, onlyFirstFrame?: boolean): Promise<{
|
|
81
|
-
width: number;
|
|
82
|
-
height: number;
|
|
83
|
-
plays: number;
|
|
84
|
-
frames: APNGFrame[];
|
|
85
|
-
}>;
|
|
86
|
-
/**
|
|
87
|
-
* Composites APNG frames onto the canvas, as the APNG specification describes (the integer "over" operation of its
|
|
88
|
-
* blend_op section, https://wiki.mozilla.org/APNG_Specification)
|
|
89
|
-
*/
|
|
90
|
-
export declare function compositeAnimation({ width, height, frames }: {
|
|
91
|
-
width: number;
|
|
92
|
-
height: number;
|
|
93
|
-
frames: APNGFrame[];
|
|
94
|
-
}): Array<{
|
|
95
|
-
pixels: Uint8Array;
|
|
96
|
-
duration: number;
|
|
97
|
-
}>;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reduces RGBA pixel data to an indexed palette
|
|
3
|
-
* Pixels with alpha < 128 are mapped to a dedicated transparent index
|
|
4
|
-
* @param {Uint8Array} pixels RGBA pixel data
|
|
5
|
-
* @param {number} width Image width in pixels (needed for dithering)
|
|
6
|
-
* @param {{maxColors?: number, quality?: number}} [options] quality (1-100) scales the palette size exponentially (1 = 8 colors, 50 = 55, 90 and above = 256)
|
|
7
|
-
* @return {{palette: Uint8Array, indices: Uint8Array, transparentIndex: number}} palette is packed RGB, transparentIndex is -1 if unused
|
|
8
|
-
*/
|
|
9
|
-
export interface QuantizeOptions {
|
|
10
|
-
maxColors?: number;
|
|
11
|
-
quality?: number;
|
|
12
|
-
}
|
|
13
|
-
export interface Quantized {
|
|
14
|
-
/** packed RGB */
|
|
15
|
-
palette: Uint8Array;
|
|
16
|
-
indices: Uint8Array;
|
|
17
|
-
/** -1 if no pixel is transparent */
|
|
18
|
-
transparentIndex: number;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Finds the color of the largest cluster among the opaque pixels that pass the filter
|
|
22
|
-
* @return packed RGB, or null if no pixel qualifies
|
|
23
|
-
*/
|
|
24
|
-
export declare function dominantColor(pixels: Uint8Array, include: (r: number, g: number, b: number) => boolean, clusters?: number): [number, number, number] | null;
|
|
25
|
-
export declare function quantize(pixels: Uint8Array, width: number, { maxColors, quality }?: QuantizeOptions): Quantized;
|