Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
* Speed up `fillStyle=` and `strokeStyle=`
### Added
* Export `rsvgVersion`.
* Control over tRNS chunk generation.
### Fixed
* Fix BMP issues. (#1497)
* Update typings to support jpg and addPage on NodeCanvasRenderingContext2D (#1509)
Expand Down
11 changes: 7 additions & 4 deletions lib/pngstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ var PNGStream = module.exports = function PNGStream(canvas, options) {
}

Readable.call(this);
this.alpha = options && options.alpha !== undefined ? options.alpha : true;

if (options &&
options.palette instanceof Uint8ClampedArray &&
options.palette.length % 4 !== 0) {
throw new Error("Palette length must be a multiple of 4.");
if (options && options.palette instanceof Uint8ClampedArray) {
var divider = this.alpha ? 4 : 3;
if (options.palette.length % divider !== 0) {
throw new Error(`Palette length must be a multiple of ${divider}.`);
}
}

this.canvas = canvas;
this.options = options || {};
};
Expand Down
16 changes: 12 additions & 4 deletions src/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,17 @@ static void parsePNGArgs(Local<Value> arg, PngClosure& pngargs) {
Local<Value> filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked();
if (filters->IsUint32()) pngargs.filters = Nan::To<uint32_t>(filters).FromMaybe(0);

Local<Value> alpha = Nan::Get(obj, Nan::New("alpha").ToLocalChecked()).ToLocalChecked();
pngargs.alpha = Nan::To<bool>(alpha).FromMaybe(true);
Local<Value> palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked();
if (palette->IsUint8ClampedArray()) {
Local<Uint8ClampedArray> palette_ta = palette.As<Uint8ClampedArray>();
pngargs.nPaletteColors = palette_ta->Length();
if (pngargs.nPaletteColors % 4 != 0) {
throw "Palette length must be a multiple of 4.";
uint8_t divider = pngargs.alpha ? 4 : 3;
if (pngargs.nPaletteColors % divider != 0) {
throw "Palette length must be a multiple of 4 (with alpha) or 3 (without alpha).";
}
pngargs.nPaletteColors /= 4;
pngargs.nPaletteColors /= divider;
Nan::TypedArrayContents<uint8_t> _paletteColors(palette_ta);
pngargs.palette = *_paletteColors;
// Optional background color index:
Expand Down Expand Up @@ -424,10 +427,15 @@ NAN_METHOD(Canvas::ToBuffer) {
try {
PngClosure closure(canvas);
parsePNGArgs(info[1], closure);
if (closure.nPaletteColors == 0xFFFFFFFF) {

if (closure.alpha && closure.nPaletteColors == 0xFFFFFFFF) {
Nan::ThrowError("Palette length must be a multiple of 4.");
return;
}
if (!closure.alpha && closure.nPaletteColors == 0xFFFFFF) {
Nan::ThrowError("Palette length must be a multiple of 3.");
return;
}

Nan::TryCatch try_catch;
status = canvas_write_to_png_stream(canvas->surface(), PngClosure::writeVec, &closure);
Expand Down
16 changes: 10 additions & 6 deletions src/PNG.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,20 +213,24 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ

png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

uint8_t backgroundIndex = closure->closure->backgroundIndex;
if (png_color_type == PNG_COLOR_TYPE_PALETTE) {
size_t nColors = closure->closure->nPaletteColors;
bool alpha = closure->closure->alpha;
uint8_t* colors = closure->closure->palette;
uint8_t backgroundIndex = closure->closure->backgroundIndex;
png_colorp pngPalette = (png_colorp)png_malloc(png, nColors * sizeof(png_colorp));
png_bytep transparency = (png_bytep)png_malloc(png, nColors * sizeof(png_bytep));
uint8_t divider = alpha ? 4 : 3;
for (i = 0; i < nColors; i++) {
pngPalette[i].red = colors[4 * i];
pngPalette[i].green = colors[4 * i + 1];
pngPalette[i].blue = colors[4 * i + 2];
transparency[i] = colors[4 * i + 3];
pngPalette[i].red = colors[divider * i];
pngPalette[i].green = colors[divider * i + 1];
pngPalette[i].blue = colors[divider * i + 2];
if (alpha) transparency[i] = colors[4 * i + 3];
}
png_set_PLTE(png, info, pngPalette, nColors);
png_set_tRNS(png, info, transparency, nColors, NULL);
if (alpha) {
png_set_tRNS(png, info, transparency, nColors, NULL);
}
png_set_packing(png); // pack pixels
// have libpng free palette and trans:
png_data_freer(png, info, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_PLTE | PNG_FREE_TRNS);
Expand Down
1 change: 1 addition & 0 deletions src/closure.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct PngClosure : Closure {
uint32_t resolution = 0; // 0 = unspecified
// Indexed PNGs:
uint32_t nPaletteColors = 0;
bool alpha = true;
uint8_t* palette = nullptr;
uint8_t backgroundIndex = 0;

Expand Down
27 changes: 24 additions & 3 deletions test/image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,27 @@ describe('Image', function () {
assert.ok(!keys.includes('setSource'));
});

it('does not create chunks tRNS and bKGD if they are irrelevant', function() {
var bKGD = "624b4744";
var PLTE = "504c5445";
var tRNS = "74524e53";
var palette = new Uint8ClampedArray([0, 0, 0, 0, 0, 100, 0, 0, 200]);

var canvas = createCanvas(10, 10);
var ctx = canvas.getContext('2d', { pixelFormat: 'A8' })
var idata = ctx.getImageData(0, 0, 10, 10);
idata[0] = 0;
idata[1] = 1;
idata[2] = 2;
ctx.putImageData(idata, 0, 0);

// getting backgroundIndex out of palette to make bKGD to not appear
var bufferRGBNoBg = canvas.toBuffer('image/png', {compressionLevel: 0, filters: undefined, palette: palette, backgroundIndex: 3});
assert.strictEqual(bufferRGBNoBg.includes(PLTE, 0, "hex"), true);
assert.strictEqual(bufferRGBNoBg.includes(tRNS, 0, "hex"), false);
assert.strictEqual(bufferRGBNoBg.includes(bKGD, 0, "hex"), false);
});

describe('supports BMP', function () {
it('parses 1-bit image', function (done) {
let img = new Image();
Expand Down Expand Up @@ -386,7 +407,7 @@ describe('Image', function () {
255, 0, 0, 127,
255, 255, 255, 127,
]);

done();
};

Expand All @@ -404,7 +425,7 @@ describe('Image', function () {
testImgd(img, [
255, 0, 0, 255,
]);

done();
};

Expand All @@ -423,7 +444,7 @@ describe('Image', function () {
255, 0, 0, 255,
0, 255, 0, 255,
]);

done();
};

Expand Down
6 changes: 5 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ export interface PngConfig {
* filters. Defaults to `canvas.PNG_ALL_FITLERS`.
*/
filters?: number
/**
* _For creating indexed PNGs._ Decide to include alpha channel. Defaults to true.
*/
alpha?: boolean
/**
* _For creating indexed PNGs._ The palette of colors. Entries should be in
* RGBA order.
* RGBA order (with alpha) or RGB (without alpha).
*/
palette?: Uint8ClampedArray
/**
Expand Down