Skip to content

Commit 5357750

Browse files
committed
Merge branch 'master' into deno
# Conflicts: # tests/decode.js # utils/png.js
2 parents fc3f005 + 91ad55c commit 5357750

2 files changed

Lines changed: 61 additions & 127 deletions

File tree

‎ImageScript.js‎

Lines changed: 59 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@ import * as gif from './utils/gif.js';
66
*/
77
export class Image {
88
/**
9-
* This is for internal use.
10-
* If you intend on creating a new image, use {@link Image.new} or {@link Image.decode} instead.
11-
* @param {SharedArrayBuffer} data
12-
* @property {number} width The images width
13-
* @property {number} height The images height
9+
* Creates a new image with the given dimensions
10+
* @param {number} width
11+
* @param {number} height
12+
* @param {number} [fillColor = 0] (0..0xffffffff)
13+
* @returns {Image}
1414
*/
15-
constructor(data) {
16-
if (!(data instanceof SharedArrayBuffer))
17-
throw new TypeError(`${this.constructor.name} is not a constructor. Use \`Image.new\` instead`);
15+
constructor(width, height, fillColor) {
16+
width = ~~width;
17+
height = ~~height;
1818

19-
/** @private */
20-
this.__array__ = new Uint8ClampedArray(data);
19+
if (width < 1)
20+
throw new RangeError('Image has to be at least 1 pixel wide');
21+
if (height < 1)
22+
throw new RangeError('Image has to be at least 1 pixel high');
23+
24+
this.__width__ = width;
25+
this.__height__ = height;
2126

2227
/**
2328
* The images RGBA pixel data
2429
* @type {Uint8ClampedArray}
2530
*/
26-
this.bitmap = this.__array__.subarray(4, this.__array__.length);
27-
28-
if (this.bitmap.length !== (this.width * this.height * 4))
29-
throw new Error('Invalid image data');
31+
this.bitmap = new Uint8ClampedArray(width * height * 4);
3032

3133
/** @private */
3234
this.refs = new Array(this.width);
@@ -37,7 +39,7 @@ export class Image {
3739
const row_length = 4 * this.width;
3840

3941
while (offset < this.bitmap.length) {
40-
this.refs[row++] = new Uint32Array(this.__array__.buffer, 4 + offset, this.width);
42+
this.refs[row++] = new Uint32Array(this.bitmap.buffer, offset, this.width);
4143

4244
offset += row_length;
4345
}
@@ -60,57 +62,23 @@ export class Image {
6062
* @returns {Image}
6163
*/
6264
static new(width, height, fillColor = 0) {
63-
width = ~~width;
64-
height = ~~height;
65-
66-
if (width > 0xffff)
67-
throw new RangeError('Image width cannot exceed 65535 pixels');
68-
if (width < 1)
69-
throw new RangeError('Image has to be at least 1 pixel wide');
70-
if (height > 0xffff)
71-
throw new RangeError('Image height cannot exceed 65535 pixels');
72-
if (height < 1)
73-
throw new RangeError('Image has to be at least 1 pixel high');
74-
if (width * height > 2048 ** 2)
75-
throw new RangeError('Invalid image size (<= 2^22px)');
76-
77-
return Image.__new__(width, height, fillColor);
78-
}
79-
80-
/**
81-
* @param {number} width
82-
* @param {number} height
83-
* @param {number} fillColor
84-
* @returns {Image}
85-
* @private
86-
*/
87-
static __new__(width, height, fillColor = 0) {
88-
const data = new SharedArrayBuffer(width * height * 4 + 4);
89-
const dataUint8Clamped = new Uint8ClampedArray(data);
90-
dataUint8Clamped.set([width >> 8 & 0xff, width & 0xff, height >> 8 & 0xff, height & 0xff]);
91-
92-
const image = new Image(data);
93-
94-
if (fillColor)
95-
image.__fast_fill__(Image.colorToRGBA(fillColor));
96-
97-
return image;
65+
return new Image(width, height, fillColor);
9866
}
9967

10068
/**
10169
* The images width
10270
* @returns {number}
10371
*/
10472
get width() {
105-
return (this.__array__[0] << 8) | this.__array__[1];
73+
return this.__width__;
10674
}
10775

10876
/**
10977
* The images height
11078
* @returns {number}
11179
*/
11280
get height() {
113-
return (this.__array__[2] << 8) | this.__array__[3];
81+
return this.__height__;
11482
}
11583

11684
/**
@@ -291,6 +259,8 @@ export class Image {
291259
* @param {number} pixelColor
292260
*/
293261
setPixelAt(x, y, pixelColor) {
262+
x = ~~x;
263+
y = ~~y;
294264
this.__check_boundaries__(x, y);
295265
this.__set_pixel__(x, y, pixelColor);
296266
return this;
@@ -303,7 +273,7 @@ export class Image {
303273
* @param {number} pixelColor
304274
*/
305275
__set_pixel__(x, y, pixelColor) {
306-
const i = (~~y - 1) * this.width * 4 + (~~x - 1) * 4;
276+
const i = ((y - 1) * this.width + (x - 1)) * 4;
307277
this.bitmap.set(Image.colorToRGBA(pixelColor), i);
308278
}
309279

@@ -372,15 +342,9 @@ export class Image {
372342
* @returns {Image}
373343
*/
374344
clone() {
375-
return new Image(this.__toSharedBuffer__().slice());
376-
}
377-
378-
/**
379-
* @private
380-
* @returns {ArrayBufferLike<SharedArrayBuffer>}
381-
*/
382-
__toSharedBuffer__() {
383-
return this.__array__.buffer;
345+
const image = Image.new(this.width, this.height);
346+
image.bitmap.set(this.bitmap);
347+
return image;
384348
}
385349

386350
/**
@@ -425,17 +389,10 @@ export class Image {
425389

426390
width = Math.floor(width);
427391
height = Math.floor(height);
428-
429-
if (width > 0xffff)
430-
throw new RangeError('Image width cannot exceed 65535 pixels');
431392
if (width < 1)
432393
throw new RangeError('Image has to be at least 1 pixel wide');
433-
if (height > 0xffff)
434-
throw new RangeError('Image height cannot exceed 65535 pixels');
435394
if (height < 1)
436395
throw new RangeError('Image has to be at least 1 pixel high');
437-
if (width * height > 2048 ** 2)
438-
throw new RangeError('Invalid image size (<= 2^22px)');
439396

440397
if (mode === Image.RESIZE_NEAREST_NEIGHBOR)
441398
return this.__resize_nearest_neighbor__(width, height);
@@ -448,9 +405,7 @@ export class Image {
448405
* @param {number} height The new height
449406
*/
450407
__resize_nearest_neighbor__(width, height) {
451-
const sharedArrayBuffer = new SharedArrayBuffer(width * height * 4 + 4);
452-
const destinationBuffer = new Uint8ClampedArray(sharedArrayBuffer);
453-
destinationBuffer.set([width >> 8 & 0xff, width & 0xff, height >> 8 & 0xff, height & 0xff]);
408+
const image = new Image(width, height);
454409

455410
for (let y = 0; y < height; y++) {
456411
for (let x = 0; x < width; x++) {
@@ -460,11 +415,13 @@ export class Image {
460415
const destPos = (y * width + x) * 4;
461416
const srcPos = (ySrc * this.width + xSrc) * 4;
462417

463-
destinationBuffer.set(this.bitmap.subarray(srcPos, srcPos + 4), destPos + 4);
418+
image.bitmap.set(this.bitmap.subarray(srcPos, srcPos + 4), destPos);
464419
}
465420
}
466421

467-
return this.__apply__(new Image(sharedArrayBuffer));
422+
this.__apply__(image);
423+
424+
return this;
468425
}
469426

470427
/**
@@ -491,17 +448,15 @@ export class Image {
491448
x = ~~x;
492449
y = ~~y;
493450

494-
const sharedArrayBuffer = new SharedArrayBuffer(width * height * 4 + 4);
495-
const destinationBuffer = new Uint8ClampedArray(sharedArrayBuffer);
496-
destinationBuffer.set([width >> 8 & 0xff, width & 0xff, height >> 8 & 0xff, height & 0xff]);
451+
const image = new Image(width, height);
497452

498453
for (let tY = 0; tY < height; tY++) {
499454
const idx = ((tY + y) * this.width + x) * 4;
500455
const tIdx = tY * width * 4;
501-
destinationBuffer.set(this.bitmap.subarray(idx, idx + width * 4), tIdx + 4);
456+
image.bitmap.set(this.bitmap.subarray(idx, idx + width * 4), tIdx);
502457
}
503458

504-
return new Image(sharedArrayBuffer);
459+
return image;
505460
}
506461

507462
/**
@@ -726,7 +681,7 @@ export class Image {
726681
const bgPixel = this.getPixelAt(tX, tY);
727682
const fgPixel = source.getPixelAt(sX, sY);
728683
if ((fgPixel & 0xff) === 0xff) this.__set_pixel__(tX, tY, fgPixel);
729-
if ((fgPixel & 0xff) === 0x00) this.__set_pixel__(tX, tY, bgPixel);
684+
else if ((fgPixel & 0xff) === 0x00) this.__set_pixel__(tX, tY, bgPixel);
730685
else this.__set_pixel__(tX, tY, Image.__alpha_blend__(fgPixel, bgPixel));
731686
}
732687

@@ -950,8 +905,10 @@ export class Image {
950905
* @returns {Image}
951906
*/
952907
__apply__(image) {
953-
this.__array__ = image.__array__;
908+
this.__width__ = image.__width__;
909+
this.__height__ = image.__height__;
954910
this.bitmap = image.bitmap;
911+
this.refs = image.refs;
955912

956913
return this;
957914
}
@@ -971,7 +928,11 @@ export class Image {
971928
* @return {Promise<Image>} The decoded image
972929
*/
973930
static async decode(buffer) {
974-
return new Image(await png.decode(new Uint8Array(buffer)));
931+
const {width, height, pixels} = await png.decode(new Uint8Array(buffer));
932+
const image = new Image(width, height);
933+
image.bitmap.set(pixels);
934+
935+
return image;
975936
}
976937
}
977938

@@ -981,15 +942,18 @@ export class Image {
981942
*/
982943
export class Frame extends Image {
983944
/**
984-
* This is for internal use.
985-
* If you intend on creating a frame from an Image, use {@link Frame.from} instead.
986-
* @param {SharedArrayBuffer} data
987-
* @param {number} [duration = 100]
945+
* Creates a new, blank frame
946+
* @param {number} width
947+
* @param {number} height
948+
* @param {number} [duration = 100] The frames duration (in ms)
949+
* @param {number} [fillColor = 0] (0..0xffffffff)
950+
* @return {Frame}
988951
*/
989-
constructor(data, duration = 100) {
990-
super(data);
991-
if (isNaN(duration) || duration < 0 || duration > 0xffff)
952+
constructor(width, height, duration = 100, fillColor) {
953+
if (isNaN(duration) || duration < 0)
992954
throw new RangeError('Invalid frame duration');
955+
956+
super(width, height, fillColor);
993957
this.duration = duration;
994958
}
995959

@@ -1005,9 +969,8 @@ export class Frame extends Image {
1005969
* @param {number} [fillColor = 0] (0..0xffffffff)
1006970
* @return {Frame}
1007971
*/
1008-
static new(width, height, duration, fillColor) {
1009-
const image = super.new(width, height, fillColor);
1010-
return Frame.from(image, duration);
972+
static new(width, height, duration = 100, fillColor) {
973+
return new Frame(width, height, duration, fillColor);
1011974
}
1012975

1013976
/**
@@ -1019,18 +982,10 @@ export class Frame extends Image {
1019982
static from(image, duration) {
1020983
if (!(image instanceof Image))
1021984
throw new TypeError('Invalid image passed');
1022-
return new Frame(image.__toSharedBuffer__(), duration);
1023-
}
985+
const frame = new Frame(image.width, image.height, duration);
986+
frame.bitmap.set(image.bitmap);
1024987

1025-
/**
1026-
* @private
1027-
*/
1028-
__toSharedTimedBuffer__() {
1029-
const imageBuffer = this.__array__;
1030-
const data = new Uint8Array(imageBuffer.byteLength + 2);
1031-
data.set([(this.duration >> 8) & 0xff, this.duration & 0xff], 0);
1032-
data.set(imageBuffer, 2);
1033-
return data;
988+
return frame;
1034989
}
1035990
}
1036991

@@ -1055,21 +1010,11 @@ export class GIF extends Array {
10551010
throw new TypeError(`Frame ${frames.indexOf(frame)} is not an instance of Frame`);
10561011
}
10571012

1058-
if (loopCount < 0 || loopCount > 0xffff)
1013+
if (loopCount < 0)
10591014
throw new RangeError('Invalid loop count');
10601015

10611016
super(...frames);
10621017
this.loopCount = loopCount;
1063-
1064-
const sizeSqrt = Math.sqrt(this.width * this.height);
1065-
const maxFrames = Math.floor(30 * 512 / sizeSqrt);
1066-
1067-
if (Math.max(this.width, this.height) > 0xffff)
1068-
throw new RangeError('Invalid GIF dimensions (<= 65535px)');
1069-
if (this.width * this.height > 2048 ** 2)
1070-
throw new RangeError('Invalid GIF size (<= 2^22px)');
1071-
if (frames.length > maxFrames)
1072-
throw new RangeError(`GIF cannot exceed ${maxFrames} frames at this resolution`);
10731018
}
10741019

10751020
toString() {
@@ -1100,13 +1045,6 @@ export class GIF extends Array {
11001045
return Math.max(...[...this].map(frame => frame.height));
11011046
}
11021047

1103-
/**
1104-
* @private
1105-
*/
1106-
__toArray__() {
1107-
return [this.loopCount, ...[...this].map(frame => frame.__toSharedTimedBuffer__())];
1108-
}
1109-
11101048
/**
11111049
* Encodes the image into a GIF
11121050
* @return {Uint8Array} The encoded data

‎utils/png.js‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,8 @@ export async function decode(array) {
117117
pixels = newPixels;
118118
}
119119

120-
const isPassable = new SharedArrayBuffer(pixels.length + 4);
121-
const dataArray = new Uint8Array(isPassable);
122-
dataArray.set([(width >> 8) & 0xff, width & 0xff, (height >> 8) & 0xff, height & 0xff]);
123-
dataArray.set(pixels, 4);
124-
return isPassable;
125-
}
120+
return {width, height, pixels};
121+
}
126122

127123
function filter_1(slice, pixels, p_offset, channels, row_length) {
128124
let i = 0;

0 commit comments

Comments
 (0)