From 541316c8749f3bb1381193bab99060214a3ed111 Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Tue, 15 Oct 2024 11:39:42 +0100 Subject: [PATCH 1/6] ABC-8769 Add support for the blend-align and blend-alpha options to try and facilitate using SS for watermarks --- src/image-ops/blending.js | 69 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index f9c313c..32a15a3 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -1,8 +1,11 @@ const axios = require('axios') exports.apply = async (image, edits) => { - if (edits.blend.processedValue) { - await this.blend(image, edits.blend.processedValue) + await this.beforeApply(image, edits) + const { blend, blendalign, blendalpha } = edits + + if (blend.processedValue) { + await this.blend(image, blend.processedValue, blendalign.processedValue, blendalpha.processedValue) } } @@ -10,12 +13,68 @@ exports.apply = async (image, edits) => { * * @param {Sharp} image * @param {String} url - */ -exports.blend = async (image, url) => { + * @param {String} gravity + * @param {number} alpha + **/ +exports.blend = async (image, url, gravity, alpha) => { const compositeInput = (await axios({ url: url, responseType: "arraybuffer" })).data; - image.composite([{input: compositeInput, tile: true}]) + let inputImage = {input: compositeInput, tile: false, gravity: gravity} + + if (alpha >= 0) { + colour = {r: 10, g: 10, b: 10, a: alpha} + inputImage = {...inputImage, create: {background: colour}} + } + + image.composite([inputImage]) } + +exports.beforeApply = async function (image, edits) { + const { blendalign, blendalpha } = edits + + const alignment = blendAlign.processedValue + if (alignment) { + switch (alignment) { + case 'center,middle' || 'middle,center': + blendalign.processedValue = 'centre' + break + case 'left,middle' || 'middle,left': + blendalign.processedValue = 'west' + break + case 'right,middle' || 'middle,right': + blendalign.processedValue = 'east' + break + case 'top,middle' || 'middle,top': + blendalign.processedValue = 'north' + break + case 'top,left' || 'left,top': + blendalign.processedValue = 'northwest' + break + case 'top,right' || 'right,top': + blendalign.processedValue = 'northeast' + break + case 'bottom,middle' || 'middle,bottom': + blendalign.processedValue = 'south' + break + case 'bottom,left' || 'left,bottom': + blendalign.processedValue = 'southwest' + break + case 'bottom,right' || 'right,bottom': + blendalign.processedValue = 'southeast' + break + default: + blendalign.processedValue = 'centre' + console.error('Invalid blending alignment: ' + alignment) + } + + if (blendalpha.processedValue) { + const alphaAsDecimal = parseFloat(blendalpha.processedValue) / 100 + blendalpha.processedValue = alphaAsDecimal + } else { + blendalpha.processedValue = -1 + } + } +} \ No newline at end of file From d9e2740fcd559bfe2c60fb4f96d3377161fb26fa Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Tue, 15 Oct 2024 11:55:38 +0100 Subject: [PATCH 2/6] ABC-8769 Try to streamline the mapping of alignment to gravity --- src/image-ops/blending.js | 60 ++++++++++++++------------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index 32a15a3..8f315fa 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -32,49 +32,31 @@ exports.blend = async (image, url, gravity, alpha) => { image.composite([inputImage]) } +const alignmentToGravity = new Map([ + ["center", "centre"], ["middle", "centre"], ["center,middle", "centre"], ["middle,center", "centre"], + ["left", "west"], ["left,middle", "west"], ["middle,left", "west"], + ["right", "east"], ["right,middle", "east"], ["middle,right", "east"], + ["top", "north"], ["top,middle", "north"], ["middle,top", "north"], + ["top,left", "northwest"], ["left,top", "northwest"], + ["top,right", "northeast"], ["right,top", "northeast"], + ["bottom", "south"], ["bottom,middle", "south"], ["middle,bottom", "south"], + ["bottom,left", "southwest"], ["left,bottom", "southwest"], + ["bottom,right", "southeast"], ["right,bottom", "southeast"], +]) + exports.beforeApply = async function (image, edits) { const { blendalign, blendalpha } = edits - const alignment = blendAlign.processedValue + const alignment = blendAlign.processedValue.replace(/ /g, "") if (alignment) { - switch (alignment) { - case 'center,middle' || 'middle,center': - blendalign.processedValue = 'centre' - break - case 'left,middle' || 'middle,left': - blendalign.processedValue = 'west' - break - case 'right,middle' || 'middle,right': - blendalign.processedValue = 'east' - break - case 'top,middle' || 'middle,top': - blendalign.processedValue = 'north' - break - case 'top,left' || 'left,top': - blendalign.processedValue = 'northwest' - break - case 'top,right' || 'right,top': - blendalign.processedValue = 'northeast' - break - case 'bottom,middle' || 'middle,bottom': - blendalign.processedValue = 'south' - break - case 'bottom,left' || 'left,bottom': - blendalign.processedValue = 'southwest' - break - case 'bottom,right' || 'right,bottom': - blendalign.processedValue = 'southeast' - break - default: - blendalign.processedValue = 'centre' - console.error('Invalid blending alignment: ' + alignment) - } + const gravity = alignmentToGravity.get(alignment) + blendalign.processedValue = gravity ? gravity : "centre" + } - if (blendalpha.processedValue) { - const alphaAsDecimal = parseFloat(blendalpha.processedValue) / 100 - blendalpha.processedValue = alphaAsDecimal - } else { - blendalpha.processedValue = -1 - } + if (blendalpha.processedValue) { + const alphaAsDecimal = parseFloat(blendalpha.processedValue) / 100 + blendalpha.processedValue = alphaAsDecimal + } else { + blendalpha.processedValue = -1 } } \ No newline at end of file From a4d3b4ac1b7af24b6e3199b0b5569dd0222edcc5 Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Tue, 15 Oct 2024 13:32:31 +0100 Subject: [PATCH 3/6] ABC-8769 Fix definition --- src/image-ops/blending.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index 8f315fa..1393ebb 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -25,7 +25,7 @@ exports.blend = async (image, url, gravity, alpha) => { let inputImage = {input: compositeInput, tile: false, gravity: gravity} if (alpha >= 0) { - colour = {r: 10, g: 10, b: 10, a: alpha} + const colour = {r: 10, g: 10, b: 10, a: alpha} inputImage = {...inputImage, create: {background: colour}} } From fe5503a7dbb7bba6df64ff20f4ab725ed9284197 Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Tue, 15 Oct 2024 14:02:29 +0100 Subject: [PATCH 4/6] ABC-8769 Fix typo --- src/image-ops/blending.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index 1393ebb..351ecbd 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -47,7 +47,7 @@ const alignmentToGravity = new Map([ exports.beforeApply = async function (image, edits) { const { blendalign, blendalpha } = edits - const alignment = blendAlign.processedValue.replace(/ /g, "") + const alignment = blendalign.processedValue.replace(/ /g, "") if (alignment) { const gravity = alignmentToGravity.get(alignment) blendalign.processedValue = gravity ? gravity : "centre" From aa35fce365106abaacd8753eb6f00a92b38e0046 Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Wed, 16 Oct 2024 16:00:59 +0100 Subject: [PATCH 5/6] ABC-8769 Add opacity control to watermarking/blending functionality --- src/image-ops/blending.js | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index 351ecbd..9d42da5 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -1,11 +1,16 @@ const axios = require('axios') +const sharp = require('sharp') exports.apply = async (image, edits) => { await this.beforeApply(image, edits) - const { blend, blendalign, blendalpha } = edits + const { blend } = edits + const blendalign = edits["blend-align"] + const blendalpha = edits["blend-alpha"] + const blendwidth = edits["blend-w"] + const blendheight = edits["blend-h"] if (blend.processedValue) { - await this.blend(image, blend.processedValue, blendalign.processedValue, blendalpha.processedValue) + await this.blend(image, blend.processedValue, blendalign.processedValue, blendalpha.processedValue, blendwidth.processedValue, blendheight.processedValue) } } @@ -15,21 +20,31 @@ exports.apply = async (image, edits) => { * @param {String} url * @param {String} gravity * @param {number} alpha + * @param {number} width + * @param {number} height **/ -exports.blend = async (image, url, gravity, alpha) => { - const compositeInput = (await axios({ +exports.blend = async (image, url, gravity, alpha, width, height) => { + let compositeImage = (await axios({ url: url, responseType: "arraybuffer" })).data; - let inputImage = {input: compositeInput, tile: false, gravity: gravity} - if (alpha >= 0) { - const colour = {r: 10, g: 10, b: 10, a: alpha} - inputImage = {...inputImage, create: {background: colour}} + compositeImage = await sharp(compositeImage).composite( + [{ + input: Buffer.from([0,0,0,Math.round(alpha*256)]), + raw: { + width: 1, + height: 1, + channels: 4, + }, + tile: true, + blend: 'dest-in', + }] + ).toBuffer() } - image.composite([inputImage]) + image.composite([{input: compositeImage, tile: false, gravity: gravity}]) } const alignmentToGravity = new Map([ @@ -45,9 +60,10 @@ const alignmentToGravity = new Map([ ]) exports.beforeApply = async function (image, edits) { - const { blendalign, blendalpha } = edits + const blendalign = edits["blend-align"] + const blendalpha = edits["blend-alpha"] - const alignment = blendalign.processedValue.replace(/ /g, "") + const alignment = blendalign.processedValue.join(",") if (alignment) { const gravity = alignmentToGravity.get(alignment) blendalign.processedValue = gravity ? gravity : "centre" From 358e3bced1d0812e76113f1861239aa5b969a26d Mon Sep 17 00:00:00 2001 From: Matt Stevenson Date: Fri, 18 Oct 2024 11:19:31 +0100 Subject: [PATCH 6/6] ABC-8769 Add buffer-image-size lib --- package-lock.json | 33 +++++++++--- package.json | 3 ++ src/image-ops/blending.js | 103 +++++++++++++++++++++++++++++++------- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0f0e60..719a908 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,16 @@ { "name": "serverless-sharp", - "version": "2.0.6", + "version": "2.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.0.6", + "name": "serverless-sharp", + "version": "2.1.1", "license": "MIT", + "dependencies": { + "buffer-image-size": "^0.6.4" + }, "devDependencies": { "@types/jest": "^25.2.1", "aws-sdk": "^2.668.0", @@ -1734,8 +1738,7 @@ "node_modules/@types/node": { "version": "14.14.35", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", - "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==", - "dev": true + "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.0", @@ -2823,6 +2826,17 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/buffermaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/buffermaker/-/buffermaker-1.2.1.tgz", @@ -16049,8 +16063,7 @@ "@types/node": { "version": "14.14.35", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", - "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==", - "dev": true + "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -16941,6 +16954,14 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "requires": { + "@types/node": "*" + } + }, "buffermaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/buffermaker/-/buffermaker-1.2.1.tgz", diff --git a/package.json b/package.json index 169b29a..03d1f5c 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,8 @@ "serverless": "^2.30.1", "sharp": "0.28.0", "standard": "^16.0.3" + }, + "dependencies": { + "buffer-image-size": "^0.6.4" } } diff --git a/src/image-ops/blending.js b/src/image-ops/blending.js index 9d42da5..946b8d8 100644 --- a/src/image-ops/blending.js +++ b/src/image-ops/blending.js @@ -1,16 +1,25 @@ const axios = require('axios') const sharp = require('sharp') +const sizeOf = require('buffer-image-size') exports.apply = async (image, edits) => { await this.beforeApply(image, edits) - const { blend } = edits - const blendalign = edits["blend-align"] - const blendalpha = edits["blend-alpha"] - const blendwidth = edits["blend-w"] - const blendheight = edits["blend-h"] + const { blend, w, h, fit } = edits + const blendAlign = edits["blend-align"] + const blendAlpha = edits["blend-alpha"] + const blendRatio = edits["blend-ratio"] + console.log("RATIO: "+blendRatio) if (blend.processedValue) { - await this.blend(image, blend.processedValue, blendalign.processedValue, blendalpha.processedValue, blendwidth.processedValue, blendheight.processedValue) + await this.blend( + image, + blend.processedValue, + blendAlign.processedValue, + blendAlpha.processedValue, + {width: w.processedValue, height: h.processedValue}, + fit, + blendRatio.processedValue + ) } } @@ -20,16 +29,18 @@ exports.apply = async (image, edits) => { * @param {String} url * @param {String} gravity * @param {number} alpha - * @param {number} width - * @param {number} height + * @param {Object} targetDimensions + * @param {String} targetFit + * @param {number} ratio **/ -exports.blend = async (image, url, gravity, alpha, width, height) => { +exports.blend = async (image, url, gravity, alpha, targetDimensions, targetFit, ratio) => { let compositeImage = (await axios({ url: url, responseType: "arraybuffer" })).data; if (alpha >= 0) { + console.log("APPLYING OPACITY TO WATERMARK: "+alpha) compositeImage = await sharp(compositeImage).composite( [{ input: Buffer.from([0,0,0,Math.round(alpha*256)]), @@ -44,9 +55,61 @@ exports.blend = async (image, url, gravity, alpha, width, height) => { ).toBuffer() } + if (ratio > 0) { + const compositeDimensions = calculateCompositeDimensions(targetDimensions, targetFit, ratio) + console.log("ADJUSTING WATERMARK IMAGE TO DIMENSIONS: "+JSON.stringify(compositeDimensions)) + compositeImage = await sharp(compositeImage).resize(compositeDimensions.width, compositeDimensions.height, {fit: "inside"}).toBuffer() + } + image.composite([{input: compositeImage, tile: false, gravity: gravity}]) } +function calculateCompositeDimensions (image, targetDimensions, targetFit, ratio) { + const originalDimensions = sizeOf(image) + let resultDimensions + if (targetFit) { + if (targetFit === "fill") { + //https://docs.imgix.com/apis/rendering/size/resize-fit-mode#fill + resultDimensions = targetDimensions + } else if (targetFit === "max") { + //https://docs.imgix.com/apis/rendering/size/resize-fit-mode#max + resultDimensions = calculateResultDimensions(originalDimensions, targetDimensions, false) + } else if (targetFit === "clip") { + //https://docs.imgix.com/apis/rendering/size/resize-fit-mode#clip + resultDimensions = calculateResultDimensions(originalDimensions, targetDimensions, true) + } + } else { + resultDimensions = originalDimensions + } + return {width: resultDimensions.width * ratio, height: resultDimensions.height * ratio} +} + +function calculateResultDimensions(originalDimensions, targetDimensions, allowUpscaling) { + const originalAspectRatio = originalDimensions.width / originalDimensions.height; + const targetAspectRatio = targetDimensions.width / targetDimensions.height; + + let resultWidth, resultHeight; + + if (originalAspectRatio > targetAspectRatio) { + // The original image is wider compared to the target aspect ratio + resultWidth = targetDimensions.width; + resultHeight = Math.floor(targetDimensions.width / originalAspectRatio); + } else { + // The original image is taller compared to the target aspect ratio + resultHeight = targetDimensions.height; + resultWidth = Math.floor(targetDimensions.height * originalAspectRatio); + } + + if (!allowUpscaling && (resultWidth > originalDimensions.width || resultHeight > originalDimensions.height)) + { + resultWidth = originalDimensions.width; + resultHeight = originalDimensions.height; + } + + return { width: resultWidth, height: resultHeight }; +} + + const alignmentToGravity = new Map([ ["center", "centre"], ["middle", "centre"], ["center,middle", "centre"], ["middle,center", "centre"], ["left", "west"], ["left,middle", "west"], ["middle,left", "west"], @@ -60,19 +123,23 @@ const alignmentToGravity = new Map([ ]) exports.beforeApply = async function (image, edits) { - const blendalign = edits["blend-align"] - const blendalpha = edits["blend-alpha"] + const blendAlign = edits["blend-align"] + const blendAlpha = edits["blend-alpha"] + const blendRatio = edits["blend-ratio"] - const alignment = blendalign.processedValue.join(",") + const alignment = blendAlign.processedValue.join(",") if (alignment) { const gravity = alignmentToGravity.get(alignment) - blendalign.processedValue = gravity ? gravity : "centre" + blendAlign.processedValue = gravity ? gravity : "centre" } - if (blendalpha.processedValue) { - const alphaAsDecimal = parseFloat(blendalpha.processedValue) / 100 - blendalpha.processedValue = alphaAsDecimal - } else { - blendalpha.processedValue = -1 + blendAlpha.processedValue = percentageAsDecimal(blendAlpha.processedValue); + blendRatio.processedValue = 0.5 //percentageAsDecimal(blendRatio.processedValue); +} + +function percentageAsDecimal(percentageValue) { + if (percentageValue) { + return parseFloat(percentageValue) / 100 } + return -1 } \ No newline at end of file