imagescript 3.2.1 → 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 +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- 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/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- 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 +11 -28
- 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/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- 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/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +25 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MAX_PIXELS } from "../util/limits.js";
|
|
2
|
+
import { decodeDIB } from "./bmp.js";
|
|
3
|
+
const u16 = (d, o) => d[o] | d[o + 1] << 8;
|
|
4
|
+
const u32 = (d, o) => (d[o] | d[o + 1] << 8 | d[o + 2] << 16 | d[o + 3] << 24) >>> 0;
|
|
5
|
+
const isPNG = (d, o) => d[o] === 0x89 && d[o + 1] === 0x50 && d[o + 2] === 0x4e && d[o + 3] === 0x47;
|
|
6
|
+
export function iconType(d) {
|
|
7
|
+
if (d.length < 22 || u16(d, 0) !== 0 || (u16(d, 2) !== 1 && u16(d, 2) !== 2))
|
|
8
|
+
return null;
|
|
9
|
+
const count = u16(d, 4);
|
|
10
|
+
if (!count || d.length < 6 + count * 16 || d[9] !== 0)
|
|
11
|
+
return null;
|
|
12
|
+
const offset = u32(d, 18);
|
|
13
|
+
if (offset < 6 + count * 16 || offset + 24 > d.length)
|
|
14
|
+
return null;
|
|
15
|
+
return isPNG(d, offset) || [12, 40, 52, 56, 108, 124].includes(u32(d, offset)) ? u16(d, 2) === 1 ? 'ico' : 'cur' : null;
|
|
16
|
+
}
|
|
17
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
18
|
+
if (!iconType(d))
|
|
19
|
+
throw new Error('ICO: invalid header');
|
|
20
|
+
let best = null;
|
|
21
|
+
for (let i = 0, count = u16(d, 4); i < count; i++) {
|
|
22
|
+
const e = 6 + i * 16;
|
|
23
|
+
const offset = u32(d, e + 12);
|
|
24
|
+
let size = u32(d, e + 8);
|
|
25
|
+
if (offset + 24 > d.length)
|
|
26
|
+
continue;
|
|
27
|
+
if (size < 16 || offset + size > d.length)
|
|
28
|
+
size = d.length - offset;
|
|
29
|
+
let area, bits;
|
|
30
|
+
if (isPNG(d, offset)) {
|
|
31
|
+
const view = new DataView(d.buffer, d.byteOffset + offset);
|
|
32
|
+
area = view.getUint32(16) * view.getUint32(20);
|
|
33
|
+
bits = 32;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
area = Math.abs(u32(d, offset + 4) * (u32(d, offset + 8) >> 1));
|
|
37
|
+
bits = u16(d, offset + 14);
|
|
38
|
+
}
|
|
39
|
+
if (!best || area > best.area || area === best.area && bits > best.bits)
|
|
40
|
+
best = { offset, size, area, bits };
|
|
41
|
+
}
|
|
42
|
+
if (!best)
|
|
43
|
+
throw new Error('ICO: no image inside the data');
|
|
44
|
+
const data = d.subarray(best.offset, best.offset + best.size);
|
|
45
|
+
if (isPNG(data, 0))
|
|
46
|
+
return { embedded: 'png', data };
|
|
47
|
+
return decodeDIB(data, 0, null, maxPixels, true);
|
|
48
|
+
}
|
package/dist/codecs/jpeg.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* (IJG license, see licenses/README.ijg). The scan decoder follows the structure of pdf.js' jpg.js
|
|
5
5
|
* (Copyright 2014 Mozilla Foundation, Apache License 2.0). See THIRD_PARTY_NOTICES.md
|
|
6
6
|
*/
|
|
7
|
+
import { ByteWriter } from "../util/bytes.js";
|
|
8
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
7
9
|
import { orientation as exifOrientation } from "./exif.js";
|
|
8
10
|
import { concat } from "../util/mem.js";
|
|
9
11
|
const ZIGZAG = Int32Array.of(0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63);
|
|
10
12
|
function clamp8(v) {
|
|
11
13
|
return v < 0 ? 0 : v > 255 ? 255 : v;
|
|
12
14
|
}
|
|
13
|
-
export function decode(data, { transform } = {}) {
|
|
15
|
+
export function decode(data, { transform, maxPixels = MAX_PIXELS } = {}) {
|
|
14
16
|
if (data[0] !== 0xff || data[1] !== 0xd8)
|
|
15
17
|
throw new Error('JPEG: invalid signature');
|
|
16
18
|
const quantizationTables = [];
|
|
@@ -84,6 +86,7 @@ export function decode(data, { transform } = {}) {
|
|
|
84
86
|
throw new Error('JPEG: multiple frames are not supported');
|
|
85
87
|
if (data[start] !== 8)
|
|
86
88
|
throw new Error(`JPEG: ${data[start]}-bit precision is not supported`);
|
|
89
|
+
checkPixels(u16(start + 3), u16(start + 1), maxPixels);
|
|
87
90
|
frame = createFrame(marker === 0xc2, u16(start + 1), u16(start + 3), data.subarray(start + 5, end));
|
|
88
91
|
break;
|
|
89
92
|
}
|
|
@@ -158,6 +161,8 @@ function createFrame(progressive, height, width, componentData) {
|
|
|
158
161
|
}
|
|
159
162
|
if (!width || !sampling.length)
|
|
160
163
|
throw new Error('JPEG: invalid dimensions');
|
|
164
|
+
if (sampling.length > 4)
|
|
165
|
+
throw new Error(`JPEG: ${sampling.length} components are not supported`);
|
|
161
166
|
const hmax = Math.max(...sampling.map(c => c.h));
|
|
162
167
|
const vmax = Math.max(...sampling.map(c => c.v));
|
|
163
168
|
const mcusPerLine = Math.ceil(width / (8 * hmax));
|
|
@@ -926,7 +931,8 @@ function fdctPass(d, o, s) {
|
|
|
926
931
|
const tmp1 = d[o + s] + d[o + 6 * s], tmp6 = d[o + s] - d[o + 6 * s];
|
|
927
932
|
const tmp2 = d[o + 2 * s] + d[o + 5 * s], tmp5 = d[o + 2 * s] - d[o + 5 * s];
|
|
928
933
|
const tmp3 = d[o + 3 * s] + d[o + 4 * s], tmp4 = d[o + 3 * s] - d[o + 4 * s];
|
|
929
|
-
let tmp10 = tmp0 + tmp3
|
|
934
|
+
let tmp10 = tmp0 + tmp3;
|
|
935
|
+
const tmp13 = tmp0 - tmp3;
|
|
930
936
|
let tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2;
|
|
931
937
|
d[o] = tmp10 + tmp11;
|
|
932
938
|
d[o + 4 * s] = tmp10 - tmp11;
|
|
@@ -946,29 +952,9 @@ function fdctPass(d, o, s) {
|
|
|
946
952
|
d[o + s] = z11 + z4;
|
|
947
953
|
d[o + 7 * s] = z11 - z4;
|
|
948
954
|
}
|
|
949
|
-
class BitWriter {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
bits;
|
|
953
|
-
count;
|
|
954
|
-
constructor(capacity) {
|
|
955
|
-
this.buffer = new Uint8Array(Math.max(1024, capacity));
|
|
956
|
-
this.length = 0;
|
|
957
|
-
this.bits = 0;
|
|
958
|
-
this.count = 0;
|
|
959
|
-
}
|
|
960
|
-
reserve(size) {
|
|
961
|
-
if (this.length + size <= this.buffer.length)
|
|
962
|
-
return;
|
|
963
|
-
const buffer = new Uint8Array(Math.max(this.buffer.length * 2, this.length + size));
|
|
964
|
-
buffer.set(this.buffer.subarray(0, this.length));
|
|
965
|
-
this.buffer = buffer;
|
|
966
|
-
}
|
|
967
|
-
bytes(...values) {
|
|
968
|
-
this.reserve(values.length);
|
|
969
|
-
for (const value of values)
|
|
970
|
-
this.buffer[this.length++] = value;
|
|
971
|
-
}
|
|
955
|
+
class BitWriter extends ByteWriter {
|
|
956
|
+
bits = 0;
|
|
957
|
+
count = 0;
|
|
972
958
|
write(value, size) {
|
|
973
959
|
this.bits = this.bits << size | value & (1 << size) - 1;
|
|
974
960
|
this.count += size;
|
|
@@ -986,7 +972,4 @@ class BitWriter {
|
|
|
986
972
|
if (this.count)
|
|
987
973
|
this.write((1 << 8 - this.count) - 1, 8 - this.count);
|
|
988
974
|
}
|
|
989
|
-
finish() {
|
|
990
|
-
return this.buffer.slice(0, this.length);
|
|
991
|
-
}
|
|
992
975
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* The JPEG XL bit reader and the field types of its headers (ISO/IEC 18181-1), ported from jxl-oxide
|
|
3
|
+
* (crates/jxl-bitstream). Copyright (c) Wonwoo Choi, MIT License (see THIRD_PARTY_NOTICES.md)
|
|
4
|
+
*/
|
|
5
|
+
export class BitReader {
|
|
6
|
+
d;
|
|
7
|
+
start;
|
|
8
|
+
end;
|
|
9
|
+
buf = 0;
|
|
10
|
+
n = 0;
|
|
11
|
+
pos;
|
|
12
|
+
constructor(d, start = 0, end = d.length) {
|
|
13
|
+
this.d = d;
|
|
14
|
+
this.start = start;
|
|
15
|
+
this.end = end;
|
|
16
|
+
this.pos = start;
|
|
17
|
+
}
|
|
18
|
+
get bitPosition() {
|
|
19
|
+
return (this.pos - this.start) * 8 - this.n;
|
|
20
|
+
}
|
|
21
|
+
refill() {
|
|
22
|
+
while (this.n <= 24) {
|
|
23
|
+
if (this.pos >= this.end) {
|
|
24
|
+
if (this.pos >= this.end + 8)
|
|
25
|
+
throw new Error('JPEG XL: truncated data');
|
|
26
|
+
this.pos++;
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
this.buf = (this.buf | this.d[this.pos++] << this.n) >>> 0;
|
|
30
|
+
this.n += 8;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
peek(n) {
|
|
34
|
+
if (this.n < n)
|
|
35
|
+
this.refill();
|
|
36
|
+
return this.buf & ((1 << n) - 1);
|
|
37
|
+
}
|
|
38
|
+
skip(n) {
|
|
39
|
+
this.buf >>>= n;
|
|
40
|
+
this.n -= n;
|
|
41
|
+
}
|
|
42
|
+
bits(n) {
|
|
43
|
+
if (n > 24) {
|
|
44
|
+
const low = this.bits(16);
|
|
45
|
+
return low + this.bits(n - 16) * 65536;
|
|
46
|
+
}
|
|
47
|
+
if (this.n < n)
|
|
48
|
+
this.refill();
|
|
49
|
+
const v = this.buf & ((1 << n) - 1);
|
|
50
|
+
this.buf >>>= n;
|
|
51
|
+
this.n -= n;
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
bool() {
|
|
55
|
+
return this.bits(1) === 1;
|
|
56
|
+
}
|
|
57
|
+
u32(d0, d1, d2, d3) {
|
|
58
|
+
const sel = this.bits(2);
|
|
59
|
+
const d = sel === 0 ? d0 : sel === 1 ? d1 : sel === 2 ? d2 : d3;
|
|
60
|
+
return typeof d === 'number' ? d : d[0] + this.bits(d[1]);
|
|
61
|
+
}
|
|
62
|
+
u64() {
|
|
63
|
+
const sel = this.bits(2);
|
|
64
|
+
if (sel === 0)
|
|
65
|
+
return 0;
|
|
66
|
+
if (sel === 1)
|
|
67
|
+
return 1 + this.bits(4);
|
|
68
|
+
if (sel === 2)
|
|
69
|
+
return 17 + this.bits(8);
|
|
70
|
+
let value = this.bits(12), shift = 12;
|
|
71
|
+
while (this.bool()) {
|
|
72
|
+
if (shift === 60) {
|
|
73
|
+
value += this.bits(4) * 2 ** shift;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
value += this.bits(8) * 2 ** shift;
|
|
77
|
+
shift += 8;
|
|
78
|
+
}
|
|
79
|
+
if (value > Number.MAX_SAFE_INTEGER)
|
|
80
|
+
throw new Error('JPEG XL: value too large');
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
f16() {
|
|
84
|
+
const v = this.bits(16);
|
|
85
|
+
const sign = v & 0x8000 ? -1 : 1, exponent = v >> 10 & 31, mantissa = v & 1023;
|
|
86
|
+
if (exponent === 31)
|
|
87
|
+
throw new Error('JPEG XL: invalid float');
|
|
88
|
+
if (exponent === 0)
|
|
89
|
+
return sign * mantissa / 1024 / 16384;
|
|
90
|
+
return sign * (1 + mantissa / 1024) * 2 ** (exponent - 15);
|
|
91
|
+
}
|
|
92
|
+
enum() {
|
|
93
|
+
const v = this.u32(0, 1, [2, 4], [18, 6]);
|
|
94
|
+
if (v > 63)
|
|
95
|
+
throw new Error('JPEG XL: invalid enum value');
|
|
96
|
+
return v;
|
|
97
|
+
}
|
|
98
|
+
skipBits(n) {
|
|
99
|
+
while (n > 24) {
|
|
100
|
+
this.bits(24);
|
|
101
|
+
n -= 24;
|
|
102
|
+
}
|
|
103
|
+
this.bits(n);
|
|
104
|
+
}
|
|
105
|
+
zeroPadToByte() {
|
|
106
|
+
const pad = (8 - this.bitPosition % 8) % 8;
|
|
107
|
+
if (this.bits(pad) !== 0)
|
|
108
|
+
throw new Error('JPEG XL: non-zero padding');
|
|
109
|
+
}
|
|
110
|
+
get bytePosition() {
|
|
111
|
+
return this.start + Math.ceil(this.bitPosition / 8);
|
|
112
|
+
}
|
|
113
|
+
checkEnd() {
|
|
114
|
+
if (this.bitPosition > (this.end - this.start) * 8)
|
|
115
|
+
throw new Error('JPEG XL: truncated data');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export const unpackSigned = (v) => v % 2 ? -(v + 1) / 2 : v / 2;
|
|
119
|
+
export function extensions(r) {
|
|
120
|
+
const bits = r.u64();
|
|
121
|
+
let lengths = 0;
|
|
122
|
+
for (let i = 0; i < 64; i++)
|
|
123
|
+
if (Math.floor(bits / 2 ** i) % 2)
|
|
124
|
+
lengths += r.u64();
|
|
125
|
+
r.skipBits(lengths);
|
|
126
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { EXTRA_ALPHA } from "./headers.js";
|
|
2
|
+
export const PB_NONE = 0, PB_REPLACE = 1, PB_ADD = 2, PB_MUL = 3, PB_BLEND_ABOVE = 4, PB_BLEND_BELOW = 5, PB_ADD_ABOVE = 6, PB_ADD_BELOW = 7;
|
|
3
|
+
const clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
|
|
4
|
+
let rows = [];
|
|
5
|
+
export function performBlending(bg, bi, fg, fi, out, oi, n, numColor, color, ec, extra) {
|
|
6
|
+
const hasAlpha = extra.some(e => e.type === EXTRA_ALPHA), numEc = extra.length, nc = numColor + numEc;
|
|
7
|
+
if (rows.length < nc || rows[0].length < n)
|
|
8
|
+
rows = Array.from({ length: Math.max(nc, rows.length) }, () => new Float32Array(Math.max(n, rows[0]?.length ?? 0)));
|
|
9
|
+
for (let i = 0; i < numEc; i++) {
|
|
10
|
+
const c = numColor + i, e = ec[i], a = numColor + e.alpha, t = rows[c], B = bg[c], F = fg[c], BA = bg[a], FA = fg[a];
|
|
11
|
+
const isAlpha = c === a, premultiplied = extra[e.alpha].alphaAssociated;
|
|
12
|
+
switch (e.mode) {
|
|
13
|
+
case PB_ADD:
|
|
14
|
+
for (let k = 0; k < n; k++)
|
|
15
|
+
t[k] = B[bi + k] + F[fi + k];
|
|
16
|
+
break;
|
|
17
|
+
case PB_BLEND_ABOVE:
|
|
18
|
+
for (let k = 0; k < n; k++)
|
|
19
|
+
t[k] = alphaBlend1(B[bi + k], BA[bi + k], F[fi + k], FA[fi + k], isAlpha, premultiplied, e.clamp);
|
|
20
|
+
break;
|
|
21
|
+
case PB_BLEND_BELOW:
|
|
22
|
+
for (let k = 0; k < n; k++)
|
|
23
|
+
t[k] = alphaBlend1(F[fi + k], FA[fi + k], B[bi + k], BA[bi + k], isAlpha, premultiplied, e.clamp);
|
|
24
|
+
break;
|
|
25
|
+
case PB_ADD_ABOVE:
|
|
26
|
+
for (let k = 0; k < n; k++)
|
|
27
|
+
t[k] = isAlpha ? B[bi + k] : B[bi + k] + F[fi + k] * (e.clamp ? clamp01(FA[fi + k]) : FA[fi + k]);
|
|
28
|
+
break;
|
|
29
|
+
case PB_ADD_BELOW:
|
|
30
|
+
for (let k = 0; k < n; k++)
|
|
31
|
+
t[k] = isAlpha ? F[fi + k] : F[fi + k] + B[bi + k] * (e.clamp ? clamp01(BA[bi + k]) : BA[bi + k]);
|
|
32
|
+
break;
|
|
33
|
+
case PB_MUL:
|
|
34
|
+
for (let k = 0; k < n; k++)
|
|
35
|
+
t[k] = B[bi + k] * (e.clamp ? clamp01(F[fi + k]) : F[fi + k]);
|
|
36
|
+
break;
|
|
37
|
+
case PB_REPLACE:
|
|
38
|
+
t.set(F.subarray(fi, fi + n));
|
|
39
|
+
break;
|
|
40
|
+
default: t.set(B.subarray(bi, bi + n));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const a = numColor + color.alpha;
|
|
44
|
+
switch (color.mode) {
|
|
45
|
+
case PB_ADD:
|
|
46
|
+
for (let c = 0; c < numColor; c++) {
|
|
47
|
+
const t = rows[c], B = bg[c], F = fg[c];
|
|
48
|
+
for (let k = 0; k < n; k++)
|
|
49
|
+
t[k] = B[bi + k] + F[fi + k];
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case PB_ADD_ABOVE:
|
|
53
|
+
case PB_ADD_BELOW: {
|
|
54
|
+
const [lo, li, hi, hj] = color.mode === PB_ADD_ABOVE ? [bg, bi, fg, fi] : [fg, fi, bg, bi];
|
|
55
|
+
for (let c = 0; c < numColor; c++) {
|
|
56
|
+
const t = rows[c], L = lo[c], H = hi[c];
|
|
57
|
+
if (!hasAlpha) {
|
|
58
|
+
for (let k = 0; k < n; k++)
|
|
59
|
+
t[k] = bg[c][bi + k] + fg[c][fi + k];
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const HA = hi[a];
|
|
63
|
+
for (let k = 0; k < n; k++)
|
|
64
|
+
t[k] = L[li + k] + H[hj + k] * (color.clamp ? clamp01(HA[hj + k]) : HA[hj + k]);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case PB_BLEND_ABOVE:
|
|
69
|
+
case PB_BLEND_BELOW: {
|
|
70
|
+
if (!hasAlpha) {
|
|
71
|
+
for (let c = 0; c < numColor; c++)
|
|
72
|
+
rows[c].set(fg[c].subarray(fi, fi + n));
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
const [lo, li, hi, hj] = color.mode === PB_BLEND_ABOVE ? [bg, bi, fg, fi] : [fg, fi, bg, bi];
|
|
76
|
+
const premultiplied = extra[color.alpha].alphaAssociated, HA = hi[a], LA = lo[a], ta = rows[a];
|
|
77
|
+
for (let k = 0; k < n; k++) {
|
|
78
|
+
const fa = color.clamp ? clamp01(HA[hj + k]) : HA[hj + k], ba = LA[li + k], na = 1 - (1 - fa) * (1 - ba);
|
|
79
|
+
if (premultiplied)
|
|
80
|
+
for (let c = 0; c < numColor; c++)
|
|
81
|
+
rows[c][k] = hi[c][hj + k] + lo[c][li + k] * (1 - fa);
|
|
82
|
+
else {
|
|
83
|
+
const rna = na > 0 ? 1 / na : 0;
|
|
84
|
+
for (let c = 0; c < numColor; c++)
|
|
85
|
+
rows[c][k] = (hi[c][hj + k] * fa + lo[c][li + k] * ba * (1 - fa)) * rna;
|
|
86
|
+
}
|
|
87
|
+
ta[k] = na;
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case PB_MUL:
|
|
92
|
+
for (let c = 0; c < numColor; c++) {
|
|
93
|
+
const t = rows[c], B = bg[c], F = fg[c];
|
|
94
|
+
for (let k = 0; k < n; k++)
|
|
95
|
+
t[k] = B[bi + k] * (color.clamp ? clamp01(F[fi + k]) : F[fi + k]);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case PB_REPLACE:
|
|
99
|
+
for (let c = 0; c < numColor; c++)
|
|
100
|
+
rows[c].set(fg[c].subarray(fi, fi + n));
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
for (let c = 0; c < numColor; c++)
|
|
104
|
+
rows[c].set(bg[c].subarray(bi, bi + n));
|
|
105
|
+
}
|
|
106
|
+
for (let c = 0; c < nc; c++)
|
|
107
|
+
out[c].set(rows[c].subarray(0, n), oi);
|
|
108
|
+
}
|
|
109
|
+
function alphaBlend1(bg, bga, fg, fga, isAlpha, premultiplied, clamp) {
|
|
110
|
+
const fa = clamp ? fga : clamp01(fga);
|
|
111
|
+
if (isAlpha)
|
|
112
|
+
return 1 - (1 - fa) * (1 - bga);
|
|
113
|
+
if (premultiplied)
|
|
114
|
+
return fg + bg * (1 - fa);
|
|
115
|
+
const na = 1 - (1 - fa) * (1 - bga);
|
|
116
|
+
return (fg * fa + bg * bga * (1 - fa)) * (na > 0 ? 1 / na : 0);
|
|
117
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { TF_PQ, TF_HLG, PRI_BT2100, PRI_P3, EXTRA_ALPHA, EXTRA_SPOT, EXTRA_BLACK } from "./headers.js";
|
|
2
|
+
import { toSRGB, hdrToLinear, REFERENCE_WHITE } from "../hdr.js";
|
|
3
|
+
const srgbEncode = (v) => v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
|
|
4
|
+
export class ColorOutput {
|
|
5
|
+
image;
|
|
6
|
+
toneMapping;
|
|
7
|
+
linear;
|
|
8
|
+
hdrSignal;
|
|
9
|
+
scale;
|
|
10
|
+
constructor(image, toneMapping) {
|
|
11
|
+
this.image = image;
|
|
12
|
+
this.toneMapping = toneMapping;
|
|
13
|
+
const c = image.colour, hdr = !c.wantICC && (c.transfer === TF_PQ || c.transfer === TF_HLG);
|
|
14
|
+
this.linear = image.xybEncoded && (hdr || image.toneMapping.intensityTarget > 255);
|
|
15
|
+
this.hdrSignal = !image.xybEncoded && hdr;
|
|
16
|
+
this.scale = this.linear ? image.toneMapping.intensityTarget / REFERENCE_WHITE : 1;
|
|
17
|
+
}
|
|
18
|
+
transform(p, ycbcr) {
|
|
19
|
+
const image = this.image, n = p.width * p.height, ch = p.channels.slice();
|
|
20
|
+
if (ycbcr) {
|
|
21
|
+
const [cb, y, cr] = ch, r = new Float32Array(n), g = new Float32Array(n), b = new Float32Array(n);
|
|
22
|
+
for (let i = 0; i < n; i++) {
|
|
23
|
+
const Y = y[i] + 128 / 255;
|
|
24
|
+
r[i] = Y + 1.402 * cr[i];
|
|
25
|
+
g[i] = Y - 0.299 * 1.402 / 0.587 * cr[i] - 0.114 * 1.772 / 0.587 * cb[i];
|
|
26
|
+
b[i] = Y + 1.772 * cb[i];
|
|
27
|
+
}
|
|
28
|
+
ch.splice(0, 3, r, g, b);
|
|
29
|
+
}
|
|
30
|
+
else if (image.xybEncoded) {
|
|
31
|
+
const [x, y, b] = ch, m = image.opsinInverse, bias = image.opsinBias, cbrt = bias.map(Math.cbrt);
|
|
32
|
+
const k = 255 / image.toneMapping.intensityTarget * this.scale;
|
|
33
|
+
const R = new Float32Array(n), G = new Float32Array(n), B = new Float32Array(n);
|
|
34
|
+
for (let i = 0; i < n; i++) {
|
|
35
|
+
const gr = y[i] + x[i] - cbrt[0], gg = y[i] - x[i] - cbrt[1], gb = b[i] - cbrt[2];
|
|
36
|
+
const mr = gr * gr * gr + bias[0], mg = gg * gg * gg + bias[1], mb = gb * gb * gb + bias[2];
|
|
37
|
+
let r = (m[0] * mr + m[1] * mg + m[2] * mb) * k, g = (m[3] * mr + m[4] * mg + m[5] * mb) * k, bl = (m[6] * mr + m[7] * mg + m[8] * mb) * k;
|
|
38
|
+
if (!this.linear) {
|
|
39
|
+
r = srgbEncode(r);
|
|
40
|
+
g = srgbEncode(g);
|
|
41
|
+
bl = srgbEncode(bl);
|
|
42
|
+
}
|
|
43
|
+
R[i] = r;
|
|
44
|
+
G[i] = g;
|
|
45
|
+
B[i] = bl;
|
|
46
|
+
}
|
|
47
|
+
ch.splice(0, 3, R, G, B);
|
|
48
|
+
}
|
|
49
|
+
return { width: p.width, height: p.height, channels: ch };
|
|
50
|
+
}
|
|
51
|
+
toRGBA(p) {
|
|
52
|
+
const n = p.width * p.height, ch = p.channels, image = this.image;
|
|
53
|
+
const numColor = ch.length - image.extraChannels.length, grey = numColor === 1;
|
|
54
|
+
let [r, g, b] = grey ? [ch[0], ch[0], ch[0]] : ch;
|
|
55
|
+
const black = image.extraChannels.findIndex(e => e.type === EXTRA_BLACK);
|
|
56
|
+
if (black >= 0) {
|
|
57
|
+
const k = ch[numColor + black];
|
|
58
|
+
if (grey)
|
|
59
|
+
r = g = b = r.slice();
|
|
60
|
+
else
|
|
61
|
+
[r, g, b] = [r.slice(), g.slice(), b.slice()];
|
|
62
|
+
for (let i = 0; i < n; i++) {
|
|
63
|
+
r[i] *= k[i];
|
|
64
|
+
if (!grey) {
|
|
65
|
+
g[i] *= k[i];
|
|
66
|
+
b[i] *= k[i];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
image.extraChannels.forEach((e, i) => {
|
|
71
|
+
if (e.type !== EXTRA_SPOT || grey)
|
|
72
|
+
return;
|
|
73
|
+
const s = ch[numColor + i], [sr, sg, sb, solidity] = e.spot;
|
|
74
|
+
if (r === ch[0])
|
|
75
|
+
[r, g, b] = [r.slice(), g.slice(), b.slice()];
|
|
76
|
+
for (let k = 0; k < n; k++) {
|
|
77
|
+
const mix = solidity * s[k];
|
|
78
|
+
r[k] = mix * sr + (1 - mix) * r[k];
|
|
79
|
+
g[k] = mix * sg + (1 - mix) * g[k];
|
|
80
|
+
b[k] = mix * sb + (1 - mix) * b[k];
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const alphaIndex = image.extraChannels.findIndex(e => e.type === EXTRA_ALPHA);
|
|
84
|
+
const alpha = alphaIndex >= 0 ? ch[ch.length - image.extraChannels.length + alphaIndex] : null;
|
|
85
|
+
const premultiplied = alphaIndex >= 0 && image.extraChannels[alphaIndex].alphaAssociated;
|
|
86
|
+
let out;
|
|
87
|
+
if (this.linear || this.hdrSignal) {
|
|
88
|
+
const rgb = new Float32Array(3 * n);
|
|
89
|
+
for (let i = 0; i < n; i++) {
|
|
90
|
+
const a = premultiplied ? Math.max(alpha[i], 2 ** -26) : 1;
|
|
91
|
+
rgb[3 * i] = r[i] / a;
|
|
92
|
+
rgb[3 * i + 1] = g[i] / a;
|
|
93
|
+
rgb[3 * i + 2] = b[i] / a;
|
|
94
|
+
}
|
|
95
|
+
const c = image.colour;
|
|
96
|
+
if (this.hdrSignal)
|
|
97
|
+
hdrToLinear(rgb, c.transfer, c.primaries === PRI_BT2100 ? 9 : c.primaries === PRI_P3 ? 12 : 1);
|
|
98
|
+
out = toSRGB(rgb, this.toneMapping);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
out = new Uint8Array(4 * n);
|
|
102
|
+
const byte = (v) => v <= 0 ? 0 : v >= 1 ? 255 : Math.round(v * 255);
|
|
103
|
+
for (let i = 0; i < n; i++) {
|
|
104
|
+
const a = premultiplied ? Math.max(alpha[i], 2 ** -26) : 1;
|
|
105
|
+
out[4 * i] = byte(r[i] / a);
|
|
106
|
+
out[4 * i + 1] = byte(g[i] / a);
|
|
107
|
+
out[4 * i + 2] = byte(b[i] / a);
|
|
108
|
+
out[4 * i + 3] = 255;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (alpha)
|
|
112
|
+
for (let i = 0; i < n; i++)
|
|
113
|
+
out[4 * i + 3] = alpha[i] <= 0 ? 0 : alpha[i] >= 1 ? 255 : Math.round(alpha[i] * 255);
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
}
|