mirror of
https://github.com/lovell/sharp.git
synced 2026-02-08 15:46:16 +01:00
Add toArrayBuffer to generate transferable output #4355
This commit is contained in:
@@ -117,6 +117,42 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
```
|
||||
|
||||
|
||||
## toArrayBuffer
|
||||
> toArrayBuffer() ⇒ <code>Promise.<{data: ArrayBuffer, info: Object}></code>
|
||||
|
||||
Write output to 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 transferable `ArrayBuffer`.
|
||||
- `info` contains properties relating to the output image such as `width` and `height`.
|
||||
|
||||
This method does not work with the Electron V8 memory cage
|
||||
and will reject with an error if used in that environment.
|
||||
Use [toBuffer](#tobuffer) instead.
|
||||
|
||||
|
||||
**Since**: v0.35.0
|
||||
**Example**
|
||||
```js
|
||||
const { data, info } = await sharp(input).toArrayBuffer();
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
const { data } = await sharp(input)
|
||||
.avif()
|
||||
.toArrayBuffer();
|
||||
const uint8Array = new Uint8Array(data);
|
||||
```
|
||||
|
||||
|
||||
## 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 `toArrayBuffer` to generate output image as 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,
|
||||
sharedOut: true,
|
||||
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 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 ArrayBuffer data and an info object containing the output image format, size (bytes), width, height and channels
|
||||
*/
|
||||
toArrayBuffer(): Promise<{ data: ArrayBuffer; info: OutputInfo }>;
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
|
||||
@@ -164,6 +164,45 @@ function toBuffer (options, callback) {
|
||||
return this._pipeline(is.fn(options) ? options : callback, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write output to 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 transferable `ArrayBuffer`.
|
||||
* - `info` contains properties relating to the output image such as `width` and `height`.
|
||||
*
|
||||
* This method does not work with the Electron V8 memory cage
|
||||
* and will reject with an error if used in that environment.
|
||||
* Use {@link #tobuffer toBuffer} instead.
|
||||
*
|
||||
* @since v0.35.0
|
||||
*
|
||||
* @example
|
||||
* const { data, info } = await sharp(input).toArrayBuffer();
|
||||
*
|
||||
* @example
|
||||
* const { data } = await sharp(input)
|
||||
* .avif()
|
||||
* .toArrayBuffer();
|
||||
* const uint8Array = new Uint8Array(data);
|
||||
*
|
||||
* @returns {Promise<{ data: ArrayBuffer, info: Object }>}
|
||||
*/
|
||||
function toArrayBuffer () {
|
||||
this.options.resolveWithObject = true;
|
||||
this.options.sharedOut = true;
|
||||
const stack = Error();
|
||||
return this._pipeline(null, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
*
|
||||
@@ -1659,6 +1698,7 @@ module.exports = (Sharp) => {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
toArrayBuffer,
|
||||
keepExif,
|
||||
withExif,
|
||||
withExifMerge,
|
||||
|
||||
@@ -786,7 +786,7 @@ namespace sharp {
|
||||
/*
|
||||
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
|
||||
*/
|
||||
std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
|
||||
std::function<void(void*, void*)> FreeCallback = [](void*, void* data) {
|
||||
g_free(data);
|
||||
};
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ namespace sharp {
|
||||
/*
|
||||
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
|
||||
*/
|
||||
extern std::function<void(void*, char*)> FreeCallback;
|
||||
extern std::function<void(void*, void*)> FreeCallback;
|
||||
|
||||
/*
|
||||
Called with warnings from the glib-registered "VIPS" domain
|
||||
|
||||
@@ -1345,12 +1345,20 @@ 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->sharedOut) {
|
||||
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 {
|
||||
try {
|
||||
Napi::ArrayBuffer data = Napi::ArrayBuffer::New(env, static_cast<char*>(baton->bufferOut),
|
||||
baton->bufferOutLength, sharp::FreeCallback);
|
||||
Callback().Call(Receiver().Value(), { env.Null(), data, info });
|
||||
} catch (const Napi::Error& e) {
|
||||
Callback().Call(Receiver().Value(), { e.Value() });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add file size to info
|
||||
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
||||
@@ -1700,6 +1708,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
// Output
|
||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||
baton->sharedOut = sharp::AttrAsBool(options, "sharedOut");
|
||||
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 sharedOut;
|
||||
std::vector<Composite *> composite;
|
||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||
int topOffsetPre;
|
||||
@@ -243,6 +244,7 @@ struct PipelineBaton {
|
||||
bufferOutLength(0),
|
||||
pageHeightOut(0),
|
||||
pagesOut(0),
|
||||
sharedOut(true),
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
channels(0),
|
||||
|
||||
@@ -86,6 +86,10 @@ let transformer = sharp()
|
||||
});
|
||||
readableStream.pipe(transformer).pipe(writableStream);
|
||||
|
||||
sharp().toArrayBuffer();
|
||||
sharp().toArrayBuffer().then(({ data }) => data);
|
||||
sharp().toArrayBuffer().then(({ data }) => new Uint8Array(data));
|
||||
|
||||
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,36 @@ 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('toArrayBuffer resolves with a transferable ArrayBuffer', async () => {
|
||||
const { data, info } = await sharp(fixtures.inputJpg)
|
||||
.resize({ width: 8, height: 8 })
|
||||
.toArrayBuffer();
|
||||
|
||||
if (isMarkedAsUntransferable) {
|
||||
assert.strictEqual(isMarkedAsUntransferable(data), false);
|
||||
}
|
||||
assert.strictEqual(ArrayBuffer.isView(data), true);
|
||||
assert.strictEqual(info.format, 'jpeg');
|
||||
assert.strictEqual(info.width, 8);
|
||||
assert.strictEqual(info.height, 8);
|
||||
|
||||
const uint8Array = new Uint8Array(data);
|
||||
assert.strictEqual(ArrayBuffer.isView(uint8Array), true);
|
||||
assert.strictEqual(uint8Array.length, info.size);
|
||||
assert.strictEqual(uint8Array[0], 0xFF);
|
||||
assert.strictEqual(uint8Array[1], 0xD8);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user