From 6d404f4d2c8673494785c8a7e2556bcc220f19c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20J=C3=B6bstl?= Date: Mon, 16 Jan 2023 10:20:42 +0100 Subject: [PATCH] Add coords to output when using attention based crop (#3470) --- lib/output.js | 1 + src/pipeline.cc | 16 ++++++++++++++- src/pipeline.h | 6 ++++++ test/fixtures/expected/crop-strategy.webp | Bin 0 -> 7138 bytes test/unit/resize-cover.js | 24 ++++++++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/expected/crop-strategy.webp diff --git a/lib/output.js b/lib/output.js index d4445e51..e19971e9 100644 --- a/lib/output.js +++ b/lib/output.js @@ -61,6 +61,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math * `info` contains the output image `format`, `size` (bytes), `width`, `height`, * `channels` and `premultiplied` (indicating if premultiplication was used). * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. + * When using attention as crop strategy also contains the center of the cropped region in the fields `attentionX` and `attentionY`. * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. * @returns {Promise} - when no callback is provided * @throws {Error} Invalid parameters diff --git a/src/pipeline.cc b/src/pipeline.cc index 01030061..6430a7d3 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -456,6 +456,7 @@ class PipelineWorker : public Napi::AsyncWorker { // Gravity-based crop int left; int top; + std::tie(left, top) = sharp::CalculateCrop( inputWidth, inputHeight, baton->width, baton->height, baton->position); int width = std::min(inputWidth, baton->width); @@ -466,16 +467,25 @@ class PipelineWorker : public Napi::AsyncWorker { left, top, width, height, nPages, &targetPageHeight) : image.extract_area(left, top, width, height); } else { + int attention_x; + int attention_y; + // Attention-based or Entropy-based crop MultiPageUnsupported(nPages, "Resize strategy"); image = image.tilecache(VImage::option() ->set("access", VIPS_ACCESS_RANDOM) ->set("threaded", TRUE)); + image = image.smartcrop(baton->width, baton->height, VImage::option() - ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)); + ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION) + ->set("attention_x", &attention_x) + ->set("attention_y", &attention_y)); baton->hasCropOffset = true; baton->cropOffsetLeft = static_cast(image.xoffset()); baton->cropOffsetTop = static_cast(image.yoffset()); + baton->hasAttentionCenter = true; + baton->attentionX = static_cast(attention_x * jpegShrinkOnLoad / scale); + baton->attentionY = static_cast(attention_y * jpegShrinkOnLoad / scale); } } } @@ -1198,6 +1208,10 @@ class PipelineWorker : public Napi::AsyncWorker { info.Set("cropOffsetLeft", static_cast(baton->cropOffsetLeft)); info.Set("cropOffsetTop", static_cast(baton->cropOffsetTop)); } + if (baton->hasAttentionCenter) { + info.Set("attentionX", static_cast(baton->attentionX)); + info.Set("attentionY", static_cast(baton->attentionY)); + } if (baton->trimThreshold > 0.0) { info.Set("trimOffsetLeft", static_cast(baton->trimOffsetLeft)); info.Set("trimOffsetTop", static_cast(baton->trimOffsetTop)); diff --git a/src/pipeline.h b/src/pipeline.h index 1e35f033..4a9f4650 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -74,6 +74,9 @@ struct PipelineBaton { bool hasCropOffset; int cropOffsetLeft; int cropOffsetTop; + bool hasAttentionCenter; + int attentionX; + int attentionY; bool premultiplied; bool tileCentre; bool fastShrinkOnLoad; @@ -236,6 +239,9 @@ struct PipelineBaton { hasCropOffset(false), cropOffsetLeft(0), cropOffsetTop(0), + hasAttentionCenter(false), + attentionX(0), + attentionY(0), premultiplied(false), tintA(128.0), tintB(128.0), diff --git a/test/fixtures/expected/crop-strategy.webp b/test/fixtures/expected/crop-strategy.webp new file mode 100644 index 0000000000000000000000000000000000000000..857a471af93c388e02d45b9eccaae54441bf0f38 GIT binary patch literal 7138 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 C>!7Is literal 0 HcmV?d00001 diff --git a/test/unit/resize-cover.js b/test/unit/resize-cover.js index 4274c9cd..ec7c9466 100644 --- a/test/unit/resize-cover.js +++ b/test/unit/resize-cover.js @@ -376,6 +376,8 @@ describe('Resize fit=cover', function () { assert.strictEqual(320, info.height); assert.strictEqual(-107, info.cropOffsetLeft); assert.strictEqual(0, info.cropOffsetTop); + assert.strictEqual(588, info.attentionX); + assert.strictEqual(640, info.attentionY); fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done); }); }); @@ -394,10 +396,32 @@ describe('Resize fit=cover', function () { assert.strictEqual(80, info.height); assert.strictEqual(0, info.cropOffsetLeft); assert.strictEqual(0, info.cropOffsetTop); + assert.strictEqual(0, info.attentionX); + assert.strictEqual(0, info.attentionY); fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); }); }); + it('WebP', function (done) { + sharp(fixtures.inputWebP) + .resize(320, 80, { + fit: 'cover', + position: sharp.strategy.attention + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('webp', info.format); + assert.strictEqual(3, info.channels); + assert.strictEqual(320, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(0, info.cropOffsetLeft); + assert.strictEqual(-161, info.cropOffsetTop); + assert.strictEqual(288, info.attentionX); + assert.strictEqual(745, info.attentionY); + fixtures.assertSimilar(fixtures.expected('crop-strategy.webp'), data, done); + }); + }); + it('supports the strategy passed as a string', function (done) { sharp(fixtures.inputPngWithTransparency) .resize(320, 80, {