mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add support to recomb operation for 4x4 matrices
This commit is contained in:
parent
eab7dc1b49
commit
60c5c5083d
@ -580,7 +580,7 @@ Recombine the image with the specified matrix.
|
|||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| inputMatrix | <code>Array.<Array.<number>></code> | 3x3 Recombination matrix |
|
| inputMatrix | <code>Array.<Array.<number>></code> | 3x3 or 4x4 Recombination matrix |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
|
@ -16,6 +16,10 @@ Requires libvips v8.15.2
|
|||||||
* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries.
|
* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries.
|
||||||
[#4132](https://github.com/lovell/sharp/issues/4132)
|
[#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
|
### v0.33.4 - 16th May 2024
|
||||||
|
|
||||||
* Remove experimental status from `pipelineColourspace`.
|
* Remove experimental status from `pipelineColourspace`.
|
||||||
|
@ -296,3 +296,6 @@ GitHub: https://github.com/adriaanmeuris
|
|||||||
|
|
||||||
Name: Richard Hillmann
|
Name: Richard Hillmann
|
||||||
GitHub: https://github.com/project0
|
GitHub: https://github.com/project0
|
||||||
|
|
||||||
|
Name: Pongsatorn Manusopit
|
||||||
|
GitHub: https://github.com/ton11797
|
||||||
|
5
lib/index.d.ts
vendored
5
lib/index.d.ts
vendored
@ -571,11 +571,11 @@ declare namespace sharp {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Recomb the image with the specified matrix.
|
* 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
|
* @throws {Error} Invalid parameters
|
||||||
* @returns A sharp instance that can be used to chain operations
|
* @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.
|
* Transforms the image using brightness, saturation, hue rotation and lightness.
|
||||||
@ -1730,6 +1730,7 @@ declare namespace sharp {
|
|||||||
|
|
||||||
type Matrix2x2 = [[number, number], [number, number]];
|
type Matrix2x2 = [[number, number], [number, number]];
|
||||||
type Matrix3x3 = [[number, number, number], [number, number, 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;
|
export = sharp;
|
||||||
|
@ -787,24 +787,22 @@ function linear (a, b) {
|
|||||||
* // With this example input, a sepia filter has been applied
|
* // With this example input, a sepia filter has been applied
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
|
* @param {Array<Array<number>>} inputMatrix - 3x3 or 4x4 Recombination matrix
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function recomb (inputMatrix) {
|
function recomb (inputMatrix) {
|
||||||
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
|
if (!Array.isArray(inputMatrix)) {
|
||||||
inputMatrix[0].length !== 3 ||
|
throw is.invalidParameterError('inputMatrix', 'array', inputMatrix);
|
||||||
inputMatrix[1].length !== 3 ||
|
|
||||||
inputMatrix[2].length !== 3
|
|
||||||
) {
|
|
||||||
// must pass in a kernel
|
|
||||||
throw new Error('Invalid recombination matrix');
|
|
||||||
}
|
}
|
||||||
this.options.recombMatrix = [
|
if (inputMatrix.length !== 3 && inputMatrix.length !== 4) {
|
||||||
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
|
throw is.invalidParameterError('inputMatrix', '3x3 or 4x4 array', inputMatrix.length);
|
||||||
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
|
}
|
||||||
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
|
const recombMatrix = inputMatrix.flat().map(Number);
|
||||||
].map(Number);
|
if (recombMatrix.length !== 9 && recombMatrix.length !== 16) {
|
||||||
|
throw is.invalidParameterError('inputMatrix', 'cardinality of 9 or 16', recombMatrix.length);
|
||||||
|
}
|
||||||
|
this.options.recombMatrix = recombMatrix;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,19 +183,21 @@ namespace sharp {
|
|||||||
* Recomb with a Matrix of the given bands/channel size.
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
* Eg. RGB will be a 3x3 matrix.
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
*/
|
*/
|
||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
|
VImage Recomb(VImage image, std::vector<double> const& matrix) {
|
||||||
double *m = matrix.get();
|
double* m = const_cast<double*>(matrix.data());
|
||||||
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
return image
|
if (matrix.size() == 9) {
|
||||||
.recomb(image.bands() == 3
|
return image
|
||||||
? VImage::new_from_memory(
|
.recomb(image.bands() == 3
|
||||||
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
|
? VImage::new_matrix(3, 3, m, 9)
|
||||||
)
|
: VImage::new_matrixv(4, 4,
|
||||||
: VImage::new_matrixv(4, 4,
|
m[0], m[1], m[2], 0.0,
|
||||||
m[0], m[1], m[2], 0.0,
|
m[3], m[4], m[5], 0.0,
|
||||||
m[3], m[4], m[5], 0.0,
|
m[6], m[7], m[8], 0.0,
|
||||||
m[6], m[7], m[8], 0.0,
|
0.0, 0.0, 0.0, 1.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,
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
@ -95,7 +95,7 @@ namespace sharp {
|
|||||||
* Recomb with a Matrix of the given bands/channel size.
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
* Eg. RGB will be a 3x3 matrix.
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
*/
|
*/
|
||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
VImage Recomb(VImage image, std::vector<double> const &matrix);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Modulate brightness, saturation, hue and lightness
|
* Modulate brightness, saturation, hue and lightness
|
||||||
|
@ -609,7 +609,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recomb
|
// Recomb
|
||||||
if (baton->recombMatrix != NULL) {
|
if (!baton->recombMatrix.empty()) {
|
||||||
image = sharp::Recomb(image, baton->recombMatrix);
|
image = sharp::Recomb(image, baton->recombMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1613,10 +1613,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.Has("recombMatrix")) {
|
if (options.Has("recombMatrix")) {
|
||||||
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
|
|
||||||
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
|
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
|
||||||
for (unsigned int i = 0; i < 9; i++) {
|
unsigned int matrixElements = recombMatrix.Length();
|
||||||
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
baton->recombMatrix.resize(matrixElements);
|
||||||
|
for (unsigned int i = 0; i < matrixElements; i++) {
|
||||||
|
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
|
baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
|
||||||
|
@ -223,7 +223,7 @@ struct PipelineBaton {
|
|||||||
VipsForeignDzDepth tileDepth;
|
VipsForeignDzDepth tileDepth;
|
||||||
std::string tileId;
|
std::string tileId;
|
||||||
std::string tileBasename;
|
std::string tileBasename;
|
||||||
std::unique_ptr<double[]> recombMatrix;
|
std::vector<double> recombMatrix;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
input(nullptr),
|
input(nullptr),
|
||||||
|
BIN
test/fixtures/d.png
vendored
Normal file
BIN
test/fixtures/d.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
test/fixtures/expected/d-opacity-30.png
vendored
Normal file
BIN
test/fixtures/expected/d-opacity-30.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
1
test/fixtures/index.js
vendored
1
test/fixtures/index.js
vendored
@ -139,6 +139,7 @@ module.exports = {
|
|||||||
|
|
||||||
testPattern: getPath('test-pattern.png'),
|
testPattern: getPath('test-pattern.png'),
|
||||||
|
|
||||||
|
inputPngWithTransparent: getPath('d.png'),
|
||||||
// Path for tests requiring human inspection
|
// Path for tests requiring human inspection
|
||||||
path: getPath,
|
path: getPath,
|
||||||
|
|
||||||
|
@ -295,6 +295,13 @@ sharp('input.gif')
|
|||||||
[0.2392, 0.4696, 0.0912],
|
[0.2392, 0.4696, 0.0912],
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.recomb([
|
||||||
|
[1,0,0,0],
|
||||||
|
[0,1,0,0],
|
||||||
|
[0,0,1,0],
|
||||||
|
[0,0,0,1],
|
||||||
|
])
|
||||||
|
|
||||||
.modulate({ brightness: 2 })
|
.modulate({ brightness: 2 })
|
||||||
.modulate({ hue: 180 })
|
.modulate({ hue: 180 })
|
||||||
.modulate({ lightness: 10 })
|
.modulate({ lightness: 10 })
|
||||||
|
@ -121,6 +121,29 @@ describe('Recomb', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies opacity 30% to the image', function (done) {
|
||||||
|
const output = fixtures.path('output.recomb-opacity.png');
|
||||||
|
sharp(fixtures.inputPngWithTransparent)
|
||||||
|
.recomb([
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 0.3]
|
||||||
|
])
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(48, info.width);
|
||||||
|
assert.strictEqual(48, info.height);
|
||||||
|
fixtures.assertMaxColourDistance(
|
||||||
|
output,
|
||||||
|
fixtures.expected('d-opacity-30.png'),
|
||||||
|
17
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('invalid matrix specification', function () {
|
describe('invalid matrix specification', function () {
|
||||||
it('missing', function () {
|
it('missing', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user