cross-image 0.2.0 → 0.2.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 +22 -16
- package/esm/mod.d.ts +3 -1
- package/esm/mod.js +3 -1
- package/esm/src/formats/apng.d.ts +50 -0
- package/esm/src/formats/apng.js +364 -0
- package/esm/src/formats/bmp.d.ts +0 -6
- package/esm/src/formats/bmp.js +24 -47
- package/esm/src/formats/dng.js +4 -4
- package/esm/src/formats/gif.d.ts +0 -2
- package/esm/src/formats/gif.js +10 -16
- package/esm/src/formats/ico.d.ts +41 -0
- package/esm/src/formats/ico.js +214 -0
- package/esm/src/formats/pcx.js +1 -1
- package/esm/src/formats/png.d.ts +2 -21
- package/esm/src/formats/png.js +5 -429
- package/esm/src/formats/png_base.d.ts +108 -0
- package/esm/src/formats/png_base.js +487 -0
- package/esm/src/formats/webp.d.ts +0 -1
- package/esm/src/formats/webp.js +4 -7
- package/esm/src/image.js +4 -0
- package/esm/src/utils/byte_utils.d.ts +30 -0
- package/esm/src/utils/byte_utils.js +50 -0
- package/esm/src/utils/gif_encoder.d.ts +3 -2
- package/esm/src/utils/gif_encoder.js +115 -48
- package/package.json +1 -1
- package/script/mod.d.ts +3 -1
- package/script/mod.js +6 -2
- package/script/src/formats/apng.d.ts +50 -0
- package/script/src/formats/apng.js +368 -0
- package/script/src/formats/bmp.d.ts +0 -6
- package/script/src/formats/bmp.js +24 -47
- package/script/src/formats/dng.js +4 -4
- package/script/src/formats/gif.d.ts +0 -2
- package/script/src/formats/gif.js +10 -16
- package/script/src/formats/ico.d.ts +41 -0
- package/script/src/formats/ico.js +218 -0
- package/script/src/formats/pcx.js +1 -1
- package/script/src/formats/png.d.ts +2 -21
- package/script/src/formats/png.js +5 -429
- package/script/src/formats/png_base.d.ts +108 -0
- package/script/src/formats/png_base.js +491 -0
- package/script/src/formats/webp.d.ts +0 -1
- package/script/src/formats/webp.js +4 -7
- package/script/src/image.js +4 -0
- package/script/src/utils/byte_utils.d.ts +30 -0
- package/script/src/utils/byte_utils.js +58 -0
- package/script/src/utils/gif_encoder.d.ts +3 -2
- package/script/src/utils/gif_encoder.js +115 -48
|
@@ -17,15 +17,20 @@ export class GIFEncoder {
|
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
-
Object.defineProperty(this, "
|
|
20
|
+
Object.defineProperty(this, "frames", {
|
|
21
21
|
enumerable: true,
|
|
22
22
|
configurable: true,
|
|
23
23
|
writable: true,
|
|
24
|
-
value:
|
|
24
|
+
value: []
|
|
25
25
|
});
|
|
26
26
|
this.width = width;
|
|
27
27
|
this.height = height;
|
|
28
|
-
|
|
28
|
+
if (data) {
|
|
29
|
+
this.addFrame(data);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
addFrame(data, delay = 0) {
|
|
33
|
+
this.frames.push({ data, delay });
|
|
29
34
|
}
|
|
30
35
|
writeBytes(output, bytes) {
|
|
31
36
|
output.push(...bytes);
|
|
@@ -54,7 +59,7 @@ export class GIFEncoder {
|
|
|
54
59
|
/**
|
|
55
60
|
* Quantize RGBA image to 256 colors using median cut algorithm
|
|
56
61
|
*/
|
|
57
|
-
quantize() {
|
|
62
|
+
quantize(data) {
|
|
58
63
|
// Simple quantization: collect unique colors and build palette
|
|
59
64
|
const colorMap = new Map();
|
|
60
65
|
const colors = [];
|
|
@@ -65,10 +70,10 @@ export class GIFEncoder {
|
|
|
65
70
|
const rgStep = 255 / RG_LEVELS; // Step size for R/G quantization
|
|
66
71
|
const bStep = 255 / B_LEVELS; // Step size for B quantization
|
|
67
72
|
// Collect unique colors
|
|
68
|
-
for (let i = 0; i <
|
|
69
|
-
const r =
|
|
70
|
-
const g =
|
|
71
|
-
const b =
|
|
73
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
74
|
+
const r = data[i];
|
|
75
|
+
const g = data[i + 1];
|
|
76
|
+
const b = data[i + 2];
|
|
72
77
|
const key = `${r},${g},${b}`;
|
|
73
78
|
if (!colorMap.has(key) && colors.length < 256) {
|
|
74
79
|
colorMap.set(key, colors.length);
|
|
@@ -83,12 +88,12 @@ export class GIFEncoder {
|
|
|
83
88
|
colorMap.clear();
|
|
84
89
|
colors.length = 0;
|
|
85
90
|
useColorReduction = true;
|
|
86
|
-
for (let i = 0; i <
|
|
91
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
87
92
|
// Reduce color depth: 3 bits for R/G channels, 2 bits for B channel
|
|
88
93
|
// This gives us 8 bits total = 256 possible colors
|
|
89
|
-
const r = this.quantizeChannel(
|
|
90
|
-
const g = this.quantizeChannel(
|
|
91
|
-
const b = this.quantizeChannel(
|
|
94
|
+
const r = this.quantizeChannel(data[i], RG_LEVELS, rgStep);
|
|
95
|
+
const g = this.quantizeChannel(data[i + 1], RG_LEVELS, rgStep);
|
|
96
|
+
const b = this.quantizeChannel(data[i + 2], B_LEVELS, bStep);
|
|
92
97
|
const key = `${r},${g},${b}`;
|
|
93
98
|
if (!colorMap.has(key)) {
|
|
94
99
|
if (colors.length < 256) {
|
|
@@ -112,10 +117,10 @@ export class GIFEncoder {
|
|
|
112
117
|
}
|
|
113
118
|
// Create indexed data
|
|
114
119
|
const indexed = new Uint8Array(this.width * this.height);
|
|
115
|
-
for (let i = 0, j = 0; i <
|
|
116
|
-
let r =
|
|
117
|
-
let g =
|
|
118
|
-
let b =
|
|
120
|
+
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
|
|
121
|
+
let r = data[i];
|
|
122
|
+
let g = data[i + 1];
|
|
123
|
+
let b = data[i + 2];
|
|
119
124
|
// Apply color reduction if it was used for building the palette
|
|
120
125
|
if (useColorReduction) {
|
|
121
126
|
r = this.quantizeChannel(r, RG_LEVELS, rgStep);
|
|
@@ -161,10 +166,14 @@ export class GIFEncoder {
|
|
|
161
166
|
return Math.max(2, bits);
|
|
162
167
|
}
|
|
163
168
|
encode() {
|
|
169
|
+
if (this.frames.length === 0) {
|
|
170
|
+
throw new Error("No frames to encode");
|
|
171
|
+
}
|
|
164
172
|
const output = [];
|
|
165
|
-
// Quantize
|
|
166
|
-
const
|
|
167
|
-
const
|
|
173
|
+
// Quantize first frame for Global Color Table
|
|
174
|
+
const firstFrame = this.frames[0];
|
|
175
|
+
const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
|
|
176
|
+
const paletteSize = globalPalette.length / 3;
|
|
168
177
|
const bitsPerColor = this.getBitsPerColor(paletteSize);
|
|
169
178
|
// Header
|
|
170
179
|
this.writeString(output, "GIF89a");
|
|
@@ -187,38 +196,96 @@ export class GIFEncoder {
|
|
|
187
196
|
// So we need to write that many colors, padding if necessary
|
|
188
197
|
const gctSize = 1 << bitsPerColor;
|
|
189
198
|
const paddedPalette = new Uint8Array(gctSize * 3);
|
|
190
|
-
paddedPalette.set(
|
|
199
|
+
paddedPalette.set(globalPalette);
|
|
191
200
|
this.writeBytes(output, paddedPalette);
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
201
|
+
// Netscape Application Extension (Looping)
|
|
202
|
+
if (this.frames.length > 1) {
|
|
203
|
+
output.push(0x21); // Extension Introducer
|
|
204
|
+
output.push(0xff); // Application Extension Label
|
|
205
|
+
output.push(11); // Block Size
|
|
206
|
+
this.writeString(output, "NETSCAPE2.0");
|
|
207
|
+
output.push(3); // Sub-block Size
|
|
208
|
+
output.push(1); // Loop Indicator (1 = loop)
|
|
209
|
+
this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
|
|
210
|
+
output.push(0); // Block Terminator
|
|
211
|
+
}
|
|
212
|
+
// Encode frames
|
|
213
|
+
for (let i = 0; i < this.frames.length; i++) {
|
|
214
|
+
const frame = this.frames[i];
|
|
215
|
+
let indexed;
|
|
216
|
+
let useLocalPalette = false;
|
|
217
|
+
let localPalette = null;
|
|
218
|
+
let localBitsPerColor = bitsPerColor;
|
|
219
|
+
if (i === 0) {
|
|
220
|
+
indexed = firstIndexed;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Quantize subsequent frames
|
|
224
|
+
// For simplicity, we use a Local Color Table for each frame to ensure colors are correct
|
|
225
|
+
const result = this.quantize(frame.data);
|
|
226
|
+
indexed = result.indexed;
|
|
227
|
+
localPalette = result.palette;
|
|
228
|
+
useLocalPalette = true;
|
|
229
|
+
const localPaletteSize = localPalette.length / 3;
|
|
230
|
+
localBitsPerColor = this.getBitsPerColor(localPaletteSize);
|
|
218
231
|
}
|
|
232
|
+
// Graphic Control Extension
|
|
233
|
+
output.push(0x21); // Extension Introducer
|
|
234
|
+
output.push(0xf9); // Graphic Control Label
|
|
235
|
+
output.push(4); // Byte Size
|
|
236
|
+
// Packed Field
|
|
237
|
+
// Reserved (3 bits)
|
|
238
|
+
// Disposal Method (3 bits): 2 (Restore to background) - usually safe for animation
|
|
239
|
+
// User Input Flag (1 bit): 0
|
|
240
|
+
// Transparent Color Flag (1 bit): 0
|
|
241
|
+
output.push(0x08); // Disposal method 2 (Restore to background)
|
|
242
|
+
// Delay Time (1/100ths of a second)
|
|
243
|
+
// Default to 10 (100ms) if not specified
|
|
244
|
+
const delay = frame.delay > 0 ? Math.round(frame.delay / 10) : 10;
|
|
245
|
+
this.writeUint16LE(output, delay);
|
|
246
|
+
// Transparent Color Index
|
|
247
|
+
output.push(0);
|
|
248
|
+
output.push(0); // Block Terminator
|
|
249
|
+
// Image Descriptor
|
|
250
|
+
output.push(0x2c); // Image Separator
|
|
251
|
+
this.writeUint16LE(output, 0); // Left
|
|
252
|
+
this.writeUint16LE(output, 0); // Top
|
|
253
|
+
this.writeUint16LE(output, this.width);
|
|
254
|
+
this.writeUint16LE(output, this.height);
|
|
255
|
+
// Packed Field
|
|
256
|
+
if (useLocalPalette && localPalette) {
|
|
257
|
+
// LCT Flag: 1
|
|
258
|
+
// Interlace: 0
|
|
259
|
+
// Sort: 0
|
|
260
|
+
// Reserved: 0
|
|
261
|
+
// Size of LCT: localBitsPerColor - 1
|
|
262
|
+
const lctPacked = 0x80 | (localBitsPerColor - 1);
|
|
263
|
+
output.push(lctPacked);
|
|
264
|
+
// Write Local Color Table
|
|
265
|
+
const lctSize = 1 << localBitsPerColor;
|
|
266
|
+
const paddedLct = new Uint8Array(lctSize * 3);
|
|
267
|
+
paddedLct.set(localPalette);
|
|
268
|
+
this.writeBytes(output, paddedLct);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
output.push(0); // No LCT
|
|
272
|
+
}
|
|
273
|
+
// LZW Minimum Code Size
|
|
274
|
+
const minCodeSize = Math.max(2, useLocalPalette ? localBitsPerColor : bitsPerColor);
|
|
275
|
+
output.push(minCodeSize);
|
|
276
|
+
// Compress image data with LZW
|
|
277
|
+
const encoder = new LZWEncoder(minCodeSize);
|
|
278
|
+
const compressed = encoder.compress(indexed);
|
|
279
|
+
// Write compressed data in sub-blocks (max 255 bytes per block)
|
|
280
|
+
for (let k = 0; k < compressed.length; k += 255) {
|
|
281
|
+
const blockSize = Math.min(255, compressed.length - k);
|
|
282
|
+
output.push(blockSize);
|
|
283
|
+
for (let j = 0; j < blockSize; j++) {
|
|
284
|
+
output.push(compressed[k + j]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
output.push(0); // Block Terminator
|
|
219
288
|
}
|
|
220
|
-
// Block Terminator
|
|
221
|
-
output.push(0);
|
|
222
289
|
// Trailer
|
|
223
290
|
output.push(0x3b);
|
|
224
291
|
return new Uint8Array(output);
|
package/package.json
CHANGED
package/script/mod.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module @cross/image
|
|
3
3
|
*
|
|
4
4
|
* A pure JavaScript, dependency-free, cross-runtime image processing library.
|
|
5
|
-
* Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, DNG, PAM, PCX).
|
|
5
|
+
* Supports decoding, resizing, and encoding common image formats (PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PCX).
|
|
6
6
|
* Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
export { Image } from "./src/image.js";
|
|
46
46
|
export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
|
|
47
47
|
export { PNGFormat } from "./src/formats/png.js";
|
|
48
|
+
export { APNGFormat } from "./src/formats/apng.js";
|
|
48
49
|
export { JPEGFormat } from "./src/formats/jpeg.js";
|
|
49
50
|
export { WebPFormat } from "./src/formats/webp.js";
|
|
50
51
|
export { GIFFormat } from "./src/formats/gif.js";
|
|
51
52
|
export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
|
|
52
53
|
export { BMPFormat } from "./src/formats/bmp.js";
|
|
54
|
+
export { ICOFormat } from "./src/formats/ico.js";
|
|
53
55
|
export { DNGFormat } from "./src/formats/dng.js";
|
|
54
56
|
export { PAMFormat } from "./src/formats/pam.js";
|
|
55
57
|
export { PCXFormat } from "./src/formats/pcx.js";
|
package/script/mod.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module @cross/image
|
|
4
4
|
*
|
|
5
5
|
* A pure JavaScript, dependency-free, cross-runtime image processing library.
|
|
6
|
-
* Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, DNG, PAM, PCX).
|
|
6
|
+
* Supports decoding, resizing, and encoding common image formats (PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PCX).
|
|
7
7
|
* Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
@@ -44,11 +44,13 @@
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.ASCIIFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.PNGFormat = exports.Image = void 0;
|
|
47
|
+
exports.ASCIIFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
|
|
48
48
|
var image_js_1 = require("./src/image.js");
|
|
49
49
|
Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
|
|
50
50
|
var png_js_1 = require("./src/formats/png.js");
|
|
51
51
|
Object.defineProperty(exports, "PNGFormat", { enumerable: true, get: function () { return png_js_1.PNGFormat; } });
|
|
52
|
+
var apng_js_1 = require("./src/formats/apng.js");
|
|
53
|
+
Object.defineProperty(exports, "APNGFormat", { enumerable: true, get: function () { return apng_js_1.APNGFormat; } });
|
|
52
54
|
var jpeg_js_1 = require("./src/formats/jpeg.js");
|
|
53
55
|
Object.defineProperty(exports, "JPEGFormat", { enumerable: true, get: function () { return jpeg_js_1.JPEGFormat; } });
|
|
54
56
|
var webp_js_1 = require("./src/formats/webp.js");
|
|
@@ -59,6 +61,8 @@ var tiff_js_1 = require("./src/formats/tiff.js");
|
|
|
59
61
|
Object.defineProperty(exports, "TIFFFormat", { enumerable: true, get: function () { return tiff_js_1.TIFFFormat; } });
|
|
60
62
|
var bmp_js_1 = require("./src/formats/bmp.js");
|
|
61
63
|
Object.defineProperty(exports, "BMPFormat", { enumerable: true, get: function () { return bmp_js_1.BMPFormat; } });
|
|
64
|
+
var ico_js_1 = require("./src/formats/ico.js");
|
|
65
|
+
Object.defineProperty(exports, "ICOFormat", { enumerable: true, get: function () { return ico_js_1.ICOFormat; } });
|
|
62
66
|
var dng_js_1 = require("./src/formats/dng.js");
|
|
63
67
|
Object.defineProperty(exports, "DNGFormat", { enumerable: true, get: function () { return dng_js_1.DNGFormat; } });
|
|
64
68
|
var pam_js_1 = require("./src/formats/pam.js");
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ImageData, ImageFormat, MultiFrameImageData } from "../types.js";
|
|
2
|
+
import { PNGBase } from "./png_base.js";
|
|
3
|
+
/**
|
|
4
|
+
* APNG (Animated PNG) format handler
|
|
5
|
+
* Implements support for animated PNG images with multiple frames
|
|
6
|
+
* APNG extends PNG with animation control chunks (acTL, fcTL, fdAT)
|
|
7
|
+
*/
|
|
8
|
+
export declare class APNGFormat extends PNGBase implements ImageFormat {
|
|
9
|
+
/** Format name identifier */
|
|
10
|
+
readonly name = "apng";
|
|
11
|
+
/** MIME type for APNG images */
|
|
12
|
+
readonly mimeType = "image/apng";
|
|
13
|
+
/**
|
|
14
|
+
* Check if this format supports multiple frames (animations)
|
|
15
|
+
* @returns true for APNG format
|
|
16
|
+
*/
|
|
17
|
+
supportsMultipleFrames(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check if the given data is an APNG image
|
|
20
|
+
* @param data Raw image data to check
|
|
21
|
+
* @returns true if data has PNG signature and contains acTL chunk
|
|
22
|
+
*/
|
|
23
|
+
canDecode(data: Uint8Array): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Decode APNG image data to RGBA (first frame only)
|
|
26
|
+
* @param data Raw APNG image data
|
|
27
|
+
* @returns Decoded image data with RGBA pixels of first frame
|
|
28
|
+
*/
|
|
29
|
+
decode(data: Uint8Array): Promise<ImageData>;
|
|
30
|
+
/**
|
|
31
|
+
* Decode all frames from APNG image
|
|
32
|
+
* @param data Raw APNG image data
|
|
33
|
+
* @returns Decoded multi-frame image data
|
|
34
|
+
*/
|
|
35
|
+
decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
|
|
36
|
+
/**
|
|
37
|
+
* Encode RGBA image data to APNG format (single frame)
|
|
38
|
+
* @param imageData Image data to encode
|
|
39
|
+
* @returns Encoded APNG image bytes
|
|
40
|
+
*/
|
|
41
|
+
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
42
|
+
/**
|
|
43
|
+
* Encode multi-frame image data to APNG format
|
|
44
|
+
* @param imageData Multi-frame image data to encode
|
|
45
|
+
* @returns Encoded APNG image bytes
|
|
46
|
+
*/
|
|
47
|
+
encodeFrames(imageData: MultiFrameImageData): Promise<Uint8Array>;
|
|
48
|
+
private decodeFrameData;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=apng.d.ts.map
|