From 60c5c5083dd12fae04db2640bb5c40564bb0728d Mon Sep 17 00:00:00 2001 From: Denice Date: Tue, 2 Jul 2024 17:06:26 +0700 Subject: [PATCH] Add support to recomb operation for 4x4 matrices --- docs/api-operation.md | 2 +- docs/changelog.md | 4 ++++ docs/humans.txt | 3 +++ lib/index.d.ts | 5 +++-- lib/operation.js | 24 ++++++++++------------ src/operations.cc | 26 +++++++++++++----------- src/operations.h | 2 +- src/pipeline.cc | 9 ++++---- src/pipeline.h | 2 +- test/fixtures/d.png | Bin 0 -> 1068 bytes test/fixtures/expected/d-opacity-30.png | Bin 0 -> 1500 bytes test/fixtures/index.js | 1 + test/types/sharp.test-d.ts | 7 +++++++ test/unit/recomb.js | 23 +++++++++++++++++++++ 14 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 test/fixtures/d.png create mode 100644 test/fixtures/expected/d-opacity-30.png diff --git a/docs/api-operation.md b/docs/api-operation.md index c4a69cdce..5abc6eed4 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -580,7 +580,7 @@ Recombine the image with the specified matrix. | Param | Type | Description | | --- | --- | --- | -| inputMatrix | Array.<Array.<number>> | 3x3 Recombination matrix | +| inputMatrix | Array.<Array.<number>> | 3x3 or 4x4 Recombination matrix | **Example** ```js diff --git a/docs/changelog.md b/docs/changelog.md index 6af2668e8..3850f50ab 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,6 +16,10 @@ Requires libvips v8.15.2 * Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries. [#4132](https://github.com/lovell/sharp/issues/4132) +* Add support to recomb operation for 4x4 matrices. + [#4147](https://github.com/lovell/sharp/pull/4147) + [@ton11797](https://github.com/ton11797) + ### v0.33.4 - 16th May 2024 * Remove experimental status from `pipelineColourspace`. diff --git a/docs/humans.txt b/docs/humans.txt index 20a9fa8e9..1c9cd1ceb 100644 --- a/docs/humans.txt +++ b/docs/humans.txt @@ -296,3 +296,6 @@ GitHub: https://github.com/adriaanmeuris Name: Richard Hillmann GitHub: https://github.com/project0 + +Name: Pongsatorn Manusopit +GitHub: https://github.com/ton11797 diff --git a/lib/index.d.ts b/lib/index.d.ts index 4dbf3b8d7..6e25529d8 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -571,11 +571,11 @@ declare namespace sharp { /** * Recomb the image with the specified matrix. - * @param inputMatrix 3x3 Recombination matrix + * @param inputMatrix 3x3 Recombination matrix or 4x4 Recombination matrix * @throws {Error} Invalid parameters * @returns A sharp instance that can be used to chain operations */ - recomb(inputMatrix: Matrix3x3): Sharp; + recomb(inputMatrix: Matrix3x3 | Matrix4x4): Sharp; /** * Transforms the image using brightness, saturation, hue rotation and lightness. @@ -1730,6 +1730,7 @@ declare namespace sharp { type Matrix2x2 = [[number, number], [number, number]]; type Matrix3x3 = [[number, number, number], [number, number, number], [number, number, number]]; + type Matrix4x4 = [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]]; } export = sharp; diff --git a/lib/operation.js b/lib/operation.js index ed6df8345..bac9c8891 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -787,24 +787,22 @@ function linear (a, b) { * // With this example input, a sepia filter has been applied * }); * - * @param {Array>} inputMatrix - 3x3 Recombination matrix + * @param {Array>} inputMatrix - 3x3 or 4x4 Recombination matrix * @returns {Sharp} * @throws {Error} Invalid parameters */ function recomb (inputMatrix) { - if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 || - inputMatrix[0].length !== 3 || - inputMatrix[1].length !== 3 || - inputMatrix[2].length !== 3 - ) { - // must pass in a kernel - throw new Error('Invalid recombination matrix'); + if (!Array.isArray(inputMatrix)) { + throw is.invalidParameterError('inputMatrix', 'array', inputMatrix); + } + if (inputMatrix.length !== 3 && inputMatrix.length !== 4) { + throw is.invalidParameterError('inputMatrix', '3x3 or 4x4 array', inputMatrix.length); + } + const recombMatrix = inputMatrix.flat().map(Number); + if (recombMatrix.length !== 9 && recombMatrix.length !== 16) { + throw is.invalidParameterError('inputMatrix', 'cardinality of 9 or 16', recombMatrix.length); } - this.options.recombMatrix = [ - inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2], - inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2], - inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2] - ].map(Number); + this.options.recombMatrix = recombMatrix; return this; } diff --git a/src/operations.cc b/src/operations.cc index c6904c50d..57790c001 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -183,19 +183,21 @@ namespace sharp { * Recomb with a Matrix of the given bands/channel size. * Eg. RGB will be a 3x3 matrix. */ - VImage Recomb(VImage image, std::unique_ptr const &matrix) { - double *m = matrix.get(); + VImage Recomb(VImage image, std::vector const& matrix) { + double* m = const_cast(matrix.data()); image = image.colourspace(VIPS_INTERPRETATION_sRGB); - return image - .recomb(image.bands() == 3 - ? VImage::new_from_memory( - m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE - ) - : VImage::new_matrixv(4, 4, - m[0], m[1], m[2], 0.0, - m[3], m[4], m[5], 0.0, - m[6], m[7], m[8], 0.0, - 0.0, 0.0, 0.0, 1.0)); + if (matrix.size() == 9) { + return image + .recomb(image.bands() == 3 + ? VImage::new_matrix(3, 3, m, 9) + : VImage::new_matrixv(4, 4, + m[0], m[1], m[2], 0.0, + m[3], m[4], m[5], 0.0, + m[6], m[7], m[8], 0.0, + 0.0, 0.0, 0.0, 1.0)); + } else { + return image.recomb(VImage::new_matrix(4, 4, m, 16)); + } } VImage Modulate(VImage image, double const brightness, double const saturation, diff --git a/src/operations.h b/src/operations.h index f2d73704a..8c8791c6b 100644 --- a/src/operations.h +++ b/src/operations.h @@ -95,7 +95,7 @@ namespace sharp { * Recomb with a Matrix of the given bands/channel size. * Eg. RGB will be a 3x3 matrix. */ - VImage Recomb(VImage image, std::unique_ptr const &matrix); + VImage Recomb(VImage image, std::vector const &matrix); /* * Modulate brightness, saturation, hue and lightness diff --git a/src/pipeline.cc b/src/pipeline.cc index 9dc22ed72..1455c5751 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -609,7 +609,7 @@ class PipelineWorker : public Napi::AsyncWorker { } // Recomb - if (baton->recombMatrix != NULL) { + if (!baton->recombMatrix.empty()) { image = sharp::Recomb(image, baton->recombMatrix); } @@ -1613,10 +1613,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { } } if (options.Has("recombMatrix")) { - baton->recombMatrix = std::unique_ptr(new double[9]); Napi::Array recombMatrix = options.Get("recombMatrix").As(); - for (unsigned int i = 0; i < 9; i++) { - baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); + unsigned int matrixElements = recombMatrix.Length(); + baton->recombMatrix.resize(matrixElements); + for (unsigned int i = 0; i < matrixElements; i++) { + baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); } } baton->colourspacePipeline = sharp::AttrAsEnum( diff --git a/src/pipeline.h b/src/pipeline.h index bc79eb2cc..163f84f1c 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -223,7 +223,7 @@ struct PipelineBaton { VipsForeignDzDepth tileDepth; std::string tileId; std::string tileBasename; - std::unique_ptr recombMatrix; + std::vector recombMatrix; PipelineBaton(): input(nullptr), diff --git a/test/fixtures/d.png b/test/fixtures/d.png new file mode 100644 index 0000000000000000000000000000000000000000..7654206608afed494462f840696a81de6bad5c6f GIT binary patch literal 1068 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s(*k@#T-^(N{*wiidul*FXhuU>up_;J_Ho!%avZ7nSu*KcrkaJ+Z-Zed>jk;6wcRn>0Z zxY6I+d+FlEnyRYEu<)~I&OCYYWY&xsLH_;=7c7vKlv=iQY5m%Y13(uFmjw9*GkiE^ zD&|o2;ve(Z=CnEbbsMf951AV=kAZ>N$kW9!#N+tdu$$9n8}RVX)Jd7-GpR|OC(Zcx zx&QxTat#uUJkM`gUiE;nME9lFuC&dSo9_NAsL!x6e_Lq(rhK;jt(UL2T`eg#i@kks zpWpkx_Uqg~&z4;;pMR(Px%fNgCx47K{w@1Zc7*NEQ7=1x%3E7&$9_gd7e{8sFNaSZ3!bz2 z&3dmD?2P9%*ko9aD7&4|^{^HzmZ_@SrS~uL;9h~(e;q9PkMVP?n6rL$!mDgffx`U@ z_$t}9E}Zee=i1++jN5P8b2_{UP1abUG(p^J`t2?yspGo#cOP=zW|YV|#kKUC%ayp< zjQ3d69ymQy*CtbVFE@A9AC9ax1!SM3yXM0b)+IaDq*mRq$Dc??B zDpmg?Icb7|zAH&g z>o4V{C0B(~boNR_&Est_dUxkjw4TMM4sPBJvnEbpHnC&dS+qy!MA4>s3shM+a^k)m z-ZJ}X&0M$F4q=nnf+ZXbmQOWbcW(Jz-o0(xBg+phnRWdpSFj4>$_yEK-Z{@Cw{3_m z@>5cOIkQ_qX|06$>huM#J>K>KD5Uym9@)}{m8DJw*8Y#@4ARQSbVHK zUnc+b_e=MdPjO3?{JAq?hr~{&G%X|k5UW=Q-n!O1r%6;M-EP{%$rxayGhx>vFYzMg zs!d-PS}WP?T(pWq>-_3I c{r)T+#=L0Ggt;r&fticJ)78&qol`;+08SGV{Qv*} literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/d-opacity-30.png b/test/fixtures/expected/d-opacity-30.png new file mode 100644 index 0000000000000000000000000000000000000000..d053cfa2b87458348663929f9ba3bfec5d671dec GIT binary patch literal 1500 zcmV<21ta>2P)a1cSvLPZ{BC;bQ`yz5IB0~|WJR92I_U*ll*;sY{Qqb6@i0q0; zzX5Q}7}K}U;936-0TH<)B6}h-C;;7L1N+Q7{~7@+mEM|xR{d*J@&G$kgedb)5fYtiF9IyZN;Y0Q1a8w-~99ECN`@VYf`gJLhft|aIfCW#r z7{w;T;|lcV=Ee~C>FHBr50AevM6Sl;5)vbeA&)=~cnt8Lf3{bhpPm^q5cvJq-^_1F ziR%#IT3y#`CDCaha4!`+re44M(Z@zQkWm7f7es^WKRFWk_gy@&8{@hE-tF= zPwpD)e!6X<;wLwe;5Y6qh{Pb2*r8IHRBikS;9CF3GqpC|MBjfiO_@H z`z8`nR-HE52=sCuh!@fPw_ks=03JgwYJesOzIyb?{7sZpX4jKRHJwfkL86opnO=~9 z?XX$pQgyA;QTgCu{jTQzuJ&N}z9EN^LpXwpMf6(oWZ-sex9aSrf~O=hnF|UKX#Ca7 zm&UVr+y~rb+l~Vm=jTtrz`8(SGu8V@#s$ZB^o$hT@N+E%eAknmTfHI)z3CEQMHn4O zLMnK|kA%=~Q@7TM6Pbh5hWY4n4FFd4o$h` zIc*h5>weODp74h1I8i|V?s7q!8&RP8keuA}KDscnU<~)WK)WqQ#E~84IUuLw zPE7yWZ2{nhlWP#-!H`LbBIAh!-XxOer)P#t8xiCL0p<@xVAx5~zG^rHWUgI}$3`Zx zs-$v)xJzV-011kBNg|kI3~JMNsxdL%M{fH3^V74|s^pV_2+>#~06-+6rH{{0hb=L* zOCsVySO41yeIeAX-D)={04uyeMFG6aa#gPrQ7RAw)Iq69+zk+LyYxJXwe=I$9Ixj@ zN0XSjNdCt*BB@c(Cg=2_;k=A;krJ2Vd9;joOQTcQwW(l87htid@EfF^BtBkfWSUs9 zJu4AH5)k%J8?;OU4DF}vf9tw7V<4hA(nNrJ{6;okN-!BWKmuU8L*i4ngl_e~`v3+k zlHi)23F%HuJE^}Y)twSzL*Oxry)Z92e5Wr{)U{Ic4&<6{7!WI-I#72wKTgV zrgkxyvOen(4|vRkyJOf|d=aIM`$vaOq6 zPbMZpO(YoKwx!J4)Tf%3W(7r5+DKf!yw&pjTP8asl$j~F)Tl>*so9W)zEv;TRt$ZU&lvYZ#*FV~i z7FRjkjqRoNCzV{SSJbhfD8F#?lVS3ub5VO$>$q7*2NuC%6L~cSm`BSAhG+U5kU6< z&)8S@*YcY6*1YIGxefnnaB^Gz+2bTEt^e)cZT