cross-image 0.4.0 → 0.4.1
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 +109 -1
- package/esm/mod.d.ts +3 -1
- package/esm/mod.js +2 -0
- package/esm/src/formats/apng.d.ts +5 -3
- package/esm/src/formats/apng.js +11 -4
- package/esm/src/formats/avif.d.ts +2 -2
- package/esm/src/formats/avif.js +11 -1
- package/esm/src/formats/gif.d.ts +3 -3
- package/esm/src/formats/gif.js +4 -4
- package/esm/src/formats/heic.d.ts +2 -2
- package/esm/src/formats/heic.js +11 -1
- package/esm/src/formats/png.d.ts +3 -2
- package/esm/src/formats/png.js +8 -2
- package/esm/src/formats/png_base.d.ts +42 -1
- package/esm/src/formats/png_base.js +198 -5
- package/esm/src/formats/tiff.js +76 -6
- package/esm/src/image.d.ts +15 -0
- package/esm/src/image.js +29 -1
- package/esm/src/types.d.ts +66 -0
- package/esm/src/utils/base64.d.ts +32 -0
- package/esm/src/utils/base64.js +173 -0
- package/esm/src/utils/gif_encoder.d.ts +3 -1
- package/esm/src/utils/gif_encoder.js +4 -2
- package/esm/src/utils/image_processing.d.ts +31 -0
- package/esm/src/utils/image_processing.js +88 -0
- package/package.json +1 -1
- package/script/mod.d.ts +3 -1
- package/script/mod.js +11 -1
- package/script/src/formats/apng.d.ts +5 -3
- package/script/src/formats/apng.js +11 -4
- package/script/src/formats/avif.d.ts +2 -2
- package/script/src/formats/avif.js +11 -1
- package/script/src/formats/gif.d.ts +3 -3
- package/script/src/formats/gif.js +4 -4
- package/script/src/formats/heic.d.ts +2 -2
- package/script/src/formats/heic.js +11 -1
- package/script/src/formats/png.d.ts +3 -2
- package/script/src/formats/png.js +8 -2
- package/script/src/formats/png_base.d.ts +42 -1
- package/script/src/formats/png_base.js +198 -5
- package/script/src/formats/tiff.js +76 -6
- package/script/src/image.d.ts +15 -0
- package/script/src/image.js +28 -0
- package/script/src/types.d.ts +66 -0
- package/script/src/utils/base64.d.ts +32 -0
- package/script/src/utils/base64.js +179 -0
- package/script/src/utils/gif_encoder.d.ts +3 -1
- package/script/src/utils/gif_encoder.js +4 -2
- package/script/src/utils/image_processing.d.ts +31 -0
- package/script/src/utils/image_processing.js +92 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base64 utilities.
|
|
4
|
+
*
|
|
5
|
+
* Designed to work across Deno, Node.js, and Bun without external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.encodeBase64 = encodeBase64;
|
|
9
|
+
exports.decodeBase64 = decodeBase64;
|
|
10
|
+
exports.toDataUrl = toDataUrl;
|
|
11
|
+
exports.parseDataUrl = parseDataUrl;
|
|
12
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
13
|
+
const DECODE_TABLE = (() => {
|
|
14
|
+
const table = new Uint8Array(256);
|
|
15
|
+
table.fill(0xff);
|
|
16
|
+
for (let i = 0; i < BASE64_ALPHABET.length; i++) {
|
|
17
|
+
table[BASE64_ALPHABET.charCodeAt(i)] = i;
|
|
18
|
+
}
|
|
19
|
+
return table;
|
|
20
|
+
})();
|
|
21
|
+
function stripWhitespace(input) {
|
|
22
|
+
// Avoid regex to keep this fast and allocation-light.
|
|
23
|
+
let out = "";
|
|
24
|
+
for (let i = 0; i < input.length; i++) {
|
|
25
|
+
const code = input.charCodeAt(i);
|
|
26
|
+
// Space, tab, CR, LF
|
|
27
|
+
if (code === 0x20 || code === 0x09 || code === 0x0d || code === 0x0a)
|
|
28
|
+
continue;
|
|
29
|
+
out += input[i];
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function normalizeBase64(input) {
|
|
34
|
+
// Accept URL-safe alphabet in decode paths.
|
|
35
|
+
let s = stripWhitespace(input.trim());
|
|
36
|
+
if (s.length === 0)
|
|
37
|
+
return s;
|
|
38
|
+
// Map base64url to base64.
|
|
39
|
+
if (s.includes("-") || s.includes("_")) {
|
|
40
|
+
s = s.replaceAll("-", "+").replaceAll("_", "/");
|
|
41
|
+
}
|
|
42
|
+
const remainder = s.length % 4;
|
|
43
|
+
if (remainder === 1) {
|
|
44
|
+
throw new Error("Invalid base64: length must not be 1 (mod 4)");
|
|
45
|
+
}
|
|
46
|
+
if (remainder === 2)
|
|
47
|
+
s += "==";
|
|
48
|
+
if (remainder === 3)
|
|
49
|
+
s += "=";
|
|
50
|
+
return s;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Encode bytes into a standard Base64 string.
|
|
54
|
+
*/
|
|
55
|
+
function encodeBase64(bytes) {
|
|
56
|
+
if (bytes.length === 0)
|
|
57
|
+
return "";
|
|
58
|
+
const outLen = Math.ceil(bytes.length / 3) * 4;
|
|
59
|
+
const out = new Array(outLen);
|
|
60
|
+
let i = 0;
|
|
61
|
+
let o = 0;
|
|
62
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
63
|
+
const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
64
|
+
out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
|
|
65
|
+
out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
|
|
66
|
+
out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
|
|
67
|
+
out[o++] = BASE64_ALPHABET[n & 63];
|
|
68
|
+
}
|
|
69
|
+
const remaining = bytes.length - i;
|
|
70
|
+
if (remaining === 1) {
|
|
71
|
+
const n = bytes[i] << 16;
|
|
72
|
+
out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
|
|
73
|
+
out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
|
|
74
|
+
out[o++] = "=";
|
|
75
|
+
out[o++] = "=";
|
|
76
|
+
}
|
|
77
|
+
else if (remaining === 2) {
|
|
78
|
+
const n = (bytes[i] << 16) | (bytes[i + 1] << 8);
|
|
79
|
+
out[o++] = BASE64_ALPHABET[(n >> 18) & 63];
|
|
80
|
+
out[o++] = BASE64_ALPHABET[(n >> 12) & 63];
|
|
81
|
+
out[o++] = BASE64_ALPHABET[(n >> 6) & 63];
|
|
82
|
+
out[o++] = "=";
|
|
83
|
+
}
|
|
84
|
+
return out.join("");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Decode a Base64 (or Base64URL) string into bytes.
|
|
88
|
+
*
|
|
89
|
+
* - Whitespace is ignored.
|
|
90
|
+
* - Missing padding is tolerated.
|
|
91
|
+
* - `-`/`_` are accepted as URL-safe variants.
|
|
92
|
+
*/
|
|
93
|
+
function decodeBase64(base64) {
|
|
94
|
+
const s = normalizeBase64(base64);
|
|
95
|
+
if (s.length === 0)
|
|
96
|
+
return new Uint8Array(0);
|
|
97
|
+
let padding = 0;
|
|
98
|
+
if (s.endsWith("=="))
|
|
99
|
+
padding = 2;
|
|
100
|
+
else if (s.endsWith("="))
|
|
101
|
+
padding = 1;
|
|
102
|
+
const quadCount = s.length / 4;
|
|
103
|
+
const outLen = quadCount * 3 - padding;
|
|
104
|
+
const out = new Uint8Array(outLen);
|
|
105
|
+
let o = 0;
|
|
106
|
+
for (let i = 0; i < s.length; i += 4) {
|
|
107
|
+
const c0 = s.charCodeAt(i);
|
|
108
|
+
const c1 = s.charCodeAt(i + 1);
|
|
109
|
+
const c2 = s.charCodeAt(i + 2);
|
|
110
|
+
const c3 = s.charCodeAt(i + 3);
|
|
111
|
+
const v0 = DECODE_TABLE[c0];
|
|
112
|
+
const v1 = DECODE_TABLE[c1];
|
|
113
|
+
if (v0 === 0xff || v1 === 0xff) {
|
|
114
|
+
throw new Error("Invalid base64: invalid character");
|
|
115
|
+
}
|
|
116
|
+
const isPad2 = c2 === 0x3d; // '='
|
|
117
|
+
const isPad3 = c3 === 0x3d;
|
|
118
|
+
const v2 = isPad2 ? 0 : DECODE_TABLE[c2];
|
|
119
|
+
const v3 = isPad3 ? 0 : DECODE_TABLE[c3];
|
|
120
|
+
if ((!isPad2 && v2 === 0xff) || (!isPad3 && v3 === 0xff)) {
|
|
121
|
+
throw new Error("Invalid base64: invalid character");
|
|
122
|
+
}
|
|
123
|
+
const n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3;
|
|
124
|
+
out[o++] = (n >> 16) & 0xff;
|
|
125
|
+
if (!isPad2) {
|
|
126
|
+
if (o >= out.length)
|
|
127
|
+
break;
|
|
128
|
+
out[o++] = (n >> 8) & 0xff;
|
|
129
|
+
}
|
|
130
|
+
if (!isPad3) {
|
|
131
|
+
if (o >= out.length)
|
|
132
|
+
break;
|
|
133
|
+
out[o++] = n & 0xff;
|
|
134
|
+
}
|
|
135
|
+
if (isPad2 || isPad3) {
|
|
136
|
+
// Padding is only valid in the final quartet.
|
|
137
|
+
if (i + 4 !== s.length) {
|
|
138
|
+
throw new Error("Invalid base64: padding can only appear at the end");
|
|
139
|
+
}
|
|
140
|
+
// If third char is padding, fourth must also be padding.
|
|
141
|
+
if (isPad2 && !isPad3) {
|
|
142
|
+
throw new Error("Invalid base64: invalid padding");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create a base64 data URL.
|
|
150
|
+
*/
|
|
151
|
+
function toDataUrl(mime, bytes) {
|
|
152
|
+
if (!mime)
|
|
153
|
+
throw new Error("mime is required");
|
|
154
|
+
return `data:${mime};base64,${encodeBase64(bytes)}`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Parse a base64 data URL.
|
|
158
|
+
*
|
|
159
|
+
* Supports `data:<mime>;base64,<payload>`.
|
|
160
|
+
*/
|
|
161
|
+
function parseDataUrl(url) {
|
|
162
|
+
if (!url.startsWith("data:")) {
|
|
163
|
+
throw new Error("Invalid data URL: must start with 'data:'");
|
|
164
|
+
}
|
|
165
|
+
const commaIndex = url.indexOf(",");
|
|
166
|
+
if (commaIndex === -1) {
|
|
167
|
+
throw new Error("Invalid data URL: missing ',' separator");
|
|
168
|
+
}
|
|
169
|
+
const meta = url.slice(5, commaIndex);
|
|
170
|
+
const payload = url.slice(commaIndex + 1);
|
|
171
|
+
// We only support base64 payloads.
|
|
172
|
+
const metaParts = meta.split(";");
|
|
173
|
+
const mime = metaParts[0] || "application/octet-stream";
|
|
174
|
+
const isBase64 = metaParts.some((p) => p.toLowerCase() === "base64");
|
|
175
|
+
if (!isBase64) {
|
|
176
|
+
throw new Error("Invalid data URL: only base64 payloads are supported");
|
|
177
|
+
}
|
|
178
|
+
return { mime, bytes: decodeBase64(payload) };
|
|
179
|
+
}
|
|
@@ -168,11 +168,13 @@ class GIFEncoder {
|
|
|
168
168
|
}
|
|
169
169
|
return Math.max(2, bits);
|
|
170
170
|
}
|
|
171
|
-
encode() {
|
|
171
|
+
encode(options) {
|
|
172
172
|
if (this.frames.length === 0) {
|
|
173
173
|
throw new Error("No frames to encode");
|
|
174
174
|
}
|
|
175
175
|
const output = [];
|
|
176
|
+
// Get loop count from options (default to 0 = infinite)
|
|
177
|
+
const loopCount = options?.loop ?? 0;
|
|
176
178
|
// Quantize first frame for Global Color Table
|
|
177
179
|
const firstFrame = this.frames[0];
|
|
178
180
|
const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
|
|
@@ -209,7 +211,7 @@ class GIFEncoder {
|
|
|
209
211
|
this.writeString(output, "NETSCAPE2.0");
|
|
210
212
|
output.push(3); // Sub-block Size
|
|
211
213
|
output.push(1); // Loop Indicator (1 = loop)
|
|
212
|
-
this.writeUint16LE(output,
|
|
214
|
+
this.writeUint16LE(output, loopCount); // Loop Count (0 = infinite, 1+ = specific count)
|
|
213
215
|
output.push(0); // Block Terminator
|
|
214
216
|
}
|
|
215
217
|
// Encode frames
|
|
@@ -186,4 +186,35 @@ export declare function flipHorizontal(data: Uint8Array, width: number, height:
|
|
|
186
186
|
* @returns Flipped image data
|
|
187
187
|
*/
|
|
188
188
|
export declare function flipVertical(data: Uint8Array, width: number, height: number): Uint8Array;
|
|
189
|
+
/**
|
|
190
|
+
* Convert RGB color to CMYK color space
|
|
191
|
+
* @param r Red component (0-255)
|
|
192
|
+
* @param g Green component (0-255)
|
|
193
|
+
* @param b Blue component (0-255)
|
|
194
|
+
* @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
|
|
195
|
+
*/
|
|
196
|
+
export declare function rgbToCmyk(r: number, g: number, b: number): [number, number, number, number];
|
|
197
|
+
/**
|
|
198
|
+
* Convert CMYK color to RGB color space
|
|
199
|
+
* @param c Cyan component (0-1)
|
|
200
|
+
* @param m Magenta component (0-1)
|
|
201
|
+
* @param y Yellow component (0-1)
|
|
202
|
+
* @param k Key/Black component (0-1)
|
|
203
|
+
* @returns RGB values: [r (0-255), g (0-255), b (0-255)]
|
|
204
|
+
*/
|
|
205
|
+
export declare function cmykToRgb(c: number, m: number, y: number, k: number): [number, number, number];
|
|
206
|
+
/**
|
|
207
|
+
* Convert RGBA image data to CMYK representation
|
|
208
|
+
* Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
|
|
209
|
+
* @param data Image data (RGBA)
|
|
210
|
+
* @returns CMYK image data as Float32Array (4 values per pixel)
|
|
211
|
+
*/
|
|
212
|
+
export declare function rgbaToCmyk(data: Uint8Array): Float32Array;
|
|
213
|
+
/**
|
|
214
|
+
* Convert CMYK image data to RGBA representation
|
|
215
|
+
* @param cmykData CMYK image data (4 values per pixel in 0-1 range)
|
|
216
|
+
* @param alpha Optional alpha value for all pixels (0-255, default: 255)
|
|
217
|
+
* @returns RGBA image data
|
|
218
|
+
*/
|
|
219
|
+
export declare function cmykToRgba(cmykData: Float32Array, alpha?: number): Uint8Array;
|
|
189
220
|
//# sourceMappingURL=image_processing.d.ts.map
|
|
@@ -24,6 +24,10 @@ exports.rotate180 = rotate180;
|
|
|
24
24
|
exports.rotate270 = rotate270;
|
|
25
25
|
exports.flipHorizontal = flipHorizontal;
|
|
26
26
|
exports.flipVertical = flipVertical;
|
|
27
|
+
exports.rgbToCmyk = rgbToCmyk;
|
|
28
|
+
exports.cmykToRgb = cmykToRgb;
|
|
29
|
+
exports.rgbaToCmyk = rgbaToCmyk;
|
|
30
|
+
exports.cmykToRgba = cmykToRgba;
|
|
27
31
|
/**
|
|
28
32
|
* Detect system endianness
|
|
29
33
|
* Returns true if little-endian (most common), false if big-endian
|
|
@@ -765,3 +769,91 @@ function flipVertical(data, width, height) {
|
|
|
765
769
|
}
|
|
766
770
|
return result;
|
|
767
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Convert RGB color to CMYK color space
|
|
774
|
+
* @param r Red component (0-255)
|
|
775
|
+
* @param g Green component (0-255)
|
|
776
|
+
* @param b Blue component (0-255)
|
|
777
|
+
* @returns CMYK values: [c (0-1), m (0-1), y (0-1), k (0-1)]
|
|
778
|
+
*/
|
|
779
|
+
function rgbToCmyk(r, g, b) {
|
|
780
|
+
// Normalize RGB values to 0-1 range
|
|
781
|
+
const rNorm = r / 255;
|
|
782
|
+
const gNorm = g / 255;
|
|
783
|
+
const bNorm = b / 255;
|
|
784
|
+
// Calculate K (key/black)
|
|
785
|
+
const k = 1 - Math.max(rNorm, gNorm, bNorm);
|
|
786
|
+
// If K is 1 (pure black), CMY are undefined but conventionally set to 0
|
|
787
|
+
if (k === 1) {
|
|
788
|
+
return [0, 0, 0, 1];
|
|
789
|
+
}
|
|
790
|
+
// Calculate CMY components
|
|
791
|
+
const c = (1 - rNorm - k) / (1 - k);
|
|
792
|
+
const m = (1 - gNorm - k) / (1 - k);
|
|
793
|
+
const y = (1 - bNorm - k) / (1 - k);
|
|
794
|
+
return [c, m, y, k];
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Convert CMYK color to RGB color space
|
|
798
|
+
* @param c Cyan component (0-1)
|
|
799
|
+
* @param m Magenta component (0-1)
|
|
800
|
+
* @param y Yellow component (0-1)
|
|
801
|
+
* @param k Key/Black component (0-1)
|
|
802
|
+
* @returns RGB values: [r (0-255), g (0-255), b (0-255)]
|
|
803
|
+
*/
|
|
804
|
+
function cmykToRgb(c, m, y, k) {
|
|
805
|
+
// Convert CMYK to RGB
|
|
806
|
+
const r = 255 * (1 - c) * (1 - k);
|
|
807
|
+
const g = 255 * (1 - m) * (1 - k);
|
|
808
|
+
const b = 255 * (1 - y) * (1 - k);
|
|
809
|
+
return [
|
|
810
|
+
Math.round(Math.max(0, Math.min(255, r))),
|
|
811
|
+
Math.round(Math.max(0, Math.min(255, g))),
|
|
812
|
+
Math.round(Math.max(0, Math.min(255, b))),
|
|
813
|
+
];
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Convert RGBA image data to CMYK representation
|
|
817
|
+
* Returns a Float32Array with 4 values per pixel (C, M, Y, K) in 0-1 range
|
|
818
|
+
* @param data Image data (RGBA)
|
|
819
|
+
* @returns CMYK image data as Float32Array (4 values per pixel)
|
|
820
|
+
*/
|
|
821
|
+
function rgbaToCmyk(data) {
|
|
822
|
+
const pixelCount = data.length / 4;
|
|
823
|
+
const result = new Float32Array(pixelCount * 4);
|
|
824
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
825
|
+
const r = data[i];
|
|
826
|
+
const g = data[i + 1];
|
|
827
|
+
const b = data[i + 2];
|
|
828
|
+
const [c, m, y, k] = rgbToCmyk(r, g, b);
|
|
829
|
+
const outIdx = (i / 4) * 4;
|
|
830
|
+
result[outIdx] = c;
|
|
831
|
+
result[outIdx + 1] = m;
|
|
832
|
+
result[outIdx + 2] = y;
|
|
833
|
+
result[outIdx + 3] = k;
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Convert CMYK image data to RGBA representation
|
|
839
|
+
* @param cmykData CMYK image data (4 values per pixel in 0-1 range)
|
|
840
|
+
* @param alpha Optional alpha value for all pixels (0-255, default: 255)
|
|
841
|
+
* @returns RGBA image data
|
|
842
|
+
*/
|
|
843
|
+
function cmykToRgba(cmykData, alpha = 255) {
|
|
844
|
+
const pixelCount = cmykData.length / 4;
|
|
845
|
+
const result = new Uint8Array(pixelCount * 4);
|
|
846
|
+
for (let i = 0; i < cmykData.length; i += 4) {
|
|
847
|
+
const c = cmykData[i];
|
|
848
|
+
const m = cmykData[i + 1];
|
|
849
|
+
const y = cmykData[i + 2];
|
|
850
|
+
const k = cmykData[i + 3];
|
|
851
|
+
const [r, g, b] = cmykToRgb(c, m, y, k);
|
|
852
|
+
const outIdx = i;
|
|
853
|
+
result[outIdx] = r;
|
|
854
|
+
result[outIdx + 1] = g;
|
|
855
|
+
result[outIdx + 2] = b;
|
|
856
|
+
result[outIdx + 3] = alpha;
|
|
857
|
+
}
|
|
858
|
+
return result;
|
|
859
|
+
}
|