Create blank image (width, height, channels, background) #470

This commit is contained in:
Lovell Fuller 2017-03-11 11:46:01 +00:00
parent 701b1c4216
commit 1aa053ce6f
12 changed files with 200 additions and 26 deletions

View File

@ -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.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.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)?**
- `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** **Examples**

View File

@ -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. 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` **[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.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.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.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** **Examples**
@ -46,6 +51,21 @@ var transformer = sharp()
readableStream.pipe(transformer).pipe(writableStream); 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 - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **[Sharp](#sharp)** Returns **[Sharp](#sharp)**

View File

@ -10,6 +10,10 @@ Requires libvips v8.4.2.
[#143](https://github.com/lovell/sharp/issues/143) [#143](https://github.com/lovell/sharp/issues/143)
[@salzhrani](https://github.com/salzhrani) [@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 - 11<sup>th</sup> February 2017 #### v0.17.2 - 11<sup>th</sup> February 2017
* Ensure Readable side of Stream can start flowing after Writable side has finished. * Ensure Readable side of Stream can start flowing after Writable side has finished.

View File

@ -38,6 +38,11 @@ const is = require('./is');
* @param {Number} [options.raw.width] * @param {Number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels] * @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} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@ -54,16 +54,35 @@ let versions = {
* }); * });
* readableStream.pipe(transformer).pipe(writableStream); * 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 * @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * 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. * 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. * 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 {Object} [options] - if present, is an Object with optional attributes.
* @param {Number} [options.density=72] - integral number representing the DPI for vector images. * @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.width]
* @param {Number} [options.raw.height] * @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} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const util = require('util'); const util = require('util');
const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); 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'); 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)) { } else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions); throw new Error('Invalid input options ' + inputOptions);
} }

View File

@ -44,7 +44,7 @@ namespace sharp {
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = node::Buffer::Data(buffer);
@ -60,6 +60,16 @@ namespace sharp {
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
} }
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
v8::Local<v8::Object> createBackground = AttrAs<v8::Object>(input, "createBackground");
for (unsigned int i = 0; i < 4; i++) {
descriptor->createBackground[i] = AttrTo<double>(createBackground, i);
}
}
return descriptor; return descriptor;
} }
@ -192,7 +202,6 @@ namespace sharp {
VImage image; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->buffer != nullptr) { if (descriptor->buffer != nullptr) {
// From buffer
if (descriptor->rawChannels > 0) { if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength, image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@ -227,26 +236,41 @@ namespace sharp {
} }
} }
} else { } else {
// From filesystem if (descriptor->createChannels > 0) {
imageType = DetermineImageType(descriptor->file.data()); // Create new image
if (imageType != ImageType::UNKNOWN) { std::vector<double> background = {
try { descriptor->createBackground[0],
vips::VOption *option = VImage::option()->set("access", accessMethod); descriptor->createBackground[1],
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { descriptor->createBackground[2]
option->set("dpi", static_cast<double>(descriptor->density)); };
} if (descriptor->createChannels == 4) {
if (imageType == ImageType::MAGICK) { background.push_back(descriptor->createBackground[3]);
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");
} }
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
imageType = ImageType::RAW;
} else { } 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<double>(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); return std::make_tuple(image, imageType);

View File

@ -52,6 +52,10 @@ namespace sharp {
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
int createChannels;
int createWidth;
int createHeight;
double createBackground[4];
InputDescriptor(): InputDescriptor():
buffer(nullptr), buffer(nullptr),
@ -59,7 +63,15 @@ namespace sharp {
density(72), density(72),
rawChannels(0), rawChannels(0),
rawWidth(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 // Convenience methods to access the attributes of a v8::Object

View File

@ -1100,7 +1100,7 @@ NAN_METHOD(pipeline) {
// Background colour // Background colour
v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background"); v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background");
for (unsigned int i = 0; i < 4; i++) { for (unsigned int i = 0; i < 4; i++) {
baton->background[i] = AttrTo<uint32_t>(background, i); baton->background[i] = AttrTo<double>(background, i);
} }
// Overlay options // Overlay options
if (HasAttr(options, "overlay")) { if (HasAttr(options, "overlay")) {

BIN
test/fixtures/expected/create-rgb.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

BIN
test/fixtures/expected/create-rgba.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

View File

@ -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) { it('Queue length change events', function (done) {
let eventCounter = 0; let eventCounter = 0;
const queueListener = function (queueLength) { const queueListener = function (queueLength) {