imagescript 3.3.0 → 3.4.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 +28 -8
- package/THIRD_PARTY_NOTICES.md +167 -2
- package/dist/animation.d.ts +18 -8
- package/dist/animation.js +23 -2
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +2 -1
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/webp.js +36 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +47 -6
- package/dist/image.js +49 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/render/hinting.js +26 -18
- package/dist/render/layout.js +1 -1
- package/dist/render/svg.js +1 -1
- package/dist/render/unicode.js +1 -9
- package/dist/util/bytes.js +9 -0
- package/dist/util/resample.js +4 -2
- package/dist/version.js +1 -1
- package/package.json +25 -2
package/dist/codecs/webp.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as vp8l from "./vp8l.js";
|
|
6
6
|
import * as vp8 from "./vp8.js";
|
|
7
|
+
import { encodeVP8, encodeAlpha } from "./vp8-enc.js";
|
|
8
|
+
import { rgbaToYUV } from "./vp8-enc-dsp.js";
|
|
7
9
|
import { orientation as exifOrientation } from "./exif.js";
|
|
8
10
|
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
9
11
|
const fourCC = (s) => s.charCodeAt(0) | s.charCodeAt(1) << 8 | s.charCodeAt(2) << 16 | s.charCodeAt(3) << 24;
|
|
@@ -218,6 +220,15 @@ function riffChunk(type, body) {
|
|
|
218
220
|
out.set(body, 8);
|
|
219
221
|
return out;
|
|
220
222
|
}
|
|
223
|
+
function concat(parts) {
|
|
224
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
225
|
+
let offset = 0;
|
|
226
|
+
for (const p of parts) {
|
|
227
|
+
out.set(p, offset);
|
|
228
|
+
offset += p.length;
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
221
232
|
function riff(chunks) {
|
|
222
233
|
let size = 4;
|
|
223
234
|
for (const c of chunks)
|
|
@@ -257,7 +268,29 @@ export function encode(rgba, width, height, exif) {
|
|
|
257
268
|
set24(vp8x, 7, height - 1);
|
|
258
269
|
return riff([riffChunk('VP8X', vp8x), bitstream, riffChunk('EXIF', exif)]);
|
|
259
270
|
}
|
|
260
|
-
export function
|
|
271
|
+
export function encodeLossy(rgba, width, height, quality, exif, method = 4) {
|
|
272
|
+
const { alpha, chunks } = lossyChunks(rgba, width, height, quality, method);
|
|
273
|
+
if (!alpha && !exif)
|
|
274
|
+
return riff(chunks);
|
|
275
|
+
const vp8x = new Uint8Array(10);
|
|
276
|
+
vp8x[0] = (exif ? 8 : 0) | (alpha ? 16 : 0);
|
|
277
|
+
set24(vp8x, 4, width - 1);
|
|
278
|
+
set24(vp8x, 7, height - 1);
|
|
279
|
+
return riff([riffChunk('VP8X', vp8x), ...chunks, ...exif ? [riffChunk('EXIF', exif)] : []]);
|
|
280
|
+
}
|
|
281
|
+
function lossyChunks(rgba, width, height, quality, method) {
|
|
282
|
+
if (width > 16383 || height > 16383)
|
|
283
|
+
throw new RangeError('WebP: lossy images can be at most 16383 pixels wide and high');
|
|
284
|
+
const picture = rgbaToYUV(rgba, width, height);
|
|
285
|
+
const frame = encodeVP8(picture, quality, method);
|
|
286
|
+
const padded = new Uint8Array(frame.length + (frame.length & 1));
|
|
287
|
+
padded.set(frame);
|
|
288
|
+
const chunks = [riffChunk('VP8 ', padded)];
|
|
289
|
+
if (picture.a)
|
|
290
|
+
chunks.unshift(riffChunk('ALPH', encodeAlpha(picture.a, width, height, method)));
|
|
291
|
+
return { alpha: !!picture.a, chunks };
|
|
292
|
+
}
|
|
293
|
+
export function encodeAnimation(frames, width, height, plays, exif, quality, method = 4) {
|
|
261
294
|
if (!frames.length)
|
|
262
295
|
throw new Error('WebP: an animation needs at least one frame');
|
|
263
296
|
if (width > 1 << 24 || height > 1 << 24)
|
|
@@ -276,7 +309,8 @@ export function encodeAnimation(frames, width, height, plays, exif) {
|
|
|
276
309
|
for (const f of frames) {
|
|
277
310
|
if (f.x & 1 || f.y & 1)
|
|
278
311
|
throw new RangeError('WebP: frame offsets have to be even');
|
|
279
|
-
const bitstream = riffChunk('VP8L', vp8l.encode(toARGB(f.pixels, f.width * f.height), f.width, f.height))
|
|
312
|
+
const bitstream = quality === undefined ? riffChunk('VP8L', vp8l.encode(toARGB(f.pixels, f.width * f.height), f.width, f.height))
|
|
313
|
+
: concat(lossyChunks(f.pixels, f.width, f.height, quality, method).chunks);
|
|
280
314
|
const header = new Uint8Array(16);
|
|
281
315
|
set24(header, 0, f.x >> 1);
|
|
282
316
|
set24(header, 3, f.y >> 1);
|
package/dist/image-type.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BinaryData } from './util/mem.js';
|
|
2
2
|
/** Image formats recognized by {@link ImageType.getType} */
|
|
3
|
-
export type ImageTypeName = 'png' | 'jpeg' | 'tiff' | 'gif' | 'webp' | 'tga';
|
|
3
|
+
export type ImageTypeName = 'png' | 'jpeg' | 'tiff' | 'gif' | 'webp' | 'avif' | 'jxl' | 'bmp' | 'ico' | 'cur' | 'qoi' | 'pnm' | 'hdr' | 'tga';
|
|
4
4
|
/** Detects image formats by their magic numbers */
|
|
5
5
|
export declare class ImageType {
|
|
6
6
|
private constructor();
|
|
@@ -20,6 +20,20 @@ export declare class ImageType {
|
|
|
20
20
|
static isGIF(data: BinaryData): boolean;
|
|
21
21
|
/** Whether the data starts like a WebP */
|
|
22
22
|
static isWebP(data: BinaryData): boolean;
|
|
23
|
+
/** Whether the data starts like an AVIF (the avif or avis brand) */
|
|
24
|
+
static isAVIF(data: BinaryData): boolean;
|
|
25
|
+
/** Whether the data starts like a JPEG XL (a codestream or the container) */
|
|
26
|
+
static isJXL(data: BinaryData): boolean;
|
|
27
|
+
/** Whether the data starts like a BMP */
|
|
28
|
+
static isBMP(data: BinaryData): boolean;
|
|
29
|
+
/** Whether the data is a Windows icon or cursor (ICO or CUR) */
|
|
30
|
+
static isICO(data: BinaryData): boolean;
|
|
31
|
+
/** Whether the data starts like a QOI */
|
|
32
|
+
static isQOI(data: BinaryData): boolean;
|
|
33
|
+
/** Whether the data starts like a Netpbm image (PBM, PGM, PPM, PAM or PFM) */
|
|
34
|
+
static isPNM(data: BinaryData): boolean;
|
|
35
|
+
/** Whether the data starts like a Radiance HDR */
|
|
36
|
+
static isHDR(data: BinaryData): boolean;
|
|
23
37
|
/** Whether the data looks like a TGA (which has no magic number: its header has to be consistent) */
|
|
24
38
|
static isTGA(data: BinaryData): boolean;
|
|
25
39
|
}
|
package/dist/image-type.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { view as bytes } from "./util/mem.js";
|
|
2
2
|
import { isTGA } from "./codecs/tga.js";
|
|
3
|
+
import { isBMP } from "./codecs/bmp.js";
|
|
4
|
+
import { iconType } from "./codecs/ico.js";
|
|
5
|
+
import { isQOI } from "./codecs/qoi.js";
|
|
6
|
+
import { isNetpbm } from "./codecs/netpbm.js";
|
|
7
|
+
import { isHDR } from "./codecs/hdr.js";
|
|
8
|
+
import { isAVIF } from "./codecs/avif.js";
|
|
9
|
+
import { isJXL } from "./codecs/jxl.js";
|
|
3
10
|
const MAGIC_NUMBERS = {
|
|
4
11
|
PNG: 0x89504e47,
|
|
5
12
|
JPEG: 0xffd8ff,
|
|
@@ -17,6 +24,7 @@ function dataView(data) {
|
|
|
17
24
|
const u8 = bytes(data);
|
|
18
25
|
return new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
|
|
19
26
|
}
|
|
27
|
+
const u8 = (view) => new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
20
28
|
export class ImageType {
|
|
21
29
|
constructor() { }
|
|
22
30
|
static getType(data) {
|
|
@@ -31,6 +39,21 @@ export class ImageType {
|
|
|
31
39
|
return 'gif';
|
|
32
40
|
if (this.isWebP(view))
|
|
33
41
|
return 'webp';
|
|
42
|
+
if (this.isAVIF(view))
|
|
43
|
+
return 'avif';
|
|
44
|
+
if (this.isJXL(view))
|
|
45
|
+
return 'jxl';
|
|
46
|
+
if (this.isBMP(view))
|
|
47
|
+
return 'bmp';
|
|
48
|
+
const icon = iconType(u8(view));
|
|
49
|
+
if (icon)
|
|
50
|
+
return icon;
|
|
51
|
+
if (this.isQOI(view))
|
|
52
|
+
return 'qoi';
|
|
53
|
+
if (this.isPNM(view))
|
|
54
|
+
return 'pnm';
|
|
55
|
+
if (this.isHDR(view))
|
|
56
|
+
return 'hdr';
|
|
34
57
|
if (this.isTGA(view))
|
|
35
58
|
return 'tga';
|
|
36
59
|
return null;
|
|
@@ -58,8 +81,28 @@ export class ImageType {
|
|
|
58
81
|
const view = dataView(data);
|
|
59
82
|
return view.byteLength >= 12 && view.getUint32(0, false) === MAGIC_NUMBERS.RIFF && view.getUint32(8, false) === MAGIC_NUMBERS.WEBP;
|
|
60
83
|
}
|
|
84
|
+
static isAVIF(data) {
|
|
85
|
+
return isAVIF(u8(dataView(data)));
|
|
86
|
+
}
|
|
87
|
+
static isJXL(data) {
|
|
88
|
+
return isJXL(u8(dataView(data)));
|
|
89
|
+
}
|
|
90
|
+
static isBMP(data) {
|
|
91
|
+
return isBMP(u8(dataView(data)));
|
|
92
|
+
}
|
|
93
|
+
static isICO(data) {
|
|
94
|
+
return iconType(u8(dataView(data))) !== null;
|
|
95
|
+
}
|
|
96
|
+
static isQOI(data) {
|
|
97
|
+
return isQOI(u8(dataView(data)));
|
|
98
|
+
}
|
|
99
|
+
static isPNM(data) {
|
|
100
|
+
return isNetpbm(u8(dataView(data)));
|
|
101
|
+
}
|
|
102
|
+
static isHDR(data) {
|
|
103
|
+
return isHDR(u8(dataView(data)));
|
|
104
|
+
}
|
|
61
105
|
static isTGA(data) {
|
|
62
|
-
|
|
63
|
-
return isTGA(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
106
|
+
return isTGA(u8(dataView(data)));
|
|
64
107
|
}
|
|
65
108
|
}
|
package/dist/image.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToneMapping } from './codecs/hdr.js';
|
|
1
2
|
import { BinaryData } from './util/mem.js';
|
|
2
3
|
import { TextLayout } from './text-layout.js';
|
|
3
4
|
import { Metadata } from './metadata.js';
|
|
@@ -65,7 +66,23 @@ export interface JPEGEncodeOptions {
|
|
|
65
66
|
/** Metadata to include, see {@link Metadata} */
|
|
66
67
|
metadata?: Metadata;
|
|
67
68
|
}
|
|
68
|
-
/** Options of {@link Image.encodeWebP}
|
|
69
|
+
/** Options of {@link Image.encodeWebP} */
|
|
70
|
+
export interface WebPEncodeOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Lossy compression (0-100, like libwebp's quality: 75 is its default): the image is encoded like cwebp encodes it
|
|
73
|
+
* with its default settings, much smaller than lossless. Without it the image is encoded losslessly
|
|
74
|
+
*/
|
|
75
|
+
quality?: number;
|
|
76
|
+
/**
|
|
77
|
+
* How much effort lossy compression takes (0-6, libwebp's method): higher is smaller but slower. 0-2 pick the
|
|
78
|
+
* predictions by how close they are, 3 and up also by how many bits they take, 5 and 6 also optimize the
|
|
79
|
+
* quantized values. Default: `4` (like cwebp)
|
|
80
|
+
*/
|
|
81
|
+
effort?: number;
|
|
82
|
+
/** Metadata to include, see {@link Metadata} */
|
|
83
|
+
metadata?: Metadata;
|
|
84
|
+
}
|
|
85
|
+
/** Options of {@link Image.encodeTGA} */
|
|
69
86
|
export interface EncodeOptions {
|
|
70
87
|
/** Metadata to include, see {@link Metadata} */
|
|
71
88
|
metadata?: Metadata;
|
|
@@ -107,6 +124,13 @@ export interface DecodeOptions {
|
|
|
107
124
|
* sharp), `Infinity` for no limit
|
|
108
125
|
*/
|
|
109
126
|
maxPixels?: number;
|
|
127
|
+
/**
|
|
128
|
+
* How Radiance HDRs, PFMs and HDR (PQ or HLG) AVIFs and JPEG XLs, whose light can be brighter than (SDR) white,
|
|
129
|
+
* become 8 bit sRGB: `'reinhard'` compresses the brightest parts like Reinhard's tone mapping operator does (the
|
|
130
|
+
* brightest pixel becomes white, images no brighter than white stay as they are), `'clamp'` clamps everything
|
|
131
|
+
* brighter than white. Default: `'reinhard'`
|
|
132
|
+
*/
|
|
133
|
+
toneMapping?: ToneMapping;
|
|
110
134
|
}
|
|
111
135
|
/** Text style options of {@link Image.renderText} */
|
|
112
136
|
export interface TextStyle {
|
|
@@ -711,18 +735,35 @@ export declare class Image {
|
|
|
711
735
|
*/
|
|
712
736
|
encodeJPEG(quality?: number): Promise<Uint8Array>;
|
|
713
737
|
/**
|
|
714
|
-
* Encodes the image as lossless
|
|
715
|
-
*
|
|
738
|
+
* Encodes the image as WebP: lossless (usually 20-30% smaller than PNG, but slower to encode), or lossy with a
|
|
739
|
+
* quality
|
|
740
|
+
* @param options See {@link WebPEncodeOptions}
|
|
716
741
|
*/
|
|
717
|
-
encodeWebP({ metadata }?:
|
|
742
|
+
encodeWebP({ quality, effort, metadata }?: WebPEncodeOptions): Promise<Uint8Array>;
|
|
718
743
|
/**
|
|
719
744
|
* Encodes the image as run length encoded TGA (32 bit, or 24 bit if the image is opaque)
|
|
720
745
|
* @param options See {@link EncodeOptions}
|
|
721
746
|
*/
|
|
722
747
|
encodeTGA({ metadata }?: EncodeOptions): Promise<Uint8Array>;
|
|
723
748
|
/**
|
|
724
|
-
*
|
|
725
|
-
*
|
|
749
|
+
* Encodes the image as BMP: with a palette if it is opaque and has at most 256 colors, else with 24 bit pixels, or
|
|
750
|
+
* 32 bit ones if it has transparency
|
|
751
|
+
*/
|
|
752
|
+
encodeBMP(): Promise<Uint8Array>;
|
|
753
|
+
/**
|
|
754
|
+
* Encodes the image as QOI (3 channels if the image is opaque)
|
|
755
|
+
*/
|
|
756
|
+
encodeQOI(): Promise<Uint8Array>;
|
|
757
|
+
/**
|
|
758
|
+
* Encodes the image as the smallest fitting Netpbm format: PBM for black and white images, PGM for greyscale ones,
|
|
759
|
+
* PPM for others, PAM if the image has transparency
|
|
760
|
+
*/
|
|
761
|
+
encodePNM(): Promise<Uint8Array>;
|
|
762
|
+
/**
|
|
763
|
+
* Decodes an image (PNG, JPEG, TIFF, WebP, BMP, ICO, CUR, QOI, PBM, PGM, PPM, PAM, PFM, Radiance HDR, TGA, AVIF or
|
|
764
|
+
* JPEG XL). For APNGs this is the default image; animated WebPs and GIFs have no single image, decode those with
|
|
765
|
+
* {@link Animation.decode}; for icons and cursors it's their largest image, for AVIF image sequences and JPEG XL
|
|
766
|
+
* animations their first frame
|
|
726
767
|
* @param input The binary data to decode
|
|
727
768
|
* @param options See {@link DecodeOptions}. JPEGs, TIFFs and PNGs are rotated/mirrored as their orientation tag
|
|
728
769
|
* (Exif, for PNGs also XMP) says by default: photos taken with a rotated camera are stored sideways
|
package/dist/image.js
CHANGED
|
@@ -3,6 +3,13 @@ import * as jpeg from "./codecs/jpeg.js";
|
|
|
3
3
|
import * as tiff from "./codecs/tiff.js";
|
|
4
4
|
import * as webp from "./codecs/webp.js";
|
|
5
5
|
import * as tga from "./codecs/tga.js";
|
|
6
|
+
import * as bmp from "./codecs/bmp.js";
|
|
7
|
+
import * as ico from "./codecs/ico.js";
|
|
8
|
+
import * as qoi from "./codecs/qoi.js";
|
|
9
|
+
import * as netpbm from "./codecs/netpbm.js";
|
|
10
|
+
import * as hdr from "./codecs/hdr.js";
|
|
11
|
+
import * as avif from "./codecs/avif.js";
|
|
12
|
+
import * as jxl from "./codecs/jxl.js";
|
|
6
13
|
import * as quantize from "./codecs/quantize.js";
|
|
7
14
|
import * as textRenderer from "./render/text.js";
|
|
8
15
|
import * as svgRenderer from "./render/svg.js";
|
|
@@ -996,15 +1003,34 @@ export class Image {
|
|
|
996
1003
|
const m = resolveMetadata(metadata);
|
|
997
1004
|
return jpeg.encode(this.__bytes__(), this.width, this.height, finite(quality, 'quality'), { exif: exifData(m), comment: m.comment ? utf8(m.comment) : undefined });
|
|
998
1005
|
}
|
|
999
|
-
async encodeWebP({ metadata = {} } = {}) {
|
|
1000
|
-
|
|
1006
|
+
async encodeWebP({ quality, effort = 4, metadata = {} } = {}) {
|
|
1007
|
+
const exif = exifData(resolveMetadata(metadata));
|
|
1008
|
+
if (quality === undefined)
|
|
1009
|
+
return webp.encode(this.__bytes__(), this.width, this.height, exif);
|
|
1010
|
+
const q = finite(quality, 'quality');
|
|
1011
|
+
if (q < 0 || q > 100)
|
|
1012
|
+
throw new RangeError(`quality must be 0-100, not ${q}`);
|
|
1013
|
+
if (!Number.isInteger(effort) || effort < 0 || effort > 6)
|
|
1014
|
+
throw new RangeError(`effort must be an integer 0-6, not ${effort}`);
|
|
1015
|
+
return webp.encodeLossy(this.__bytes__(), this.width, this.height, q, exif, effort);
|
|
1001
1016
|
}
|
|
1002
1017
|
async encodeTGA({ metadata = {} } = {}) {
|
|
1003
1018
|
const { author, comment, creationTime: time, software } = resolveMetadata(metadata);
|
|
1004
1019
|
return tga.encode(this.__bytes__(), this.width, this.height, { author, comment, time, software });
|
|
1005
1020
|
}
|
|
1021
|
+
async encodeBMP() {
|
|
1022
|
+
return bmp.encode(this.__bytes__(), this.width, this.height);
|
|
1023
|
+
}
|
|
1024
|
+
async encodeQOI() {
|
|
1025
|
+
return qoi.encode(this.__bytes__(), this.width, this.height);
|
|
1026
|
+
}
|
|
1027
|
+
async encodePNM() {
|
|
1028
|
+
return netpbm.encode(this.__bytes__(), this.width, this.height);
|
|
1029
|
+
}
|
|
1006
1030
|
static async decode(input, options = {}) {
|
|
1007
|
-
const { applyOrientation = true, maxPixels = MAX_PIXELS } = options;
|
|
1031
|
+
const { applyOrientation = true, maxPixels = MAX_PIXELS, toneMapping = 'reinhard' } = options;
|
|
1032
|
+
if (toneMapping !== 'reinhard' && toneMapping !== 'clamp')
|
|
1033
|
+
throw new TypeError(`toneMapping must be 'reinhard' or 'clamp', not ${String(toneMapping)}`);
|
|
1008
1034
|
const data = view(input);
|
|
1009
1035
|
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1010
1036
|
const decoded = await decoding(() => {
|
|
@@ -1019,6 +1045,26 @@ export class Image {
|
|
|
1019
1045
|
throw new Error('Animated WebPs have to be decoded with Animation.decode()');
|
|
1020
1046
|
return webp.decode(data, maxPixels);
|
|
1021
1047
|
}
|
|
1048
|
+
const bitmap = async (decoded) => {
|
|
1049
|
+
if (!('embedded' in decoded))
|
|
1050
|
+
return decoded;
|
|
1051
|
+
const { width, height, pixels } = decoded.embedded === 'png' ? await png.decode(decoded.data, maxPixels) : jpeg.decode(decoded.data, { maxPixels });
|
|
1052
|
+
return { width, height, pixels };
|
|
1053
|
+
};
|
|
1054
|
+
if (ImageType.isAVIF(dataView))
|
|
1055
|
+
return avif.decode(data, maxPixels, toneMapping);
|
|
1056
|
+
if (ImageType.isJXL(dataView))
|
|
1057
|
+
return jxl.decode(data, maxPixels, toneMapping);
|
|
1058
|
+
if (ImageType.isBMP(dataView))
|
|
1059
|
+
return bitmap(bmp.decode(data, maxPixels));
|
|
1060
|
+
if (ImageType.isICO(dataView))
|
|
1061
|
+
return bitmap(ico.decode(data, maxPixels));
|
|
1062
|
+
if (ImageType.isQOI(dataView))
|
|
1063
|
+
return qoi.decode(data, maxPixels);
|
|
1064
|
+
if (ImageType.isPNM(dataView))
|
|
1065
|
+
return netpbm.decode(data, maxPixels, toneMapping);
|
|
1066
|
+
if (ImageType.isHDR(dataView))
|
|
1067
|
+
return hdr.decode(data, maxPixels, toneMapping);
|
|
1022
1068
|
if (ImageType.isTGA(dataView))
|
|
1023
1069
|
return tga.decode(data, maxPixels);
|
|
1024
1070
|
throw new Error('Unsupported image type');
|
package/dist/index.d.ts
CHANGED
|
@@ -3,16 +3,17 @@ import { Image, DecodeOptions } from './image.js';
|
|
|
3
3
|
import { GIF } from './gif.js';
|
|
4
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, ResizeOptions, AdjustOptions, SharpenOptions, PNGEncodeOptions, JPEGEncodeOptions, EncodeOptions, SVGOptions, TextOptions, RoundCornersOptions, FisheyeOptions, TrimOptions } from './image.js';
|
|
6
|
+
export type { Color, ColorFunction, ResizeMode, SVGScaleMode, PNGMetadata, TextStyle, DecodeOptions, CompositeOptions, BlendMode, CropCircleOptions, DominantColorOptions, RotateOptions, ColorToAlphaOptions, ResizeOptions, AdjustOptions, SharpenOptions, PNGEncodeOptions, JPEGEncodeOptions, WebPEncodeOptions, EncodeOptions, SVGOptions, TextOptions, RoundCornersOptions, FisheyeOptions, TrimOptions } from './image.js';
|
|
7
7
|
export { Frame } from './frame.js';
|
|
8
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
|
+
export type { AnimationOptions, AnimationDecodeOptions, AnimationEncodeOptions, WebPAnimationEncodeOptions, GIFEncodeOptions, APNGEncodeOptions } from './animation.js';
|
|
12
12
|
export { TextLayout } from './text-layout.js';
|
|
13
13
|
export type { TextLayoutOptions, WrapStyle, HorizontalAlign, VerticalAlign } from './text-layout.js';
|
|
14
14
|
export { ImageType } from './image-type.js';
|
|
15
15
|
export type { ImageTypeName } from './image-type.js';
|
|
16
|
+
export type { ToneMapping } from './codecs/hdr.js';
|
|
16
17
|
export type { BinaryData } from './util/mem.js';
|
|
17
18
|
export type { Metadata } from './metadata.js';
|
|
18
19
|
/**
|
package/dist/index.js
CHANGED
|
@@ -12,11 +12,11 @@ export { Animation } from "./animation.js";
|
|
|
12
12
|
export { TextLayout } from "./text-layout.js";
|
|
13
13
|
export { ImageType } from "./image-type.js";
|
|
14
14
|
export function decode(data, onlyFirstFrameOrOptions = false) {
|
|
15
|
-
const { onlyFirstFrame = false, applyOrientation, maxPixels } = isOptions(onlyFirstFrameOrOptions) ? onlyFirstFrameOrOptions : { onlyFirstFrame: onlyFirstFrameOrOptions };
|
|
15
|
+
const { onlyFirstFrame = false, applyOrientation, maxPixels, toneMapping } = isOptions(onlyFirstFrameOrOptions) ? onlyFirstFrameOrOptions : { onlyFirstFrame: onlyFirstFrameOrOptions };
|
|
16
16
|
const type = ImageType.getType(data);
|
|
17
17
|
if (type === 'gif')
|
|
18
18
|
return GIF.decode(data, { onlyFirstFrame, maxPixels });
|
|
19
19
|
if (type === 'webp' && webp.isAnimated(view(data)))
|
|
20
20
|
return Animation.decode(data, { onlyFirstFrame, maxPixels });
|
|
21
|
-
return Image.decode(data, { applyOrientation, maxPixels });
|
|
21
|
+
return Image.decode(data, { applyOrientation, maxPixels, toneMapping });
|
|
22
22
|
}
|
package/dist/render/hinting.js
CHANGED
|
@@ -12,6 +12,12 @@ class SkipInstruction extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
const roundHalfAway = (v) => v < 0 ? -Math.round(-v) : Math.round(v);
|
|
14
14
|
const mulFix = (a, b) => roundHalfAway(a * b / 65536);
|
|
15
|
+
const mulDiv = (a, b, c) => {
|
|
16
|
+
if (!c)
|
|
17
|
+
return a * b < 0 ? -0x7fffffff : 0x7fffffff;
|
|
18
|
+
const d = Math.floor((Math.abs(a * b) + Math.abs(c) / 2) / Math.abs(c));
|
|
19
|
+
return a * b * c < 0 ? -d : d;
|
|
20
|
+
};
|
|
15
21
|
const divFix = (a, b) => b === 0 ? 0 : roundHalfAway(a * 65536 / b);
|
|
16
22
|
const ROUND_GRID = { period: 64, phase: 0, threshold: 32 };
|
|
17
23
|
const defaultState = () => ({
|
|
@@ -347,10 +353,10 @@ class Execution {
|
|
|
347
353
|
}
|
|
348
354
|
lineVector(p1, p2, perpendicular, original) {
|
|
349
355
|
const z1 = this.zone(this.gs.zp1), z2 = this.zone(this.gs.zp2);
|
|
350
|
-
this.point(z1,
|
|
351
|
-
this.point(z2,
|
|
352
|
-
let x = (original ? z1.orgX[
|
|
353
|
-
let y = (original ? z1.orgY[
|
|
356
|
+
this.point(z1, p1);
|
|
357
|
+
this.point(z2, p2);
|
|
358
|
+
let x = (original ? z1.orgX[p1] - z2.orgX[p2] : z1.curX[p1] - z2.curX[p2]);
|
|
359
|
+
let y = (original ? z1.orgY[p1] - z2.orgY[p2] : z1.curY[p1] - z2.curY[p2]);
|
|
354
360
|
if (x === 0 && y === 0) {
|
|
355
361
|
x = 1;
|
|
356
362
|
y = 0;
|
|
@@ -475,18 +481,19 @@ class Execution {
|
|
|
475
481
|
[a0, a1].forEach(i => this.point(za, i));
|
|
476
482
|
[b0, b1].forEach(i => this.point(zb, i));
|
|
477
483
|
this.point(z, p);
|
|
478
|
-
const ax0 = za.curX[a0], ay0 = za.curY[a0], ax1 = za.curX[a1], ay1 = za.curY[a1];
|
|
479
|
-
const bx0 = zb.curX[b0], by0 = zb.curY[b0], bx1 = zb.curX[b1], by1 = zb.curY[b1];
|
|
484
|
+
const ax0 = Math.round(za.curX[a0]), ay0 = Math.round(za.curY[a0]), ax1 = Math.round(za.curX[a1]), ay1 = Math.round(za.curY[a1]);
|
|
485
|
+
const bx0 = Math.round(zb.curX[b0]), by0 = Math.round(zb.curY[b0]), bx1 = Math.round(zb.curX[b1]), by1 = Math.round(zb.curY[b1]);
|
|
480
486
|
const dax = ax1 - ax0, day = ay1 - ay0, dbx = bx1 - bx0, dby = by1 - by0;
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
487
|
+
const discriminant = mulDiv(dax, -dby, 64) + mulDiv(day, dbx, 64);
|
|
488
|
+
const dotProduct = mulDiv(dax, dbx, 64) + mulDiv(day, dby, 64);
|
|
489
|
+
if (19 * Math.abs(discriminant) > Math.abs(dotProduct)) {
|
|
490
|
+
const v = mulDiv(bx0 - ax0, -dby, 64) + mulDiv(by0 - ay0, dbx, 64);
|
|
491
|
+
z.curX[p] = ax0 + mulDiv(v, dax, discriminant);
|
|
492
|
+
z.curY[p] = ay0 + mulDiv(v, day, discriminant);
|
|
485
493
|
}
|
|
486
494
|
else {
|
|
487
|
-
|
|
488
|
-
z.
|
|
489
|
-
z.curY[p] = ay0 + t * day;
|
|
495
|
+
z.curX[p] = Math.trunc((ax0 + ax1 + bx0 + bx1) / 4);
|
|
496
|
+
z.curY[p] = Math.trunc((ay0 + ay1 + by0 + by1) / 4);
|
|
490
497
|
}
|
|
491
498
|
z.touchX[p] = z.touchY[p] = 1;
|
|
492
499
|
break;
|
|
@@ -540,11 +547,12 @@ class Execution {
|
|
|
540
547
|
case 0x76:
|
|
541
548
|
case 0x77: {
|
|
542
549
|
const n = this.pop();
|
|
543
|
-
const grid = op === 0x76 ?
|
|
544
|
-
const period = [grid
|
|
545
|
-
const phase = [0, period
|
|
550
|
+
const grid = op === 0x76 ? 0x4000 : 0x2d41;
|
|
551
|
+
const period = [grid >> 1, grid, grid * 2, grid][n >> 6 & 3];
|
|
552
|
+
const phase = [0, period >> 2, period >> 1, Math.trunc(period * 3 / 4)][n >> 4 & 3];
|
|
546
553
|
const t = n & 15;
|
|
547
|
-
|
|
554
|
+
const threshold = t === 0 ? period - 1 : Math.trunc((t - 4) * period / 8);
|
|
555
|
+
gs.round = { period: period >> 8, phase: phase >> 8, threshold: threshold >> 8 };
|
|
548
556
|
break;
|
|
549
557
|
}
|
|
550
558
|
case 0x1a:
|
|
@@ -1024,7 +1032,7 @@ class Execution {
|
|
|
1024
1032
|
const z0 = this.zone(gs.zp0), z1 = this.zone(gs.zp1);
|
|
1025
1033
|
this.point(z0, p2);
|
|
1026
1034
|
this.point(z1, p1);
|
|
1027
|
-
const d = this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1]) / 2;
|
|
1035
|
+
const d = Math.trunc(Math.round(this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1])) / 2);
|
|
1028
1036
|
this.move(z1, p1, d);
|
|
1029
1037
|
this.move(z0, p2, -d);
|
|
1030
1038
|
break;
|
package/dist/render/layout.js
CHANGED
|
@@ -207,7 +207,7 @@ export class ApplyContext {
|
|
|
207
207
|
this.initIters();
|
|
208
208
|
}
|
|
209
209
|
randomNumber() {
|
|
210
|
-
this.randomState = this.randomState
|
|
210
|
+
this.randomState = (Math.imul(this.randomState, 48271) >>> 0) % 2147483647;
|
|
211
211
|
return this.randomState;
|
|
212
212
|
}
|
|
213
213
|
recurse(lookupIndex) {
|
package/dist/render/svg.js
CHANGED
|
@@ -421,7 +421,7 @@ class Document {
|
|
|
421
421
|
const presentation = {};
|
|
422
422
|
for (const name in node.attrs)
|
|
423
423
|
if (PROPERTIES.has(name))
|
|
424
|
-
presentation[name] = node.attrs[name];
|
|
424
|
+
presentation[name] = node.attrs[name].trim();
|
|
425
425
|
apply(presentation);
|
|
426
426
|
const rules = this.rules.filter(rule => rule.match(node));
|
|
427
427
|
const inline = node.attrs.style ? parseDeclarations(node.attrs.style) : null;
|
package/dist/render/unicode.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
+
import { base64 } from "../util/bytes.js";
|
|
1
2
|
const decoded = new WeakMap();
|
|
2
|
-
const fromBase64 = Uint8Array.fromBase64;
|
|
3
|
-
function base64(text) {
|
|
4
|
-
if (fromBase64)
|
|
5
|
-
return fromBase64(text);
|
|
6
|
-
const binary = atob(text), bytes = new Uint8Array(binary.length);
|
|
7
|
-
for (let i = 0; i < binary.length; i++)
|
|
8
|
-
bytes[i] = binary.charCodeAt(i);
|
|
9
|
-
return bytes;
|
|
10
|
-
}
|
|
11
3
|
function decode(property) {
|
|
12
4
|
let ranges = decoded.get(property);
|
|
13
5
|
if (ranges)
|
package/dist/util/bytes.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
const fromBase64 = Uint8Array.fromBase64;
|
|
2
|
+
export function base64(text) {
|
|
3
|
+
if (fromBase64)
|
|
4
|
+
return fromBase64(text);
|
|
5
|
+
const binary = atob(text), bytes = new Uint8Array(binary.length);
|
|
6
|
+
for (let i = 0; i < binary.length; i++)
|
|
7
|
+
bytes[i] = binary.charCodeAt(i);
|
|
8
|
+
return bytes;
|
|
9
|
+
}
|
|
1
10
|
export class ByteWriter {
|
|
2
11
|
buffer;
|
|
3
12
|
length = 0;
|
package/dist/util/resample.js
CHANGED
|
@@ -192,7 +192,8 @@ function resampleOpaque(src, width, height, outWidth, outHeight, filter, out) {
|
|
|
192
192
|
}
|
|
193
193
|
else {
|
|
194
194
|
for (let x = 0; x < outWidth; x++, o++) {
|
|
195
|
-
let r = 0, g = 0, b = 0, k = 0, s = row + start[x] * 2
|
|
195
|
+
let r = 0, g = 0, b = 0, k = 0, s = row + start[x] * 2;
|
|
196
|
+
const w = x * taps;
|
|
196
197
|
for (; k + 1 < taps; k += 2, s += 4) {
|
|
197
198
|
const a0 = lanes[s], a1 = lanes[s + 2], w0 = weights[w + k], w1 = weights[w + k + 1];
|
|
198
199
|
r += (a0 & 0xffff) * w0 + (a1 & 0xffff) * w1;
|
|
@@ -367,7 +368,8 @@ function resamplePremultiplied(src, width, height, outWidth, outHeight, filter,
|
|
|
367
368
|
a = plane[s + 3] * ONE;
|
|
368
369
|
}
|
|
369
370
|
else {
|
|
370
|
-
let k = 0, s = row + start[x] * 4
|
|
371
|
+
let k = 0, s = row + start[x] * 4;
|
|
372
|
+
const w = x * taps;
|
|
371
373
|
for (; k + 1 < taps; k += 2, s += 8) {
|
|
372
374
|
const w0 = weights[w + k], w1 = weights[w + k + 1];
|
|
373
375
|
r += plane[s] * w0 + plane[s + 4] * w1;
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '3.
|
|
1
|
+
export const VERSION = '3.4.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imagescript",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "zero-dependency javascript image manipulation",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "node scripts/build.js",
|
|
26
26
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
27
|
+
"lint": "eslint",
|
|
27
28
|
"docs": "node scripts/docs.js",
|
|
28
29
|
"test": "npm run build && node ./tests/run.js",
|
|
30
|
+
"coverage": "node scripts/build.js --source-maps && c8 node ./tests/run.js && node scripts/coverage-report.js",
|
|
29
31
|
"prepack": "npm run build",
|
|
30
32
|
"version": "node scripts/version.js && git add src/version.ts jsr.json"
|
|
31
33
|
},
|
|
@@ -49,9 +51,25 @@
|
|
|
49
51
|
"picture",
|
|
50
52
|
"thumbnail",
|
|
51
53
|
"webp",
|
|
54
|
+
"avif",
|
|
55
|
+
"av1",
|
|
56
|
+
"jxl",
|
|
57
|
+
"jpeg xl",
|
|
52
58
|
"apng",
|
|
53
59
|
"tiff",
|
|
54
60
|
"tga",
|
|
61
|
+
"bmp",
|
|
62
|
+
"ico",
|
|
63
|
+
"cur",
|
|
64
|
+
"qoi",
|
|
65
|
+
"netpbm",
|
|
66
|
+
"ppm",
|
|
67
|
+
"pgm",
|
|
68
|
+
"pbm",
|
|
69
|
+
"pam",
|
|
70
|
+
"pfm",
|
|
71
|
+
"hdr",
|
|
72
|
+
"radiance",
|
|
55
73
|
"animation",
|
|
56
74
|
"text rendering",
|
|
57
75
|
"font"
|
|
@@ -66,7 +84,12 @@
|
|
|
66
84
|
"node": ">=22.13.0"
|
|
67
85
|
},
|
|
68
86
|
"devDependencies": {
|
|
87
|
+
"@eslint/js": "9.39.5",
|
|
88
|
+
"c8": "12.0.0",
|
|
89
|
+
"eslint": "9.39.5",
|
|
90
|
+
"eslint-plugin-sonarjs": "4.2.2",
|
|
69
91
|
"typedoc": "0.27.9",
|
|
70
|
-
"typescript": "5.7.3"
|
|
92
|
+
"typescript": "5.7.3",
|
|
93
|
+
"typescript-eslint": "8.72.0"
|
|
71
94
|
}
|
|
72
95
|
}
|