mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 05:36:18 +01:00
Add toUint8Array for output backed by transferable ArrayBuffer #4355
This commit is contained in:
@@ -117,6 +117,38 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
```
|
||||
|
||||
|
||||
## toUint8Array
|
||||
> toUint8Array() ⇒ <code>Promise.<{data: Uint8Array, info: Object}></code>
|
||||
|
||||
Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
|
||||
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||
|
||||
Use [toFormat](#toformat) or one of the format-specific functions such as [jpeg](#jpeg), [png](#png) etc. to set the output format.
|
||||
|
||||
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
|
||||
|
||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
See [keepExif](#keepexif) and similar methods for control over this.
|
||||
|
||||
Resolves with an `Object` containing:
|
||||
- `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
|
||||
- `info` contains properties relating to the output image such as `width` and `height`.
|
||||
|
||||
|
||||
**Since**: v0.35.0
|
||||
**Example**
|
||||
```js
|
||||
const { data, info } = await sharp(input).toUint8Array();
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
const { data } = await sharp(input)
|
||||
.avif()
|
||||
.toUint8Array();
|
||||
const base64String = data.toBase64();
|
||||
```
|
||||
|
||||
|
||||
## keepExif
|
||||
> keepExif() ⇒ <code>Sharp</code>
|
||||
|
||||
|
||||
@@ -12,3 +12,6 @@ slug: changelog/v0.35.0
|
||||
|
||||
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
|
||||
[#4314](https://github.com/lovell/sharp/issues/4314)
|
||||
|
||||
* Add `toUint8Array` for output image as a `TypedArray` backed by a transferable `ArrayBuffer`.
|
||||
[#4355](https://github.com/lovell/sharp/issues/4355)
|
||||
|
||||
@@ -306,6 +306,7 @@ const Sharp = function (input, options) {
|
||||
fileOut: '',
|
||||
formatOut: 'input',
|
||||
streamOut: false,
|
||||
typedArrayOut: false,
|
||||
keepMetadata: 0,
|
||||
withMetadataOrientation: -1,
|
||||
withMetadataDensity: 0,
|
||||
|
||||
7
lib/index.d.ts
vendored
7
lib/index.d.ts
vendored
@@ -693,6 +693,13 @@ declare namespace sharp {
|
||||
*/
|
||||
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
|
||||
|
||||
/**
|
||||
* Write output to a Uint8Array backed by a transferable ArrayBuffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and RAW output are supported.
|
||||
* By default, the format will match the input image, except SVG input which becomes PNG output.
|
||||
* @returns A promise that resolves with an object containing the Uint8Array data and an info object containing the output image format, size (bytes), width, height and channels
|
||||
*/
|
||||
toUint8Array(): Promise<{ data: Uint8Array; info: OutputInfo }>;
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
|
||||
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
|
||||
return this._pipeline(is.fn(options) ? options : callback, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
|
||||
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||
*
|
||||
* Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
|
||||
*
|
||||
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
|
||||
*
|
||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
* See {@link #keepexif keepExif} and similar methods for control over this.
|
||||
*
|
||||
* Resolves with an `Object` containing:
|
||||
* - `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
|
||||
* - `info` contains properties relating to the output image such as `width` and `height`.
|
||||
*
|
||||
* @since v0.35.0
|
||||
*
|
||||
* @example
|
||||
* const { data, info } = await sharp(input).toUint8Array();
|
||||
*
|
||||
* @example
|
||||
* const { data } = await sharp(input)
|
||||
* .avif()
|
||||
* .toUint8Array();
|
||||
* const base64String = data.toBase64();
|
||||
*
|
||||
* @returns {Promise<{ data: Uint8Array, info: Object }>}
|
||||
*/
|
||||
function toUint8Array () {
|
||||
this.options.resolveWithObject = true;
|
||||
this.options.typedArrayOut = true;
|
||||
const stack = Error();
|
||||
return this._pipeline(null, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
*
|
||||
@@ -1659,6 +1694,7 @@ module.exports = (Sharp) => {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
toUint8Array,
|
||||
keepExif,
|
||||
withExif,
|
||||
withExifMerge,
|
||||
|
||||
@@ -1345,12 +1345,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
if (baton->bufferOutLength > 0) {
|
||||
// Add buffer size to info
|
||||
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
||||
// Pass ownership of output data to Buffer instance
|
||||
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
||||
baton->bufferOutLength, sharp::FreeCallback);
|
||||
Callback().Call(Receiver().Value(), { env.Null(), data, info });
|
||||
if (baton->typedArrayOut) {
|
||||
// ECMAScript ArrayBuffer with Uint8Array view
|
||||
Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
|
||||
memcpy(ab.Data(), baton->bufferOut, baton->bufferOutLength);
|
||||
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
|
||||
Napi::TypedArrayOf<uint8_t> data = Napi::TypedArrayOf<uint8_t>::New(env,
|
||||
baton->bufferOutLength, ab, 0, napi_uint8_array);
|
||||
Callback().Call(Receiver().Value(), { env.Null(), data, info });
|
||||
} else {
|
||||
// Node.js Buffer
|
||||
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
||||
baton->bufferOutLength, sharp::FreeCallback);
|
||||
Callback().Call(Receiver().Value(), { env.Null(), data, info });
|
||||
}
|
||||
} else {
|
||||
// Add file size to info
|
||||
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
||||
@@ -1700,6 +1709,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
// Output
|
||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
|
||||
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
||||
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
||||
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
||||
|
||||
@@ -48,6 +48,7 @@ struct PipelineBaton {
|
||||
size_t bufferOutLength;
|
||||
int pageHeightOut;
|
||||
int pagesOut;
|
||||
bool typedArrayOut;
|
||||
std::vector<Composite *> composite;
|
||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||
int topOffsetPre;
|
||||
@@ -243,6 +244,7 @@ struct PipelineBaton {
|
||||
bufferOutLength(0),
|
||||
pageHeightOut(0),
|
||||
pagesOut(0),
|
||||
typedArrayOut(false),
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
channels(0),
|
||||
|
||||
@@ -228,6 +228,19 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-buffer-uint8array', {
|
||||
defer: true,
|
||||
fn: (deferred) => {
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.toUint8Array()
|
||||
.then(() => {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: (deferred) => {
|
||||
@@ -266,6 +279,19 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-uint8array', {
|
||||
defer: true,
|
||||
fn: (deferred) => {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(width, height)
|
||||
.toUint8Array()
|
||||
.then(() => {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}).add('sharp-promise', {
|
||||
defer: true,
|
||||
fn: (deferred) => {
|
||||
|
||||
@@ -86,6 +86,9 @@ let transformer = sharp()
|
||||
});
|
||||
readableStream.pipe(transformer).pipe(writableStream);
|
||||
|
||||
sharp().toUint8Array();
|
||||
sharp().toUint8Array().then(({ data }) => data.byteLength);
|
||||
|
||||
console.log(sharp.format);
|
||||
console.log(sharp.versions);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { afterEach, beforeEach, describe, it } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { isMarkedAsUntransferable } = require('node:worker_threads');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
@@ -1092,4 +1093,39 @@ describe('Input/output', () => {
|
||||
assert.strictEqual(channels, 3);
|
||||
assert.strictEqual(format, 'jpeg');
|
||||
});
|
||||
|
||||
it('toBuffer resolves with an untransferable Buffer', async () => {
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize({ width: 8, height: 8 })
|
||||
.toBuffer();
|
||||
|
||||
if (isMarkedAsUntransferable) {
|
||||
assert.strictEqual(isMarkedAsUntransferable(data.buffer), true);
|
||||
}
|
||||
assert.strictEqual(ArrayBuffer.isView(data), true);
|
||||
assert.strictEqual(ArrayBuffer.isView(data.buffer), false);
|
||||
});
|
||||
|
||||
it('toUint8Array resolves with a transferable Uint8Array', async () => {
|
||||
const { data, info } = await sharp(fixtures.inputJpg)
|
||||
.resize({ width: 8, height: 8 })
|
||||
.toUint8Array();
|
||||
|
||||
assert.strictEqual(data instanceof Uint8Array, true);
|
||||
if (isMarkedAsUntransferable) {
|
||||
assert.strictEqual(isMarkedAsUntransferable(data.buffer), false);
|
||||
}
|
||||
assert.strictEqual(ArrayBuffer.isView(data), true);
|
||||
assert.strictEqual(info.format, 'jpeg');
|
||||
assert.strictEqual(info.width, 8);
|
||||
assert.strictEqual(info.height, 8);
|
||||
assert.strictEqual(data.byteLength, info.size);
|
||||
assert.strictEqual(data[0], 0xFF);
|
||||
assert.strictEqual(data[1], 0xD8);
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual(metadata.format, 'jpeg');
|
||||
assert.strictEqual(metadata.width, 8);
|
||||
assert.strictEqual(metadata.height, 8);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user