mirror of
https://github.com/lovell/sharp.git
synced 2026-02-07 15:16:17 +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
|
||||||
> keepExif() ⇒ <code>Sharp</code>
|
> keepExif() ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
|||||||
@@ -12,3 +12,6 @@ slug: changelog/v0.35.0
|
|||||||
|
|
||||||
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
|
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
|
||||||
[#4314](https://github.com/lovell/sharp/issues/4314)
|
[#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: '',
|
fileOut: '',
|
||||||
formatOut: 'input',
|
formatOut: 'input',
|
||||||
streamOut: false,
|
streamOut: false,
|
||||||
|
typedArrayOut: false,
|
||||||
keepMetadata: 0,
|
keepMetadata: 0,
|
||||||
withMetadataOrientation: -1,
|
withMetadataOrientation: -1,
|
||||||
withMetadataDensity: 0,
|
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 }>;
|
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.
|
* Keep all EXIF metadata from the input image in the output image.
|
||||||
* EXIF metadata is unsupported for TIFF output.
|
* EXIF metadata is unsupported for TIFF output.
|
||||||
|
|||||||
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
|
|||||||
return this._pipeline(is.fn(options) ? options : callback, stack);
|
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.
|
* Keep all EXIF metadata from the input image in the output image.
|
||||||
*
|
*
|
||||||
@@ -1659,6 +1694,7 @@ module.exports = (Sharp) => {
|
|||||||
// Public
|
// Public
|
||||||
toFile,
|
toFile,
|
||||||
toBuffer,
|
toBuffer,
|
||||||
|
toUint8Array,
|
||||||
keepExif,
|
keepExif,
|
||||||
withExif,
|
withExif,
|
||||||
withExifMerge,
|
withExifMerge,
|
||||||
|
|||||||
@@ -1345,12 +1345,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (baton->bufferOutLength > 0) {
|
if (baton->bufferOutLength > 0) {
|
||||||
// Add buffer size to info
|
|
||||||
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
||||||
// Pass ownership of output data to Buffer instance
|
if (baton->typedArrayOut) {
|
||||||
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
// ECMAScript ArrayBuffer with Uint8Array view
|
||||||
baton->bufferOutLength, sharp::FreeCallback);
|
Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
|
||||||
Callback().Call(Receiver().Value(), { env.Null(), data, info });
|
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 {
|
} else {
|
||||||
// Add file size to info
|
// Add file size to info
|
||||||
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
||||||
@@ -1700,6 +1709,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
// Output
|
// Output
|
||||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||||
|
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
|
||||||
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
||||||
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
||||||
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ struct PipelineBaton {
|
|||||||
size_t bufferOutLength;
|
size_t bufferOutLength;
|
||||||
int pageHeightOut;
|
int pageHeightOut;
|
||||||
int pagesOut;
|
int pagesOut;
|
||||||
|
bool typedArrayOut;
|
||||||
std::vector<Composite *> composite;
|
std::vector<Composite *> composite;
|
||||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||||
int topOffsetPre;
|
int topOffsetPre;
|
||||||
@@ -243,6 +244,7 @@ struct PipelineBaton {
|
|||||||
bufferOutLength(0),
|
bufferOutLength(0),
|
||||||
pageHeightOut(0),
|
pageHeightOut(0),
|
||||||
pagesOut(0),
|
pagesOut(0),
|
||||||
|
typedArrayOut(false),
|
||||||
topOffsetPre(-1),
|
topOffsetPre(-1),
|
||||||
topOffsetPost(-1),
|
topOffsetPost(-1),
|
||||||
channels(0),
|
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', {
|
}).add('sharp-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: (deferred) => {
|
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', {
|
}).add('sharp-promise', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: (deferred) => {
|
fn: (deferred) => {
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ let transformer = sharp()
|
|||||||
});
|
});
|
||||||
readableStream.pipe(transformer).pipe(writableStream);
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
|
|
||||||
|
sharp().toUint8Array();
|
||||||
|
sharp().toUint8Array().then(({ data }) => data.byteLength);
|
||||||
|
|
||||||
console.log(sharp.format);
|
console.log(sharp.format);
|
||||||
console.log(sharp.versions);
|
console.log(sharp.versions);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const fs = require('node:fs');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { afterEach, beforeEach, describe, it } = require('node:test');
|
const { afterEach, beforeEach, describe, it } = require('node:test');
|
||||||
const assert = require('node:assert');
|
const assert = require('node:assert');
|
||||||
|
const { isMarkedAsUntransferable } = require('node:worker_threads');
|
||||||
|
|
||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
const fixtures = require('../fixtures');
|
const fixtures = require('../fixtures');
|
||||||
@@ -1092,4 +1093,39 @@ describe('Input/output', () => {
|
|||||||
assert.strictEqual(channels, 3);
|
assert.strictEqual(channels, 3);
|
||||||
assert.strictEqual(format, 'jpeg');
|
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