From 664d5e48313269db8344db337ac34eaa4968e25b Mon Sep 17 00:00:00 2001 From: mastrodaro Date: Sat, 25 Jul 2020 01:13:41 +0200 Subject: [PATCH 1/2] Added possibility to skip tRNS chunk and set palette dividable by 3 --- lib/pngstream.js | 11 +++++++---- src/Canvas.cc | 16 ++++++++++++---- src/PNG.h | 16 ++++++++++------ src/closure.h | 1 + test/image.test.js | 27 ++++++++++++++++++++++++--- types/index.d.ts | 6 +++++- 6 files changed, 59 insertions(+), 18 deletions(-) diff --git a/lib/pngstream.js b/lib/pngstream.js index 021bb7fd9..50903f08a 100644 --- a/lib/pngstream.js +++ b/lib/pngstream.js @@ -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 || {}; }; diff --git a/src/Canvas.cc b/src/Canvas.cc index 2119c68e1..47357695d 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -263,14 +263,17 @@ static void parsePNGArgs(Local arg, PngClosure& pngargs) { Local filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked(); if (filters->IsUint32()) pngargs.filters = Nan::To(filters).FromMaybe(0); + Local alpha = Nan::Get(obj, Nan::New("alpha").ToLocalChecked()).ToLocalChecked(); + pngargs.alpha = Nan::To(alpha).FromMaybe(true); Local palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked(); if (palette->IsUint8ClampedArray()) { Local palette_ta = palette.As(); 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 _paletteColors(palette_ta); pngargs.palette = *_paletteColors; // Optional background color index: @@ -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); diff --git a/src/PNG.h b/src/PNG.h index 30b88f85f..9f27ad219 100644 --- a/src/PNG.h +++ b/src/PNG.h @@ -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); diff --git a/src/closure.h b/src/closure.h index 3126114eb..8b5dbb3b5 100644 --- a/src/closure.h +++ b/src/closure.h @@ -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; diff --git a/test/image.test.js b/test/image.test.js index f8d37e465..1b9196c35 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -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(); @@ -386,7 +407,7 @@ describe('Image', function () { 255, 0, 0, 127, 255, 255, 255, 127, ]); - + done(); }; @@ -404,7 +425,7 @@ describe('Image', function () { testImgd(img, [ 255, 0, 0, 255, ]); - + done(); }; @@ -423,7 +444,7 @@ describe('Image', function () { 255, 0, 0, 255, 0, 255, 0, 255, ]); - + done(); }; diff --git a/types/index.d.ts b/types/index.d.ts index f4128045f..50d0c1dae 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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 /** From 7d82162e173e777c396bae158f904e83d68c7dbb Mon Sep 17 00:00:00 2001 From: mastrodaro Date: Sat, 25 Jul 2020 01:19:40 +0200 Subject: [PATCH 2/2] updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10aac0d31..fa1824253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)