From 2e9cd83ed29af7dc20872540ec8991e07019d604 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 25 Jun 2016 17:48:01 +0200 Subject: [PATCH] Add support for clipping/cutting out (#435) (#448) USAGE: overlayWith('overlayimage.png', { cutout: true } ) --- index.js | 14 +++- package.json | 3 +- src/common.cc | 13 ++++ src/common.h | 2 + src/operations.cc | 59 +++++++++++++++++ src/operations.h | 5 ++ src/pipeline.cc | 18 ++++-- src/pipeline.h | 2 + .../overlay-cutout-gravity-center.jpg | Bin 0 -> 795 bytes .../overlay-cutout-gravity-centre.jpg | Bin 0 -> 795 bytes .../expected/overlay-cutout-gravity-east.jpg | Bin 0 -> 692 bytes .../expected/overlay-cutout-gravity-north.jpg | Bin 0 -> 745 bytes .../overlay-cutout-gravity-northeast.jpg | Bin 0 -> 633 bytes .../overlay-cutout-gravity-northwest.jpg | Bin 0 -> 725 bytes .../expected/overlay-cutout-gravity-south.jpg | Bin 0 -> 823 bytes .../overlay-cutout-gravity-southeast.jpg | Bin 0 -> 745 bytes .../overlay-cutout-gravity-southwest.jpg | Bin 0 -> 743 bytes .../expected/overlay-cutout-gravity-west.jpg | Bin 0 -> 745 bytes ...lay-cutout-rotated90-gravity-northwest.jpg | Bin 0 -> 699 bytes .../expected/overlay-cutout-rotated90.jpg | Bin 0 -> 842 bytes test/unit/overlay.js | 61 +++++++++++++++++- 21 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/expected/overlay-cutout-gravity-center.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-centre.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-east.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-north.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-northeast.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-northwest.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-south.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-southeast.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-southwest.jpg create mode 100644 test/fixtures/expected/overlay-cutout-gravity-west.jpg create mode 100644 test/fixtures/expected/overlay-cutout-rotated90-gravity-northwest.jpg create mode 100644 test/fixtures/expected/overlay-cutout-rotated90.jpg diff --git a/index.js b/index.js index 8cef1a59..bd3f9555 100644 --- a/index.js +++ b/index.js @@ -93,6 +93,7 @@ var Sharp = function(input, options) { overlayBufferIn: null, overlayGravity: 0, overlayTile: false, + overlayCutout: false, // output options formatOut: 'input', fileOut: '', @@ -361,9 +362,18 @@ Sharp.prototype.overlayWith = function(overlay, options) { else if (isBoolean(options.tile)) { this.options.overlayTile = options.tile; } else { - throw new Error(' Invalid Value for tile ' + options.tile + 'Only Boolean Values allowed for overlay.tile.'); + throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean Values allowed for overlay.tile.'); } - + + if (typeof options.cutout === 'undefined') { + this.options.overlayCutout = false; + } + else if (isBoolean(options.cutout)) { + this.options.overlayCutout = options.cutout; + } else { + throw new Error('Invalid Value for cutout ' + options.cutout + ' Only Boolean Values allowed for overlay.cutout.'); + } + if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) { this.options.overlayGravity = options.gravity; } else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) { diff --git a/package.json b/package.json index 0b83f592..d988a5db 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "Felix Bünemann ", "Samy Al Zahrani ", "Chintan Thakkar ", - "F. Orlando Galashan " + "F. Orlando Galashan ", + "Kleis Auke Wolthuizen " ], "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "scripts": { diff --git a/src/common.cc b/src/common.cc index 0afe6a65..cce65eb7 100644 --- a/src/common.cc +++ b/src/common.cc @@ -277,4 +277,17 @@ namespace sharp { return std::make_tuple(left, top); } + /* + Return the image alpha maximum. Useful for combining alpha bands. scRGB + images are 0 - 1 for image data, but the alpha is 0 - 255. + */ + int MaximumImageAlpha(VipsInterpretation interpretation) { + if(interpretation == VIPS_INTERPRETATION_RGB16 || + interpretation == VIPS_INTERPRETATION_GREY16) { + return (65535); + } else { + return (255); + } + } + } // namespace sharp diff --git a/src/common.h b/src/common.h index 41c02aed..c615d3e5 100644 --- a/src/common.h +++ b/src/common.h @@ -108,6 +108,8 @@ namespace sharp { std::tuple CalculateCrop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity); + int MaximumImageAlpha(VipsInterpretation interpretation); + } // namespace sharp #endif // SRC_COMMON_H_ diff --git a/src/operations.cc b/src/operations.cc index ac5b4d4d..8e146a5f 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -81,6 +81,65 @@ namespace sharp { return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0); } + /* + Cutout src over dst with given gravity. + */ + VImage Cutout(VImage mask, VImage dst, const int gravity) { + using sharp::CalculateCrop; + using sharp::HasAlpha; + using sharp::MaximumImageAlpha; + + bool maskHasAlpha = HasAlpha(mask); + + if (!maskHasAlpha && mask.bands() > 1) { + throw VError("Overlay image must have an alpha channel or one band"); + } + if (!HasAlpha(dst)) { + throw VError("Image to be overlaid must have an alpha channel"); + } + if (mask.width() > dst.width() || mask.height() > dst.height()) { + throw VError("Overlay image must have same dimensions or smaller"); + } + + // Enlarge overlay mask, if required + if (mask.width() < dst.width() || mask.height() < dst.height()) { + // Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity. + int left; + int top; + std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), mask.width(), mask.height(), gravity); + // Embed onto transparent background + std::vector background { 0.0, 0.0, 0.0, 0.0 }; + mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option() + ->set("extend", VIPS_EXTEND_BACKGROUND) + ->set("background", background) + ); + } + + // we use the mask alpha if it has alpha + if(maskHasAlpha) { + mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));; + } + + // Split dst into an optional alpha + VImage dstAlpha = dst.extract_band(dst.bands() - 1, VImage::option()->set("n", 1)); + + // we use the dst non-alpha + dst = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1)); + + // the range of the mask and the image need to match .. one could be + // 16-bit, one 8-bit + int dstMax = MaximumImageAlpha(dst.interpretation()); + int maskMax = MaximumImageAlpha(mask.interpretation()); + + // combine the new mask and the existing alpha ... there are + // many ways of doing this, mult is the simplest + mask = dstMax * ((mask / maskMax) * (dstAlpha / dstMax)); + + // append the mask to the image data ... the mask might be float now, + // we must cast the format down to match the image data + return dst.bandjoin(mask.cast(dst.format())); + } + /* * Stretch luminance to cover full dynamic range. */ diff --git a/src/operations.h b/src/operations.h index 4877b431..fa10b7d2 100644 --- a/src/operations.h +++ b/src/operations.h @@ -14,6 +14,11 @@ namespace sharp { */ VImage Composite(VImage src, VImage dst, const int gravity); + /* + Cutout src over dst with given gravity. + */ + VImage Cutout(VImage src, VImage dst, const int gravity); + /* * Stretch luminance to cover full dynamic range. */ diff --git a/src/pipeline.cc b/src/pipeline.cc index f9b01456..07464540 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -45,6 +45,7 @@ using vips::VOption; using vips::VError; using sharp::Composite; +using sharp::Cutout; using sharp::Normalize; using sharp::Gamma; using sharp::Blur; @@ -464,8 +465,9 @@ class PipelineWorker : public AsyncWorker { bool shouldBlur = baton->blurSigma != 0.0; bool shouldSharpen = baton->sharpenSigma != 0.0; bool shouldThreshold = baton->threshold != 0; + bool shouldCutout = baton->overlayCutout; bool shouldPremultiplyAlpha = HasAlpha(image) && - (shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay); + (shouldAffineTransform || shouldBlur || shouldSharpen || (hasOverlay && !shouldCutout)); // Premultiply image alpha channel before all transformations to avoid // dark fringing around bright pixels @@ -699,10 +701,15 @@ class PipelineWorker : public AsyncWorker { // the overlayGravity was used for extract_area, therefore set it back to its default value of 0 baton->overlayGravity = 0; } - // Ensure overlay is premultiplied sRGB - overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply(); - // Composite images with given gravity - image = Composite(overlayImage, image, baton->overlayGravity); + if(shouldCutout) { + // 'cut out' the image, premultiplication is not required + image = Cutout(overlayImage, image, baton->overlayGravity); + } else { + // Ensure overlay is premultiplied sRGB + overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply(); + // Composite images with given gravity + image = Composite(overlayImage, image, baton->overlayGravity); + } } // Reverse premultiplication after all transformations: @@ -1086,6 +1093,7 @@ NAN_METHOD(pipeline) { } baton->overlayGravity = attrAs(options, "overlayGravity"); baton->overlayTile = attrAs(options, "overlayTile"); + baton->overlayCutout = attrAs(options, "overlayCutout"); // Resize options baton->withoutEnlargement = attrAs(options, "withoutEnlargement"); baton->crop = attrAs(options, "crop"); diff --git a/src/pipeline.h b/src/pipeline.h index f366af8d..5a6b9a64 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -34,6 +34,7 @@ struct PipelineBaton { size_t overlayBufferInLength; int overlayGravity; bool overlayTile; + bool overlayCutout; int topOffsetPre; int leftOffsetPre; int widthPre; @@ -99,6 +100,7 @@ struct PipelineBaton { overlayBufferInLength(0), overlayGravity(0), overlayTile(false), + overlayCutout(false), topOffsetPre(-1), topOffsetPost(-1), channels(0), diff --git a/test/fixtures/expected/overlay-cutout-gravity-center.jpg b/test/fixtures/expected/overlay-cutout-gravity-center.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e7bbaeb6cb9acef85035f9d3f8d23f02dd0a019 GIT binary patch literal 795 zcmex=5A1R0qH8UG()kYQkCU}9!qWsw zfrlCB2tj5+2788869pLs1q9)QaQ(J#b$fT}hFsioJO5SCt2d{E4A`3w@8t5gYxw-6 zw&#N9vQz7gwN_nbTJ~$}&d`(xLe<|N&yS2>`fTcr$fq8AHwEdsAMA~HUXqe+9-f;;h|NE~KViO6;6L;2{m#e7@`YEQSpFf#r|Q_^{sgldeLCxwJ!snV zYs0rUwlxQZdDE_bc`VnuWBdFE>Wq(<+!y{?@W)`{%*mUr mH}4ipvzg?tGPCD?#EIkct4pn1TwbZTIJmg5U?KSb-vj_%0~eJ5 literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-centre.jpg b/test/fixtures/expected/overlay-cutout-gravity-centre.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e7bbaeb6cb9acef85035f9d3f8d23f02dd0a019 GIT binary patch literal 795 zcmex=5A1R0qH8UG()kYQkCU}9!qWsw zfrlCB2tj5+2788869pLs1q9)QaQ(J#b$fT}hFsioJO5SCt2d{E4A`3w@8t5gYxw-6 zw&#N9vQz7gwN_nbTJ~$}&d`(xLe<|N&yS2>`fTcr$fq8AHwEdsAMA~HUXqe+9-f;;h|NE~KViO6;6L;2{m#e7@`YEQSpFf#r|Q_^{sgldeLCxwJ!snV zYs0rUwlxQZdDE_bc`VnuWBdFE>Wq(<+!y{?@W)`{%*mUr mH}4ipvzg?tGPCD?#EIkct4pn1TwbZTIJmg5U?KSb-vj_%0~eJ5 literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-east.jpg b/test/fixtures/expected/overlay-cutout-gravity-east.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4ed987daf20e4f5614d31d31abcdfec0c772314 GIT binary patch literal 692 zcmex=5A1R0qH8UG()kYZqDWMp7wWIzBmb|w~94xpT-00R>fBQp~lBP&dnk%^gwRZx*l zNLb0xF>qlbd*j572Z2h(fu;b>K?clBEI-K2T$npC7{fgfnxx6{fYRXcd ztJB=`WTI~7{`|Cm(R=S}>Z?n>EpJbotF>~o+Uz*znsQau+b`ekG}x2<_UJzG77nxxO;{`JO@s5g)WLXJBCbe-i+x!0r72 literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-north.jpg b/test/fixtures/expected/overlay-cutout-gravity-north.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a9256b19e91d6c2c029c462d24c057e62ccf8f7b GIT binary patch literal 745 zcmex=5A1R0qH8UG()kYZqDU}j`wWIzC*D>;~0fO6Ub3`~s7%*;$|%rJRICT12vRyH9; zLq~RDqrk+*g&UO$izWgUO90J)D}xxu#KOu9l#v%?1e(KwED1J7$WT#9dEx(C3_L(7 zCP8LF278954h&4mj<>JRI(v4nme681)2GKov%Lk+pI(&z!Fjsx;vj$To(GRsMPJL4 zcz>Dy+liE!Ys=exLc620OSWdkO}U-%t?sY)t?gfx)$WAYuGsN-n{dAEujg+N zBmoVwrYPkN-6zLw#;A0JL<+l%*G8orzAd2uz@ zmUOLBMOBS+`h2=&nLkqU;{rF8Z0F8<=^LG$Rc(7}>$2>dc3U;QSN~@S%vkuY`n0as zQtRDX8)o~rdK`Uv^sjgP(tDe3-Fvy=_V0IV_P+FQ-L_n1hsxzcYj5|-iL1;y{4c`u z5A1R0qH8UG()kYr$FU}j`wKmtI=vU32Xv;-KK7@3)wSXr21vW!g3EP||Tib94; zj)932g$oxR6xnzYs8*a21YpX*G&2*(d?`V&46+;}lOS`T;{RI=Jj{$hhcF8=*fV@u z0M^5~ZC2^gGa?TbOPGArwEOd0IP+!5%R7femuenec3(m*_odY|iOeM7!ub1drWW$V z9DIDjYTf!pmnNT;+T32JGWqY+?^dB#xs2CpXa+U;1$RG8ef-DtO48CWF3-fR^EiM1 zTcT^WmP$`W7GjJ~|v>HWU%S!vGv-bdb*S&u_5DW+#jIv=%3w>;T(@75gs zb-EfsQ&hPt?3QhwYH1gKB>AV__O`G8Ts3dJ>5A1R0qH8UG()kYZp20tRLV1Yl!lVPxe1%4rHPFfucvnzCSH69R4NWKg%M!}kj26bGGAU0s0RpOCIBTEfo2E_DH)0=I{v@Kz{3nw zAjmApV9#)+YQLcqwz;a?@FBw@0#Dde@%*Jm=d5i}!1;T&=21zU(tE z`fkd1PNhoT&c!#ge;ngr*=_l}o&Wv(%gWJebB!+T;7=-d-6CYp!=Cc_{)sPpUUlS! znkt+Yp3Pm!V6l6?%DgL|H%83t{I>bg?Im%_Go~cJF$@jyc$IE7&&Ye*#62eI4g59r z+E@H@SGF-*&*D*dTwzn*e`?Rt=A7z?OEr&G{qD;tZ`@{kmcQn?&STX*^RE@`_C9Z8 zv{y-a${{7)8yp=C9Uam4BV?04=2`?NonM|ukEe%wC)G}COZXWq^~*@euv z%$eRXw}}aGvMW4jbzk@7Yn?KlNvS-M+s2_{T4* zeSMb8Ql|ZQGsT{hS1Uov+`&001C+B F2>=5J?Dzlx literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-south.jpg b/test/fixtures/expected/overlay-cutout-gravity-south.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4d0fced46bff4cdd27da1326c866cddd7a680e36 GIT binary patch literal 823 zcmex=5A1R0qH8UG()kYZqDU}j`wWIzBGR(1|1HlUoI00ScnBQrA-3mY>;nvsEtnMIIQ z(U6VZQAk)sDKJq~*{HB_;lzyxf$GJ9=Af9wz`)AP1eA~$1ZrerWJR)tk%?JIkOgFf z;{RI=Jj_4^g3N*p_6$#5TpV0nSg;d83QM!f+MQYYGanvY&i*s-+V!LQWQa zoi$xy=7%=JMQ$e#Hy$(l8*!{A{hU~3{_XX;hRN@uTkTqJ-A(&yoORaL#pT86N20I9 zf6tWHd(y-6cSq>0=lP{))zo}#r3;+qaJMtRJG`ZVff0v$~}~1UDF$Q zy!Q2`x+quOBOD#O)0C8$Ztj+?+~4@h^Ry#1ytJl5%L?_kjvi_}0 z?+=!>oNv0K_tnVl6aQa14r{@Uk!eRIv)61rJgqP~qcs1hyRh@plr?`h)L&Y6L z-{?D?+Q&_;m^gJG%e{`0IdSs&yco0FJ)aM9r8i3#EPZxv&!?5|+m^EKTi&;|-1KeZ z(svbQljo=K)^J&qHQ|rqyx(VTzbGi4 LhaBCG|8D{SN9rcD literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-southeast.jpg b/test/fixtures/expected/overlay-cutout-gravity-southeast.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bdc21ceabb22868a95c5057f073e80bb29dca644 GIT binary patch literal 745 zcmex=5A1R0qH8UG()kYZqDU}j`wWIzC*E16h0fN}-`3`|Un%*;$I%$!hZMkZz!RzXEJ zLr0-NcHzWE5u=4cg+)yh7i~Ov5vW4~XcDSn3@ktsfHKm8Ks%V2k?jGRB`Bn5`2Q9I z4^W&*kXewyp5bQz!XaoZ_Wk@GSBx{K-ncQ{Z>qTd%$K*U)i>YVC;v_-*8E~wmcgz+ zw{=&`t#s2pB;&c|o0tFQ$8~4syy&@=c=45)X;h!fs=VkezrE|kWiQ=+`PJ&uyG?JS zZiJWIvtN5UReiJf_MhtKZT3F&In1g0`xJNhi|UPQ^11bPYvsOK+uiMbJmvdeu6^hIC?w($um|u4ew< z71{TV_U2zJ%X)m{%mG$@ke665FUgIRwVm87nl!~o^wOm0wR3{J`ybXXG+&Z*)y;c~ zYW@1t*?%SX@x}hIEKPY7t+jsbd&%R+|Hhep%Gf0zU1xh}&54VC+0&QWyIq|3@BHy? z=ik2Z-_HHE%QUt3QRsett)o#ruV+X9%W&QLJ&sHK=w7ciYyR5vZ56kvdTM#rzb@_C z#q7ggn*SNhzs`Kc-(hgz$aZn>wYuINyA#ir?$@7wX8N|<#piQ-*1gWzb@O(~)6~tA ir#v^`J-KGv{b|W=tM#kiy?HL1+`t%>0F5B_|2F}?UjUH+ literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/overlay-cutout-gravity-southwest.jpg b/test/fixtures/expected/overlay-cutout-gravity-southwest.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5dcdde1ac092b05f38a37743251e4c025dcdb0c1 GIT binary patch literal 743 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 X-)>9TV|3=X0)q{5Y-6(P|K9`vqscdg literal 0 HcmV?d00001 diff --git a/test/unit/overlay.js b/test/unit/overlay.js index dec3ff02..f1e31cc1 100644 --- a/test/unit/overlay.js +++ b/test/unit/overlay.js @@ -264,7 +264,6 @@ describe('Overlays', function() { }); }); - it('With tile enabled and image rotated 90 degrees', function(done) { var expected = fixtures.expected('overlay-tile-rotated90.jpg'); sharp(fixtures.inputJpg) @@ -283,7 +282,6 @@ describe('Overlays', function() { }); }); - it('With tile enabled and image rotated 90 degrees and gravity northwest', function(done) { var expected = fixtures.expected('overlay-tile-rotated90-gravity-northwest.jpg'); sharp(fixtures.inputJpg) @@ -303,4 +301,63 @@ describe('Overlays', function() { }); }); + describe('Overlay with cutout enabled and gravity', function() { + Object.keys(sharp.gravity).forEach(function(gravity) { + it(gravity, function(done) { + var expected = fixtures.expected('overlay-cutout-gravity-' + gravity + '.jpg'); + sharp(fixtures.inputJpg) + .resize(80) + .overlayWith(fixtures.inputPngWithTransparency16bit, { + cutout: true, + gravity: gravity + }) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual('jpeg', info.format); + assert.strictEqual(80, info.width); + assert.strictEqual(65, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(expected, data, done); + }); + }); + }); + }); + + it('With cutout enabled and image rotated 90 degrees', function(done) { + var expected = fixtures.expected('overlay-cutout-rotated90.jpg'); + sharp(fixtures.inputJpg) + .rotate(90) + .resize(80) + .overlayWith(fixtures.inputPngWithTransparency16bit, { + cutout: true + }) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual('jpeg', info.format); + assert.strictEqual(80, info.width); + assert.strictEqual(98, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(expected, data, done); + }); + }); + + it('With cutout enabled and image rotated 90 degrees and gravity northwest', function(done) { + var expected = fixtures.expected('overlay-cutout-rotated90-gravity-northwest.jpg'); + sharp(fixtures.inputJpg) + .rotate(90) + .resize(80) + .overlayWith(fixtures.inputPngWithTransparency16bit, { + cutout: true, + gravity: 'northwest' + }) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual('jpeg', info.format); + assert.strictEqual(80, info.width); + assert.strictEqual(98, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(expected, data, done); + }); + }); + });