From 086947af420b3aee2b84dbd52d14c30dd25a490e Mon Sep 17 00:00:00 2001 From: Zach Bjornson Date: Mon, 11 Jun 2018 11:45:32 -0700 Subject: [PATCH] Add resolution option for PNG format Fixes #766 Fixes #716 --- CHANGELOG.md | 2 ++ Readme.md | 12 ++++++++---- src/PNG.h | 6 +++++- src/closure.h | 1 + test/canvas.test.js | 20 ++++++++++++++++++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88215e7f9..b9a108919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,8 @@ canvas.createJPEGStream() // new * Support for `canvas.toBuffer("image/jpeg")` * Unified configuration options for `canvas.toBuffer()`, `canvas.pngStream()` and `canvas.jpegStream()` + * Added `resolution` option for `canvas.toBuffer("image/png")` and + `canvas.createPNGStream()` 1.6.x (unreleased) ================== diff --git a/Readme.md b/Readme.md index 994afa94e..dd4a74f3b 100644 --- a/Readme.md +++ b/Readme.md @@ -154,10 +154,14 @@ image contained in the canvas. `{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties are optional. * For `image/png`, an object specifying the ZLIB compression level (between 0 - and 9), the compression filter(s), the palette (indexed PNGs only) and/or - the background palette index (indexed PNGs only): - `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0}`. + and 9), the compression filter(s), the palette (indexed PNGs only), the + the background palette index (indexed PNGs only) and/or the resolution (ppi): + `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional. + + Note that the PNG format encodes the resolution in pixels per meter, so if + you specify `96`, the file will encode 3780 ppm (~96.01 ppi). The resolution + is undefined by default to match common browser behavior. **Return value** @@ -211,7 +215,7 @@ that emits PNG-encoded data. * `config` An object specifying the ZLIB compression level (between 0 and 9), the compression filter(s), the palette (indexed PNGs only) and/or the background palette index (indexed PNGs only): - `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0}`. + `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional. #### Examples diff --git a/src/PNG.h b/src/PNG.h index e35f24f09..e0bd86a66 100644 --- a/src/PNG.h +++ b/src/PNG.h @@ -6,7 +6,7 @@ #include #include #include - +#include // round #include "closure.h" #if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) @@ -166,6 +166,10 @@ static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr writ png_set_write_fn(png, closure, write_func, canvas_png_flush); png_set_compression_level(png, closure->closure->compressionLevel); png_set_filter(png, 0, closure->closure->filters); + if (closure->closure->resolution != 0) { + uint32_t res = static_cast(round(static_cast(closure->closure->resolution) * 39.3701)); + png_set_pHYs(png, info, res, res, PNG_RESOLUTION_METER); + } cairo_format_t format = cairo_image_surface_get_format(surface); diff --git a/src/closure.h b/src/closure.h index cdfa91214..0c2a18075 100644 --- a/src/closure.h +++ b/src/closure.h @@ -45,6 +45,7 @@ struct PdfSvgClosure : Closure { struct PngClosure : Closure { uint32_t compressionLevel = 6; uint32_t filters = PNG_ALL_FILTERS; + uint32_t resolution = 0; // 0 = unspecified // Indexed PNGs: uint32_t nPaletteColors = 0; uint8_t* palette = NULL; diff --git a/test/canvas.test.js b/test/canvas.test.js index 600633bed..85e8b32e5 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -497,6 +497,26 @@ describe('Canvas', function () { var buf = createCanvas(200,200).toBuffer('image/png'); assert.equal('PNG', buf.slice(1,4).toString()); }); + + it('Canvas#toBuffer("image/png", {resolution: 96})', function () { + const buf = createCanvas(200, 200).toBuffer('image/png', {resolution: 96}); + // 3780 ppm ~= 96 ppi + for (let i = 0; i < buf.length - 12; i++) { + if (buf[i] === 0x70 && + buf[i + 1] === 0x48 && + buf[i + 2] === 0x59 && + buf[i + 3] === 0x73) { // pHYs + assert.equal(buf[i + 4], 0); + assert.equal(buf[i + 5], 0); + assert.equal(buf[i + 6], 0x0e); + assert.equal(buf[i + 7], 0xc4); // x + assert.equal(buf[i + 8], 0); + assert.equal(buf[i + 9], 0); + assert.equal(buf[i + 10], 0x0e); + assert.equal(buf[i + 11], 0xc4); // y + } + } + }) it('Canvas#toBuffer("image/png", {compressionLevel: 5})', function () { var buf = createCanvas(200,200).toBuffer('image/png', {compressionLevel: 5});