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
package/dist/image.js
CHANGED
|
@@ -9,9 +9,11 @@ import * as svgRenderer from "./render/svg.js";
|
|
|
9
9
|
import { view } from "./util/mem.js";
|
|
10
10
|
import { ImageType } from "./image-type.js";
|
|
11
11
|
import { TextLayout } from "./text-layout.js";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveMetadata, pngText, exifData } from "./metadata.js";
|
|
13
|
+
import { encode as utf8 } from "./util/utf8.js";
|
|
13
14
|
import { resample, BOX, TRIANGLE, CUBIC } from "./util/resample.js";
|
|
14
15
|
import { gaussianBlur, premultiply, unpremultiply } from "./util/blur.js";
|
|
16
|
+
import { isOptions } from "./util/options.js";
|
|
15
17
|
export class Image {
|
|
16
18
|
__width__;
|
|
17
19
|
__height__;
|
|
@@ -206,8 +208,8 @@ export class Image {
|
|
|
206
208
|
static get RESIZE_AUTO() {
|
|
207
209
|
return -1;
|
|
208
210
|
}
|
|
209
|
-
scale(factor,
|
|
210
|
-
const image = this.__scale__(factor,
|
|
211
|
+
scale(factor, modeOrOptions) {
|
|
212
|
+
const image = this.__scale__(factor, resizeMode(modeOrOptions));
|
|
211
213
|
return this.__apply__(image);
|
|
212
214
|
}
|
|
213
215
|
__scale__(factor, mode = Image.RESIZE_BILINEAR) {
|
|
@@ -215,23 +217,27 @@ export class Image {
|
|
|
215
217
|
return this;
|
|
216
218
|
return this.__resize__(this.width * factor, this.height * factor, mode);
|
|
217
219
|
}
|
|
218
|
-
resize(width, height,
|
|
220
|
+
resize(width, height, modeOrOptions) {
|
|
221
|
+
const mode = resizeMode(modeOrOptions);
|
|
219
222
|
const image = this.__resize__(width, height, mode);
|
|
220
223
|
return this.__apply__(image);
|
|
221
224
|
}
|
|
222
|
-
contain(width, height,
|
|
225
|
+
contain(width, height, modeOrOptions) {
|
|
226
|
+
const mode = resizeMode(modeOrOptions);
|
|
223
227
|
width = Math.floor(width);
|
|
224
228
|
height = Math.floor(height);
|
|
225
229
|
const scale = Math.min(width / this.width, height / this.height);
|
|
226
230
|
return this.resize(Math.max(1, Math.min(width, Math.round(this.width * scale))), Math.max(1, Math.min(height, Math.round(this.height * scale))), mode);
|
|
227
231
|
}
|
|
228
|
-
fit(width, height,
|
|
232
|
+
fit(width, height, modeOrOptions) {
|
|
233
|
+
const mode = resizeMode(modeOrOptions);
|
|
229
234
|
const result = new Image(width, height);
|
|
230
235
|
this.contain(width, height, mode);
|
|
231
236
|
result.composite(this, (width - this.width) / 2, (height - this.height) / 2);
|
|
232
237
|
return this.__apply__(result);
|
|
233
238
|
}
|
|
234
|
-
cover(width, height,
|
|
239
|
+
cover(width, height, modeOrOptions) {
|
|
240
|
+
const mode = resizeMode(modeOrOptions);
|
|
235
241
|
width = Math.floor(width);
|
|
236
242
|
height = Math.floor(height);
|
|
237
243
|
const scale = Math.max(width / this.width, height / this.height);
|
|
@@ -282,6 +288,51 @@ export class Image {
|
|
|
282
288
|
throw new RangeError('Crop region does not overlap the image');
|
|
283
289
|
return this.__apply__(this.__crop__(left, top, right - left, bottom - top));
|
|
284
290
|
}
|
|
291
|
+
trim(color, options = {}) {
|
|
292
|
+
const bounds = this.__trim_bounds__(color, options);
|
|
293
|
+
return bounds ? this.__apply__(this.__crop__(...bounds)) : this;
|
|
294
|
+
}
|
|
295
|
+
__trim_bounds__(color, { tolerance = 0 }) {
|
|
296
|
+
if (!(tolerance >= 0 && tolerance <= 1))
|
|
297
|
+
throw new RangeError('Invalid tolerance, it has to be between 0 and 1');
|
|
298
|
+
const bitmap = this.bitmap;
|
|
299
|
+
const { width, height } = this;
|
|
300
|
+
const c = color === undefined ? [bitmap[0], bitmap[1], bitmap[2], bitmap[3]] : [color >>> 24, color >> 16 & 255, color >> 8 & 255, color & 255];
|
|
301
|
+
const limit = tolerance * 255;
|
|
302
|
+
const matches = (x, y) => {
|
|
303
|
+
const i = (y * width + x) * 4;
|
|
304
|
+
if (Math.abs(bitmap[i + 3] - c[3]) > limit)
|
|
305
|
+
return false;
|
|
306
|
+
if (bitmap[i + 3] === 0 && c[3] === 0)
|
|
307
|
+
return true;
|
|
308
|
+
return Math.abs(bitmap[i] - c[0]) <= limit && Math.abs(bitmap[i + 1] - c[1]) <= limit && Math.abs(bitmap[i + 2] - c[2]) <= limit;
|
|
309
|
+
};
|
|
310
|
+
const rowMatches = (y) => {
|
|
311
|
+
for (let x = 0; x < width; x++)
|
|
312
|
+
if (!matches(x, y))
|
|
313
|
+
return false;
|
|
314
|
+
return true;
|
|
315
|
+
};
|
|
316
|
+
const columnMatches = (x, top, bottom) => {
|
|
317
|
+
for (let y = top; y < bottom; y++)
|
|
318
|
+
if (!matches(x, y))
|
|
319
|
+
return false;
|
|
320
|
+
return true;
|
|
321
|
+
};
|
|
322
|
+
let top = 0, bottom = height;
|
|
323
|
+
while (top < height && rowMatches(top))
|
|
324
|
+
top++;
|
|
325
|
+
if (top === height)
|
|
326
|
+
return null;
|
|
327
|
+
while (rowMatches(bottom - 1))
|
|
328
|
+
bottom--;
|
|
329
|
+
let left = 0, right = width;
|
|
330
|
+
while (columnMatches(left, top, bottom))
|
|
331
|
+
left++;
|
|
332
|
+
while (columnMatches(right - 1, top, bottom))
|
|
333
|
+
right--;
|
|
334
|
+
return [left, top, right - left, bottom - top];
|
|
335
|
+
}
|
|
285
336
|
__crop__(x, y, width, height) {
|
|
286
337
|
x = ~~x;
|
|
287
338
|
y = ~~y;
|
|
@@ -369,25 +420,29 @@ export class Image {
|
|
|
369
420
|
}
|
|
370
421
|
return this;
|
|
371
422
|
}
|
|
372
|
-
opacity(opacity,
|
|
423
|
+
opacity(opacity, absoluteOrOptions = false) {
|
|
424
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
373
425
|
if (isNaN(opacity) || opacity < 0)
|
|
374
426
|
throw new RangeError('Invalid opacity value');
|
|
375
427
|
this.__set_channel_value__(opacity, absolute, 3);
|
|
376
428
|
return this;
|
|
377
429
|
}
|
|
378
|
-
red(factor,
|
|
430
|
+
red(factor, absoluteOrOptions = false) {
|
|
431
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
379
432
|
if (isNaN(factor) || factor < 0)
|
|
380
433
|
throw new RangeError('Invalid red factor');
|
|
381
434
|
this.__set_channel_value__(factor, absolute, 0);
|
|
382
435
|
return this;
|
|
383
436
|
}
|
|
384
|
-
green(factor,
|
|
437
|
+
green(factor, absoluteOrOptions = false) {
|
|
438
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
385
439
|
if (isNaN(factor) || factor < 0)
|
|
386
440
|
throw new RangeError('Invalid green factor');
|
|
387
441
|
this.__set_channel_value__(factor, absolute, 1);
|
|
388
442
|
return this;
|
|
389
443
|
}
|
|
390
|
-
blue(factor,
|
|
444
|
+
blue(factor, absoluteOrOptions = false) {
|
|
445
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
391
446
|
if (isNaN(factor) || factor < 0)
|
|
392
447
|
throw new RangeError('Invalid blue factor');
|
|
393
448
|
this.__set_channel_value__(factor, absolute, 2);
|
|
@@ -397,18 +452,22 @@ export class Image {
|
|
|
397
452
|
for (let i = offset; i < this.bitmap.length; i += 4)
|
|
398
453
|
this.bitmap[i] = value * (absolute ? 255 : this.bitmap[i]);
|
|
399
454
|
}
|
|
400
|
-
lightness(value,
|
|
455
|
+
lightness(value, absoluteOrOptions = false) {
|
|
456
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
401
457
|
if (isNaN(value) || value < 0)
|
|
402
458
|
throw new RangeError('Invalid lightness value');
|
|
403
459
|
return this.__map_hsla__(c => { c.l = value * (absolute ? 1 : c.l); });
|
|
404
460
|
}
|
|
405
|
-
saturation(value,
|
|
461
|
+
saturation(value, absoluteOrOptions = false) {
|
|
462
|
+
const absolute = isOptions(absoluteOrOptions) ? absoluteOrOptions.absolute ?? false : absoluteOrOptions;
|
|
406
463
|
if (isNaN(value) || value < 0)
|
|
407
464
|
throw new RangeError('Invalid saturation value');
|
|
408
465
|
return this.__map_hsla__(c => { c.s = value * (absolute ? 1 : c.s); });
|
|
409
466
|
}
|
|
410
|
-
composite(source,
|
|
467
|
+
composite(source, xOrOptions = 0, yArgument = 0, optionsArgument = {}) {
|
|
468
|
+
const options = isOptions(xOrOptions) ? xOrOptions : optionsArgument;
|
|
411
469
|
const { opacity = 1, mode = 'normal' } = options;
|
|
470
|
+
let x = isOptions(xOrOptions) ? xOrOptions.x ?? 0 : xOrOptions, y = isOptions(xOrOptions) ? xOrOptions.y ?? 0 : yArgument;
|
|
412
471
|
x |= 0;
|
|
413
472
|
y |= 0;
|
|
414
473
|
if (isNaN(opacity) || opacity < 0 || opacity > 1)
|
|
@@ -558,7 +617,8 @@ export class Image {
|
|
|
558
617
|
unpremultiply(pixels, this.bitmap);
|
|
559
618
|
return this;
|
|
560
619
|
}
|
|
561
|
-
sharpen(
|
|
620
|
+
sharpen(amountOrOptions = 1, radiusArgument = 1) {
|
|
621
|
+
const { amount = 1, radius = 1 } = isOptions(amountOrOptions) ? amountOrOptions : { amount: amountOrOptions, radius: radiusArgument };
|
|
562
622
|
if (isNaN(amount) || amount < 0)
|
|
563
623
|
throw new RangeError('Invalid sharpen amount');
|
|
564
624
|
if (isNaN(radius) || radius <= 0)
|
|
@@ -820,7 +880,8 @@ export class Image {
|
|
|
820
880
|
throw new RangeError(`Invalid gradient position: ${position}`);
|
|
821
881
|
};
|
|
822
882
|
}
|
|
823
|
-
roundCorners(
|
|
883
|
+
roundCorners(radiusOrOptions) {
|
|
884
|
+
let radius = (isOptions(radiusOrOptions) ? radiusOrOptions.radius : radiusOrOptions) ?? Math.min(this.width, this.height) / 4;
|
|
824
885
|
radius = Math.max(0, Math.min(radius, this.width / 2, this.height / 2));
|
|
825
886
|
const size = Math.ceil(radius);
|
|
826
887
|
for (let y = 0; y < size; y++) {
|
|
@@ -852,7 +913,8 @@ export class Image {
|
|
|
852
913
|
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (a & 0xff)) >>> 0;
|
|
853
914
|
};
|
|
854
915
|
}
|
|
855
|
-
fisheye(
|
|
916
|
+
fisheye(strengthOrOptions) {
|
|
917
|
+
const strength = (isOptions(strengthOrOptions) ? strengthOrOptions.strength : strengthOrOptions) ?? 2;
|
|
856
918
|
const r = new Image(this.width, this.height);
|
|
857
919
|
const w = this.width;
|
|
858
920
|
const h = this.height;
|
|
@@ -877,37 +939,31 @@ export class Image {
|
|
|
877
939
|
}
|
|
878
940
|
return this.__apply__(r);
|
|
879
941
|
}
|
|
880
|
-
async encode(
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
compression =
|
|
884
|
-
|
|
942
|
+
async encode(compressionOrOptions = 1, metadataArgument = {}) {
|
|
943
|
+
let compression = 1, metadata = metadataArgument;
|
|
944
|
+
if (!isOptions(compressionOrOptions))
|
|
945
|
+
compression = compressionOrOptions;
|
|
946
|
+
else if ('compression' in compressionOrOptions || 'metadata' in compressionOrOptions)
|
|
947
|
+
({ compression = 1, metadata = {} } = compressionOrOptions);
|
|
948
|
+
else
|
|
949
|
+
metadata = compressionOrOptions;
|
|
885
950
|
const level = Math.min(9, Math.max(0, ~~compression));
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
return
|
|
902
|
-
}
|
|
903
|
-
async encodeJPEG(quality = 90) {
|
|
904
|
-
return jpeg.encode(this.__bytes__(), this.width, this.height, quality);
|
|
905
|
-
}
|
|
906
|
-
async encodeWebP() {
|
|
907
|
-
return webp.encode(this.__bytes__(), this.width, this.height);
|
|
908
|
-
}
|
|
909
|
-
async encodeTGA() {
|
|
910
|
-
return tga.encode(this.__bytes__(), this.width, this.height);
|
|
951
|
+
return png.encodeRGBA(this.__bytes__(), this.width, this.height, level, pngText(resolveMetadata(metadata)));
|
|
952
|
+
}
|
|
953
|
+
encodePNG(compressionOrOptions, metadata) {
|
|
954
|
+
return this.encode(compressionOrOptions, metadata);
|
|
955
|
+
}
|
|
956
|
+
async encodeJPEG(qualityOrOptions = 90) {
|
|
957
|
+
const { quality = 90, metadata = {} } = isOptions(qualityOrOptions) ? qualityOrOptions : { quality: qualityOrOptions };
|
|
958
|
+
const m = resolveMetadata(metadata);
|
|
959
|
+
return jpeg.encode(this.__bytes__(), this.width, this.height, quality, { exif: exifData(m), comment: m.comment ? utf8(m.comment) : undefined });
|
|
960
|
+
}
|
|
961
|
+
async encodeWebP({ metadata = {} } = {}) {
|
|
962
|
+
return webp.encode(this.__bytes__(), this.width, this.height, exifData(resolveMetadata(metadata)));
|
|
963
|
+
}
|
|
964
|
+
async encodeTGA({ metadata = {} } = {}) {
|
|
965
|
+
const { author, comment, creationTime: time, software } = resolveMetadata(metadata);
|
|
966
|
+
return tga.encode(this.__bytes__(), this.width, this.height, { author, comment, time, software });
|
|
911
967
|
}
|
|
912
968
|
static async decode(input, options = {}) {
|
|
913
969
|
const { applyOrientation = true } = options;
|
|
@@ -988,7 +1044,8 @@ export class Image {
|
|
|
988
1044
|
static get SVG_MODE_HEIGHT() {
|
|
989
1045
|
return 3;
|
|
990
1046
|
}
|
|
991
|
-
static async renderSVG(svg,
|
|
1047
|
+
static async renderSVG(svg, sizeOrOptions = 1, modeArgument = Image.SVG_MODE_SCALE) {
|
|
1048
|
+
const { size = 1, mode = Image.SVG_MODE_SCALE } = isOptions(sizeOrOptions) ? sizeOrOptions : { size: sizeOrOptions, mode: modeArgument };
|
|
992
1049
|
if (![1, 2, 3].includes(mode))
|
|
993
1050
|
throw new Error('Invalid SVG scaling mode');
|
|
994
1051
|
if (mode === Image.SVG_MODE_SCALE && size <= 0)
|
|
@@ -1000,8 +1057,9 @@ export class Image {
|
|
|
1000
1057
|
image.bitmap.set(pixels);
|
|
1001
1058
|
return image;
|
|
1002
1059
|
}
|
|
1003
|
-
static async renderText(font, size, text,
|
|
1004
|
-
const
|
|
1060
|
+
static async renderText(font, size, text, colorOrOptions = 0xffffffff, layoutArgument, styleArgument = {}) {
|
|
1061
|
+
const options = isOptions(colorOrOptions) ? colorOrOptions : { ...styleArgument, color: colorOrOptions, layout: layoutArgument };
|
|
1062
|
+
const { color = 0xffffffff, layout = new TextLayout(), strokeWidth = 0, strokeColor = 0x000000ff, hinting = true } = options;
|
|
1005
1063
|
if (isNaN(strokeWidth) || strokeWidth < 0)
|
|
1006
1064
|
throw new RangeError('Invalid stroke width');
|
|
1007
1065
|
const rendered = textRenderer.render(view(font), size, text, layout, hinting);
|
|
@@ -1076,6 +1134,9 @@ function hue2rgb(p, q, t) {
|
|
|
1076
1134
|
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1077
1135
|
return p;
|
|
1078
1136
|
}
|
|
1137
|
+
function resizeMode(modeOrOptions) {
|
|
1138
|
+
return (isOptions(modeOrOptions) ? modeOrOptions.mode : modeOrOptions) ?? Image.RESIZE_BILINEAR;
|
|
1139
|
+
}
|
|
1079
1140
|
const hardLight = (b, s) => s <= 0.5 ? b * 2 * s : 1 - (1 - b) * (1 - (2 * s - 1));
|
|
1080
1141
|
const BLEND_MODES = {
|
|
1081
1142
|
'normal': (_b, s) => s,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { BinaryData } from './util/mem.js';
|
|
2
|
-
import { Image } from './image.js';
|
|
2
|
+
import { Image, DecodeOptions } from './image.js';
|
|
3
3
|
import { GIF } from './gif.js';
|
|
4
|
-
import { Animation } from './animation.js';
|
|
4
|
+
import { Animation, AnimationDecodeOptions } from './animation.js';
|
|
5
5
|
export { Image } from './image.js';
|
|
6
|
-
export type { Color, ColorFunction, ResizeMode, SVGScaleMode, PNGMetadata, TextStyle, DecodeOptions, CompositeOptions, BlendMode, CropCircleOptions, DominantColorOptions, RotateOptions, ColorToAlphaOptions } from './image.js';
|
|
6
|
+
export type { Color, ColorFunction, ResizeMode, SVGScaleMode, PNGMetadata, TextStyle, DecodeOptions, CompositeOptions, BlendMode, CropCircleOptions, DominantColorOptions, RotateOptions, ColorToAlphaOptions, ResizeOptions, AdjustOptions, SharpenOptions, PNGEncodeOptions, JPEGEncodeOptions, EncodeOptions, SVGOptions, TextOptions, RoundCornersOptions, FisheyeOptions, TrimOptions } from './image.js';
|
|
7
7
|
export { Frame } from './frame.js';
|
|
8
|
-
export type { FrameDisposalModeName, FrameDisposalModeId } from './frame.js';
|
|
8
|
+
export type { FrameDisposalModeName, FrameDisposalModeId, FrameOptions } from './frame.js';
|
|
9
9
|
export { GIF } from './gif.js';
|
|
10
10
|
export { Animation } from './animation.js';
|
|
11
|
+
export type { AnimationOptions, AnimationDecodeOptions, AnimationEncodeOptions, GIFEncodeOptions, APNGEncodeOptions } from './animation.js';
|
|
11
12
|
export { TextLayout } from './text-layout.js';
|
|
12
13
|
export type { TextLayoutOptions, WrapStyle, HorizontalAlign, VerticalAlign } from './text-layout.js';
|
|
13
14
|
export { ImageType } from './image-type.js';
|
|
14
15
|
export type { ImageTypeName } from './image-type.js';
|
|
15
16
|
export type { BinaryData } from './util/mem.js';
|
|
17
|
+
export type { Metadata } from './metadata.js';
|
|
18
|
+
/**
|
|
19
|
+
* Decodes any supported image: GIFs are returned as {@link GIF} (an {@link Animation}), animated WebPs as
|
|
20
|
+
* {@link Animation}, everything else (including APNGs, as their default image) as {@link Image}.
|
|
21
|
+
* {@link Animation.decode} decodes the frames of GIFs, APNGs and WebPs
|
|
22
|
+
* @param data The image data
|
|
23
|
+
* @param options See {@link AnimationDecodeOptions} (for animations) and {@link DecodeOptions} (for images)
|
|
24
|
+
*/
|
|
25
|
+
export declare function decode(data: BinaryData, options?: AnimationDecodeOptions & DecodeOptions): Promise<GIF | Animation | Image>;
|
|
16
26
|
/**
|
|
17
27
|
* Decodes any supported image: GIFs are returned as {@link GIF} (an {@link Animation}), animated WebPs as
|
|
18
28
|
* {@link Animation}, everything else (including APNGs, as their default image) as {@link Image}.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Image } from "./image.js";
|
|
2
2
|
import { GIF } from "./gif.js";
|
|
3
3
|
import { Animation } from "./animation.js";
|
|
4
|
+
import { isOptions } from "./util/options.js";
|
|
4
5
|
import * as webp from "./codecs/webp.js";
|
|
5
6
|
import { view } from "./util/mem.js";
|
|
6
7
|
import { ImageType } from "./image-type.js";
|
|
@@ -10,11 +11,12 @@ export { GIF } from "./gif.js";
|
|
|
10
11
|
export { Animation } from "./animation.js";
|
|
11
12
|
export { TextLayout } from "./text-layout.js";
|
|
12
13
|
export { ImageType } from "./image-type.js";
|
|
13
|
-
export function decode(data,
|
|
14
|
+
export function decode(data, onlyFirstFrameOrOptions = false) {
|
|
15
|
+
const { onlyFirstFrame = false, applyOrientation } = isOptions(onlyFirstFrameOrOptions) ? onlyFirstFrameOrOptions : { onlyFirstFrame: onlyFirstFrameOrOptions };
|
|
14
16
|
const type = ImageType.getType(data);
|
|
15
17
|
if (type === 'gif')
|
|
16
|
-
return GIF.decode(data,
|
|
18
|
+
return GIF.decode(data, { onlyFirstFrame });
|
|
17
19
|
if (type === 'webp' && webp.isAnimated(view(data)))
|
|
18
|
-
return Animation.decode(data,
|
|
19
|
-
return Image.decode(data);
|
|
20
|
+
return Animation.decode(data, { onlyFirstFrame });
|
|
21
|
+
return Image.decode(data, { applyOrientation });
|
|
20
22
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata written by the encoders. Each format stores the fields it has a place for:
|
|
3
|
+
*
|
|
4
|
+
* | Format | Fields |
|
|
5
|
+
* |---|---|
|
|
6
|
+
* | PNG, APNG | all (as text chunks) |
|
|
7
|
+
* | JPEG | author, description, copyright, creation time, software (Exif); comment (comment marker) |
|
|
8
|
+
* | WebP | author, description, copyright, creation time, software (Exif) |
|
|
9
|
+
* | GIF | software, comment (comment extensions) |
|
|
10
|
+
* | TGA | author, comment, creation time, software (extension area) |
|
|
11
|
+
*
|
|
12
|
+
* Empty fields are left out
|
|
13
|
+
*/
|
|
14
|
+
export interface Metadata {
|
|
15
|
+
/** The image's title */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** The image's author */
|
|
18
|
+
author?: string;
|
|
19
|
+
/** The image's description */
|
|
20
|
+
description?: string;
|
|
21
|
+
/** The image's copyright info */
|
|
22
|
+
copyright?: string;
|
|
23
|
+
/** The image's creation time. Default: now */
|
|
24
|
+
creationTime?: string | number | Date;
|
|
25
|
+
/** The software used to create the image. Default: `'github.com/matmen/ImageScript vX.X.X'`, `''` leaves it out */
|
|
26
|
+
software?: string;
|
|
27
|
+
/** A disclaimer for the image */
|
|
28
|
+
disclaimer?: string;
|
|
29
|
+
/** A warning for the image */
|
|
30
|
+
warning?: string;
|
|
31
|
+
/** The image's source */
|
|
32
|
+
source?: string;
|
|
33
|
+
/** A comment for the image */
|
|
34
|
+
comment?: string;
|
|
35
|
+
}
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { VERSION } from "./version.js";
|
|
2
|
+
import { encode as utf8 } from "./util/utf8.js";
|
|
3
|
+
import * as exif from "./codecs/exif.js";
|
|
4
|
+
const TEXT_FIELDS = ['title', 'author', 'description', 'copyright', 'software', 'disclaimer', 'warning', 'source', 'comment'];
|
|
5
|
+
export function resolveMetadata(metadata = {}) {
|
|
6
|
+
const { creationTime, software } = metadata || {};
|
|
7
|
+
const time = new Date(creationTime === undefined ? Date.now() : creationTime);
|
|
8
|
+
if (isNaN(time.getTime()))
|
|
9
|
+
throw new RangeError('Invalid creation time');
|
|
10
|
+
const resolved = { creationTime: time };
|
|
11
|
+
for (const key of TEXT_FIELDS) {
|
|
12
|
+
const value = key === 'software' && software === undefined ? `github.com/matmen/ImageScript v${VERSION}` : metadata?.[key];
|
|
13
|
+
if (value)
|
|
14
|
+
resolved[key] = String(value);
|
|
15
|
+
}
|
|
16
|
+
return resolved;
|
|
17
|
+
}
|
|
18
|
+
export function pngText(m) {
|
|
19
|
+
return {
|
|
20
|
+
Title: m.title,
|
|
21
|
+
Author: m.author,
|
|
22
|
+
Description: m.description,
|
|
23
|
+
Copyright: m.copyright,
|
|
24
|
+
'Creation Time': m.creationTime.toUTCString(),
|
|
25
|
+
Software: m.software,
|
|
26
|
+
Disclaimer: m.disclaimer,
|
|
27
|
+
Warning: m.warning,
|
|
28
|
+
Source: m.source,
|
|
29
|
+
Comment: m.comment
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function exifData(m) {
|
|
33
|
+
return exif.encode({
|
|
34
|
+
description: m.description,
|
|
35
|
+
software: m.software,
|
|
36
|
+
dateTime: m.creationTime,
|
|
37
|
+
artist: m.author,
|
|
38
|
+
copyright: m.copyright
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function gifComments(m) {
|
|
42
|
+
return [m.software, m.comment].filter((c) => !!c).map(utf8);
|
|
43
|
+
}
|
package/dist/render/svg.js
CHANGED
|
@@ -875,14 +875,25 @@ class Layer {
|
|
|
875
875
|
width;
|
|
876
876
|
height;
|
|
877
877
|
data;
|
|
878
|
+
x0 = Infinity;
|
|
879
|
+
y0 = Infinity;
|
|
880
|
+
x1 = -Infinity;
|
|
881
|
+
y1 = -Infinity;
|
|
878
882
|
constructor(width, height) {
|
|
879
883
|
this.width = width;
|
|
880
884
|
this.height = height;
|
|
881
885
|
this.data = new Float32Array(width * height * 4);
|
|
882
886
|
}
|
|
887
|
+
extend(x0, y0, x1, y1) {
|
|
888
|
+
this.x0 = Math.min(this.x0, x0);
|
|
889
|
+
this.y0 = Math.min(this.y0, y0);
|
|
890
|
+
this.x1 = Math.max(this.x1, x1);
|
|
891
|
+
this.y1 = Math.max(this.y1, y1);
|
|
892
|
+
}
|
|
883
893
|
fill({ x0, y0, w, h, data }, paint) {
|
|
884
894
|
const d = this.data;
|
|
885
895
|
const solid = paint.solid;
|
|
896
|
+
this.extend(x0, y0, x0 + w, y0 + h);
|
|
886
897
|
for (let y = 0; y < h; y++) {
|
|
887
898
|
let i = ((y0 + y) * this.width + x0) * 4;
|
|
888
899
|
for (let x = 0, o = y * w; x < w; x++, o++, i += 4) {
|
|
@@ -921,33 +932,40 @@ class Layer {
|
|
|
921
932
|
}
|
|
922
933
|
}
|
|
923
934
|
composite(layer, opacity, mask) {
|
|
935
|
+
if (layer.x1 <= layer.x0)
|
|
936
|
+
return;
|
|
937
|
+
this.extend(layer.x0, layer.y0, layer.x1, layer.y1);
|
|
924
938
|
const s = layer.data, d = this.data;
|
|
925
|
-
for (let
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
939
|
+
for (let y = layer.y0; y < layer.y1; y++) {
|
|
940
|
+
for (let p = y * this.width + layer.x0, end = y * this.width + layer.x1, i = p * 4; p < end; p++, i += 4) {
|
|
941
|
+
if (s[i + 3] <= 0)
|
|
942
|
+
continue;
|
|
943
|
+
const f = mask ? opacity * mask[p] : opacity;
|
|
944
|
+
if (f <= 0)
|
|
945
|
+
continue;
|
|
946
|
+
const a = s[i + 3] * f;
|
|
947
|
+
const inv = 1 - a;
|
|
948
|
+
d[i] = s[i] * f + d[i] * inv;
|
|
949
|
+
d[i + 1] = s[i + 1] * f + d[i + 1] * inv;
|
|
950
|
+
d[i + 2] = s[i + 2] * f + d[i + 2] * inv;
|
|
951
|
+
d[i + 3] = a + d[i + 3] * inv;
|
|
952
|
+
}
|
|
937
953
|
}
|
|
938
954
|
}
|
|
939
955
|
toRGBA() {
|
|
940
956
|
const out = new Uint8ClampedArray(this.width * this.height * 4);
|
|
941
957
|
const d = this.data;
|
|
942
|
-
for (let
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
958
|
+
for (let y = this.y0; y < this.y1; y++) {
|
|
959
|
+
for (let i = (y * this.width + this.x0) * 4, end = (y * this.width + this.x1) * 4; i < end; i += 4) {
|
|
960
|
+
const a = d[i + 3];
|
|
961
|
+
if (a <= 0)
|
|
962
|
+
continue;
|
|
963
|
+
const f = a >= 1 ? 255 : 255 / a;
|
|
964
|
+
out[i] = d[i] * f;
|
|
965
|
+
out[i + 1] = d[i + 1] * f;
|
|
966
|
+
out[i + 2] = d[i + 2] * f;
|
|
967
|
+
out[i + 3] = a * 255;
|
|
968
|
+
}
|
|
951
969
|
}
|
|
952
970
|
return new Uint8Array(out.buffer);
|
|
953
971
|
}
|