From 17f942c802fd31eb7ebd4ed67f0b04d4365c4e28 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Tue, 2 Oct 2018 17:11:25 +0100 Subject: [PATCH] Add chromaSubsampling and isProgressive to metadata #1186 --- docs/api-input.md | 2 ++ docs/changelog.md | 3 ++ lib/input.js | 2 ++ src/metadata.cc | 12 +++++++ src/metadata.h | 3 ++ test/unit/metadata.js | 74 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+) diff --git a/docs/api-input.md b/docs/api-input.md index ba94da13..f15677b6 100644 --- a/docs/api-input.md +++ b/docs/api-input.md @@ -31,6 +31,8 @@ A Promises/A+ promise is returned when `callback` is not provided. - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2] - `density`: Number of pixels per inch (DPI), if present +- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK +- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan - `hasProfile`: Boolean indicating the presence of an embedded ICC profile - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `orientation`: Number value of the EXIF Orientation header, if present diff --git a/docs/changelog.md b/docs/changelog.md index c2906e0c..348a78cf 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -22,6 +22,9 @@ Requires libvips v8.7.0. * Switch from custom trim operation to `vips_find_trim`. [#914](https://github.com/lovell/sharp/issues/914) +* Add `chromaSubsampling` and `isProgressive` properties to `metadata` response. + [#1186](https://github.com/lovell/sharp/issues/1186) + * Drop Node 4 support. [#1212](https://github.com/lovell/sharp/issues/1212) diff --git a/lib/input.js b/lib/input.js index 0182df49..5850f76b 100644 --- a/lib/input.js +++ b/lib/input.js @@ -183,6 +183,8 @@ function clone () { * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672) * - `density`: Number of pixels per inch (DPI), if present + * - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK + * - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `orientation`: Number value of the EXIF Orientation header, if present diff --git a/src/metadata.cc b/src/metadata.cc index 996cf3f0..12490d1d 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -62,6 +62,12 @@ class MetadataWorker : public Nan::AsyncWorker { if (sharp::HasDensity(image)) { baton->density = sharp::GetDensity(image); } + if (image.get_typeof("jpeg-chroma-subsample") == VIPS_TYPE_REF_STRING) { + baton->chromaSubsampling = image.get_string("jpeg-chroma-subsample"); + } + if (image.get_typeof("interlaced") == G_TYPE_INT) { + baton->isProgressive = image.get_int("interlaced") == 1; + } baton->hasProfile = sharp::HasProfile(image); // Derived attributes baton->hasAlpha = sharp::HasAlpha(image); @@ -125,6 +131,12 @@ class MetadataWorker : public Nan::AsyncWorker { if (baton->density > 0) { Set(info, New("density").ToLocalChecked(), New(baton->density)); } + if (!baton->chromaSubsampling.empty()) { + Set(info, + New("chromaSubsampling").ToLocalChecked(), + New(baton->chromaSubsampling).ToLocalChecked()); + } + Set(info, New("isProgressive").ToLocalChecked(), New(baton->isProgressive)); Set(info, New("hasProfile").ToLocalChecked(), New(baton->hasProfile)); Set(info, New("hasAlpha").ToLocalChecked(), New(baton->hasAlpha)); if (baton->orientation > 0) { diff --git a/src/metadata.h b/src/metadata.h index c57a6b1a..6907b182 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -31,6 +31,8 @@ struct MetadataBaton { int channels; std::string depth; int density; + std::string chromaSubsampling; + bool isProgressive; bool hasProfile; bool hasAlpha; int orientation; @@ -50,6 +52,7 @@ struct MetadataBaton { height(0), channels(0), density(0), + isProgressive(false), hasProfile(false), hasAlpha(false), orientation(0), diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 92820530..569d12f3 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -19,6 +19,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -38,6 +40,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual(72, metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(8, metadata.orientation); @@ -85,6 +89,8 @@ describe('Image metadata', function () { assert.strictEqual(1, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual(300, metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(1, metadata.orientation); @@ -104,6 +110,8 @@ describe('Image metadata', function () { assert.strictEqual(1, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual(300, metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -123,6 +131,8 @@ describe('Image metadata', function () { assert.strictEqual(4, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual(72, metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -142,6 +152,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -160,6 +172,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -177,6 +191,8 @@ describe('Image metadata', function () { assert.strictEqual(2, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('undefined', typeof metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -195,6 +211,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -224,6 +242,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -247,6 +267,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -268,6 +290,8 @@ describe('Image metadata', function () { assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth); assert.strictEqual('undefined', typeof metadata.density); + assert.strictEqual('4:2:0', metadata.chromaSubsampling); + assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); @@ -346,6 +370,56 @@ describe('Image metadata', function () { }); }); + it('chromaSubsampling 4:4:4:4 CMYK JPEG', function () { + return sharp(fixtures.inputJpgWithCmykProfile) + .metadata() + .then(function (metadata) { + assert.strictEqual('4:4:4:4', metadata.chromaSubsampling); + }); + }); + + it('chromaSubsampling 4:4:4 RGB JPEG', function () { + return sharp(fixtures.inputJpg) + .resize(10, 10) + .jpeg({ chromaSubsampling: '4:4:4' }) + .toBuffer() + .then(function (data) { + return sharp(data) + .metadata() + .then(function (metadata) { + assert.strictEqual('4:4:4', metadata.chromaSubsampling); + }); + }); + }); + + it('isProgressive JPEG', function () { + return sharp(fixtures.inputJpg) + .resize(10, 10) + .jpeg({ progressive: true }) + .toBuffer() + .then(function (data) { + return sharp(data) + .metadata() + .then(function (metadata) { + assert.strictEqual(true, metadata.isProgressive); + }); + }); + }); + + it('isProgressive PNG', function () { + return sharp(fixtures.inputJpg) + .resize(10, 10) + .png({ progressive: true }) + .toBuffer() + .then(function (data) { + return sharp(data) + .metadata() + .then(function (metadata) { + assert.strictEqual(true, metadata.isProgressive); + }); + }); + }); + it('File input with corrupt header fails gracefully', function (done) { sharp(fixtures.inputJpgWithCorruptHeader) .metadata(function (err) {