cross-image 0.1.4 → 0.2.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 +155 -121
- package/esm/mod.d.ts +32 -8
- package/esm/mod.js +32 -8
- package/esm/src/formats/dng.d.ts +27 -0
- package/esm/src/formats/dng.js +191 -0
- package/esm/src/formats/pam.d.ts +43 -0
- package/esm/src/formats/pam.js +177 -0
- package/esm/src/formats/pcx.d.ts +13 -0
- package/esm/src/formats/pcx.js +204 -0
- package/esm/src/formats/tiff.d.ts +7 -7
- package/esm/src/image.d.ts +133 -1
- package/esm/src/image.js +250 -11
- package/esm/src/utils/image_processing.d.ts +91 -0
- package/esm/src/utils/image_processing.js +231 -0
- package/esm/src/utils/webp_decoder.js +47 -12
- package/esm/src/utils/webp_encoder.js +97 -39
- package/package.json +4 -1
- package/script/mod.d.ts +32 -8
- package/script/mod.js +36 -10
- package/script/src/formats/dng.d.ts +27 -0
- package/script/src/formats/dng.js +195 -0
- package/script/src/formats/pam.d.ts +43 -0
- package/script/src/formats/pam.js +181 -0
- package/script/src/formats/pcx.d.ts +13 -0
- package/script/src/formats/pcx.js +208 -0
- package/script/src/formats/tiff.d.ts +7 -7
- package/script/src/image.d.ts +133 -1
- package/script/src/image.js +250 -11
- package/script/src/utils/image_processing.d.ts +91 -0
- package/script/src/utils/image_processing.js +242 -0
- package/script/src/utils/webp_decoder.js +47 -12
- package/script/src/utils/webp_encoder.js +97 -39
- package/esm/src/formats/raw.d.ts +0 -40
- package/esm/src/formats/raw.js +0 -118
- package/script/src/formats/raw.d.ts +0 -40
- package/script/src/formats/raw.js +0 -122
package/esm/src/image.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { resizeBilinear, resizeNearest } from "./utils/resize.js";
|
|
2
|
+
import { adjustBrightness, adjustContrast, adjustExposure, adjustSaturation, composite, crop, fillRect, grayscale, invert, } from "./utils/image_processing.js";
|
|
2
3
|
import { PNGFormat } from "./formats/png.js";
|
|
3
4
|
import { JPEGFormat } from "./formats/jpeg.js";
|
|
4
5
|
import { WebPFormat } from "./formats/webp.js";
|
|
5
6
|
import { GIFFormat } from "./formats/gif.js";
|
|
6
7
|
import { TIFFFormat } from "./formats/tiff.js";
|
|
7
8
|
import { BMPFormat } from "./formats/bmp.js";
|
|
8
|
-
import {
|
|
9
|
+
import { DNGFormat } from "./formats/dng.js";
|
|
10
|
+
import { PAMFormat } from "./formats/pam.js";
|
|
11
|
+
import { PCXFormat } from "./formats/pcx.js";
|
|
9
12
|
import { ASCIIFormat } from "./formats/ascii.js";
|
|
10
13
|
import { validateImageDimensions } from "./utils/security.js";
|
|
11
14
|
/**
|
|
@@ -151,12 +154,12 @@ export class Image {
|
|
|
151
154
|
return Image.formats;
|
|
152
155
|
}
|
|
153
156
|
/**
|
|
154
|
-
*
|
|
157
|
+
* Decode an image from bytes
|
|
155
158
|
* @param data Raw image data
|
|
156
159
|
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
157
160
|
* @returns Image instance
|
|
158
161
|
*/
|
|
159
|
-
static async
|
|
162
|
+
static async decode(data, format) {
|
|
160
163
|
const image = new Image();
|
|
161
164
|
// Try specified format first
|
|
162
165
|
if (format) {
|
|
@@ -176,12 +179,22 @@ export class Image {
|
|
|
176
179
|
throw new Error("Unsupported or unrecognized image format");
|
|
177
180
|
}
|
|
178
181
|
/**
|
|
179
|
-
* Read
|
|
182
|
+
* Read an image from bytes
|
|
183
|
+
* @deprecated Use `decode()` instead. This method will be removed in a future version.
|
|
184
|
+
* @param data Raw image data
|
|
185
|
+
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
186
|
+
* @returns Image instance
|
|
187
|
+
*/
|
|
188
|
+
static read(data, format) {
|
|
189
|
+
return Image.decode(data, format);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Decode all frames from a multi-frame image (GIF animation, multi-page TIFF)
|
|
180
193
|
* @param data Raw image data
|
|
181
194
|
* @param format Optional format hint (e.g., "gif", "tiff")
|
|
182
195
|
* @returns MultiFrameImageData with all frames
|
|
183
196
|
*/
|
|
184
|
-
static async
|
|
197
|
+
static async decodeFrames(data, format) {
|
|
185
198
|
// Try specified format first
|
|
186
199
|
if (format) {
|
|
187
200
|
const handler = Image.formats.find((f) => f.name === format);
|
|
@@ -198,13 +211,23 @@ export class Image {
|
|
|
198
211
|
throw new Error("Unsupported or unrecognized multi-frame image format");
|
|
199
212
|
}
|
|
200
213
|
/**
|
|
201
|
-
*
|
|
214
|
+
* Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
|
|
215
|
+
* @deprecated Use `decodeFrames()` instead. This method will be removed in a future version.
|
|
216
|
+
* @param data Raw image data
|
|
217
|
+
* @param format Optional format hint (e.g., "gif", "tiff")
|
|
218
|
+
* @returns MultiFrameImageData with all frames
|
|
219
|
+
*/
|
|
220
|
+
static readFrames(data, format) {
|
|
221
|
+
return Image.decodeFrames(data, format);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Encode multi-frame image data to bytes in the specified format
|
|
202
225
|
* @param format Format name (e.g., "gif", "tiff")
|
|
203
|
-
* @param imageData Multi-frame image data to
|
|
226
|
+
* @param imageData Multi-frame image data to encode
|
|
204
227
|
* @param options Optional format-specific encoding options
|
|
205
228
|
* @returns Encoded image bytes
|
|
206
229
|
*/
|
|
207
|
-
static async
|
|
230
|
+
static async encodeFrames(format, imageData, options) {
|
|
208
231
|
const handler = Image.formats.find((f) => f.name === format);
|
|
209
232
|
if (!handler) {
|
|
210
233
|
throw new Error(`Unsupported format: ${format}`);
|
|
@@ -214,6 +237,17 @@ export class Image {
|
|
|
214
237
|
}
|
|
215
238
|
return await handler.encodeFrames(imageData, options);
|
|
216
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Save multi-frame image data to bytes in the specified format
|
|
242
|
+
* @deprecated Use `encodeFrames()` instead. This method will be removed in a future version.
|
|
243
|
+
* @param format Format name (e.g., "gif", "tiff")
|
|
244
|
+
* @param imageData Multi-frame image data to encode
|
|
245
|
+
* @param options Optional format-specific encoding options
|
|
246
|
+
* @returns Encoded image bytes
|
|
247
|
+
*/
|
|
248
|
+
static saveFrames(format, imageData, options) {
|
|
249
|
+
return Image.encodeFrames(format, imageData, options);
|
|
250
|
+
}
|
|
217
251
|
/**
|
|
218
252
|
* Create an image from raw RGBA data
|
|
219
253
|
* @param width Image width
|
|
@@ -231,6 +265,31 @@ export class Image {
|
|
|
231
265
|
image.imageData = { width, height, data: new Uint8Array(data) };
|
|
232
266
|
return image;
|
|
233
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Create a blank image with the specified dimensions and color
|
|
270
|
+
* @param width Image width
|
|
271
|
+
* @param height Image height
|
|
272
|
+
* @param r Red component (0-255, default: 0)
|
|
273
|
+
* @param g Green component (0-255, default: 0)
|
|
274
|
+
* @param b Blue component (0-255, default: 0)
|
|
275
|
+
* @param a Alpha component (0-255, default: 255)
|
|
276
|
+
* @returns Image instance
|
|
277
|
+
*/
|
|
278
|
+
static create(width, height, r = 0, g = 0, b = 0, a = 255) {
|
|
279
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
280
|
+
validateImageDimensions(width, height);
|
|
281
|
+
const data = new Uint8Array(width * height * 4);
|
|
282
|
+
// Fill with the specified color
|
|
283
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
284
|
+
data[i] = r;
|
|
285
|
+
data[i + 1] = g;
|
|
286
|
+
data[i + 2] = b;
|
|
287
|
+
data[i + 3] = a;
|
|
288
|
+
}
|
|
289
|
+
const image = new Image();
|
|
290
|
+
image.imageData = { width, height, data };
|
|
291
|
+
return image;
|
|
292
|
+
}
|
|
234
293
|
/**
|
|
235
294
|
* Resize the image
|
|
236
295
|
* @param options Resize options
|
|
@@ -270,12 +329,12 @@ export class Image {
|
|
|
270
329
|
return this;
|
|
271
330
|
}
|
|
272
331
|
/**
|
|
273
|
-
*
|
|
332
|
+
* Encode the image to bytes in the specified format
|
|
274
333
|
* @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
|
|
275
334
|
* @param options Optional format-specific encoding options
|
|
276
335
|
* @returns Encoded image bytes
|
|
277
336
|
*/
|
|
278
|
-
async
|
|
337
|
+
async encode(format, options) {
|
|
279
338
|
if (!this.imageData)
|
|
280
339
|
throw new Error("No image loaded");
|
|
281
340
|
const handler = Image.formats.find((f) => f.name === format);
|
|
@@ -284,6 +343,16 @@ export class Image {
|
|
|
284
343
|
}
|
|
285
344
|
return await handler.encode(this.imageData, options);
|
|
286
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Save the image to bytes in the specified format
|
|
348
|
+
* @deprecated Use `encode()` instead. This method will be removed in a future version.
|
|
349
|
+
* @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
|
|
350
|
+
* @param options Optional format-specific encoding options
|
|
351
|
+
* @returns Encoded image bytes
|
|
352
|
+
*/
|
|
353
|
+
save(format, options) {
|
|
354
|
+
return this.encode(format, options);
|
|
355
|
+
}
|
|
287
356
|
/**
|
|
288
357
|
* Clone this image
|
|
289
358
|
* @returns New image instance with copied data and metadata
|
|
@@ -307,6 +376,174 @@ export class Image {
|
|
|
307
376
|
};
|
|
308
377
|
return image;
|
|
309
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Composite another image on top of this image at the specified position
|
|
381
|
+
* @param overlay Image to place on top
|
|
382
|
+
* @param x X position (can be negative)
|
|
383
|
+
* @param y Y position (can be negative)
|
|
384
|
+
* @param opacity Opacity of overlay (0-1, default: 1)
|
|
385
|
+
* @returns This image instance for chaining
|
|
386
|
+
*/
|
|
387
|
+
composite(overlay, x, y, opacity = 1) {
|
|
388
|
+
if (!this.imageData)
|
|
389
|
+
throw new Error("No image loaded");
|
|
390
|
+
if (!overlay.imageData)
|
|
391
|
+
throw new Error("Overlay has no image loaded");
|
|
392
|
+
this.imageData.data = composite(this.imageData.data, this.imageData.width, this.imageData.height, overlay.imageData.data, overlay.imageData.width, overlay.imageData.height, x, y, opacity);
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Adjust brightness of the image
|
|
397
|
+
* @param amount Brightness adjustment (-1 to 1, where 0 is no change)
|
|
398
|
+
* @returns This image instance for chaining
|
|
399
|
+
*/
|
|
400
|
+
brightness(amount) {
|
|
401
|
+
if (!this.imageData)
|
|
402
|
+
throw new Error("No image loaded");
|
|
403
|
+
this.imageData.data = adjustBrightness(this.imageData.data, amount);
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Adjust contrast of the image
|
|
408
|
+
* @param amount Contrast adjustment (-1 to 1, where 0 is no change)
|
|
409
|
+
* @returns This image instance for chaining
|
|
410
|
+
*/
|
|
411
|
+
contrast(amount) {
|
|
412
|
+
if (!this.imageData)
|
|
413
|
+
throw new Error("No image loaded");
|
|
414
|
+
this.imageData.data = adjustContrast(this.imageData.data, amount);
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Adjust exposure of the image
|
|
419
|
+
* @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
|
|
420
|
+
* @returns This image instance for chaining
|
|
421
|
+
*/
|
|
422
|
+
exposure(amount) {
|
|
423
|
+
if (!this.imageData)
|
|
424
|
+
throw new Error("No image loaded");
|
|
425
|
+
this.imageData.data = adjustExposure(this.imageData.data, amount);
|
|
426
|
+
return this;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Adjust saturation of the image
|
|
430
|
+
* @param amount Saturation adjustment (-1 to 1, where 0 is no change)
|
|
431
|
+
* @returns This image instance for chaining
|
|
432
|
+
*/
|
|
433
|
+
saturation(amount) {
|
|
434
|
+
if (!this.imageData)
|
|
435
|
+
throw new Error("No image loaded");
|
|
436
|
+
this.imageData.data = adjustSaturation(this.imageData.data, amount);
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Invert colors of the image
|
|
441
|
+
* @returns This image instance for chaining
|
|
442
|
+
*/
|
|
443
|
+
invert() {
|
|
444
|
+
if (!this.imageData)
|
|
445
|
+
throw new Error("No image loaded");
|
|
446
|
+
this.imageData.data = invert(this.imageData.data);
|
|
447
|
+
return this;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Convert the image to grayscale
|
|
451
|
+
* @returns This image instance for chaining
|
|
452
|
+
*/
|
|
453
|
+
grayscale() {
|
|
454
|
+
if (!this.imageData)
|
|
455
|
+
throw new Error("No image loaded");
|
|
456
|
+
this.imageData.data = grayscale(this.imageData.data);
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Fill a rectangular region with a color
|
|
461
|
+
* @param x Starting X position
|
|
462
|
+
* @param y Starting Y position
|
|
463
|
+
* @param width Width of the fill region
|
|
464
|
+
* @param height Height of the fill region
|
|
465
|
+
* @param r Red component (0-255)
|
|
466
|
+
* @param g Green component (0-255)
|
|
467
|
+
* @param b Blue component (0-255)
|
|
468
|
+
* @param a Alpha component (0-255, default: 255)
|
|
469
|
+
* @returns This image instance for chaining
|
|
470
|
+
*/
|
|
471
|
+
fillRect(x, y, width, height, r, g, b, a = 255) {
|
|
472
|
+
if (!this.imageData)
|
|
473
|
+
throw new Error("No image loaded");
|
|
474
|
+
this.imageData.data = fillRect(this.imageData.data, this.imageData.width, this.imageData.height, x, y, width, height, r, g, b, a);
|
|
475
|
+
return this;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Crop the image to a rectangular region
|
|
479
|
+
* @param x Starting X position
|
|
480
|
+
* @param y Starting Y position
|
|
481
|
+
* @param width Width of the crop region
|
|
482
|
+
* @param height Height of the crop region
|
|
483
|
+
* @returns This image instance for chaining
|
|
484
|
+
*/
|
|
485
|
+
crop(x, y, width, height) {
|
|
486
|
+
if (!this.imageData)
|
|
487
|
+
throw new Error("No image loaded");
|
|
488
|
+
const result = crop(this.imageData.data, this.imageData.width, this.imageData.height, x, y, width, height);
|
|
489
|
+
this.imageData.width = result.width;
|
|
490
|
+
this.imageData.height = result.height;
|
|
491
|
+
this.imageData.data = result.data;
|
|
492
|
+
// Update physical dimensions if DPI is set
|
|
493
|
+
if (this.imageData.metadata) {
|
|
494
|
+
const metadata = this.imageData.metadata;
|
|
495
|
+
if (metadata.dpiX) {
|
|
496
|
+
this.imageData.metadata.physicalWidth = result.width / metadata.dpiX;
|
|
497
|
+
}
|
|
498
|
+
if (metadata.dpiY) {
|
|
499
|
+
this.imageData.metadata.physicalHeight = result.height / metadata.dpiY;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get the pixel color at the specified position
|
|
506
|
+
* @param x X position
|
|
507
|
+
* @param y Y position
|
|
508
|
+
* @returns Object with r, g, b, a components (0-255) or undefined if out of bounds
|
|
509
|
+
*/
|
|
510
|
+
getPixel(x, y) {
|
|
511
|
+
if (!this.imageData)
|
|
512
|
+
throw new Error("No image loaded");
|
|
513
|
+
if (x < 0 || x >= this.imageData.width || y < 0 || y >= this.imageData.height) {
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
const idx = (y * this.imageData.width + x) * 4;
|
|
517
|
+
return {
|
|
518
|
+
r: this.imageData.data[idx],
|
|
519
|
+
g: this.imageData.data[idx + 1],
|
|
520
|
+
b: this.imageData.data[idx + 2],
|
|
521
|
+
a: this.imageData.data[idx + 3],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Set the pixel color at the specified position
|
|
526
|
+
* @param x X position
|
|
527
|
+
* @param y Y position
|
|
528
|
+
* @param r Red component (0-255)
|
|
529
|
+
* @param g Green component (0-255)
|
|
530
|
+
* @param b Blue component (0-255)
|
|
531
|
+
* @param a Alpha component (0-255, default: 255)
|
|
532
|
+
* @returns This image instance for chaining
|
|
533
|
+
*/
|
|
534
|
+
setPixel(x, y, r, g, b, a = 255) {
|
|
535
|
+
if (!this.imageData)
|
|
536
|
+
throw new Error("No image loaded");
|
|
537
|
+
if (x < 0 || x >= this.imageData.width || y < 0 || y >= this.imageData.height) {
|
|
538
|
+
return this;
|
|
539
|
+
}
|
|
540
|
+
const idx = (y * this.imageData.width + x) * 4;
|
|
541
|
+
this.imageData.data[idx] = r;
|
|
542
|
+
this.imageData.data[idx + 1] = g;
|
|
543
|
+
this.imageData.data[idx + 2] = b;
|
|
544
|
+
this.imageData.data[idx + 3] = a;
|
|
545
|
+
return this;
|
|
546
|
+
}
|
|
310
547
|
}
|
|
311
548
|
Object.defineProperty(Image, "formats", {
|
|
312
549
|
enumerable: true,
|
|
@@ -319,7 +556,9 @@ Object.defineProperty(Image, "formats", {
|
|
|
319
556
|
new GIFFormat(),
|
|
320
557
|
new TIFFFormat(),
|
|
321
558
|
new BMPFormat(),
|
|
322
|
-
new
|
|
559
|
+
new DNGFormat(),
|
|
560
|
+
new PAMFormat(),
|
|
561
|
+
new PCXFormat(),
|
|
323
562
|
new ASCIIFormat(),
|
|
324
563
|
]
|
|
325
564
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image processing utilities for common operations like compositing,
|
|
3
|
+
* level adjustments, and color manipulations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Composite one image on top of another at a specified position
|
|
7
|
+
* @param base Base image data (RGBA)
|
|
8
|
+
* @param baseWidth Base image width
|
|
9
|
+
* @param baseHeight Base image height
|
|
10
|
+
* @param overlay Overlay image data (RGBA)
|
|
11
|
+
* @param overlayWidth Overlay image width
|
|
12
|
+
* @param overlayHeight Overlay image height
|
|
13
|
+
* @param x X position to place overlay (can be negative)
|
|
14
|
+
* @param y Y position to place overlay (can be negative)
|
|
15
|
+
* @param opacity Opacity of overlay (0-1, default: 1)
|
|
16
|
+
* @returns New image data with overlay composited on base
|
|
17
|
+
*/
|
|
18
|
+
export declare function composite(base: Uint8Array, baseWidth: number, baseHeight: number, overlay: Uint8Array, overlayWidth: number, overlayHeight: number, x: number, y: number, opacity?: number): Uint8Array;
|
|
19
|
+
/**
|
|
20
|
+
* Adjust brightness of an image
|
|
21
|
+
* @param data Image data (RGBA)
|
|
22
|
+
* @param amount Brightness adjustment (-1 to 1, where 0 is no change)
|
|
23
|
+
* @returns New image data with adjusted brightness
|
|
24
|
+
*/
|
|
25
|
+
export declare function adjustBrightness(data: Uint8Array, amount: number): Uint8Array;
|
|
26
|
+
/**
|
|
27
|
+
* Adjust contrast of an image
|
|
28
|
+
* @param data Image data (RGBA)
|
|
29
|
+
* @param amount Contrast adjustment (-1 to 1, where 0 is no change)
|
|
30
|
+
* @returns New image data with adjusted contrast
|
|
31
|
+
*/
|
|
32
|
+
export declare function adjustContrast(data: Uint8Array, amount: number): Uint8Array;
|
|
33
|
+
/**
|
|
34
|
+
* Adjust exposure of an image
|
|
35
|
+
* @param data Image data (RGBA)
|
|
36
|
+
* @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
|
|
37
|
+
* @returns New image data with adjusted exposure
|
|
38
|
+
*/
|
|
39
|
+
export declare function adjustExposure(data: Uint8Array, amount: number): Uint8Array;
|
|
40
|
+
/**
|
|
41
|
+
* Adjust saturation of an image
|
|
42
|
+
* @param data Image data (RGBA)
|
|
43
|
+
* @param amount Saturation adjustment (-1 to 1, where 0 is no change)
|
|
44
|
+
* @returns New image data with adjusted saturation
|
|
45
|
+
*/
|
|
46
|
+
export declare function adjustSaturation(data: Uint8Array, amount: number): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Invert colors of an image
|
|
49
|
+
* @param data Image data (RGBA)
|
|
50
|
+
* @returns New image data with inverted colors
|
|
51
|
+
*/
|
|
52
|
+
export declare function invert(data: Uint8Array): Uint8Array;
|
|
53
|
+
/**
|
|
54
|
+
* Convert image to grayscale
|
|
55
|
+
* @param data Image data (RGBA)
|
|
56
|
+
* @returns New image data in grayscale
|
|
57
|
+
*/
|
|
58
|
+
export declare function grayscale(data: Uint8Array): Uint8Array;
|
|
59
|
+
/**
|
|
60
|
+
* Fill a rectangular region with a color
|
|
61
|
+
* @param data Image data (RGBA)
|
|
62
|
+
* @param width Image width
|
|
63
|
+
* @param height Image height
|
|
64
|
+
* @param x Starting X position
|
|
65
|
+
* @param y Starting Y position
|
|
66
|
+
* @param fillWidth Width of the fill region
|
|
67
|
+
* @param fillHeight Height of the fill region
|
|
68
|
+
* @param r Red component (0-255)
|
|
69
|
+
* @param g Green component (0-255)
|
|
70
|
+
* @param b Blue component (0-255)
|
|
71
|
+
* @param a Alpha component (0-255)
|
|
72
|
+
* @returns Modified image data
|
|
73
|
+
*/
|
|
74
|
+
export declare function fillRect(data: Uint8Array, width: number, height: number, x: number, y: number, fillWidth: number, fillHeight: number, r: number, g: number, b: number, a: number): Uint8Array;
|
|
75
|
+
/**
|
|
76
|
+
* Crop an image to a rectangular region
|
|
77
|
+
* @param data Image data (RGBA)
|
|
78
|
+
* @param width Image width
|
|
79
|
+
* @param height Image height
|
|
80
|
+
* @param x Starting X position
|
|
81
|
+
* @param y Starting Y position
|
|
82
|
+
* @param cropWidth Width of the crop region
|
|
83
|
+
* @param cropHeight Height of the crop region
|
|
84
|
+
* @returns Cropped image data and dimensions
|
|
85
|
+
*/
|
|
86
|
+
export declare function crop(data: Uint8Array, width: number, height: number, x: number, y: number, cropWidth: number, cropHeight: number): {
|
|
87
|
+
data: Uint8Array;
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=image_processing.d.ts.map
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image processing utilities for common operations like compositing,
|
|
3
|
+
* level adjustments, and color manipulations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Composite one image on top of another at a specified position
|
|
7
|
+
* @param base Base image data (RGBA)
|
|
8
|
+
* @param baseWidth Base image width
|
|
9
|
+
* @param baseHeight Base image height
|
|
10
|
+
* @param overlay Overlay image data (RGBA)
|
|
11
|
+
* @param overlayWidth Overlay image width
|
|
12
|
+
* @param overlayHeight Overlay image height
|
|
13
|
+
* @param x X position to place overlay (can be negative)
|
|
14
|
+
* @param y Y position to place overlay (can be negative)
|
|
15
|
+
* @param opacity Opacity of overlay (0-1, default: 1)
|
|
16
|
+
* @returns New image data with overlay composited on base
|
|
17
|
+
*/
|
|
18
|
+
export function composite(base, baseWidth, baseHeight, overlay, overlayWidth, overlayHeight, x, y, opacity = 1) {
|
|
19
|
+
const result = new Uint8Array(base);
|
|
20
|
+
// Clamp opacity to valid range
|
|
21
|
+
const finalOpacity = Math.max(0, Math.min(1, opacity));
|
|
22
|
+
// Calculate the region to composite
|
|
23
|
+
const startX = Math.max(0, x);
|
|
24
|
+
const startY = Math.max(0, y);
|
|
25
|
+
const endX = Math.min(baseWidth, x + overlayWidth);
|
|
26
|
+
const endY = Math.min(baseHeight, y + overlayHeight);
|
|
27
|
+
// Iterate over the overlapping region
|
|
28
|
+
for (let py = startY; py < endY; py++) {
|
|
29
|
+
for (let px = startX; px < endX; px++) {
|
|
30
|
+
// Calculate indices
|
|
31
|
+
const baseIdx = (py * baseWidth + px) * 4;
|
|
32
|
+
const overlayX = px - x;
|
|
33
|
+
const overlayY = py - y;
|
|
34
|
+
const overlayIdx = (overlayY * overlayWidth + overlayX) * 4;
|
|
35
|
+
// Get overlay pixel with opacity
|
|
36
|
+
const overlayR = overlay[overlayIdx];
|
|
37
|
+
const overlayG = overlay[overlayIdx + 1];
|
|
38
|
+
const overlayB = overlay[overlayIdx + 2];
|
|
39
|
+
const overlayA = (overlay[overlayIdx + 3] / 255) * finalOpacity;
|
|
40
|
+
// Get base pixel
|
|
41
|
+
const baseR = result[baseIdx];
|
|
42
|
+
const baseG = result[baseIdx + 1];
|
|
43
|
+
const baseB = result[baseIdx + 2];
|
|
44
|
+
const baseA = result[baseIdx + 3] / 255;
|
|
45
|
+
// Alpha compositing using "over" operation
|
|
46
|
+
const outA = overlayA + baseA * (1 - overlayA);
|
|
47
|
+
if (outA > 0) {
|
|
48
|
+
result[baseIdx] = Math.round((overlayR * overlayA + baseR * baseA * (1 - overlayA)) / outA);
|
|
49
|
+
result[baseIdx + 1] = Math.round((overlayG * overlayA + baseG * baseA * (1 - overlayA)) / outA);
|
|
50
|
+
result[baseIdx + 2] = Math.round((overlayB * overlayA + baseB * baseA * (1 - overlayA)) / outA);
|
|
51
|
+
result[baseIdx + 3] = Math.round(outA * 255);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Adjust brightness of an image
|
|
59
|
+
* @param data Image data (RGBA)
|
|
60
|
+
* @param amount Brightness adjustment (-1 to 1, where 0 is no change)
|
|
61
|
+
* @returns New image data with adjusted brightness
|
|
62
|
+
*/
|
|
63
|
+
export function adjustBrightness(data, amount) {
|
|
64
|
+
const result = new Uint8Array(data.length);
|
|
65
|
+
const adjust = Math.max(-1, Math.min(1, amount)) * 255;
|
|
66
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
67
|
+
result[i] = Math.max(0, Math.min(255, data[i] + adjust)); // R
|
|
68
|
+
result[i + 1] = Math.max(0, Math.min(255, data[i + 1] + adjust)); // G
|
|
69
|
+
result[i + 2] = Math.max(0, Math.min(255, data[i + 2] + adjust)); // B
|
|
70
|
+
result[i + 3] = data[i + 3]; // A
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Adjust contrast of an image
|
|
76
|
+
* @param data Image data (RGBA)
|
|
77
|
+
* @param amount Contrast adjustment (-1 to 1, where 0 is no change)
|
|
78
|
+
* @returns New image data with adjusted contrast
|
|
79
|
+
*/
|
|
80
|
+
export function adjustContrast(data, amount) {
|
|
81
|
+
const result = new Uint8Array(data.length);
|
|
82
|
+
const contrast = Math.max(-1, Math.min(1, amount));
|
|
83
|
+
const factor = (259 * (contrast * 255 + 255)) /
|
|
84
|
+
(255 * (259 - contrast * 255));
|
|
85
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
86
|
+
result[i] = Math.max(0, Math.min(255, factor * (data[i] - 128) + 128)); // R
|
|
87
|
+
result[i + 1] = Math.max(0, Math.min(255, factor * (data[i + 1] - 128) + 128)); // G
|
|
88
|
+
result[i + 2] = Math.max(0, Math.min(255, factor * (data[i + 2] - 128) + 128)); // B
|
|
89
|
+
result[i + 3] = data[i + 3]; // A
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adjust exposure of an image
|
|
95
|
+
* @param data Image data (RGBA)
|
|
96
|
+
* @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
|
|
97
|
+
* @returns New image data with adjusted exposure
|
|
98
|
+
*/
|
|
99
|
+
export function adjustExposure(data, amount) {
|
|
100
|
+
const result = new Uint8Array(data.length);
|
|
101
|
+
const stops = Math.max(-3, Math.min(3, amount));
|
|
102
|
+
const multiplier = Math.pow(2, stops);
|
|
103
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
104
|
+
result[i] = Math.max(0, Math.min(255, data[i] * multiplier)); // R
|
|
105
|
+
result[i + 1] = Math.max(0, Math.min(255, data[i + 1] * multiplier)); // G
|
|
106
|
+
result[i + 2] = Math.max(0, Math.min(255, data[i + 2] * multiplier)); // B
|
|
107
|
+
result[i + 3] = data[i + 3]; // A
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Adjust saturation of an image
|
|
113
|
+
* @param data Image data (RGBA)
|
|
114
|
+
* @param amount Saturation adjustment (-1 to 1, where 0 is no change)
|
|
115
|
+
* @returns New image data with adjusted saturation
|
|
116
|
+
*/
|
|
117
|
+
export function adjustSaturation(data, amount) {
|
|
118
|
+
const result = new Uint8Array(data.length);
|
|
119
|
+
const sat = Math.max(-1, Math.min(1, amount)) + 1; // Convert to 0-2 range
|
|
120
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
121
|
+
const r = data[i];
|
|
122
|
+
const g = data[i + 1];
|
|
123
|
+
const b = data[i + 2];
|
|
124
|
+
// Calculate grayscale value using luminosity method
|
|
125
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
126
|
+
// Interpolate between gray and original color based on saturation
|
|
127
|
+
result[i] = Math.max(0, Math.min(255, gray + (r - gray) * sat));
|
|
128
|
+
result[i + 1] = Math.max(0, Math.min(255, gray + (g - gray) * sat));
|
|
129
|
+
result[i + 2] = Math.max(0, Math.min(255, gray + (b - gray) * sat));
|
|
130
|
+
result[i + 3] = data[i + 3];
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Invert colors of an image
|
|
136
|
+
* @param data Image data (RGBA)
|
|
137
|
+
* @returns New image data with inverted colors
|
|
138
|
+
*/
|
|
139
|
+
export function invert(data) {
|
|
140
|
+
const result = new Uint8Array(data.length);
|
|
141
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
142
|
+
result[i] = 255 - data[i]; // R
|
|
143
|
+
result[i + 1] = 255 - data[i + 1]; // G
|
|
144
|
+
result[i + 2] = 255 - data[i + 2]; // B
|
|
145
|
+
result[i + 3] = data[i + 3]; // A
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Convert image to grayscale
|
|
151
|
+
* @param data Image data (RGBA)
|
|
152
|
+
* @returns New image data in grayscale
|
|
153
|
+
*/
|
|
154
|
+
export function grayscale(data) {
|
|
155
|
+
const result = new Uint8Array(data.length);
|
|
156
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
157
|
+
// Using luminosity method for grayscale conversion
|
|
158
|
+
const gray = Math.round(0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]);
|
|
159
|
+
result[i] = gray; // R
|
|
160
|
+
result[i + 1] = gray; // G
|
|
161
|
+
result[i + 2] = gray; // B
|
|
162
|
+
result[i + 3] = data[i + 3]; // A
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Fill a rectangular region with a color
|
|
168
|
+
* @param data Image data (RGBA)
|
|
169
|
+
* @param width Image width
|
|
170
|
+
* @param height Image height
|
|
171
|
+
* @param x Starting X position
|
|
172
|
+
* @param y Starting Y position
|
|
173
|
+
* @param fillWidth Width of the fill region
|
|
174
|
+
* @param fillHeight Height of the fill region
|
|
175
|
+
* @param r Red component (0-255)
|
|
176
|
+
* @param g Green component (0-255)
|
|
177
|
+
* @param b Blue component (0-255)
|
|
178
|
+
* @param a Alpha component (0-255)
|
|
179
|
+
* @returns Modified image data
|
|
180
|
+
*/
|
|
181
|
+
export function fillRect(data, width, height, x, y, fillWidth, fillHeight, r, g, b, a) {
|
|
182
|
+
const result = new Uint8Array(data);
|
|
183
|
+
// Calculate bounds
|
|
184
|
+
const startX = Math.max(0, x);
|
|
185
|
+
const startY = Math.max(0, y);
|
|
186
|
+
const endX = Math.min(width, x + fillWidth);
|
|
187
|
+
const endY = Math.min(height, y + fillHeight);
|
|
188
|
+
// Fill the region
|
|
189
|
+
for (let py = startY; py < endY; py++) {
|
|
190
|
+
for (let px = startX; px < endX; px++) {
|
|
191
|
+
const idx = (py * width + px) * 4;
|
|
192
|
+
result[idx] = r;
|
|
193
|
+
result[idx + 1] = g;
|
|
194
|
+
result[idx + 2] = b;
|
|
195
|
+
result[idx + 3] = a;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Crop an image to a rectangular region
|
|
202
|
+
* @param data Image data (RGBA)
|
|
203
|
+
* @param width Image width
|
|
204
|
+
* @param height Image height
|
|
205
|
+
* @param x Starting X position
|
|
206
|
+
* @param y Starting Y position
|
|
207
|
+
* @param cropWidth Width of the crop region
|
|
208
|
+
* @param cropHeight Height of the crop region
|
|
209
|
+
* @returns Cropped image data and dimensions
|
|
210
|
+
*/
|
|
211
|
+
export function crop(data, width, height, x, y, cropWidth, cropHeight) {
|
|
212
|
+
// Clamp crop region to image bounds
|
|
213
|
+
const startX = Math.max(0, x);
|
|
214
|
+
const startY = Math.max(0, y);
|
|
215
|
+
const endX = Math.min(width, x + cropWidth);
|
|
216
|
+
const endY = Math.min(height, y + cropHeight);
|
|
217
|
+
const actualWidth = endX - startX;
|
|
218
|
+
const actualHeight = endY - startY;
|
|
219
|
+
const result = new Uint8Array(actualWidth * actualHeight * 4);
|
|
220
|
+
for (let py = 0; py < actualHeight; py++) {
|
|
221
|
+
for (let px = 0; px < actualWidth; px++) {
|
|
222
|
+
const srcIdx = ((startY + py) * width + (startX + px)) * 4;
|
|
223
|
+
const dstIdx = (py * actualWidth + px) * 4;
|
|
224
|
+
result[dstIdx] = data[srcIdx];
|
|
225
|
+
result[dstIdx + 1] = data[srcIdx + 1];
|
|
226
|
+
result[dstIdx + 2] = data[srcIdx + 2];
|
|
227
|
+
result[dstIdx + 3] = data[srcIdx + 3];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { data: result, width: actualWidth, height: actualHeight };
|
|
231
|
+
}
|