From 4d1f7e051dbce01a7c8a08f44f20e67d001333bd Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 12 Jun 2025 11:32:24 +0200 Subject: [PATCH] Support composite op with non-sRGB pipeline colourspace (#4412) --- src/pipeline.cc | 11 ++++++---- .../fixtures/expected/composite-red-scrgb.png | Bin 0 -> 1709 bytes test/unit/composite.js | 20 ++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/expected/composite-red-scrgb.png diff --git a/src/pipeline.cc b/src/pipeline.cc index 7acbd967..679c8329 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -669,7 +669,6 @@ class PipelineWorker : public Napi::AsyncWorker { sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; composite->input->access = access; std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); - compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline); if (composite->input->autoOrient) { // Respect EXIF Orientation @@ -734,8 +733,7 @@ class PipelineWorker : public Napi::AsyncWorker { // gravity was used for extract_area, set it back to its default value of 0 composite->gravity = 0; } - // Ensure image to composite is sRGB with unpremultiplied alpha - compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); + // Ensure image to composite is with unpremultiplied alpha compositeImage = sharp::EnsureAlpha(compositeImage, 1); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply(); // Calculate position @@ -760,7 +758,12 @@ class PipelineWorker : public Napi::AsyncWorker { xs.push_back(left); ys.push_back(top); } - image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys)); + image = VImage::composite(images, modes, VImage::option() + ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST + ? VIPS_INTERPRETATION_sRGB + : baton->colourspacePipeline) + ->set("x", xs) + ->set("y", ys)); image = sharp::RemoveGifPalette(image); } diff --git a/test/fixtures/expected/composite-red-scrgb.png b/test/fixtures/expected/composite-red-scrgb.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2bdf940b2f4e3d494c086a6c89413a780d751e GIT binary patch literal 1709 zcmV;e22%NnP)1FrIgEP*?>8ia~7xD)Lj*I(QFvEOc8exFN>ESG%*^l@sb#C|H-50?I7xe zF-$c6Z~{rcdC%vZ_nhaP7X%Ldmxli>z(*G^a_aPHeskpt|22S5E?;Ie8f8mM3#+QC zSY2Ammi6m7YBux5V340*xNyfXw*~lWW`-w^9b;El7u{|*J(ZPg%+2NQq$CCa`T#Zn zYzH`~X&lbV;z_TU?@dnf=eOU!EzsWre0%jOpMUBp_Vx9#wzihl9uFJLW_GS!%RWux z5WpUQtpIEFdOEYRXiZF{1)$?5R46Wvjczyh?%m5*pLvEq%+CHT%sjvkZ@j^m&Ya=k z;2;|s8dz6X$L7jPcCKH~P<%Wi01p5>1aJ!As{oGx9M&}YQ&Z_P7}&aUCEEb@0}KN6 zSgowCu2yHsnc;9)1twX&1>oY@vutf`RUg*Z*DLGK&gP>49|0Hzcu=orZ*DF_77LGL zWN=o~_~Gwa_!{6R0B`&O%&TcCb8~0SnaAT%LEZxJ?)mfd`F!l`>|}d;JDZxC=(E{4 zv2rC3tXacANeOq>*DJs|Yx8(Gws|vW)6@CIU3c+Qy`IxaNgU10WK&t$pT4cBsbNJ$ z1xrgydHm?nTLC_M5+c4241r27~iixt;ydxun}}x6eB+EG(qM;oyQ@uzUAz zYKJxAB#5h^BkE89(hdFn{p{`SRaey9+`Opm*s+7<6vPaFkt4NSj~&YqqgX#k-LPRFfg1!)Qm3n6YH9}c`ef?$HsmQj%!}m3vN8agdQI~HVxo2eU@#oK z8T}~Nt#i<9E@4Va3xOeOS~tVteTxIU^}-7@c}+2ZCJ>K;xB{;7uVP*}FV-FXpebZh zYBmQUL&|P)X7nB!jpdAw4-$wFXl7z!B_+T`0Yr=n>I!1%_kjrm3B-92`@xKX z4C2Wcz^2e94h#&;LpYsI-gjT?&FGIXEv=F%DTM@@2zZpOUTx=h*RCxNAmh{WfJGG= z`oOG)EaC|eCqONv#r_Y1TY9$4x1~Tx0U8V=1mylZn4N89YU&1Rnu|akfiR1UYZjk| zbGEH+8!-Z9rYaMYN0W)?LHI!&1JMl@1&ijzNler(EiFo5ioiB*+O&lRgMoVeb^?c0 z20A)IO9K#riHpQhelog^yw`UxaVC~})rNksu)AR@n63Te)#EGQ={P^aZ3?2&7=rhvka?<5-G47!_;vo=6VgS~FRVk!E$iR|8laTPB zauy%&qS07Eoi0q^VWy=OGa7wt!OdvFm27%)lH0d$SG!+UFpD@0A_}4lwE1l+a)FTP zDFDgI(r)e4v?^*^JAodqUR}-0PfsqomMyvyelb1Gj*bqN)s)3DAP}9PwadytAfO;b z1qI-qdkU#(Qt>Wjci&yd52h|HZzud~XN--F#Uxrj?*Q$cb~TIyLR8RdwbEoVF)b~F ziHT+w6x8y|*JhTqAuo9?pE>s|bHX{QHN9l9Cc`+_+IqM5&{to_j~{ zg0E+0)bNqzM6O04L12dFP!wdK=vF%jnk|H+cH^akW1Rgs7loU}9ooSudnz zKh55sno>n5T_PLxa?klkkJIn3U#Gkg|H&Eu`Rn*M9(LA<@u9a`00000NkvXXu0mjf D5=S!z literal 0 HcmV?d00001 diff --git a/test/unit/composite.js b/test/unit/composite.js index 865ac598..d748d8f7 100644 --- a/test/unit/composite.js +++ b/test/unit/composite.js @@ -122,6 +122,26 @@ describe('composite', () => { }); }); + it('scrgb pipeline', () => { + const filename = 'composite-red-scrgb.png'; + const actual = fixtures.path(`output.${filename}`); + const expected = fixtures.expected(filename); + return sharp({ + create: { + width: 32, height: 32, channels: 4, background: red + } + }) + .pipelineColourspace('scrgb') + .composite([{ + input: fixtures.inputPngWithTransparency16bit, + blend: 'color-burn' + }]) + .toFile(actual) + .then(() => { + fixtures.assertMaxColourDistance(actual, expected); + }); + }); + it('multiple', async () => { const filename = 'composite-multiple.png'; const actual = fixtures.path(`output.${filename}`);