diff --git a/docs/api-composite.md b/docs/api-composite.md
index f1a47bac..f5caccc8 100644
--- a/docs/api-composite.md
+++ b/docs/api-composite.md
@@ -25,6 +25,11 @@ If both `top` and `left` options are provided, they take precedence over `gravit
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.create` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes a blank overlay to be created.
+ - `options.create.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.create.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.create.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 3-4
+ - `options.create.background` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))?** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
**Examples**
diff --git a/docs/api-constructor.md b/docs/api-constructor.md
index 09fac196..2d6e8e92 100644
--- a/docs/api-constructor.md
+++ b/docs/api-constructor.md
@@ -17,10 +17,15 @@
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** if present, is an Object with optional attributes.
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** integral number representing the DPI for vector images. (optional, default `72`)
- - `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel image data. See `raw()` for pixel ordering.
+ - `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- - `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 1-4
+ - `options.create` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes a new image to be created.
+ - `options.create.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.create.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
+ - `options.create.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 3-4
+ - `options.create.background` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))?** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
**Examples**
@@ -46,6 +51,21 @@ var transformer = sharp()
readableStream.pipe(transformer).pipe(writableStream);
```
+```javascript
+// Create a blank 300x200 PNG image of semi-transluent red pixels
+sharp(null, {
+ create: {
+ width: 300,
+ height: 200,
+ channels: 4,
+ background: { r: 255, g: 0, b: 0, alpha: 128 }
+ }
+})
+.png()
+.toBuffer()
+.then( ... );
+```
+
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **[Sharp](#sharp)**
diff --git a/docs/changelog.md b/docs/changelog.md
index 3a8f2733..169e19bb 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -10,6 +10,10 @@ Requires libvips v8.4.2.
[#143](https://github.com/lovell/sharp/issues/143)
[@salzhrani](https://github.com/salzhrani)
+* Create blank image of given width, height, channels and background.
+ [#470](https://github.com/lovell/sharp/issues/470)
+ [@pjarts](https://github.com/pjarts)
+
#### v0.17.2 - 11th February 2017
* Ensure Readable side of Stream can start flowing after Writable side has finished.
diff --git a/lib/composite.js b/lib/composite.js
index 6e2205c8..39d503ee 100644
--- a/lib/composite.js
+++ b/lib/composite.js
@@ -38,6 +38,11 @@ const is = require('./is');
* @param {Number} [options.raw.width]
* @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels]
+ * @param {Object} [options.create] - describes a blank overlay to be created.
+ * @param {Number} [options.create.width]
+ * @param {Number} [options.create.height]
+ * @param {Number} [options.create.channels] - 3-4
+ * @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.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
diff --git a/lib/constructor.js b/lib/constructor.js
index dc490751..50d3e105 100644
--- a/lib/constructor.js
+++ b/lib/constructor.js
@@ -54,16 +54,35 @@ let versions = {
* });
* readableStream.pipe(transformer).pipe(writableStream);
*
+ * @example
+ * // Create a blank 300x200 PNG image of semi-transluent red pixels
+ * sharp(null, {
+ * create: {
+ * width: 300,
+ * height: 200,
+ * channels: 4,
+ * background: { r: 255, g: 0, b: 0, alpha: 128 }
+ * }
+ * })
+ * .png()
+ * .toBuffer()
+ * .then( ... );
+ *
* @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Number} [options.density=72] - integral number representing the DPI for vector images.
- * @param {Object} [options.raw] - describes raw pixel image data. See `raw()` for pixel ordering.
+ * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width]
* @param {Number} [options.raw.height]
- * @param {Number} [options.raw.channels]
+ * @param {Number} [options.raw.channels] - 1-4
+ * @param {Object} [options.create] - describes a new image to be created.
+ * @param {Number} [options.create.width]
+ * @param {Number} [options.create.height]
+ * @param {Number} [options.create.channels] - 3-4
+ * @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.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
diff --git a/lib/input.js b/lib/input.js
index 4bd2de0b..36e94fb2 100644
--- a/lib/input.js
+++ b/lib/input.js
@@ -1,6 +1,7 @@
'use strict';
const util = require('util');
+const color = require('color');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
@@ -46,6 +47,30 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
throw new Error('Expected width, height and channels for raw pixel input');
}
}
+ // Create new image
+ if (is.defined(inputOptions.create)) {
+ if (
+ is.object(inputOptions.create) &&
+ is.integer(inputOptions.create.width) && is.inRange(inputOptions.create.width, 1, this.constructor.maximum.width) &&
+ is.integer(inputOptions.create.height) && is.inRange(inputOptions.create.height, 1, this.constructor.maximum.height) &&
+ is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
+ is.defined(inputOptions.create.background)
+ ) {
+ inputDescriptor.createWidth = inputOptions.create.width;
+ inputDescriptor.createHeight = inputOptions.create.height;
+ inputDescriptor.createChannels = inputOptions.create.channels;
+ const background = color(inputOptions.create.background);
+ inputDescriptor.createBackground = [
+ background.red(),
+ background.green(),
+ background.blue(),
+ Math.round(background.alpha() * 255)
+ ];
+ delete inputDescriptor.buffer;
+ } else {
+ throw new Error('Expected width, height, channels and background to create a new input image');
+ }
+ }
} else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions);
}
diff --git a/src/common.cc b/src/common.cc
index 62f9e532..57e1d6b7 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -44,7 +44,7 @@ namespace sharp {
InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file");
- } else {
+ } else if (HasAttr(input, "buffer")) {
v8::Local buffer = AttrAs(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer);
@@ -60,6 +60,16 @@ namespace sharp {
descriptor->rawWidth = AttrTo(input, "rawWidth");
descriptor->rawHeight = AttrTo(input, "rawHeight");
}
+ // Create new image
+ if (HasAttr(input, "createChannels")) {
+ descriptor->createChannels = AttrTo(input, "createChannels");
+ descriptor->createWidth = AttrTo(input, "createWidth");
+ descriptor->createHeight = AttrTo(input, "createHeight");
+ v8::Local createBackground = AttrAs(input, "createBackground");
+ for (unsigned int i = 0; i < 4; i++) {
+ descriptor->createBackground[i] = AttrTo(createBackground, i);
+ }
+ }
return descriptor;
}
@@ -192,7 +202,6 @@ namespace sharp {
VImage image;
ImageType imageType;
if (descriptor->buffer != nullptr) {
- // From buffer
if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@@ -227,26 +236,41 @@ namespace sharp {
}
}
} else {
- // From filesystem
- imageType = DetermineImageType(descriptor->file.data());
- if (imageType != ImageType::UNKNOWN) {
- try {
- vips::VOption *option = VImage::option()->set("access", accessMethod);
- if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
- option->set("dpi", static_cast(descriptor->density));
- }
- if (imageType == ImageType::MAGICK) {
- option->set("density", std::to_string(descriptor->density).data());
- }
- image = VImage::new_from_file(descriptor->file.data(), option);
- if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
- SetDensity(image, descriptor->density);
- }
- } catch (...) {
- throw vips::VError("Input file has corrupt header");
+ if (descriptor->createChannels > 0) {
+ // Create new image
+ std::vector background = {
+ descriptor->createBackground[0],
+ descriptor->createBackground[1],
+ descriptor->createBackground[2]
+ };
+ if (descriptor->createChannels == 4) {
+ background.push_back(descriptor->createBackground[3]);
}
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
+ image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
+ imageType = ImageType::RAW;
} else {
- throw vips::VError("Input file is missing or of an unsupported image format");
+ // From filesystem
+ imageType = DetermineImageType(descriptor->file.data());
+ if (imageType != ImageType::UNKNOWN) {
+ try {
+ vips::VOption *option = VImage::option()->set("access", accessMethod);
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
+ option->set("dpi", static_cast(descriptor->density));
+ }
+ if (imageType == ImageType::MAGICK) {
+ option->set("density", std::to_string(descriptor->density).data());
+ }
+ image = VImage::new_from_file(descriptor->file.data(), option);
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
+ SetDensity(image, descriptor->density);
+ }
+ } catch (...) {
+ throw vips::VError("Input file has corrupt header");
+ }
+ } else {
+ throw vips::VError("Input file is missing or of an unsupported image format");
+ }
}
}
return std::make_tuple(image, imageType);
diff --git a/src/common.h b/src/common.h
index 60e1f857..20790c5f 100644
--- a/src/common.h
+++ b/src/common.h
@@ -52,6 +52,10 @@ namespace sharp {
int rawChannels;
int rawWidth;
int rawHeight;
+ int createChannels;
+ int createWidth;
+ int createHeight;
+ double createBackground[4];
InputDescriptor():
buffer(nullptr),
@@ -59,7 +63,15 @@ namespace sharp {
density(72),
rawChannels(0),
rawWidth(0),
- rawHeight(0) {}
+ rawHeight(0),
+ createChannels(0),
+ createWidth(0),
+ createHeight(0) {
+ createBackground[0] = 0.0;
+ createBackground[1] = 0.0;
+ createBackground[2] = 0.0;
+ createBackground[3] = 255.0;
+ }
};
// Convenience methods to access the attributes of a v8::Object
diff --git a/src/pipeline.cc b/src/pipeline.cc
index 37148c1a..ec202b9f 100644
--- a/src/pipeline.cc
+++ b/src/pipeline.cc
@@ -1100,7 +1100,7 @@ NAN_METHOD(pipeline) {
// Background colour
v8::Local background = AttrAs(options, "background");
for (unsigned int i = 0; i < 4; i++) {
- baton->background[i] = AttrTo(background, i);
+ baton->background[i] = AttrTo(background, i);
}
// Overlay options
if (HasAttr(options, "overlay")) {
diff --git a/test/fixtures/expected/create-rgb.jpg b/test/fixtures/expected/create-rgb.jpg
new file mode 100644
index 00000000..15c57413
Binary files /dev/null and b/test/fixtures/expected/create-rgb.jpg differ
diff --git a/test/fixtures/expected/create-rgba.png b/test/fixtures/expected/create-rgba.png
new file mode 100644
index 00000000..0e1d966d
Binary files /dev/null and b/test/fixtures/expected/create-rgba.png differ
diff --git a/test/unit/io.js b/test/unit/io.js
index 894848a2..772d944e 100644
--- a/test/unit/io.js
+++ b/test/unit/io.js
@@ -1156,6 +1156,66 @@ describe('Input/output', function () {
});
});
+ describe('create new image', function () {
+ it('RGB', function (done) {
+ const create = {
+ width: 10,
+ height: 20,
+ channels: 3,
+ background: { r: 0, g: 255, b: 0 }
+ };
+ sharp(null, { create: create })
+ .jpeg()
+ .toBuffer(function (err, data, info) {
+ if (err) throw err;
+ assert.strictEqual(create.width, info.width);
+ assert.strictEqual(create.height, info.height);
+ assert.strictEqual(create.channels, info.channels);
+ assert.strictEqual('jpeg', info.format);
+ fixtures.assertSimilar(fixtures.expected('create-rgb.jpg'), data, done);
+ });
+ });
+ it('RGBA', function (done) {
+ const create = {
+ width: 20,
+ height: 10,
+ channels: 4,
+ background: { r: 255, g: 0, b: 0, alpha: 128 }
+ };
+ sharp(null, { create: create })
+ .png()
+ .toBuffer(function (err, data, info) {
+ if (err) throw err;
+ assert.strictEqual(create.width, info.width);
+ assert.strictEqual(create.height, info.height);
+ assert.strictEqual(create.channels, info.channels);
+ assert.strictEqual('png', info.format);
+ fixtures.assertSimilar(fixtures.expected('create-rgba.png'), data, done);
+ });
+ });
+ it('Invalid channels', function () {
+ const create = {
+ width: 10,
+ height: 20,
+ channels: 2,
+ background: { r: 0, g: 0, b: 0 }
+ };
+ assert.throws(function () {
+ sharp(null, { create: create });
+ });
+ });
+ it('Missing background', function () {
+ const create = {
+ width: 10,
+ height: 20,
+ channels: 3
+ };
+ assert.throws(function () {
+ sharp(null, { create: create });
+ });
+ });
+ });
+
it('Queue length change events', function (done) {
let eventCounter = 0;
const queueListener = function (queueLength) {