cross-image 0.4.1 → 0.4.3

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @cross/image
2
2
 
3
- A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and
4
- Bun. Decode, encode, manipulate, and process images in multiple formats including PNG, JPEG, WebP,
5
- GIF, and more—all without native dependencies.
3
+ A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, Bun
4
+ and browsers. Decode, encode, manipulate, and process images in multiple formats including PNG,
5
+ JPEG, WebP, GIF, and more—all without native dependencies.
6
6
 
7
7
  📚 **[Full Documentation](https://cross-image.56k.guru/)**
8
8
 
@@ -10,7 +10,7 @@ GIF, and more—all without native dependencies.
10
10
 
11
11
  - 🚀 **Pure JavaScript** - No native dependencies
12
12
  - 🔌 **Pluggable formats** - Easy to extend with custom formats
13
- - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), and Bun
13
+ - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), Bun and Browsers.
14
14
  - 🎨 **Multiple formats** - PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PPM, PCX, ASCII,
15
15
  HEIC, and AVIF support
16
16
  - ✂️ **Image manipulation** - Resize, crop, composite, and more
@@ -284,6 +284,79 @@ const compressed = await image.encode("tiff", {
284
284
  - **Full compression support**: CMYK works with all TIFF compression methods
285
285
  - **Industry standard**: TIFF is the preferred format for CMYK images in print production
286
286
 
287
+ ## JPEG Coefficient Extraction
288
+
289
+ The library provides advanced APIs for extracting and encoding JPEG quantized DCT coefficients,
290
+ enabling coefficient-domain steganography and other frequency-domain processing techniques.
291
+
292
+ ### Extracting Coefficients
293
+
294
+ ```typescript
295
+ import { Image } from "jsr:@cross/image";
296
+
297
+ const data = await Deno.readFile("photo.jpg");
298
+
299
+ // Extract quantized DCT coefficients
300
+ const coefficients = await Image.extractCoefficients(data, "jpeg");
301
+
302
+ if (coefficients) {
303
+ console.log(`Image: ${coefficients.width}x${coefficients.height}`);
304
+ console.log(`Progressive: ${coefficients.isProgressive}`);
305
+ console.log(`Components: ${coefficients.components.length}`);
306
+
307
+ // Access coefficient blocks (Y, Cb, Cr components)
308
+ for (const comp of coefficients.components) {
309
+ console.log(`Component ${comp.id}: ${comp.blocks.length} block rows`);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### Modifying and Re-encoding Coefficients
315
+
316
+ ```typescript
317
+ import { Image } from "jsr:@cross/image";
318
+
319
+ const data = await Deno.readFile("input.jpg");
320
+ const coefficients = await Image.extractCoefficients(data, "jpeg");
321
+
322
+ if (coefficients) {
323
+ // Modify coefficients (e.g., for steganography)
324
+ for (const comp of coefficients.components) {
325
+ for (const row of comp.blocks) {
326
+ for (const block of row) {
327
+ // Modify AC coefficients (indices 1-63)
328
+ // DC coefficient is at index 0
329
+ // block is an Int32Array of 64 quantized DCT values in zigzag order
330
+ }
331
+ }
332
+ }
333
+
334
+ // Re-encode to JPEG
335
+ const encoded = await Image.encodeFromCoefficients(coefficients, "jpeg");
336
+ await Deno.writeFile("output.jpg", encoded);
337
+ }
338
+ ```
339
+
340
+ ### Coefficient Structure
341
+
342
+ The `JPEGQuantizedCoefficients` type provides:
343
+
344
+ - `width`, `height` - Image dimensions
345
+ - `isProgressive` - Whether the source was progressive JPEG
346
+ - `components` - Array of Y, Cb, Cr (or grayscale) component data
347
+ - `quantizationTables` - The quantization tables used
348
+ - `mcuWidth`, `mcuHeight` - MCU block dimensions
349
+
350
+ Each component contains `blocks[][]` where each block is an `Int32Array` of 64 quantized DCT
351
+ coefficients in zigzag order.
352
+
353
+ **Use Cases:**
354
+
355
+ - DCT-domain steganography (survives JPEG re-compression)
356
+ - Coefficient analysis and visualization
357
+ - Custom frequency-domain filtering
358
+ - Forensic analysis of JPEG compression artifacts
359
+
287
360
  ## Base64 / Data URLs
288
361
 
289
362
  The library includes small utilities for working with base64 and `data:` URLs.
package/esm/mod.d.ts CHANGED
@@ -43,7 +43,7 @@
43
43
  * ```
44
44
  */
45
45
  export { Image } from "./src/image.js";
46
- export type { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGEncoderOptions, MultiFrameImageData, PNGEncoderOptions, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
46
+ export type { APNGEncoderOptions, ASCIIEncoderOptions, AVIFEncoderOptions, CoefficientData, FrameMetadata, GIFEncoderOptions, HEICEncoderOptions, ImageData, ImageDecoderOptions, ImageFormat, ImageFrame, ImageMetadata, JPEGComponentCoefficients, JPEGEncoderOptions, JPEGQuantizedCoefficients, MultiFrameImageData, PNGEncoderOptions, ResizeOptions, TIFFEncoderOptions, WebPEncoderOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
48
  export { APNGFormat } from "./src/formats/apng.js";
49
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
@@ -48,6 +48,7 @@ export declare class GIFFormat implements ImageFormat {
48
48
  * Encode multi-frame image data to animated GIF
49
49
  */
50
50
  encodeFrames(imageData: MultiFrameImageData, options?: GIFEncoderOptions): Promise<Uint8Array>;
51
+ private mapDisposalMethodToNumber;
51
52
  private mapDisposalMethod;
52
53
  private decodeUsingRuntime;
53
54
  private readDataSubBlocks;
@@ -243,12 +243,29 @@ export class GIFFormat {
243
243
  }
244
244
  const encoder = new GIFEncoder(imageData.width, imageData.height);
245
245
  for (const frame of imageData.frames) {
246
- // Get delay from metadata (default to 100ms if not set)
247
246
  const delay = frame.frameMetadata?.delay ?? 100;
248
- encoder.addFrame(frame.data, delay);
247
+ encoder.addFrame(frame.data, delay, {
248
+ left: frame.frameMetadata?.left ?? 0,
249
+ top: frame.frameMetadata?.top ?? 0,
250
+ width: frame.width,
251
+ height: frame.height,
252
+ disposal: this.mapDisposalMethodToNumber(frame.frameMetadata?.disposal),
253
+ });
249
254
  }
250
255
  return Promise.resolve(encoder.encode(options));
251
256
  }
257
+ mapDisposalMethodToNumber(disposal) {
258
+ switch (disposal) {
259
+ case "none":
260
+ return 1;
261
+ case "background":
262
+ return 2;
263
+ case "previous":
264
+ return 3;
265
+ default:
266
+ return 0;
267
+ }
268
+ }
252
269
  mapDisposalMethod(disposal) {
253
270
  switch (disposal) {
254
271
  case 0:
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions } from "../types.js";
1
+ import type { CoefficientData, ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions, JPEGQuantizedCoefficients } from "../types.js";
2
2
  /**
3
3
  * JPEG format handler
4
4
  * Implements a basic JPEG decoder and encoder
@@ -42,6 +42,26 @@ export declare class JPEGFormat implements ImageFormat {
42
42
  * Get the list of metadata fields supported by JPEG format
43
43
  */
44
44
  getSupportedMetadata(): Array<keyof ImageMetadata>;
45
+ /**
46
+ * Extract quantized DCT coefficients from JPEG data
47
+ * These coefficients can be modified for steganography and re-encoded
48
+ * @param data Raw JPEG data
49
+ * @param options Decoder options
50
+ * @returns JPEGQuantizedCoefficients or undefined if extraction fails
51
+ */
52
+ extractCoefficients(data: Uint8Array, options?: ImageDecoderOptions): Promise<JPEGQuantizedCoefficients | undefined>;
53
+ /**
54
+ * Type guard to check if coefficient data is JPEG format
55
+ */
56
+ private isJPEGCoefficients;
57
+ /**
58
+ * Encode JPEG from quantized DCT coefficients
59
+ * Useful for steganography - modify coefficients and re-encode
60
+ * @param coeffs JPEG quantized coefficients
61
+ * @param options Encoding options
62
+ * @returns Encoded JPEG bytes
63
+ */
64
+ encodeFromCoefficients(coeffs: CoefficientData, options?: JPEGEncoderOptions): Promise<Uint8Array>;
45
65
  /**
46
66
  * Extract metadata from JPEG data without fully decoding the pixel data
47
67
  * This quickly parses JFIF and EXIF markers to extract metadata
@@ -1072,6 +1072,65 @@ export class JPEGFormat {
1072
1072
  "dpiY",
1073
1073
  ];
1074
1074
  }
1075
+ /**
1076
+ * Extract quantized DCT coefficients from JPEG data
1077
+ * These coefficients can be modified for steganography and re-encoded
1078
+ * @param data Raw JPEG data
1079
+ * @param options Decoder options
1080
+ * @returns JPEGQuantizedCoefficients or undefined if extraction fails
1081
+ */
1082
+ async extractCoefficients(data, options) {
1083
+ if (!this.canDecode(data)) {
1084
+ return undefined;
1085
+ }
1086
+ try {
1087
+ // Force pure-JS decoding since runtime decoders don't expose coefficients
1088
+ const { JPEGDecoder } = await import("../utils/jpeg_decoder.js");
1089
+ const decoder = new JPEGDecoder(data, {
1090
+ tolerantDecoding: options?.tolerantDecoding ?? true,
1091
+ onWarning: options?.onWarning,
1092
+ extractCoefficients: true,
1093
+ });
1094
+ // Decode to extract coefficients
1095
+ decoder.decode();
1096
+ // Get the quantized coefficients
1097
+ return decoder.getQuantizedCoefficients();
1098
+ }
1099
+ catch (error) {
1100
+ if (options?.onWarning) {
1101
+ options.onWarning(`Failed to extract JPEG coefficients: ${error}`, error);
1102
+ }
1103
+ return undefined;
1104
+ }
1105
+ }
1106
+ /**
1107
+ * Type guard to check if coefficient data is JPEG format
1108
+ */
1109
+ isJPEGCoefficients(coeffs) {
1110
+ return ("format" in coeffs &&
1111
+ coeffs.format === "jpeg" &&
1112
+ "components" in coeffs &&
1113
+ "quantizationTables" in coeffs &&
1114
+ "isProgressive" in coeffs);
1115
+ }
1116
+ /**
1117
+ * Encode JPEG from quantized DCT coefficients
1118
+ * Useful for steganography - modify coefficients and re-encode
1119
+ * @param coeffs JPEG quantized coefficients
1120
+ * @param options Encoding options
1121
+ * @returns Encoded JPEG bytes
1122
+ */
1123
+ async encodeFromCoefficients(coeffs, options) {
1124
+ if (!this.isJPEGCoefficients(coeffs)) {
1125
+ throw new Error("Invalid coefficient format for JPEG");
1126
+ }
1127
+ const { JPEGEncoder } = await import("../utils/jpeg_encoder.js");
1128
+ const encoder = new JPEGEncoder({
1129
+ quality: options?.quality,
1130
+ progressive: options?.progressive ?? coeffs.isProgressive,
1131
+ });
1132
+ return encoder.encodeFromCoefficients(coeffs, options);
1133
+ }
1075
1134
  /**
1076
1135
  * Extract metadata from JPEG data without fully decoding the pixel data
1077
1136
  * This quickly parses JFIF and EXIF markers to extract metadata
@@ -1,4 +1,4 @@
1
- import type { ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
1
+ import type { CoefficientData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
2
2
  /**
3
3
  * Main Image class for reading, manipulating, and saving images
4
4
  */
@@ -98,6 +98,44 @@ export declare class Image {
98
98
  * @returns Metadata extracted from the image, or undefined if extraction fails or format is unsupported
99
99
  */
100
100
  static extractMetadata(data: Uint8Array, format?: string): Promise<ImageMetadata | undefined>;
101
+ /**
102
+ * Extract coefficients from encoded image data
103
+ * For JPEG, this returns quantized DCT coefficients that can be modified for steganography
104
+ * and re-encoded using encodeFromCoefficients()
105
+ * @param data Raw image data
106
+ * @param format Optional format hint (e.g., "jpeg")
107
+ * @param options Optional decoder options
108
+ * @returns Format-specific coefficient structure or undefined if not supported
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * // Extract JPEG coefficients for steganography
113
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
114
+ * if (coeffs) {
115
+ * // Modify coefficients for steganography...
116
+ * const modified = await Image.encodeFromCoefficients(coeffs, "jpeg");
117
+ * }
118
+ * ```
119
+ */
120
+ static extractCoefficients(data: Uint8Array, format?: string, options?: ImageDecoderOptions): Promise<CoefficientData | undefined>;
121
+ /**
122
+ * Encode image from coefficients
123
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG file
124
+ * Useful for steganography where coefficients are extracted, modified, and re-encoded
125
+ * @param coeffs Format-specific coefficient structure
126
+ * @param format Optional format hint (auto-detected from coeffs.format if available)
127
+ * @param options Optional format-specific encoding options
128
+ * @returns Encoded image bytes
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * // Re-encode modified JPEG coefficients
133
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
134
+ * // Modify coefficients...
135
+ * const encoded = await Image.encodeFromCoefficients(coeffs, "jpeg");
136
+ * ```
137
+ */
138
+ static encodeFromCoefficients(coeffs: CoefficientData, format?: string, options?: unknown): Promise<Uint8Array>;
101
139
  /**
102
140
  * Read an image from bytes
103
141
  * @deprecated Use `decode()` instead. This method will be removed in a future version.
package/esm/src/image.js CHANGED
@@ -230,6 +230,74 @@ export class Image {
230
230
  }
231
231
  return undefined;
232
232
  }
233
+ /**
234
+ * Extract coefficients from encoded image data
235
+ * For JPEG, this returns quantized DCT coefficients that can be modified for steganography
236
+ * and re-encoded using encodeFromCoefficients()
237
+ * @param data Raw image data
238
+ * @param format Optional format hint (e.g., "jpeg")
239
+ * @param options Optional decoder options
240
+ * @returns Format-specific coefficient structure or undefined if not supported
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // Extract JPEG coefficients for steganography
245
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
246
+ * if (coeffs) {
247
+ * // Modify coefficients for steganography...
248
+ * const modified = await Image.encodeFromCoefficients(coeffs, "jpeg");
249
+ * }
250
+ * ```
251
+ */
252
+ static async extractCoefficients(data, format, options) {
253
+ // Try specified format first
254
+ if (format) {
255
+ const handler = Image.formats.find((f) => f.name === format);
256
+ if (handler && handler.canDecode(data) && handler.extractCoefficients) {
257
+ return await handler.extractCoefficients(data, options);
258
+ }
259
+ }
260
+ // Auto-detect format
261
+ for (const handler of Image.formats) {
262
+ if (handler.canDecode(data) && handler.extractCoefficients) {
263
+ return await handler.extractCoefficients(data, options);
264
+ }
265
+ }
266
+ return undefined;
267
+ }
268
+ /**
269
+ * Encode image from coefficients
270
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG file
271
+ * Useful for steganography where coefficients are extracted, modified, and re-encoded
272
+ * @param coeffs Format-specific coefficient structure
273
+ * @param format Optional format hint (auto-detected from coeffs.format if available)
274
+ * @param options Optional format-specific encoding options
275
+ * @returns Encoded image bytes
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * // Re-encode modified JPEG coefficients
280
+ * const coeffs = await Image.extractCoefficients(jpegData, "jpeg");
281
+ * // Modify coefficients...
282
+ * const encoded = await Image.encodeFromCoefficients(coeffs, "jpeg");
283
+ * ```
284
+ */
285
+ static async encodeFromCoefficients(coeffs, format, options) {
286
+ // Detect format from coefficient structure or use provided format
287
+ const detectedFormat = format ??
288
+ coeffs.format;
289
+ if (!detectedFormat) {
290
+ throw new Error("Format must be specified or present in coefficient data");
291
+ }
292
+ const handler = Image.formats.find((f) => f.name === detectedFormat);
293
+ if (!handler) {
294
+ throw new Error(`Unknown format: ${detectedFormat}`);
295
+ }
296
+ if (!handler.encodeFromCoefficients) {
297
+ throw new Error(`Format ${detectedFormat} does not support encoding from coefficients`);
298
+ }
299
+ return await handler.encodeFromCoefficients(coeffs, options);
300
+ }
233
301
  /**
234
302
  * Read an image from bytes
235
303
  * @deprecated Use `decode()` instead. This method will be removed in a future version.
@@ -1,3 +1,49 @@
1
+ /**
2
+ * JPEG quantized DCT coefficients for steganography and advanced processing
3
+ * Contains the frequency-domain representation of the image
4
+ */
5
+ export interface JPEGQuantizedCoefficients {
6
+ /** Format identifier */
7
+ format: "jpeg";
8
+ /** Image width in pixels */
9
+ width: number;
10
+ /** Image height in pixels */
11
+ height: number;
12
+ /** Whether the JPEG is progressive */
13
+ isProgressive: boolean;
14
+ /** Component data (Y, Cb, Cr for color images) */
15
+ components: JPEGComponentCoefficients[];
16
+ /** Quantization tables used (indexed by table ID) */
17
+ quantizationTables: (Uint8Array | number[])[];
18
+ /** MCU width (number of 8x8 blocks horizontally) */
19
+ mcuWidth: number;
20
+ /** MCU height (number of 8x8 blocks vertically) */
21
+ mcuHeight: number;
22
+ }
23
+ /**
24
+ * Coefficients for a single JPEG component (Y, Cb, or Cr)
25
+ */
26
+ export interface JPEGComponentCoefficients {
27
+ /** Component ID (1=Y, 2=Cb, 3=Cr typically) */
28
+ id: number;
29
+ /** Horizontal sampling factor */
30
+ h: number;
31
+ /** Vertical sampling factor */
32
+ v: number;
33
+ /** Quantization table index */
34
+ qTable: number;
35
+ /**
36
+ * Quantized DCT coefficient blocks
37
+ * blocks[blockRow][blockCol] contains a 64-element array in zigzag order
38
+ * Coefficients are quantized (divided by quantization table values)
39
+ */
40
+ blocks: Int32Array[][];
41
+ }
42
+ /**
43
+ * Union type for coefficient data from different formats
44
+ * Currently only JPEG is supported, but this allows for future extension
45
+ */
46
+ export type CoefficientData = JPEGQuantizedCoefficients;
1
47
  /**
2
48
  * Image metadata
3
49
  */
@@ -339,5 +385,22 @@ export interface ImageFormat {
339
385
  * @returns Metadata extracted from the image, or undefined if extraction fails
340
386
  */
341
387
  extractMetadata?(data: Uint8Array): Promise<ImageMetadata | undefined>;
388
+ /**
389
+ * Extract coefficients from encoded image data (optional)
390
+ * For JPEG, this returns quantized DCT coefficients
391
+ * Useful for steganography and advanced image processing
392
+ * @param data Raw image data
393
+ * @param options Decoder options
394
+ * @returns Format-specific coefficient structure or undefined if not supported
395
+ */
396
+ extractCoefficients?(data: Uint8Array, options?: ImageDecoderOptions): Promise<CoefficientData | undefined>;
397
+ /**
398
+ * Encode image from coefficients (optional)
399
+ * For JPEG, accepts quantized DCT coefficients and produces a valid JPEG
400
+ * @param coeffs Format-specific coefficient structure
401
+ * @param options Format-specific encoding options
402
+ * @returns Encoded image bytes
403
+ */
404
+ encodeFromCoefficients?(coeffs: CoefficientData, options?: unknown): Promise<Uint8Array>;
342
405
  }
343
406
  //# sourceMappingURL=types.d.ts.map
@@ -3,11 +3,20 @@
3
3
  * Supports GIF89a format with LZW compression
4
4
  */
5
5
  export declare class GIFEncoder {
6
- private width;
7
- private height;
6
+ private canvasWidth;
7
+ private canvasHeight;
8
8
  private frames;
9
9
  constructor(width: number, height: number, data?: Uint8Array);
10
- addFrame(data: Uint8Array, delay?: number): void;
10
+ /**
11
+ * Add a frame with optional metadata for partial frame support
12
+ */
13
+ addFrame(data: Uint8Array, delay?: number, options?: {
14
+ left?: number;
15
+ top?: number;
16
+ width?: number;
17
+ height?: number;
18
+ disposal?: number;
19
+ }): void;
11
20
  private writeBytes;
12
21
  private writeUint16LE;
13
22
  private writeString;
@@ -20,7 +29,7 @@ export declare class GIFEncoder {
20
29
  */
21
30
  private quantizeChannel;
22
31
  /**
23
- * Quantize RGBA image to 256 colors using median cut algorithm
32
+ * Quantize RGBA image to 256 colors with transparency support
24
33
  */
25
34
  private quantize;
26
35
  private nextPowerOf2;