imagescript 1.2.8 → 1.2.15
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/ImageScript.d.ts +223 -0
- package/ImageScript.js +60 -192
- package/LICENSE +619 -0
- package/README.md +17 -16
- package/bench/_.mjs +166 -0
- package/bench/all.mjs +4 -0
- package/bench/blur.mjs +59 -0
- package/bench/flip.mjs +63 -0
- package/bench/overlay.mjs +35 -0
- package/bench/package.json +7 -0
- package/bench/readme.md +64 -0
- package/bench/resize.mjs +52 -0
- package/codecs/LICENSE +21 -0
- package/codecs/lib.d.ts +50 -0
- package/codecs/lib.js +6 -0
- package/codecs/lib.mjs +1 -0
- package/codecs/node/bin/arm64-darwin.node +0 -0
- package/{node → codecs/node/bin}/arm64-linux.node +0 -0
- package/codecs/node/bin/x64-darwin.node +0 -0
- package/{node → codecs/node/bin}/x64-linux.node +0 -0
- package/{node → codecs/node/bin}/x64-win32.node +0 -0
- package/codecs/node/index.js +3 -0
- package/codecs/node/index.mjs +6 -0
- package/codecs/package.json +14 -0
- package/codecs/readme.md +38 -0
- package/{wasm/simd.wasm → codecs/tests/gif.js} +0 -0
- package/{wasm/unknown.wasm → codecs/tests/jpeg.js} +0 -0
- package/codecs/tests/png.js +0 -0
- package/codecs/tests/results/gif/1024.nis.gif +0 -0
- package/codecs/tests/results/gif/1024.skyra.gif +0 -0
- package/codecs/tests/results/gif/256.nis.gif +0 -0
- package/codecs/tests/results/gif/256.skyra.gif +0 -0
- package/codecs/tests/results/jpeg/256.jpegjs.jpeg +0 -0
- package/codecs/tests/results/jpeg/256.nis.jpeg +0 -0
- package/codecs/tests/results/jpeg/256.sharp.jpeg +0 -0
- package/codecs/tests/results/jpeg/512.jpegjs.jpeg +0 -0
- package/codecs/tests/results/jpeg/512.nis.jpeg +0 -0
- package/codecs/tests/results/jpeg/512.sharp.jpeg +0 -0
- package/codecs/tests/results/jpeg/64.jpegjs.jpeg +0 -0
- package/codecs/tests/results/jpeg/64.nis.jpeg +0 -0
- package/codecs/tests/results/jpeg/64.sharp.jpeg +0 -0
- package/codecs/tests/results/png/256.jis.png +0 -0
- package/codecs/tests/results/png/256.nis.png +0 -0
- package/codecs/tests/results/png/256.sharp.png +0 -0
- package/codecs/tests/results/png/256.upng.png +0 -0
- package/codecs/tests/results/png/512.jis.png +0 -0
- package/codecs/tests/results/png/512.nis.png +0 -0
- package/codecs/tests/results/png/512.sharp.png +0 -0
- package/codecs/tests/results/png/512.upng.png +0 -0
- package/codecs/tests/results/png/64.jis.png +0 -0
- package/codecs/tests/results/png/64.nis.png +0 -0
- package/codecs/tests/results/png/64.sharp.png +0 -0
- package/codecs/tests/results/png/64.upng.png +0 -0
- package/codecs/tests/webp.js +0 -0
- package/codecs/wasm/bin/codecs.wasm +0 -0
- package/codecs/wasm/bin/simd.wasm +0 -0
- package/codecs/wasm/bundle/browser.js +1 -0
- package/codecs/wasm/index.js +1 -0
- package/codecs/wasm/index.mjs +1 -0
- package/package.json +17 -4
- package/png/README.md +1 -0
- package/png/src/{crc.js → crc.mjs} +0 -0
- package/png/src/{mem.js → mem.mjs} +0 -0
- package/png/src/{png.js → png.mjs} +3 -3
- package/png/src/{zlib.js → zlib.mjs} +0 -0
- package/tsconfig.json +21 -0
- package/v2/framebuffer.js +94 -115
- package/v2/framebuffer.mjs +9 -10
- package/v2/framebuffer.rs +1264 -0
- package/v2/index.js +1 -1
- package/v2/ops/{blur.js → blur.mjs} +0 -0
- package/v2/ops/{color.js → color.mjs} +0 -0
- package/v2/ops/{crop.js → crop.mjs} +0 -0
- package/v2/ops/{fill.js → fill.mjs} +0 -0
- package/v2/ops/{flip.js → flip.mjs} +0 -0
- package/v2/ops/index.mjs +9 -0
- package/v2/ops/{iterator.js → iterator.mjs} +6 -6
- package/v2/ops/overlay.mjs +63 -0
- package/v2/ops/{resize.js → resize.mjs} +3 -3
- package/v2/ops/{rotate.js → rotate.mjs} +8 -9
- package/v2/util/{mem.js → mem.mjs} +3 -5
- package/wasm/any/font.wasm +0 -0
- package/wasm/any/png.wasm +0 -0
- package/wasm/node/png.js +49 -0
- package/node/arm64-darwin.node +0 -0
- package/node/index.js +0 -7
- package/node/x64-darwin.node +0 -0
- package/v2/ops/index.js +0 -9
- package/v2/ops/overlay.js +0 -55
- package/wasm/node.js +0 -23
- package/wasm/web.js +0 -10
package/ImageScript.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
export class Image {
|
|
2
|
+
private __width__: number;
|
|
3
|
+
private __height__: number;
|
|
4
|
+
private __buffer__: ArrayBuffer;
|
|
5
|
+
private __view__: DataView;
|
|
6
|
+
private __u32__: Uint32Array;
|
|
7
|
+
bitmap: Uint8ClampedArray;
|
|
8
|
+
|
|
9
|
+
constructor(width: number, height: number);
|
|
10
|
+
|
|
11
|
+
private toString(): string;
|
|
12
|
+
|
|
13
|
+
get width(): number;
|
|
14
|
+
|
|
15
|
+
get height(): number;
|
|
16
|
+
|
|
17
|
+
* [Symbol.iterator](): void;
|
|
18
|
+
|
|
19
|
+
* iterateWithColors(): Generator<[x: number, y: number, color: number], void, unknown>;
|
|
20
|
+
|
|
21
|
+
static rgbaToColor(r: number, g: number, b: number, a: number): number;
|
|
22
|
+
|
|
23
|
+
static rgbToColor(r: number, g: number, b: number): number;
|
|
24
|
+
|
|
25
|
+
static hslaToColor(h: number, s: number, l: number, a: number): number;
|
|
26
|
+
|
|
27
|
+
static hslToColor(h: number, s: number, l: number): number;
|
|
28
|
+
|
|
29
|
+
static rgbaToHSLA(r: number, g: number, b: number, a: number): number[];
|
|
30
|
+
|
|
31
|
+
static colorToRGBA(color: number): number[];
|
|
32
|
+
|
|
33
|
+
static colorToRGB(color: number): number[];
|
|
34
|
+
|
|
35
|
+
getPixelAt(x: number, y: number): number;
|
|
36
|
+
|
|
37
|
+
getRGBAAt(x: number, y: number): Uint8ClampedArray;
|
|
38
|
+
|
|
39
|
+
setPixelAt(x: number, y: number, pixelColor: number): Image;
|
|
40
|
+
|
|
41
|
+
private __set_pixel__(x: number, y: number, pixelColor: number): void;
|
|
42
|
+
|
|
43
|
+
private __check_boundaries__(x: number, y: number): void;
|
|
44
|
+
|
|
45
|
+
private static get __out_of_bounds__(): string;
|
|
46
|
+
|
|
47
|
+
fill(color: number | colorFunction): Image;
|
|
48
|
+
|
|
49
|
+
clone(): Image;
|
|
50
|
+
|
|
51
|
+
static get RESIZE_NEAREST_NEIGHBOR(): string;
|
|
52
|
+
|
|
53
|
+
static get RESIZE_AUTO(): number;
|
|
54
|
+
|
|
55
|
+
scale(factor: number, mode?: string): Image;
|
|
56
|
+
|
|
57
|
+
private __scale__(factor: number, mode?: string);
|
|
58
|
+
|
|
59
|
+
resize(width: number, height: number, mode?: string): Image;
|
|
60
|
+
|
|
61
|
+
contain(width: number, height: number, mode?: string): Image;
|
|
62
|
+
|
|
63
|
+
fit(width: number, height: number, mode?: string): Image;
|
|
64
|
+
|
|
65
|
+
cover(width: number, height: number, mode?: string): Image;
|
|
66
|
+
|
|
67
|
+
private __resize__(width: number, height: number, mode?: string): Image;
|
|
68
|
+
|
|
69
|
+
private __resize_nearest_neighbor__(width: number, height: number): Image;
|
|
70
|
+
|
|
71
|
+
crop(x: number, y: number, width: number, height: number): Image;
|
|
72
|
+
|
|
73
|
+
private __crop__(x: number, y: number, width: number, height: number): Image;
|
|
74
|
+
|
|
75
|
+
drawBox(x: number, y: number, width: number, height: number, color: number | colorFunction): Image;
|
|
76
|
+
|
|
77
|
+
private __fast_box__(x: number, y: number, width: number, height: number, color: number): Image;
|
|
78
|
+
|
|
79
|
+
drawCircle(x: number, y: number, radius: number, color: number | colorFunction): Image;
|
|
80
|
+
|
|
81
|
+
cropCircle(max?: boolean, feathering?: number): Image;
|
|
82
|
+
|
|
83
|
+
opacity(opacity: number, absolute?: boolean): Image;
|
|
84
|
+
|
|
85
|
+
red(saturation: number, absolute?: boolean): Image;
|
|
86
|
+
|
|
87
|
+
green(saturation: number, absolute?: boolean): Image;
|
|
88
|
+
|
|
89
|
+
blue(saturation: number, absolute?: boolean): Image;
|
|
90
|
+
|
|
91
|
+
private __set_channel_value__(value: number, absolute: boolean, offset: number): void;
|
|
92
|
+
|
|
93
|
+
lightness(value: number, absolute?: boolean): Image;
|
|
94
|
+
|
|
95
|
+
saturation(value: number, absolute?: boolean): Image;
|
|
96
|
+
|
|
97
|
+
composite(source: Image, x?: number, y?: number): Image;
|
|
98
|
+
|
|
99
|
+
invert(): Image;
|
|
100
|
+
|
|
101
|
+
invertValue(): Image;
|
|
102
|
+
|
|
103
|
+
invertSaturation(): Image;
|
|
104
|
+
|
|
105
|
+
invertHue(): Image;
|
|
106
|
+
|
|
107
|
+
hueShift(degrees: number): Image;
|
|
108
|
+
|
|
109
|
+
averageColor(): number;
|
|
110
|
+
|
|
111
|
+
dominantColor(ignoreBlack?: boolean, ignoreWhite?: boolean, bwThreshold?: number): number;
|
|
112
|
+
|
|
113
|
+
rotate(angle: number, resize?: boolean): Image;
|
|
114
|
+
|
|
115
|
+
private __apply__(image: Image | Frame): Image | Frame;
|
|
116
|
+
|
|
117
|
+
static gradient(colors: { [position: number]: number; }): ((position: number) => number);
|
|
118
|
+
|
|
119
|
+
roundCorners(radius?: number): Image;
|
|
120
|
+
|
|
121
|
+
private static __gradient__(startColor: number, endColor: number): number;
|
|
122
|
+
|
|
123
|
+
fisheye(radius?: number): Image;
|
|
124
|
+
|
|
125
|
+
async encode(compression?: number, metadata?: PNGMetadata): Promise<Uint8Array>;
|
|
126
|
+
async encode(metadata?: PNGMetadata): Promise<Uint8Array>;
|
|
127
|
+
|
|
128
|
+
async encodeJPEG(quality?: number): Promise<Uint8Array>;
|
|
129
|
+
|
|
130
|
+
async encodeWEBP(quality?: null | number): Promise<Uint8Array>;
|
|
131
|
+
|
|
132
|
+
static async encode(data: Buffer | Uint8Array): Promise<Image>
|
|
133
|
+
|
|
134
|
+
static get SVG_MODE_SCALE(): number;
|
|
135
|
+
|
|
136
|
+
static get SVG_MODE_WIDTH(): number;
|
|
137
|
+
|
|
138
|
+
static get SVG_MODE_HEIGHT(): number;
|
|
139
|
+
|
|
140
|
+
static async renderSVG(svg: string, size?: number, mode?: number): Promise<Image>;
|
|
141
|
+
|
|
142
|
+
static async renderText(font: Uint8Array, scale: number, text: string, color?: number, layout?: TextLayout): Promise<Image>;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export class Frame extends Image {
|
|
146
|
+
static get DISPOSAL_KEEP(): string;
|
|
147
|
+
|
|
148
|
+
static get DISPOSAL_PREVIOUS(): string;
|
|
149
|
+
|
|
150
|
+
static get DISPOSAL_BACKGROUND(): string
|
|
151
|
+
|
|
152
|
+
private static __convert_disposal_mode__(mode: string | number): any;
|
|
153
|
+
|
|
154
|
+
constructor(width: number, height: number, duration?: number, xOffset?: number, yOffset?: number, disposalMode?: typeof Frame.DISPOSAL_KEEP | string);
|
|
155
|
+
|
|
156
|
+
toString(): string;
|
|
157
|
+
|
|
158
|
+
static from(image: Image, duration?: number, xOffset?: number, yOffset?: number, disposalMode?: typeof Frame.DISPOSAL_KEEP | string): Frame;
|
|
159
|
+
|
|
160
|
+
resize(width: number, height: number, mode?: typeof Image.RESIZE_NEAREST_NEIGHBOR | string): Image;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export class GIF extends Array {
|
|
164
|
+
constructor(frames: Frame[], loopCount?: number);
|
|
165
|
+
|
|
166
|
+
get width(): number;
|
|
167
|
+
|
|
168
|
+
get height(): number;
|
|
169
|
+
|
|
170
|
+
toString(): string;
|
|
171
|
+
|
|
172
|
+
* [Symbol.iterator](): Generator<Frame, void, *>
|
|
173
|
+
|
|
174
|
+
slice(start: number, end: number): GIF;
|
|
175
|
+
|
|
176
|
+
get duration(): number;
|
|
177
|
+
|
|
178
|
+
async encode(quality?: number): Promise<Uint8Array>
|
|
179
|
+
|
|
180
|
+
static async decode(data: Buffer | Uint8Array, onlyExtractFirstFrame?: boolean): Promise<GIF>;
|
|
181
|
+
|
|
182
|
+
resize(width: number, height: number, mode?: typeof Image.RESIZE_NEAREST_NEIGHBOR | string): void;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export class TextLayout {
|
|
186
|
+
constructor(options?: {
|
|
187
|
+
maxWidth?: number,
|
|
188
|
+
maxHeight?: number,
|
|
189
|
+
wrapStyle?: string,
|
|
190
|
+
verticalAlign?: string,
|
|
191
|
+
horizontalAlign?: string,
|
|
192
|
+
wrapHardBreaks?: boolean
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export class ImageType {
|
|
197
|
+
static getType(data: Buffer | Uint8Array): string | null;
|
|
198
|
+
|
|
199
|
+
static isPNG(view: DataView): boolean;
|
|
200
|
+
|
|
201
|
+
static isJPEG(view: DataView): boolean;
|
|
202
|
+
|
|
203
|
+
static isTIFF(view: DataView): boolean;
|
|
204
|
+
|
|
205
|
+
static isGIF(view: DataView): boolean;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export function decode(data: Uint8Array | Buffer, onlyExtractFirstFrame?: boolean): Promise<GIF | Image>;
|
|
209
|
+
|
|
210
|
+
type colorFunction = (x: number, y: number) => number;
|
|
211
|
+
|
|
212
|
+
type PNGMetadata = {
|
|
213
|
+
title?: string,
|
|
214
|
+
author?: string,
|
|
215
|
+
description?: string,
|
|
216
|
+
copyright?: string,
|
|
217
|
+
creationTime?: string | number | Date,
|
|
218
|
+
software?: string,
|
|
219
|
+
disclaimer?: string,
|
|
220
|
+
warning?: string,
|
|
221
|
+
source?: string,
|
|
222
|
+
comment?: string
|
|
223
|
+
};
|
package/ImageScript.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const png = require('./png/node.js');
|
|
2
2
|
const mem = require('./utils/mem.js');
|
|
3
|
-
const codecs = require('./node/index.js');
|
|
4
3
|
const {version} = require('./package.json');
|
|
4
|
+
const codecs = require('./codecs/node/index.js');
|
|
5
|
+
const { default: v2 } = require('./v2/framebuffer.js');
|
|
5
6
|
|
|
6
7
|
// old
|
|
7
8
|
const svglib = require('./wasm/node/svg.js');
|
|
8
9
|
const giflib = require('./wasm/node/gif.js');
|
|
10
|
+
const pnglib = require('./wasm/node/png.js');
|
|
9
11
|
const fontlib = require('./wasm/node/font.js');
|
|
10
12
|
const jpeglib = require('./wasm/node/jpeg.js');
|
|
11
13
|
const tifflib = require('./wasm/node/tiff.js');
|
|
@@ -82,26 +84,16 @@ class Image {
|
|
|
82
84
|
* @yields {number[]} The coordinates of the pixel ([x, y])
|
|
83
85
|
* @returns {void}
|
|
84
86
|
*/
|
|
85
|
-
*
|
|
86
|
-
for (
|
|
87
|
-
for (let x = 1; x <= this.width; x++) {
|
|
88
|
-
yield [x, y];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
87
|
+
*[Symbol.iterator]() {
|
|
88
|
+
for (const x of new v2(this.width, this.height, this.bitmap)[Symbol.iterator]()) yield (x[0]++, x[1]++, x)
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/**
|
|
94
92
|
* Yields an [x, y, color] array for every pixel in the image
|
|
95
93
|
* @yields {number[]} The coordinates and color of the pixel ([x, y, color])
|
|
96
94
|
*/
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
for (let y = 1; y <= this.height; y++) {
|
|
100
|
-
for (let x = 1; x <= this.width; x++) {
|
|
101
|
-
yield [x, y, this.__view__.getUint32(offset, false)];
|
|
102
|
-
offset += 4;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
95
|
+
*iterateWithColors() {
|
|
96
|
+
for (const x of new v2(this.width, this.height, this.bitmap).pixels('int')) yield (x[0]++, x[1]++, x);
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
/**
|
|
@@ -319,19 +311,7 @@ class Image {
|
|
|
319
311
|
* @returns {Image}
|
|
320
312
|
*/
|
|
321
313
|
fill(color) {
|
|
322
|
-
|
|
323
|
-
if (type !== 'function') {
|
|
324
|
-
this.__view__.setUint32(0, color, false);
|
|
325
|
-
this.__u32__.fill(this.__u32__[0]);
|
|
326
|
-
} else {
|
|
327
|
-
let offset = 0;
|
|
328
|
-
for (let y = 1; y <= this.height; y++) {
|
|
329
|
-
for (let x = 1; x <= this.width; x++) {
|
|
330
|
-
this.__view__.setUint32(offset, color(x, y), false);
|
|
331
|
-
offset += 4;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
314
|
+
new v2(this.width, this.height, this.bitmap).fill(color);
|
|
335
315
|
|
|
336
316
|
return this;
|
|
337
317
|
}
|
|
@@ -392,6 +372,48 @@ class Image {
|
|
|
392
372
|
return this.__apply__(image);
|
|
393
373
|
}
|
|
394
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Resizes the image so it is contained in the given bounding box.
|
|
377
|
+
* Can return an image with one axis smaller than the given bounding box.
|
|
378
|
+
* @param {number} width The width of the bounding box
|
|
379
|
+
* @param {number} height The height of the bounding box
|
|
380
|
+
* @param {string} [mode=Image.RESIZE_NEAREST_NEIGHBOR] The resizing mode to use
|
|
381
|
+
* @returns {Image} The resized image
|
|
382
|
+
*/
|
|
383
|
+
contain(width, height, mode = Image.RESIZE_NEAREST_NEIGHBOR) {
|
|
384
|
+
const scaleFactor = width / height > this.width / this.height ? height / this.height : width / this.width;
|
|
385
|
+
return this.scale(scaleFactor, mode);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Resizes the image so it is contained in the given bounding box, placing it in the center of the given bounding box.
|
|
390
|
+
* Always returns the exact dimensions of the bounding box.
|
|
391
|
+
* @param {number} width The width of the bounding box
|
|
392
|
+
* @param {number} height The height of the bounding box
|
|
393
|
+
* @param {string} [mode=Image.RESIZE_NEAREST_NEIGHBOR] The resizing mode to use
|
|
394
|
+
* @returns {Image} The resized image
|
|
395
|
+
*/
|
|
396
|
+
fit(width, height, mode = Image.RESIZE_NEAREST_NEIGHBOR) {
|
|
397
|
+
const result = new Image(width, height);
|
|
398
|
+
this.contain(width, height, mode);
|
|
399
|
+
result.composite(this, (width - this.width) / 2, (height - this.height) / 2);
|
|
400
|
+
return this.__apply__(result);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Resizes the image so it covers the given bounding box, cropping the overflowing edges.
|
|
405
|
+
* Always returns the exact dimensions of the bounding box.
|
|
406
|
+
* @param {number} width The width of the bounding box
|
|
407
|
+
* @param {number} height The height of the bounding box
|
|
408
|
+
* @param {string} [mode=Image.RESIZE_NEAREST_NEIGHBOR] The resizing mode to use
|
|
409
|
+
* @returns {Image} The resized image
|
|
410
|
+
*/
|
|
411
|
+
cover(width, height, mode = Image.RESIZE_NEAREST_NEIGHBOR) {
|
|
412
|
+
const scaleFactor = width / height > this.width / this.height ? width / this.width : height / this.height;
|
|
413
|
+
const result = this.scale(scaleFactor, mode);
|
|
414
|
+
return result.crop((result.width - width) / 2, (result.height - height) / 2, width, height);
|
|
415
|
+
}
|
|
416
|
+
|
|
395
417
|
/** @private */
|
|
396
418
|
__resize__(width, height, mode = Image.RESIZE_NEAREST_NEIGHBOR) {
|
|
397
419
|
if (width === Image.RESIZE_AUTO && height === Image.RESIZE_AUTO) throw new Error('RESIZE_AUTO can only be used for either width or height, not for both');
|
|
@@ -420,18 +442,9 @@ class Image {
|
|
|
420
442
|
*/
|
|
421
443
|
__resize_nearest_neighbor__(width, height) {
|
|
422
444
|
const image = new this.constructor(width, height);
|
|
445
|
+
const frame = new v2(this.width, this.height, this.bitmap).resize('nearest', width, height);
|
|
423
446
|
|
|
424
|
-
|
|
425
|
-
for (let x = 0; x < width; x++) {
|
|
426
|
-
const ySrc = Math.floor((y * this.height) / height);
|
|
427
|
-
const xSrc = Math.floor((x * this.width) / width);
|
|
428
|
-
|
|
429
|
-
const destPos = (y * width + x) * 4;
|
|
430
|
-
const srcPos = (ySrc * this.width + xSrc) * 4;
|
|
431
|
-
|
|
432
|
-
image.__view__.setUint32(destPos, this.__view__.getUint32(srcPos, false), false);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
447
|
+
image.bitmap.set(frame.u8);
|
|
435
448
|
|
|
436
449
|
return image;
|
|
437
450
|
}
|
|
@@ -565,19 +578,7 @@ class Image {
|
|
|
565
578
|
* @returns {Image}
|
|
566
579
|
*/
|
|
567
580
|
cropCircle(max = false, feathering = 0) {
|
|
568
|
-
|
|
569
|
-
const radSquared = rad ** 2;
|
|
570
|
-
const centerX = this.width / 2;
|
|
571
|
-
const centerY = this.height / 2;
|
|
572
|
-
|
|
573
|
-
for (const [x, y] of this) {
|
|
574
|
-
const distanceFromCenter = (x - centerX) ** 2 + (y - centerY) ** 2;
|
|
575
|
-
const alphaIdx = ((y - 1) * this.width + (x - 1)) * 4 + 3;
|
|
576
|
-
if (distanceFromCenter > radSquared)
|
|
577
|
-
this.bitmap[alphaIdx] = 0;
|
|
578
|
-
else if (feathering)
|
|
579
|
-
this.bitmap[alphaIdx] *= Math.max(0, Math.min(1, 1 - (distanceFromCenter / radSquared) * feathering ** (1 / 2)));
|
|
580
|
-
}
|
|
581
|
+
new v2(this.width, this.height, this.bitmap).crop('circle', feathering);
|
|
581
582
|
|
|
582
583
|
return this;
|
|
583
584
|
}
|
|
@@ -693,48 +694,11 @@ class Image {
|
|
|
693
694
|
* @returns {Image}
|
|
694
695
|
*/
|
|
695
696
|
composite(source, x = 0, y = 0) {
|
|
696
|
-
|
|
697
|
-
y = ~~y;
|
|
698
|
-
|
|
699
|
-
for (let yy = 0; yy < source.height; yy++) {
|
|
700
|
-
let y_offset = y + yy;
|
|
701
|
-
if (y_offset < 0) continue;
|
|
702
|
-
if (y_offset >= this.height) break;
|
|
703
|
-
|
|
704
|
-
for (let xx = 0; xx < source.width; xx++) {
|
|
705
|
-
let x_offset = x + xx;
|
|
706
|
-
if (x_offset < 0) continue;
|
|
707
|
-
if (x_offset >= this.width) break;
|
|
708
|
-
|
|
709
|
-
const offset = 4 * (x_offset + y_offset * this.width);
|
|
710
|
-
const fg = source.__view__.getUint32(4 * (xx + yy * source.width), false);
|
|
711
|
-
const bg = this.__view__.getUint32(offset, false);
|
|
712
|
-
|
|
713
|
-
if ((fg & 0xff) === 0xff) this.__view__.setUint32(offset, fg, false);
|
|
714
|
-
else if ((fg & 0xff) === 0x00) this.__view__.setUint32(offset, bg, false);
|
|
715
|
-
else this.__view__.setUint32(offset, Image.__alpha_blend__(fg, bg), false);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
697
|
+
new v2(this.width, this.height, this.bitmap).overlay(new v2(source.width, source.height, source.bitmap), x, y);
|
|
718
698
|
|
|
719
699
|
return this;
|
|
720
700
|
}
|
|
721
701
|
|
|
722
|
-
/**
|
|
723
|
-
* @private
|
|
724
|
-
* @param {number} fg
|
|
725
|
-
* @param {number} bg
|
|
726
|
-
* @returns {number}
|
|
727
|
-
*/
|
|
728
|
-
static __alpha_blend__(fg, bg) {
|
|
729
|
-
const fa = fg & 0xff;
|
|
730
|
-
const alpha = fa + 1;
|
|
731
|
-
const inv_alpha = 256 - fa;
|
|
732
|
-
const r = (alpha * (fg >>> 24) + inv_alpha * (bg >>> 24)) >> 8;
|
|
733
|
-
const b = (alpha * (fg >> 8 & 0xff) + inv_alpha * (bg >> 8 & 0xff)) >> 8;
|
|
734
|
-
const g = (alpha * (fg >> 16 & 0xff) + inv_alpha * (bg >> 16 & 0xff)) >> 8;
|
|
735
|
-
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (Math.max(fa, bg & 0xff) & 0xff));
|
|
736
|
-
}
|
|
737
|
-
|
|
738
702
|
/**
|
|
739
703
|
* Inverts the images colors
|
|
740
704
|
* @returns {Image}
|
|
@@ -857,110 +821,14 @@ class Image {
|
|
|
857
821
|
* @param {boolean} resize Whether to resize the image so it fits all pixels or just ignore outlying pixels
|
|
858
822
|
*/
|
|
859
823
|
rotate(angle, resize = true) {
|
|
860
|
-
|
|
861
|
-
if (angle % 180 === 0) return this.__rotate_180__();
|
|
862
|
-
|
|
863
|
-
const rad = Math.PI * (angle / 180);
|
|
864
|
-
|
|
865
|
-
const sin = Math.sin(rad);
|
|
866
|
-
const cos = Math.cos(rad);
|
|
867
|
-
|
|
868
|
-
const width = resize
|
|
869
|
-
? Math.abs(this.width * sin) + Math.abs(this.height * cos)
|
|
870
|
-
: this.width;
|
|
871
|
-
const height = resize
|
|
872
|
-
? Math.abs(this.width * cos) + Math.abs(this.height * sin)
|
|
873
|
-
: this.height;
|
|
874
|
-
|
|
875
|
-
const out = new Image(width, height);
|
|
824
|
+
const frame = new v2(this.width, this.height, this.bitmap).rotate(360 - (angle % 360), resize);
|
|
876
825
|
|
|
877
|
-
const
|
|
878
|
-
const out_cy = height / 2 - .5;
|
|
879
|
-
const src_cx = this.width / 2 - .5;
|
|
880
|
-
const src_cy = this.height / 2 - .5;
|
|
881
|
-
|
|
882
|
-
let h = 0;
|
|
883
|
-
do {
|
|
884
|
-
let w = 0;
|
|
885
|
-
const ysin = src_cx - sin * (h - out_cy);
|
|
886
|
-
const ycos = src_cy + cos * (h - out_cy);
|
|
887
|
-
|
|
888
|
-
do {
|
|
889
|
-
const xf = ysin + cos * (w - out_cx);
|
|
890
|
-
const yf = ycos + sin * (w - out_cx);
|
|
891
|
-
Image.__interpolate__(this, out, w, h, xf, yf);
|
|
892
|
-
} while (w++ < width);
|
|
893
|
-
} while (h++ < height);
|
|
826
|
+
const out = new Image(frame.width, frame.height);
|
|
894
827
|
|
|
828
|
+
out.bitmap.set(frame.u8);
|
|
895
829
|
return this.__apply__(out);
|
|
896
830
|
}
|
|
897
831
|
|
|
898
|
-
/**
|
|
899
|
-
* @returns {Image}
|
|
900
|
-
* @private
|
|
901
|
-
*/
|
|
902
|
-
__rotate_180__() {
|
|
903
|
-
let offset = 0;
|
|
904
|
-
this.bitmap.reverse();
|
|
905
|
-
while (offset < this.bitmap.length) this.bitmap.subarray(offset, offset += 4).reverse();
|
|
906
|
-
|
|
907
|
-
return this;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* @param {Image} src
|
|
912
|
-
* @param {Image} out
|
|
913
|
-
* @param {number} x0
|
|
914
|
-
* @param {number} y0
|
|
915
|
-
* @param {number} x1
|
|
916
|
-
* @param {number} y1
|
|
917
|
-
* @private
|
|
918
|
-
*/
|
|
919
|
-
static __interpolate__(src, out, x0, y0, x1, y1) {
|
|
920
|
-
const x2 = ~~x1;
|
|
921
|
-
const y2 = ~~y1;
|
|
922
|
-
const xq = x1 - x2;
|
|
923
|
-
const yq = y1 - y2;
|
|
924
|
-
const out_slice = out.bitmap.subarray(4 * (x0 + y0 * out.width), -4);
|
|
925
|
-
|
|
926
|
-
const ref = {
|
|
927
|
-
r: 0,
|
|
928
|
-
g: 0,
|
|
929
|
-
b: 0,
|
|
930
|
-
a: 0,
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
Image.__pawn__(x2, y2, (1 - xq) * (1 - yq), ref, src);
|
|
934
|
-
Image.__pawn__(1 + x2, y2, xq * (1 - yq), ref, src);
|
|
935
|
-
Image.__pawn__(x2, 1 + y2, (1 - xq) * yq, ref, src);
|
|
936
|
-
Image.__pawn__(1 + x2, 1 + y2, xq * yq, ref, src);
|
|
937
|
-
|
|
938
|
-
out_slice[3] = ref.a;
|
|
939
|
-
out_slice[0] = ref.r / ref.a;
|
|
940
|
-
out_slice[1] = ref.g / ref.a;
|
|
941
|
-
out_slice[2] = ref.b / ref.a;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/** @private */
|
|
945
|
-
static __pawn__(point0, point1, weight, ref, src) {
|
|
946
|
-
if (
|
|
947
|
-
point0 > 0
|
|
948
|
-
&& point1 > 0
|
|
949
|
-
&& point0 < src.width
|
|
950
|
-
&& point1 < src.height
|
|
951
|
-
) {
|
|
952
|
-
const offset = 4 * (point0 + point1 * src.width);
|
|
953
|
-
const src_slice = src.bitmap.subarray(offset, offset + 4);
|
|
954
|
-
|
|
955
|
-
const wa = weight * src_slice[3];
|
|
956
|
-
|
|
957
|
-
ref.a += wa;
|
|
958
|
-
ref.r += wa * src_slice[0];
|
|
959
|
-
ref.g += wa * src_slice[1];
|
|
960
|
-
ref.b += wa * src_slice[2];
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
832
|
/**
|
|
965
833
|
* @private
|
|
966
834
|
* @param {Image|Frame} image
|
|
@@ -1215,9 +1083,9 @@ class Image {
|
|
|
1215
1083
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1216
1084
|
|
|
1217
1085
|
if (ImageType.isPNG(view)) { // PNG
|
|
1218
|
-
const {width, height,
|
|
1086
|
+
const {width, height, framebuffer} = (await pnglib.init()).decode(data);
|
|
1219
1087
|
image = new Image(width, height);
|
|
1220
|
-
image.bitmap.set(
|
|
1088
|
+
image.bitmap.set(framebuffer);
|
|
1221
1089
|
} else if (ImageType.isJPEG(view)) { // JPEG
|
|
1222
1090
|
const framebuffer = (await jpeglib.init()).decode(data);
|
|
1223
1091
|
|
|
@@ -1631,7 +1499,7 @@ class GIF extends Array {
|
|
|
1631
1499
|
}
|
|
1632
1500
|
|
|
1633
1501
|
else if (0 === mode || 1 === mode) {
|
|
1634
|
-
t8.set(u8)
|
|
1502
|
+
t8.set(u8);
|
|
1635
1503
|
for (let y = 0 | 0; y < height; y++) {
|
|
1636
1504
|
const y_offset = fx + gwidth * (y + fy) | 0;
|
|
1637
1505
|
|