diff --git a/package.json b/package.json index 0e5077c1e..4661e10be 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "rimraf": "^6.0.1", "tailwindcss": "^3.4.17", "typescript": "~5.7.3", + "uint8-base64": "^1.0.0", "vite": "^6.0.8", "vitest": "^2.1.8" } diff --git a/src/save/__tests__/__snapshots__/encodeDataURL.test.ts.snap b/src/save/__tests__/__snapshots__/encodeDataURL.test.ts.snap new file mode 100644 index 000000000..93dacbe31 --- /dev/null +++ b/src/save/__tests__/__snapshots__/encodeDataURL.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`basic image 2 (jpeg) 1`] = `"/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2MBERISGBUYLxoaL2NCOEJjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY//AABEIAAUABQMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOu0zTbmyvL+efUZbpLmTfHE+cQDLHaMk+oHbpQB/9k="`; diff --git a/src/save/__tests__/encodeDataURL.test.ts b/src/save/__tests__/encodeDataURL.test.ts new file mode 100644 index 000000000..d21ea4a93 --- /dev/null +++ b/src/save/__tests__/encodeDataURL.test.ts @@ -0,0 +1,47 @@ +import { encode } from '../encode.js'; +import { encodeDataURL } from '../encodeDataURL.js'; + +test('basic image (png)', () => { + const image = testUtils.createGreyImage([ + [0, 0, 0, 0, 0], + [0, 255, 255, 255, 0], + [0, 255, 0, 255, 0], + [0, 255, 255, 255, 0], + [255, 0, 255, 0, 255], + ]); + const base64Url = encodeDataURL(image); + + expect(base64Url).toBe( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAAAAACoBHk5AAAAFklEQVR4XmNggID///+DSCCEskHM/wCAnQr2TY5mOAAAAABJRU5ErkJggg==', + ); +}); + +test('basic image 2 (jpeg)', () => { + const image = testUtils.createGreyImage([ + [255, 255, 255, 255, 255], + [255, 0, 0, 0, 255], + [255, 0, 0, 0, 255], + [255, 0, 0, 0, 255], + [255, 255, 255, 255, 255], + ]); + const format = 'jpeg'; + const base64 = encodeDataURL(image, { format }); + const base64Data = Buffer.from(encode(image, { format })).toString('base64'); + expect(typeof base64).toBe('string'); + expect(base64Data).toMatchSnapshot(); +}); + +test('legacy image-js test', () => { + const image = testUtils.createRgbaImage( + ` + 255 0 0 / 255 | 0 255 0 / 255 | 0 0 255 / 255 + 255 255 0 / 255 | 255 0 255 / 255 | 0 255 255 / 255 + 0 0 0 / 255 | 255 255 255 / 255 | 127 127 127 / 255 + `, + ); + const format = 'jpeg'; + const url = encodeDataURL(image, { format }); + const base64Data = Buffer.from(encode(image, { format })).toString('base64'); + expect(typeof url).toBe('string'); + expect(base64Data).toBe(url.slice(url.indexOf(',') + 1)); +}); diff --git a/src/save/encode.ts b/src/save/encode.ts index 3f99f3b6c..3bd5e769c 100644 --- a/src/save/encode.ts +++ b/src/save/encode.ts @@ -29,7 +29,7 @@ export interface EncodeOptionsJpeg { export interface EncodeOptionsBmp { format: 'bmp'; } -const defaultPng: EncodeOptionsPng = { format: 'png' }; +export const defaultPng: EncodeOptionsPng = { format: 'png' }; /** * Encodes the image to the specified format diff --git a/src/save/encodeDataURL.ts b/src/save/encodeDataURL.ts new file mode 100644 index 000000000..013002322 --- /dev/null +++ b/src/save/encodeDataURL.ts @@ -0,0 +1,25 @@ +import { encode as uint8encode } from 'uint8-base64'; + +import type { Image } from '../Image.js'; + +import type { + EncodeOptionsBmp, + EncodeOptionsJpeg, + EncodeOptionsPng, +} from './encode.js'; +import { encode, defaultPng } from './encode.js'; +/** + * Converts image into Data URL string. + * @param image - Image to get base64 encoding from. + * @param options - Encoding options. + * @returns base64 string. + */ +export function encodeDataURL( + image: Image, + options: EncodeOptionsBmp | EncodeOptionsPng | EncodeOptionsJpeg = defaultPng, +) { + const buffer = encode(image, options); + const base64 = uint8encode(buffer); + const base64Data = new TextDecoder().decode(base64); + return `data:image/${options.format};base64,${base64Data}`; +} diff --git a/src/save/index.ts b/src/save/index.ts index b2a09f6fe..c9572c576 100644 --- a/src/save/index.ts +++ b/src/save/index.ts @@ -3,3 +3,4 @@ export * from './encodePng.js'; export * from './encodeJpeg.js'; export * from './write.js'; export * from './writeCanvas.js'; +export * from './encodeDataURL.js';