Skip to content

Commit 0767604

Browse files
mtuchiclaude
andcommitted
image-utils: replace toBase64/toBuffer with base64 option on each function
Remove standalone toBase64/toBuffer operations. All image functions (resize, compress, strip) now accept a base64: true option — when set the output buffer key is replaced with a base64 string. Uses buffer.toString('base64') directly since util.encode is text/JSON- oriented and not suitable for binary image data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4068de4 commit 0767604

2 files changed

Lines changed: 71 additions & 101 deletions

File tree

packages/image-utils/src/Adaptor.js

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ function resolveInput(raw) {
1212
return Buffer.isBuffer(raw) ? raw : decodeBase64Image(raw);
1313
}
1414

15+
// util.encode from language-common is text/JSON-oriented (utf-8 encoding).
16+
// For binary image buffers we encode directly with buffer.toString('base64').
17+
function applyBase64Option(result, encode) {
18+
if (!encode) return result;
19+
const { buffer, ...rest } = result;
20+
return { ...rest, base64: buffer.toString('base64') };
21+
}
22+
1523
/**
1624
* State object
1725
* @typedef {Object} ImageState
@@ -30,14 +38,15 @@ function resolveInput(raw) {
3038
* @param {object} [options={}]
3139
* @param {number} [options.width=1200] - Output width in pixels
3240
* @param {number} [options.height=1600] - Output height in pixels
41+
* @param {boolean} [options.base64=false] - When true, returns `{ base64, width, height }` instead of `{ buffer, width, height }`
3342
* @state {ImageState}
3443
* @returns {Operation}
3544
*/
3645
export function resize(base64ImgOrBuffer, options = {}) {
3746
return async state => {
3847
const [resolvedImg, resolvedOptions] = expandReferences(state, base64ImgOrBuffer, options);
3948
const result = await resizeImage(resolveInput(resolvedImg), resolvedOptions);
40-
return composeNextState(state, result);
49+
return composeNextState(state, applyBase64Option(result, resolvedOptions.base64));
4150
};
4251
}
4352

@@ -53,14 +62,15 @@ export function resize(base64ImgOrBuffer, options = {}) {
5362
* @param {number} [options.maxBytes=716800] - Maximum output file size in bytes
5463
* @param {number} [options.minQuality=20] - JPEG quality floor (1–100); compression stops here even if maxBytes is not met
5564
* @param {string} [options.comment] - String to embed in the EXIF UserComment field
65+
* @param {boolean} [options.base64=false] - When true, returns `{ base64, size, quality }` instead of `{ buffer, size, quality }`
5666
* @state {ImageState}
5767
* @returns {Operation}
5868
*/
5969
export function compress(base64ImgOrBuffer, options = {}) {
6070
return async state => {
6171
const [resolvedImg, resolvedOptions] = expandReferences(state, base64ImgOrBuffer, options);
6272
const result = await compressImage(resolveInput(resolvedImg), resolvedOptions);
63-
return composeNextState(state, result);
73+
return composeNextState(state, applyBase64Option(result, resolvedOptions.base64));
6474
};
6575
}
6676

@@ -71,14 +81,16 @@ export function compress(base64ImgOrBuffer, options = {}) {
7181
* strip(state.data.photoBase64)
7282
* @function
7383
* @param {string|Buffer|Function} base64ImgOrBuffer - Base64 string, data URL, Buffer, or resolver fn
84+
* @param {object} [options={}]
85+
* @param {boolean} [options.base64=false] - When true, returns `{ base64 }` instead of `{ buffer }`
7486
* @state {ImageState}
7587
* @returns {Operation}
7688
*/
77-
export function strip(base64ImgOrBuffer) {
89+
export function strip(base64ImgOrBuffer, options = {}) {
7890
return async state => {
79-
const [resolvedImg] = expandReferences(state, base64ImgOrBuffer);
91+
const [resolvedImg, resolvedOptions] = expandReferences(state, base64ImgOrBuffer, options);
8092
const result = await stripImage(resolveInput(resolvedImg));
81-
return composeNextState(state, result);
93+
return composeNextState(state, applyBase64Option(result, resolvedOptions.base64));
8294
};
8395
}
8496

@@ -105,42 +117,6 @@ export function metadata(base64ImgOrBuffer) {
105117
};
106118
}
107119

108-
/**
109-
* Convert a Buffer to a base64 string.
110-
* Writes the base64 string to `state.data`.
111-
* @example
112-
* toBase64(state.data.buffer)
113-
* @function
114-
* @param {Buffer|Function} bufferOrRef - Buffer or resolver fn
115-
* @state {ImageState}
116-
* @returns {Operation}
117-
*/
118-
export function toBase64(bufferOrRef) {
119-
return async state => {
120-
const [resolved] = expandReferences(state, bufferOrRef);
121-
const base64 = Buffer.isBuffer(resolved) ? resolved.toString('base64') : resolved;
122-
return composeNextState(state, base64);
123-
};
124-
}
125-
126-
/**
127-
* Convert a base64 string or data URL to a Buffer.
128-
* Writes the Buffer to `state.data`.
129-
* @example
130-
* toBuffer(state.data.photoBase64)
131-
* @function
132-
* @param {string|Function} base64OrRef - Base64 string, data URL, or resolver fn
133-
* @state {ImageState}
134-
* @returns {Operation}
135-
*/
136-
export function toBuffer(base64OrRef) {
137-
return async state => {
138-
const [resolved] = expandReferences(state, base64OrRef);
139-
const buffer = Buffer.isBuffer(resolved) ? resolved : decodeBase64Image(resolved);
140-
return composeNextState(state, buffer);
141-
};
142-
}
143-
144120
export {
145121
as,
146122
combine,

packages/image-utils/test/Adaptor.test.js

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { expect } from 'chai';
55
import { Jimp } from 'jimp';
66
import piexif from 'piexifjs';
77

8-
import { resize, compress, strip, metadata, toBase64, toBuffer } from '../src/Adaptor.js';
8+
import { resize, compress, strip, metadata } from '../src/Adaptor.js';
99
import { resizeImage } from '../src/Utils.js';
1010

1111
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -148,6 +148,19 @@ describe('resize', () => {
148148
expect(finalState.references[0]).to.deep.equal(seed.data);
149149
});
150150
});
151+
152+
describe('base64 option', () => {
153+
it('returns { base64, width, height } when base64: true', async () => {
154+
const finalState = await resize(
155+
toBase64Str('portrait-small.jpg'),
156+
{ ...DEFAULT_RESIZE_OPTS, base64: true },
157+
)(state);
158+
159+
expect(finalState.data).to.have.keys(['base64', 'width', 'height']);
160+
expect(finalState.data.base64).to.be.a('string');
161+
expect(finalState.data.base64.length).to.be.greaterThan(0);
162+
});
163+
});
151164
});
152165

153166
// ─── compress ────────────────────────────────────────────────────────────────
@@ -272,6 +285,19 @@ describe('compress', () => {
272285
expect(finalState.references[0]).to.deep.equal(seed.data);
273286
});
274287
});
288+
289+
describe('base64 option', () => {
290+
it('returns { base64, size, quality } when base64: true', async () => {
291+
const inputBuffer = await getResizedBuffer('portrait-small.jpg');
292+
const finalState = await compress(
293+
inputBuffer,
294+
{ ...DEFAULT_COMPRESS_OPTS, base64: true },
295+
)(state);
296+
297+
expect(finalState.data).to.have.keys(['base64', 'size', 'quality']);
298+
expect(finalState.data.base64).to.be.a('string');
299+
});
300+
});
275301
});
276302

277303
// ─── strip ───────────────────────────────────────────────────────────────────
@@ -329,6 +355,13 @@ describe('strip', () => {
329355
expect(finalState.data).to.have.keys(['buffer']);
330356
expect(finalState.data.buffer).to.be.instanceOf(Buffer);
331357
});
358+
359+
it('returns { base64 } when base64: true', async () => {
360+
const finalState = await strip(toBase64Str('portrait-small.jpg'), { base64: true })(state);
361+
362+
expect(finalState.data).to.have.keys(['base64']);
363+
expect(finalState.data.base64).to.be.a('string');
364+
});
332365
});
333366

334367
// ─── metadata ────────────────────────────────────────────────────────────────
@@ -384,73 +417,34 @@ describe('metadata', () => {
384417
});
385418
});
386419

387-
// ─── toBase64 / toBuffer ─────────────────────────────────────────────────────
420+
// ─── buffer passing between steps ────────────────────────────────────────────
388421

389-
describe('toBase64', () => {
390-
it('converts a Buffer to a base64 string', async () => {
391-
const buf = toBuf('portrait-small.jpg');
392-
const finalState = await toBase64(buf)(state);
393-
394-
expect(finalState.data).to.be.a('string');
395-
expect(finalState.data).to.equal(buf.toString('base64'));
396-
});
397-
398-
it('writes the base64 string to state.data', async () => {
399-
const buf = toBuf('portrait-small.jpg');
400-
const finalState = await toBase64(buf)(state);
401-
402-
expect(finalState.data).to.be.a('string');
403-
expect(finalState.data.length).to.be.greaterThan(0);
404-
});
405-
406-
it('is a no-op when given a string (already base64)', async () => {
407-
const b64 = toBase64Str('portrait-small.jpg');
408-
const finalState = await toBase64(b64)(state);
409-
410-
expect(finalState.data).to.equal(b64);
411-
});
412-
});
413-
414-
describe('toBuffer', () => {
415-
it('converts a base64 string to a Buffer', async () => {
416-
const b64 = toBase64Str('portrait-small.jpg');
417-
const finalState = await toBuffer(b64)(state);
418-
419-
expect(finalState.data).to.be.instanceOf(Buffer);
420-
expect(finalState.data).to.deep.equal(toBuf('portrait-small.jpg'));
421-
});
422-
423-
it('converts a data URL to a Buffer', async () => {
424-
const dataUrl = `data:image/jpeg;base64,${toBase64Str('portrait-small.jpg')}`;
425-
const finalState = await toBuffer(dataUrl)(state);
426-
427-
expect(finalState.data).to.be.instanceOf(Buffer);
428-
expect(finalState.data).to.deep.equal(toBuf('portrait-small.jpg'));
429-
});
430-
431-
it('is a no-op when given a Buffer', async () => {
432-
const buf = toBuf('portrait-small.jpg');
433-
const finalState = await toBuffer(buf)(state);
422+
describe('buffer passing between steps', () => {
423+
it('passes buffer from resize() to compress() explicitly', async () => {
424+
const resizeState = await resize(toBase64Str('portrait-large.jpg'), DEFAULT_RESIZE_OPTS)(state);
425+
const compressState = await compress(resizeState.data.buffer, DEFAULT_COMPRESS_OPTS)(resizeState);
434426

435-
expect(finalState.data).to.deep.equal(buf);
427+
expect(compressState.data.size).to.be.at.most(DEFAULT_COMPRESS_OPTS.maxBytes);
428+
expect(compressState.data.buffer).to.be.instanceOf(Buffer);
436429
});
437430

438-
it('round-trips a buffer through base64 without data loss', async () => {
439-
const resizeState = await resize(toBase64Str('portrait-small.jpg'), DEFAULT_RESIZE_OPTS)(state);
440-
const originalBuffer = resizeState.data.buffer;
431+
it('passes buffer from resize() to strip() explicitly', async () => {
432+
const resizeState = await resize(toBase64Str('with-gps.JPG'), DEFAULT_RESIZE_OPTS)(state);
433+
const stripState = await strip(resizeState.data.buffer)(resizeState);
441434

442-
const b64State = await toBase64(originalBuffer)(resizeState);
443-
expect(b64State.data).to.be.a('string');
444-
445-
const bufState = await toBuffer(b64State.data)(b64State);
446-
expect(bufState.data).to.deep.equal(originalBuffer);
435+
expect(stripState.data.buffer).to.be.instanceOf(Buffer);
436+
expect(stripState.data.buffer[0]).to.equal(0xff);
437+
expect(stripState.data.buffer[1]).to.equal(0xd8);
447438
});
448439

449-
it('passes a buffer from resize() to compress() as an explicit argument', async () => {
450-
const resizeState = await resize(toBase64Str('portrait-large.jpg'), DEFAULT_RESIZE_OPTS)(state);
451-
const compressState = await compress(resizeState.data.buffer, DEFAULT_COMPRESS_OPTS)(resizeState);
440+
it('base64 output from resize() can be fed directly into compress()', async () => {
441+
const resizeState = await resize(
442+
toBase64Str('portrait-large.jpg'),
443+
{ ...DEFAULT_RESIZE_OPTS, base64: true },
444+
)(state);
445+
expect(resizeState.data.base64).to.be.a('string');
452446

447+
const compressState = await compress(resizeState.data.base64, DEFAULT_COMPRESS_OPTS)(resizeState);
453448
expect(compressState.data.size).to.be.at.most(DEFAULT_COMPRESS_OPTS.maxBytes);
454-
expect(compressState.data.buffer).to.be.instanceOf(Buffer);
455449
});
456450
});

0 commit comments

Comments
 (0)