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
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
|
+
import { toSRGB } from "./hdr.js";
|
|
3
|
+
const isSpace = (c) => c === 0x20 || (c >= 0x09 && c <= 0x0d);
|
|
4
|
+
export function isNetpbm(d) {
|
|
5
|
+
if (d.length < 3 || d[0] !== 0x50)
|
|
6
|
+
return false;
|
|
7
|
+
if (d[1] === 0x37)
|
|
8
|
+
return d[2] === 0x0a;
|
|
9
|
+
return (d[1] >= 0x31 && d[1] <= 0x36 || d[1] === 0x46 || d[1] === 0x66) && isSpace(d[2]);
|
|
10
|
+
}
|
|
11
|
+
class Reader {
|
|
12
|
+
d;
|
|
13
|
+
o;
|
|
14
|
+
constructor(d, o) {
|
|
15
|
+
this.d = d;
|
|
16
|
+
this.o = o;
|
|
17
|
+
}
|
|
18
|
+
skip() {
|
|
19
|
+
const d = this.d;
|
|
20
|
+
while (this.o < d.length) {
|
|
21
|
+
if (d[this.o] === 0x23)
|
|
22
|
+
while (this.o < d.length && d[this.o] !== 0x0a && d[this.o] !== 0x0d)
|
|
23
|
+
this.o++;
|
|
24
|
+
else if (isSpace(d[this.o]))
|
|
25
|
+
this.o++;
|
|
26
|
+
else
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
token() {
|
|
31
|
+
this.skip();
|
|
32
|
+
const start = this.o;
|
|
33
|
+
while (this.o < this.d.length && !isSpace(this.d[this.o]) && this.d[this.o] !== 0x23)
|
|
34
|
+
this.o++;
|
|
35
|
+
if (start === this.o)
|
|
36
|
+
throw new Error('Netpbm: truncated header');
|
|
37
|
+
return String.fromCharCode(...this.d.subarray(start, this.o));
|
|
38
|
+
}
|
|
39
|
+
integer(name) {
|
|
40
|
+
const token = this.token();
|
|
41
|
+
if (!/^\d{1,10}$/.test(token))
|
|
42
|
+
throw new Error(`Netpbm: invalid ${name} ${token.slice(0, 20)}`);
|
|
43
|
+
return +token;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function pamHeader(d) {
|
|
47
|
+
const fields = {};
|
|
48
|
+
const types = [];
|
|
49
|
+
let o = 3;
|
|
50
|
+
for (;;) {
|
|
51
|
+
let end = o;
|
|
52
|
+
while (end < d.length && d[end] !== 0x0a)
|
|
53
|
+
end++;
|
|
54
|
+
if (end >= d.length)
|
|
55
|
+
throw new Error('PAM: truncated header');
|
|
56
|
+
const line = String.fromCharCode(...d.subarray(o, end)).trim();
|
|
57
|
+
o = end + 1;
|
|
58
|
+
if (line === 'ENDHDR')
|
|
59
|
+
break;
|
|
60
|
+
if (!line || line[0] === '#')
|
|
61
|
+
continue;
|
|
62
|
+
const [name, ...value] = line.split(/\s+/);
|
|
63
|
+
if (name === 'TUPLTYPE')
|
|
64
|
+
types.push(value.join(' '));
|
|
65
|
+
else
|
|
66
|
+
fields[name] = value.join(' ');
|
|
67
|
+
}
|
|
68
|
+
const number = (name) => {
|
|
69
|
+
if (!/^\d{1,10}$/.test(fields[name] ?? ''))
|
|
70
|
+
throw new Error(`PAM: invalid ${name}`);
|
|
71
|
+
return +fields[name];
|
|
72
|
+
};
|
|
73
|
+
return { width: number('WIDTH'), height: number('HEIGHT'), depth: number('DEPTH'), maxval: number('MAXVAL'), tupleType: types.join(' '), data: o };
|
|
74
|
+
}
|
|
75
|
+
export function decode(d, maxPixels = MAX_PIXELS, toneMapping = 'reinhard') {
|
|
76
|
+
if (!isNetpbm(d))
|
|
77
|
+
throw new Error('Netpbm: invalid header');
|
|
78
|
+
const kind = String.fromCharCode(d[1]);
|
|
79
|
+
if (kind === 'F' || kind === 'f')
|
|
80
|
+
return decodePFM(d, kind === 'F' ? 3 : 1, maxPixels, toneMapping);
|
|
81
|
+
let header;
|
|
82
|
+
const reader = new Reader(d, 2);
|
|
83
|
+
if (kind === '7')
|
|
84
|
+
header = pamHeader(d);
|
|
85
|
+
else {
|
|
86
|
+
const width = reader.integer('width'), height = reader.integer('height');
|
|
87
|
+
const bitmap = kind === '1' || kind === '4';
|
|
88
|
+
const maxval = bitmap ? 1 : reader.integer('maxval');
|
|
89
|
+
if (kind >= '4' && reader.o >= d.length)
|
|
90
|
+
throw new Error('Netpbm: truncated header');
|
|
91
|
+
header = { width, height, depth: kind === '3' || kind === '6' ? 3 : 1, maxval, tupleType: bitmap ? 'PBM' : '', data: reader.o + 1 };
|
|
92
|
+
}
|
|
93
|
+
const { width, height, depth, maxval } = header;
|
|
94
|
+
if (!width || !height)
|
|
95
|
+
throw new Error(`Netpbm: invalid dimensions ${width}x${height}`);
|
|
96
|
+
if (!maxval || maxval > 65535)
|
|
97
|
+
throw new Error(`Netpbm: invalid maxval ${maxval}`);
|
|
98
|
+
if (depth < 1 || depth > 4)
|
|
99
|
+
throw new Error(`PAM: unsupported depth ${depth}`);
|
|
100
|
+
checkPixels(width, height, maxPixels);
|
|
101
|
+
const count = width * height, samples = new Uint8Array(count * depth);
|
|
102
|
+
const scale = (v) => v >= maxval ? 255 : Math.round(v * 255 / maxval);
|
|
103
|
+
if (kind === '1' || kind === '2' || kind === '3') {
|
|
104
|
+
let o = header.data - 1;
|
|
105
|
+
for (let i = 0; i < samples.length; i++) {
|
|
106
|
+
while (o < d.length && (isSpace(d[o]) || d[o] === 0x23)) {
|
|
107
|
+
if (d[o] === 0x23)
|
|
108
|
+
while (o < d.length && d[o] !== 0x0a)
|
|
109
|
+
o++;
|
|
110
|
+
else
|
|
111
|
+
o++;
|
|
112
|
+
}
|
|
113
|
+
if (o >= d.length)
|
|
114
|
+
throw new Error('Netpbm: truncated image data');
|
|
115
|
+
let v = 0;
|
|
116
|
+
if (kind === '1')
|
|
117
|
+
v = d[o++] - 0x30;
|
|
118
|
+
else {
|
|
119
|
+
const start = o;
|
|
120
|
+
while (o < d.length && d[o] >= 0x30 && d[o] <= 0x39 && o - start < 6)
|
|
121
|
+
v = v * 10 + d[o++] - 0x30;
|
|
122
|
+
if (start === o)
|
|
123
|
+
throw new Error('Netpbm: invalid sample');
|
|
124
|
+
}
|
|
125
|
+
if (v < 0 || v > maxval)
|
|
126
|
+
throw new Error('Netpbm: invalid sample');
|
|
127
|
+
samples[i] = kind === '1' ? (v ? 0 : 255) : scale(v);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (kind === '4') {
|
|
131
|
+
const stride = width + 7 >> 3;
|
|
132
|
+
if (header.data + stride * height > d.length)
|
|
133
|
+
throw new Error('Netpbm: truncated image data');
|
|
134
|
+
for (let y = 0; y < height; y++)
|
|
135
|
+
for (let x = 0; x < width; x++)
|
|
136
|
+
samples[y * width + x] = d[header.data + y * stride + (x >> 3)] >> (7 - (x & 7)) & 1 ? 0 : 255;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const wide = maxval > 255;
|
|
140
|
+
if (header.data + samples.length * (wide ? 2 : 1) > d.length)
|
|
141
|
+
throw new Error('Netpbm: truncated image data');
|
|
142
|
+
for (let i = 0, o = header.data; i < samples.length; i++, o += wide ? 2 : 1)
|
|
143
|
+
samples[i] = scale(wide ? d[o] << 8 | d[o + 1] : d[o]);
|
|
144
|
+
}
|
|
145
|
+
const pixels = new Uint8Array(count * 4);
|
|
146
|
+
for (let i = 0; i < count; i++) {
|
|
147
|
+
const s = i * depth, p = i * 4;
|
|
148
|
+
if (depth < 3) {
|
|
149
|
+
pixels[p] = pixels[p + 1] = pixels[p + 2] = samples[s];
|
|
150
|
+
pixels[p + 3] = depth === 2 ? samples[s + 1] : 255;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
pixels[p] = samples[s];
|
|
154
|
+
pixels[p + 1] = samples[s + 1];
|
|
155
|
+
pixels[p + 2] = samples[s + 2];
|
|
156
|
+
pixels[p + 3] = depth === 4 ? samples[s + 3] : 255;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { width, height, pixels };
|
|
160
|
+
}
|
|
161
|
+
function decodePFM(d, channels, maxPixels, toneMapping) {
|
|
162
|
+
const reader = new Reader(d, 2);
|
|
163
|
+
const width = reader.integer('width'), height = reader.integer('height');
|
|
164
|
+
const scale = +reader.token();
|
|
165
|
+
if (!width || !height)
|
|
166
|
+
throw new Error(`PFM: invalid dimensions ${width}x${height}`);
|
|
167
|
+
if (!Number.isFinite(scale) || !scale)
|
|
168
|
+
throw new Error('PFM: invalid scale');
|
|
169
|
+
checkPixels(width, height, maxPixels);
|
|
170
|
+
const start = reader.o + 1;
|
|
171
|
+
if (start + width * height * channels * 4 > d.length)
|
|
172
|
+
throw new Error('PFM: truncated image data');
|
|
173
|
+
const view = new DataView(d.buffer, d.byteOffset + start);
|
|
174
|
+
const little = scale < 0;
|
|
175
|
+
const rgb = new Float32Array(width * height * 3);
|
|
176
|
+
for (let y = 0; y < height; y++) {
|
|
177
|
+
for (let x = 0; x < width; x++) {
|
|
178
|
+
const s = ((height - 1 - y) * width + x) * channels * 4, p = (y * width + x) * 3;
|
|
179
|
+
for (let c = 0; c < 3; c++)
|
|
180
|
+
rgb[p + c] = view.getFloat32(s + (channels === 3 ? c * 4 : 0), little);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { width, height, pixels: toSRGB(rgb, toneMapping) };
|
|
184
|
+
}
|
|
185
|
+
export function encode(rgba, width, height) {
|
|
186
|
+
if (width < 1 || height < 1)
|
|
187
|
+
throw new RangeError('Netpbm: dimensions must be at least 1');
|
|
188
|
+
const count = width * height;
|
|
189
|
+
let opaque = true, grey = true, bitmap = true;
|
|
190
|
+
for (let p = 0; p < count * 4; p += 4) {
|
|
191
|
+
const r = rgba[p], g = rgba[p + 1], b = rgba[p + 2], a = rgba[p + 3];
|
|
192
|
+
if (a !== 255)
|
|
193
|
+
opaque = false;
|
|
194
|
+
if (r !== g || g !== b)
|
|
195
|
+
grey = false;
|
|
196
|
+
if (r !== 0 && r !== 255 || r !== g || g !== b)
|
|
197
|
+
bitmap = false;
|
|
198
|
+
if (!opaque && !grey)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
const ascii = (text) => Uint8Array.from(text, c => c.charCodeAt(0));
|
|
202
|
+
let header, depth;
|
|
203
|
+
if (!opaque) {
|
|
204
|
+
depth = grey ? 2 : 4;
|
|
205
|
+
header = ascii(`P7\nWIDTH ${width}\nHEIGHT ${height}\nDEPTH ${depth}\nMAXVAL 255\nTUPLTYPE ${grey ? 'GRAYSCALE_ALPHA' : 'RGB_ALPHA'}\nENDHDR\n`);
|
|
206
|
+
}
|
|
207
|
+
else if (bitmap) {
|
|
208
|
+
depth = 0;
|
|
209
|
+
header = ascii(`P4\n${width} ${height}\n`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
depth = grey ? 1 : 3;
|
|
213
|
+
header = ascii(`P${grey ? 5 : 6}\n${width} ${height}\n255\n`);
|
|
214
|
+
}
|
|
215
|
+
const stride = depth ? width * depth : width + 7 >> 3;
|
|
216
|
+
const out = new Uint8Array(header.length + stride * height);
|
|
217
|
+
out.set(header);
|
|
218
|
+
let o = header.length;
|
|
219
|
+
if (!depth) {
|
|
220
|
+
for (let y = 0; y < height; y++, o += stride)
|
|
221
|
+
for (let x = 0; x < width; x++)
|
|
222
|
+
if (!rgba[(y * width + x) * 4])
|
|
223
|
+
out[o + (x >> 3)] |= 128 >> (x & 7);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
for (let p = 0; p < count * 4; p += 4) {
|
|
227
|
+
if (depth === 1 || depth === 2)
|
|
228
|
+
out[o++] = rgba[p];
|
|
229
|
+
else {
|
|
230
|
+
out[o++] = rgba[p];
|
|
231
|
+
out[o++] = rgba[p + 1];
|
|
232
|
+
out[o++] = rgba[p + 2];
|
|
233
|
+
}
|
|
234
|
+
if (depth === 2 || depth === 4)
|
|
235
|
+
out[o++] = rgba[p + 3];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The Quite OK Image format (https://qoiformat.org/qoi-specification.pdf). The encoder makes the choices of the
|
|
3
|
+
* reference encoder qoi.h, so it writes the same bytes. Copyright (c) 2021 Dominic Szablewski, MIT License (see
|
|
4
|
+
* THIRD_PARTY_NOTICES.md)
|
|
5
|
+
*/
|
|
6
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
7
|
+
const OP_INDEX = 0x00, OP_DIFF = 0x40, OP_LUMA = 0x80, OP_RUN = 0xc0, OP_RGB = 0xfe, OP_RGBA = 0xff;
|
|
8
|
+
export function isQOI(d) {
|
|
9
|
+
return d.length >= 14 && d[0] === 0x71 && d[1] === 0x6f && d[2] === 0x69 && d[3] === 0x66;
|
|
10
|
+
}
|
|
11
|
+
const hash = (r, g, b, a) => (r * 3 + g * 5 + b * 7 + a * 11) & 63;
|
|
12
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
13
|
+
if (!isQOI(d))
|
|
14
|
+
throw new Error('QOI: invalid header');
|
|
15
|
+
const view = new DataView(d.buffer, d.byteOffset, d.byteLength);
|
|
16
|
+
const width = view.getUint32(4), height = view.getUint32(8);
|
|
17
|
+
if (!width || !height)
|
|
18
|
+
throw new Error(`QOI: invalid dimensions ${width}x${height}`);
|
|
19
|
+
checkPixels(width, height, maxPixels);
|
|
20
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
21
|
+
const index = new Uint8Array(256);
|
|
22
|
+
let r = 0, g = 0, b = 0, a = 255;
|
|
23
|
+
for (let p = 0, o = 14; p < pixels.length;) {
|
|
24
|
+
if (o >= d.length)
|
|
25
|
+
throw new Error('QOI: truncated image data');
|
|
26
|
+
const op = d[o++];
|
|
27
|
+
let run = 1;
|
|
28
|
+
if (op === OP_RGB) {
|
|
29
|
+
r = d[o];
|
|
30
|
+
g = d[o + 1];
|
|
31
|
+
b = d[o + 2];
|
|
32
|
+
o += 3;
|
|
33
|
+
}
|
|
34
|
+
else if (op === OP_RGBA) {
|
|
35
|
+
r = d[o];
|
|
36
|
+
g = d[o + 1];
|
|
37
|
+
b = d[o + 2];
|
|
38
|
+
a = d[o + 3];
|
|
39
|
+
o += 4;
|
|
40
|
+
}
|
|
41
|
+
else if ((op & 0xc0) === OP_INDEX) {
|
|
42
|
+
const i = op * 4;
|
|
43
|
+
r = index[i];
|
|
44
|
+
g = index[i + 1];
|
|
45
|
+
b = index[i + 2];
|
|
46
|
+
a = index[i + 3];
|
|
47
|
+
}
|
|
48
|
+
else if ((op & 0xc0) === OP_DIFF) {
|
|
49
|
+
r = r + (op >> 4 & 3) - 2 & 255;
|
|
50
|
+
g = g + (op >> 2 & 3) - 2 & 255;
|
|
51
|
+
b = b + (op & 3) - 2 & 255;
|
|
52
|
+
}
|
|
53
|
+
else if ((op & 0xc0) === OP_LUMA) {
|
|
54
|
+
const dg = (op & 63) - 32, next = d[o++];
|
|
55
|
+
r = r + dg - 8 + (next >> 4) & 255;
|
|
56
|
+
g = g + dg & 255;
|
|
57
|
+
b = b + dg - 8 + (next & 15) & 255;
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
run = (op & 63) + 1;
|
|
61
|
+
if (o > d.length)
|
|
62
|
+
throw new Error('QOI: truncated image data');
|
|
63
|
+
const i = hash(r, g, b, a) * 4;
|
|
64
|
+
index[i] = r;
|
|
65
|
+
index[i + 1] = g;
|
|
66
|
+
index[i + 2] = b;
|
|
67
|
+
index[i + 3] = a;
|
|
68
|
+
for (const stop = Math.min(pixels.length, p + run * 4); p < stop; p += 4) {
|
|
69
|
+
pixels[p] = r;
|
|
70
|
+
pixels[p + 1] = g;
|
|
71
|
+
pixels[p + 2] = b;
|
|
72
|
+
pixels[p + 3] = a;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { width, height, pixels };
|
|
76
|
+
}
|
|
77
|
+
export function encode(rgba, width, height) {
|
|
78
|
+
if (width < 1 || height < 1)
|
|
79
|
+
throw new RangeError('QOI: dimensions must be at least 1');
|
|
80
|
+
const count = width * height;
|
|
81
|
+
let opaque = true;
|
|
82
|
+
for (let i = 3; i < count * 4; i += 4)
|
|
83
|
+
if (rgba[i] !== 255) {
|
|
84
|
+
opaque = false;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const out = new Uint8Array(14 + count * (opaque ? 4 : 5) + 8);
|
|
88
|
+
const view = new DataView(out.buffer);
|
|
89
|
+
out.set([0x71, 0x6f, 0x69, 0x66]);
|
|
90
|
+
view.setUint32(4, width);
|
|
91
|
+
view.setUint32(8, height);
|
|
92
|
+
out[12] = opaque ? 3 : 4;
|
|
93
|
+
out[13] = 0;
|
|
94
|
+
const index = new Uint8Array(256);
|
|
95
|
+
let o = 14, run = 0;
|
|
96
|
+
let pr = 0, pg = 0, pb = 0, pa = 255;
|
|
97
|
+
for (let p = 0, last = count * 4 - 4; p <= last; p += 4) {
|
|
98
|
+
const r = rgba[p], g = rgba[p + 1], b = rgba[p + 2], a = rgba[p + 3];
|
|
99
|
+
if (r === pr && g === pg && b === pb && a === pa) {
|
|
100
|
+
run++;
|
|
101
|
+
if (run === 62 || p === last) {
|
|
102
|
+
out[o++] = OP_RUN | run - 1;
|
|
103
|
+
run = 0;
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (run) {
|
|
108
|
+
out[o++] = OP_RUN | run - 1;
|
|
109
|
+
run = 0;
|
|
110
|
+
}
|
|
111
|
+
const h = hash(r, g, b, a), i = h * 4;
|
|
112
|
+
if (index[i] === r && index[i + 1] === g && index[i + 2] === b && index[i + 3] === a)
|
|
113
|
+
out[o++] = OP_INDEX | h;
|
|
114
|
+
else {
|
|
115
|
+
index[i] = r;
|
|
116
|
+
index[i + 1] = g;
|
|
117
|
+
index[i + 2] = b;
|
|
118
|
+
index[i + 3] = a;
|
|
119
|
+
if (a === pa) {
|
|
120
|
+
const vr = (r - pr) << 24 >> 24, vg = (g - pg) << 24 >> 24, vb = (b - pb) << 24 >> 24;
|
|
121
|
+
const vgr = vr - vg, vgb = vb - vg;
|
|
122
|
+
if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2)
|
|
123
|
+
out[o++] = OP_DIFF | vr + 2 << 4 | vg + 2 << 2 | vb + 2;
|
|
124
|
+
else if (vgr > -9 && vgr < 8 && vg > -33 && vg < 32 && vgb > -9 && vgb < 8) {
|
|
125
|
+
out[o++] = OP_LUMA | vg + 32;
|
|
126
|
+
out[o++] = vgr + 8 << 4 | vgb + 8;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
out[o++] = OP_RGB;
|
|
130
|
+
out[o++] = r;
|
|
131
|
+
out[o++] = g;
|
|
132
|
+
out[o++] = b;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
out[o++] = OP_RGBA;
|
|
137
|
+
out[o++] = r;
|
|
138
|
+
out[o++] = g;
|
|
139
|
+
out[o++] = b;
|
|
140
|
+
out[o++] = a;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
pr = r;
|
|
144
|
+
pg = g;
|
|
145
|
+
pb = b;
|
|
146
|
+
pa = a;
|
|
147
|
+
}
|
|
148
|
+
out[o + 7] = 1;
|
|
149
|
+
return out.slice(0, o + 8);
|
|
150
|
+
}
|