sharp 0.27.0 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,7 +102,7 @@ covers reporting bugs, requesting features and submitting code changes.
102
102
 
103
103
  ### Licensing
104
104
 
105
- Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
105
+ Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
106
106
 
107
107
  Licensed under the Apache License, Version 2.0 (the "License");
108
108
  you may not use this file except in compliance with the License.
package/lib/colour.js CHANGED
@@ -57,6 +57,13 @@ function grayscale (grayscale) {
57
57
  /**
58
58
  * Set the output colourspace.
59
59
  * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
60
+ *
61
+ * @example
62
+ * // Output 16 bits per pixel RGB
63
+ * await sharp(input)
64
+ * .toColourspace('rgb16')
65
+ * .toFile('16-bpp.png')
66
+ *
60
67
  * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
61
68
  * @returns {Sharp}
62
69
  * @throws {Error} Invalid parameters
@@ -90,8 +90,37 @@ const debuglog = util.debuglog('sharp');
90
90
  * // Convert an animated GIF to an animated WebP
91
91
  * await sharp('in.gif', { animated: true }).toFile('out.webp');
92
92
  *
93
- * @param {(Buffer|string)} [input] - if present, can be
94
- * a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
93
+ * @example
94
+ * // Read a raw array of pixels and save it to a png
95
+ * const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
96
+ * const image = sharp(input, {
97
+ * // because the input does not contain its dimensions or how many channels it has
98
+ * // we need to specify it in the constructor options
99
+ * raw: {
100
+ * width: 2,
101
+ * height: 1,
102
+ * channels: 3
103
+ * }
104
+ * });
105
+ * await image.toFile('my-two-pixels.png');
106
+ *
107
+ * @example
108
+ * // Generate RGB Gaussian noise
109
+ * await sharp({
110
+ * create: {
111
+ * width: 300,
112
+ * height: 200,
113
+ * channels: 3,
114
+ * noise: {
115
+ * type: 'gaussian',
116
+ * mean: 128,
117
+ * sigma: 30
118
+ * }
119
+ * }
120
+ * }.toFile('noise.png');
121
+ *
122
+ * @param {(Buffer|Uint8Array|Uint8ClampedArray|string)} [input] - if present, can be
123
+ * a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
95
124
  * a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
96
125
  * JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
97
126
  * @param {Object} [options] - if present, is an Object with optional attributes.
@@ -108,14 +137,18 @@ const debuglog = util.debuglog('sharp');
108
137
  * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
109
138
  * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
110
139
  * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
111
- * @param {number} [options.raw.width]
112
- * @param {number} [options.raw.height]
113
- * @param {number} [options.raw.channels] - 1-4
140
+ * @param {number} [options.raw.width] - integral number of pixels wide.
141
+ * @param {number} [options.raw.height] - integral number of pixels high.
142
+ * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
114
143
  * @param {Object} [options.create] - describes a new image to be created.
115
- * @param {number} [options.create.width]
116
- * @param {number} [options.create.height]
117
- * @param {number} [options.create.channels] - 3-4
144
+ * @param {number} [options.create.width] - integral number of pixels wide.
145
+ * @param {number} [options.create.height] - integral number of pixels high.
146
+ * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
118
147
  * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
148
+ * @param {Object} [options.create.noise] - describes a noise to be created.
149
+ * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
150
+ * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
151
+ * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
119
152
  * @returns {Sharp}
120
153
  * @throws {Error} Invalid parameters
121
154
  */
@@ -237,6 +270,7 @@ const Sharp = function (input, options) {
237
270
  heifLossless: false,
238
271
  heifCompression: 'av1',
239
272
  heifSpeed: 5,
273
+ heifChromaSubsampling: '4:2:0',
240
274
  tileSize: 256,
241
275
  tileOverlap: 0,
242
276
  tileContainer: 'fs',
package/lib/input.js CHANGED
@@ -31,6 +31,9 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
31
31
  } else if (is.buffer(input)) {
32
32
  // Buffer
33
33
  inputDescriptor.buffer = input;
34
+ } else if (is.uint8Array(input)) {
35
+ // Uint8Array or Uint8ClampedArray
36
+ inputDescriptor.buffer = Buffer.from(input.buffer);
34
37
  } else if (is.plainObject(input) && !is.defined(inputOptions)) {
35
38
  // Plain Object descriptor, e.g. create
36
39
  inputOptions = input;
@@ -134,22 +137,50 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
134
137
  is.object(inputOptions.create) &&
135
138
  is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
136
139
  is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
137
- is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
138
- is.defined(inputOptions.create.background)
140
+ is.integer(inputOptions.create.channels)
139
141
  ) {
140
142
  inputDescriptor.createWidth = inputOptions.create.width;
141
143
  inputDescriptor.createHeight = inputOptions.create.height;
142
144
  inputDescriptor.createChannels = inputOptions.create.channels;
143
- const background = color(inputOptions.create.background);
144
- inputDescriptor.createBackground = [
145
- background.red(),
146
- background.green(),
147
- background.blue(),
148
- Math.round(background.alpha() * 255)
149
- ];
145
+ // Noise
146
+ if (is.defined(inputOptions.create.noise)) {
147
+ if (!is.object(inputOptions.create.noise)) {
148
+ throw new Error('Expected noise to be an object');
149
+ }
150
+ if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
151
+ throw new Error('Only gaussian noise is supported at the moment');
152
+ }
153
+ if (!is.inRange(inputOptions.create.channels, 1, 4)) {
154
+ throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
155
+ }
156
+ inputDescriptor.createNoiseType = inputOptions.create.noise.type;
157
+ if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
158
+ inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
159
+ } else {
160
+ throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
161
+ }
162
+ if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
163
+ inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
164
+ } else {
165
+ throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
166
+ }
167
+ } else if (is.defined(inputOptions.create.background)) {
168
+ if (!is.inRange(inputOptions.create.channels, 3, 4)) {
169
+ throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
170
+ }
171
+ const background = color(inputOptions.create.background);
172
+ inputDescriptor.createBackground = [
173
+ background.red(),
174
+ background.green(),
175
+ background.blue(),
176
+ Math.round(background.alpha() * 255)
177
+ ];
178
+ } else {
179
+ throw new Error('Expected valid noise or background to create a new input image');
180
+ }
150
181
  delete inputDescriptor.buffer;
151
182
  } else {
152
- throw new Error('Expected width, height, channels and background to create a new input image');
183
+ throw new Error('Expected valid width, height and channels to create a new input image');
153
184
  }
154
185
  }
155
186
  } else if (is.defined(inputOptions)) {
package/lib/is.js CHANGED
@@ -48,6 +48,15 @@ const buffer = function (val) {
48
48
  return val instanceof Buffer;
49
49
  };
50
50
 
51
+ /**
52
+ * Is this value a Uint8Array or Uint8ClampedArray object?
53
+ * @private
54
+ */
55
+ const uint8Array = function (val) {
56
+ // allow both since Uint8ClampedArray simply clamps the values between 0-255
57
+ return val instanceof Uint8Array || val instanceof Uint8ClampedArray;
58
+ };
59
+
51
60
  /**
52
61
  * Is this value a non-empty string?
53
62
  * @private
@@ -110,6 +119,7 @@ module.exports = {
110
119
  fn: fn,
111
120
  bool: bool,
112
121
  buffer: buffer,
122
+ uint8Array: uint8Array,
113
123
  string: string,
114
124
  number: number,
115
125
  integer: integer,
package/lib/libvips.js CHANGED
@@ -39,8 +39,9 @@ const cachePath = function () {
39
39
 
40
40
  const globalLibvipsVersion = function () {
41
41
  if (process.platform !== 'win32') {
42
- const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
43
- return globalLibvipsVersion.trim();
42
+ const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
43
+ /* istanbul ignore next */
44
+ return (globalLibvipsVersion || '').trim();
44
45
  } else {
45
46
  return '';
46
47
  }
package/lib/output.js CHANGED
@@ -104,6 +104,20 @@ function toFile (fileOut, callback) {
104
104
  * .then(({ data, info }) => { ... })
105
105
  * .catch(err => { ... });
106
106
  *
107
+ * @example
108
+ * const data = await sharp('my-image.jpg')
109
+ * // output the raw pixels
110
+ * .raw()
111
+ * .toBuffer();
112
+ *
113
+ * // create a more type safe way to work with the raw pixel data
114
+ * // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
115
+ * // so `data` and `pixelArray` point to the same memory location
116
+ * const pixelArray = new Uint8ClampedArray(data.buffer);
117
+ *
118
+ * // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
119
+ * await sharp(pixelArray).toFile('my-changed-image.jpg');
120
+ *
107
121
  * @param {Object} [options]
108
122
  * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
109
123
  * @param {Function} [callback]
@@ -472,15 +486,15 @@ function trySetAnimationOptions (source, target) {
472
486
  * @param {Object} [options] - output options
473
487
  * @param {number} [options.quality=80] - quality, integer 1-100
474
488
  * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
475
- * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
476
- * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
489
+ * @param {string} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
490
+ * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
477
491
  * @param {boolean} [options.pyramid=false] - write an image pyramid
478
492
  * @param {boolean} [options.tile=false] - write a tiled tiff
479
- * @param {boolean} [options.tileWidth=256] - horizontal tile size
480
- * @param {boolean} [options.tileHeight=256] - vertical tile size
493
+ * @param {number} [options.tileWidth=256] - horizontal tile size
494
+ * @param {number} [options.tileHeight=256] - vertical tile size
481
495
  * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
482
496
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
483
- * @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
497
+ * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
484
498
  * @returns {Sharp}
485
499
  * @throws {Error} Invalid options
486
500
  */
@@ -569,6 +583,7 @@ function tiff (options) {
569
583
  * @param {number} [options.quality=50] - quality, integer 1-100
570
584
  * @param {boolean} [options.lossless=false] - use lossless compression
571
585
  * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
586
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
572
587
  * @returns {Sharp}
573
588
  * @throws {Error} Invalid options
574
589
  */
@@ -586,9 +601,10 @@ function avif (options) {
586
601
  *
587
602
  * @param {Object} [options] - output options
588
603
  * @param {number} [options.quality=50] - quality, integer 1-100
589
- * @param {boolean} [options.compression='av1'] - compression format: av1, hevc
604
+ * @param {string} [options.compression='av1'] - compression format: av1, hevc
590
605
  * @param {boolean} [options.lossless=false] - use lossless compression
591
- * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
606
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
607
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
592
608
  * @returns {Sharp}
593
609
  * @throws {Error} Invalid options
594
610
  */
@@ -622,6 +638,13 @@ function heif (options) {
622
638
  throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
623
639
  }
624
640
  }
641
+ if (is.defined(options.chromaSubsampling)) {
642
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
643
+ this.options.heifChromaSubsampling = options.chromaSubsampling;
644
+ } else {
645
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
646
+ }
647
+ }
625
648
  }
626
649
  return this._updateFormatOut('heif', options);
627
650
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
3
  "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
- "version": "0.27.0",
4
+ "version": "0.27.1",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -72,17 +72,19 @@
72
72
  "Robert O'Rourke <robert@o-rourke.org>",
73
73
  "Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
74
74
  "Christian Flintrup <chr@gigahost.dk>",
75
- "Manan Jadhav <manan@motionden.com>"
75
+ "Manan Jadhav <manan@motionden.com>",
76
+ "Leon Radley <leon@radley.se>",
77
+ "alza54 <alza54@thiocod.in>"
76
78
  ],
77
79
  "scripts": {
78
80
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
79
81
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
80
82
  "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
81
- "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
83
+ "test-unit": "nyc --reporter=lcov --branches=99 mocha --parallel --slow=5000 --timeout=60000 ./test/unit/*.js",
82
84
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
83
85
  "test-coverage": "./test/coverage/report.sh",
84
86
  "test-leak": "./test/leak/leak.sh",
85
- "docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
87
+ "docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
86
88
  "docs-serve": "cd docs && npx serve",
87
89
  "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
88
90
  },
@@ -129,8 +131,8 @@
129
131
  "devDependencies": {
130
132
  "async": "^3.2.0",
131
133
  "cc": "^3.0.1",
132
- "decompress-zip": "^0.3.2",
133
- "documentation": "^13.1.0",
134
+ "decompress-zip": "^0.3.3",
135
+ "documentation": "^13.1.1",
134
136
  "exif-reader": "^1.0.3",
135
137
  "icc": "^2.0.0",
136
138
  "license-checker": "^25.0.1",
package/src/common.cc CHANGED
@@ -109,7 +109,13 @@ namespace sharp {
109
109
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
110
110
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
111
111
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
112
- descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
112
+ if (HasAttr(input, "createNoiseType")) {
113
+ descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
114
+ descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
115
+ descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
116
+ } else {
117
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
118
+ }
113
119
  }
114
120
  // Limit input images to a given number of pixels, where pixels = width * height
115
121
  descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -189,31 +195,38 @@ namespace sharp {
189
195
  return id;
190
196
  }
191
197
 
198
+ /**
199
+ * Regenerate this table with something like:
200
+ *
201
+ * $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
202
+ *
203
+ * Plus a bit of editing.
204
+ */
192
205
  std::map<std::string, ImageType> loaderToType = {
193
- { "jpegload", ImageType::JPEG },
194
- { "jpegload_buffer", ImageType::JPEG },
195
- { "pngload", ImageType::PNG },
196
- { "pngload_buffer", ImageType::PNG },
197
- { "webpload", ImageType::WEBP },
198
- { "webpload_buffer", ImageType::WEBP },
199
- { "tiffload", ImageType::TIFF },
200
- { "tiffload_buffer", ImageType::TIFF },
201
- { "gifload", ImageType::GIF },
202
- { "gifload_buffer", ImageType::GIF },
203
- { "svgload", ImageType::SVG },
204
- { "svgload_buffer", ImageType::SVG },
205
- { "heifload", ImageType::HEIF },
206
- { "heifload_buffer", ImageType::HEIF },
207
- { "pdfload", ImageType::PDF },
208
- { "pdfload_buffer", ImageType::PDF },
209
- { "magickload", ImageType::MAGICK },
210
- { "magickload_buffer", ImageType::MAGICK },
211
- { "openslideload", ImageType::OPENSLIDE },
212
- { "ppmload", ImageType::PPM },
213
- { "fitsload", ImageType::FITS },
214
- { "openexrload", ImageType::EXR },
215
- { "vipsload", ImageType::VIPS },
216
- { "rawload", ImageType::RAW }
206
+ { "VipsForeignLoadJpegFile", ImageType::JPEG },
207
+ { "VipsForeignLoadJpegBuffer", ImageType::JPEG },
208
+ { "VipsForeignLoadPngFile", ImageType::PNG },
209
+ { "VipsForeignLoadPngBuffer", ImageType::PNG },
210
+ { "VipsForeignLoadWebpFile", ImageType::WEBP },
211
+ { "VipsForeignLoadWebpBuffer", ImageType::WEBP },
212
+ { "VipsForeignLoadTiffFile", ImageType::TIFF },
213
+ { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
214
+ { "VipsForeignLoadGifFile", ImageType::GIF },
215
+ { "VipsForeignLoadGifBuffer", ImageType::GIF },
216
+ { "VipsForeignLoadSvgFile", ImageType::SVG },
217
+ { "VipsForeignLoadSvgBuffer", ImageType::SVG },
218
+ { "VipsForeignLoadHeifFile", ImageType::HEIF },
219
+ { "VipsForeignLoadHeifBuffer", ImageType::HEIF },
220
+ { "VipsForeignLoadPdfFile", ImageType::PDF },
221
+ { "VipsForeignLoadPdfBuffer", ImageType::PDF },
222
+ { "VipsForeignLoadMagickFile", ImageType::MAGICK },
223
+ { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
224
+ { "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
225
+ { "VipsForeignLoadPpmFile", ImageType::PPM },
226
+ { "VipsForeignLoadFits", ImageType::FITS },
227
+ { "VipsForeignLoadOpenexr", ImageType::EXR },
228
+ { "VipsForeignLoadVips", ImageType::VIPS },
229
+ { "VipsForeignLoadRaw", ImageType::RAW }
217
230
  };
218
231
 
219
232
  /*
@@ -223,7 +236,7 @@ namespace sharp {
223
236
  ImageType imageType = ImageType::UNKNOWN;
224
237
  char const *load = vips_foreign_find_load_buffer(buffer, length);
225
238
  if (load != nullptr) {
226
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
239
+ auto it = loaderToType.find(load);
227
240
  if (it != loaderToType.end()) {
228
241
  imageType = it->second;
229
242
  }
@@ -238,7 +251,7 @@ namespace sharp {
238
251
  ImageType imageType = ImageType::UNKNOWN;
239
252
  char const *load = vips_foreign_find_load(file);
240
253
  if (load != nullptr) {
241
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
254
+ auto it = loaderToType.find(load);
242
255
  if (it != loaderToType.end()) {
243
256
  imageType = it->second;
244
257
  }
@@ -318,15 +331,35 @@ namespace sharp {
318
331
  } else {
319
332
  if (descriptor->createChannels > 0) {
320
333
  // Create new image
321
- std::vector<double> background = {
322
- descriptor->createBackground[0],
323
- descriptor->createBackground[1],
324
- descriptor->createBackground[2]
325
- };
326
- if (descriptor->createChannels == 4) {
327
- background.push_back(descriptor->createBackground[3]);
334
+ if (descriptor->createNoiseType == "gaussian") {
335
+ int const channels = descriptor->createChannels;
336
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
337
+ std::vector<VImage> bands = {};
338
+ bands.reserve(channels);
339
+ for (int _band = 0; _band < channels; _band++) {
340
+ bands.push_back(image.gaussnoise(
341
+ descriptor->createWidth,
342
+ descriptor->createHeight,
343
+ VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
344
+ }
345
+ image = image.bandjoin(bands);
346
+ image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
347
+ if (channels < 3) {
348
+ image = image.colourspace(VIPS_INTERPRETATION_B_W);
349
+ } else {
350
+ image = image.colourspace(VIPS_INTERPRETATION_sRGB);
351
+ }
352
+ } else {
353
+ std::vector<double> background = {
354
+ descriptor->createBackground[0],
355
+ descriptor->createBackground[1],
356
+ descriptor->createBackground[2]
357
+ };
358
+ if (descriptor->createChannels == 4) {
359
+ background.push_back(descriptor->createBackground[3]);
360
+ }
361
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
328
362
  }
329
- image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
330
363
  image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
331
364
  imageType = ImageType::RAW;
332
365
  } else {
package/src/common.h CHANGED
@@ -64,6 +64,9 @@ namespace sharp {
64
64
  int createWidth;
65
65
  int createHeight;
66
66
  std::vector<double> createBackground;
67
+ std::string createNoiseType;
68
+ double createNoiseMean;
69
+ double createNoiseSigma;
67
70
 
68
71
  InputDescriptor():
69
72
  buffer(nullptr),
@@ -82,7 +85,9 @@ namespace sharp {
82
85
  createChannels(0),
83
86
  createWidth(0),
84
87
  createHeight(0),
85
- createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
88
+ createBackground{ 0.0, 0.0, 0.0, 255.0 },
89
+ createNoiseMean(0.0),
90
+ createNoiseSigma(0.0) {}
86
91
  };
87
92
 
88
93
  // Convenience methods to access the attributes of a Napi::Object
package/src/metadata.cc CHANGED
@@ -74,6 +74,9 @@ class MetadataWorker : public Napi::AsyncWorker {
74
74
  if (image.get_typeof("heif-primary") == G_TYPE_INT) {
75
75
  baton->pagePrimary = image.get_int("heif-primary");
76
76
  }
77
+ if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
78
+ baton->compression = image.get_string("heif-compression");
79
+ }
77
80
  if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
78
81
  int const levels = std::stoi(image.get_string("openslide.level-count"));
79
82
  for (int l = 0; l < levels; l++) {
@@ -186,6 +189,9 @@ class MetadataWorker : public Napi::AsyncWorker {
186
189
  if (baton->pagePrimary > -1) {
187
190
  info.Set("pagePrimary", baton->pagePrimary);
188
191
  }
192
+ if (!baton->compression.empty()) {
193
+ info.Set("compression", baton->compression);
194
+ }
189
195
  if (!baton->levels.empty()) {
190
196
  int i = 0;
191
197
  Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
package/src/metadata.h CHANGED
@@ -39,6 +39,7 @@ struct MetadataBaton {
39
39
  int loop;
40
40
  std::vector<int> delay;
41
41
  int pagePrimary;
42
+ std::string compression;
42
43
  std::vector<std::pair<int, int>> levels;
43
44
  bool hasProfile;
44
45
  bool hasAlpha;
package/src/pipeline.cc CHANGED
@@ -730,7 +730,7 @@ class PipelineWorker : public Napi::AsyncWorker {
730
730
  if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
731
731
  // Write JPEG to buffer
732
732
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
733
- VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
733
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
734
734
  ->set("strip", !baton->withMetadata)
735
735
  ->set("Q", baton->jpegQuality)
736
736
  ->set("interlace", baton->jpegProgressive)
@@ -757,7 +757,7 @@ class PipelineWorker : public Napi::AsyncWorker {
757
757
  inputImageType == sharp::ImageType::SVG))) {
758
758
  // Write PNG to buffer
759
759
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
760
- VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
760
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
761
761
  ->set("strip", !baton->withMetadata)
762
762
  ->set("interlace", baton->pngProgressive)
763
763
  ->set("compression", baton->pngCompressionLevel)
@@ -775,7 +775,7 @@ class PipelineWorker : public Napi::AsyncWorker {
775
775
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
776
776
  // Write WEBP to buffer
777
777
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
778
- VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
778
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
779
779
  ->set("strip", !baton->withMetadata)
780
780
  ->set("Q", baton->webpQuality)
781
781
  ->set("lossless", baton->webpLossless)
@@ -792,7 +792,7 @@ class PipelineWorker : public Napi::AsyncWorker {
792
792
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
793
793
  // Write GIF to buffer
794
794
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
795
- VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
795
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
796
796
  ->set("strip", !baton->withMetadata)
797
797
  ->set("optimize_gif_frames", TRUE)
798
798
  ->set("optimize_gif_transparency", TRUE)
@@ -813,7 +813,7 @@ class PipelineWorker : public Napi::AsyncWorker {
813
813
  if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
814
814
  image = image.cast(VIPS_FORMAT_FLOAT);
815
815
  }
816
- VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
816
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
817
817
  ->set("strip", !baton->withMetadata)
818
818
  ->set("Q", baton->tiffQuality)
819
819
  ->set("bitdepth", baton->tiffBitdepth)
@@ -833,11 +833,15 @@ class PipelineWorker : public Napi::AsyncWorker {
833
833
  } else if (baton->formatOut == "heif" ||
834
834
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
835
835
  // Write HEIF to buffer
836
- VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
836
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
837
837
  ->set("strip", !baton->withMetadata)
838
838
  ->set("compression", baton->heifCompression)
839
839
  ->set("Q", baton->heifQuality)
840
840
  ->set("speed", baton->heifSpeed)
841
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
842
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
843
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
844
+ #endif
841
845
  ->set("lossless", baton->heifLossless)));
842
846
  baton->bufferOut = static_cast<char*>(area->data);
843
847
  baton->bufferOutLength = area->length;
@@ -951,6 +955,10 @@ class PipelineWorker : public Napi::AsyncWorker {
951
955
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
952
956
  baton->channels = std::min(baton->channels, 3);
953
957
  }
958
+ // Cast pixel values to float, if required
959
+ if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
960
+ image = image.cast(VIPS_FORMAT_FLOAT);
961
+ }
954
962
  image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
955
963
  ->set("strip", !baton->withMetadata)
956
964
  ->set("Q", baton->tiffQuality)
@@ -972,6 +980,10 @@ class PipelineWorker : public Napi::AsyncWorker {
972
980
  ->set("Q", baton->heifQuality)
973
981
  ->set("compression", baton->heifCompression)
974
982
  ->set("speed", baton->heifSpeed)
983
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
984
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
985
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
986
+ #endif
975
987
  ->set("lossless", baton->heifLossless));
976
988
  baton->formatOut = "heif";
977
989
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -1396,6 +1408,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1396
1408
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1397
1409
  sharp::AttrAsStr(options, "heifCompression").data()));
1398
1410
  baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
1411
+ baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1399
1412
 
1400
1413
  // Animated output
1401
1414
  if (sharp::HasAttr(options, "pageHeight")) {
package/src/pipeline.h CHANGED
@@ -162,6 +162,7 @@ struct PipelineBaton {
162
162
  int heifQuality;
163
163
  VipsForeignHeifCompression heifCompression;
164
164
  int heifSpeed;
165
+ std::string heifChromaSubsampling;
165
166
  bool heifLossless;
166
167
  std::string err;
167
168
  bool withMetadata;
@@ -282,6 +283,7 @@ struct PipelineBaton {
282
283
  heifQuality(50),
283
284
  heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
284
285
  heifSpeed(5),
286
+ heifChromaSubsampling("4:2:0"),
285
287
  heifLossless(false),
286
288
  withMetadata(false),
287
289
  withMetadataOrientation(-1),
package/src/sharp.cc CHANGED
@@ -23,7 +23,6 @@
23
23
 
24
24
  static void* sharp_vips_init(void*) {
25
25
  g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
26
- g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
27
26
  vips_init("sharp");
28
27
  return nullptr;
29
28
  }