From 838250d7d3144b90750a9baf7361dc08392e59c8 Mon Sep 17 00:00:00 2001 From: RedSparr0w Date: Tue, 24 Mar 2020 10:16:37 +1300 Subject: [PATCH 1/2] Add top/bottom/left/right/width/height options --- README.md | 24 ++++++++++++++++++--- src/index.js | 14 +++++++++++- test/fixtures/face-custom-height-width.png | Bin 0 -> 7987 bytes test/unit.js | 23 +++++++++++++++++--- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/face-custom-height-width.png diff --git a/README.md b/README.md index 38ba4cd..f219154 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,13 @@ And that would update the `img` element to show this image: ### Positioning -Those source png images were already the right dimensions to be overlaid on top of each other. You can also supply an array of objects with x/y co-ords to manually position each image: +Those source png images were already the right dimensions to be overlaid on top of each other. You can also supply an array of objects with top/right/bottom/left/x/y co-ords to manually position each image: ```js mergeImages([ - { src: 'body.png', x: 0, y: 0 }, + { src: 'body.png', left: 0, top: 0 }, { src: 'eyes.png', x: 32, y: 0 }, - { src: 'mouth.png', x: 16, y: 0 } + { src: 'mouth.png', right: -16, bottom: 0 } ]) .then(b64 => ...); // data:image/png;base64,iVBORw0KGgoAA... @@ -63,6 +63,24 @@ Using the same source images as above would output this: +### Resize + +Those source png images were already the right dimensions to be overlaid on top of each other. You can also supply an array of objects with width/height to manually resize each image: + +```js +mergeImages([ + { src: 'body.png', left: 0, top: 0 }, + { src: 'eyes.png', x: 32, y: 64, height: 180 }, + { src: 'mouth.png', right: 32, bottom: 0, width: 128, height: 180 } +]) + .then(b64 => ...); + // data:image/png;base64,iVBORw0KGgoAA... +``` + +Using the same source images as above would output this: + + + ### Opacity The opacity can also be tweaked on each image. diff --git a/src/index.js b/src/index.js index 1f069d7..4fe8aa4 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,18 @@ const defaultOptions = { crossOrigin: undefined }; +const getX = (image, width) => { + if (image.right != undefined) + return width - (image.right + (image.width || image.img.width)); + return image.left || image.x || 0; +} + +const getY = (image, height) => { + if (image.bottom != undefined) + return height - (image.bottom + (image.height || image.img.height)); + return image.top || image.y || 0; +} + // Return Promise const mergeImages = (sources = [], options = {}) => new Promise(resolve => { options = Object.assign({}, defaultOptions, options); @@ -45,7 +57,7 @@ const mergeImages = (sources = [], options = {}) => new Promise(resolve => { // Draw images to canvas images.forEach(image => { ctx.globalAlpha = image.opacity ? image.opacity : 1; - return ctx.drawImage(image.img, image.x || 0, image.y || 0); + return ctx.drawImage(image.img, getX(image, canvas.width), getY(image, canvas.height), image.width || image.img.width, image.height || image.img.height); }); if (options.Canvas && options.format === 'image/jpeg') { diff --git a/test/fixtures/face-custom-height-width.png b/test/fixtures/face-custom-height-width.png new file mode 100644 index 0000000000000000000000000000000000000000..73e5668bf247f31d1c916702c1a0c992d7ec5e4b GIT binary patch literal 7987 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 QkOc>n { test('mergeImages uses custom positions', async t => { t.plan(1); const images = await Promise.all([ - { src: 'body.png', x: 0, y: 0 }, - { src: 'eyes.png', x: 32, y: 0 }, - { src: 'mouth.png', x: 16, y: 0 } + { src: 'body.png', left: 0, top: 0 }, + { src: 'eyes.png', x: 32, y: 0 }, + { src: 'mouth.png', right: -16, bottom: 0 } ].map(image => fixtures.getImage(image.src).then(src => { image.src = src; return image; @@ -76,6 +76,23 @@ test('mergeImages uses custom positions', async t => { t.true(b64 === expectedB64); }); +test('mergeImages uses custom sizing', async t => { + t.plan(1); + const images = await Promise.all([ + { src: 'body.png', left: 0, top: 0 }, + { src: 'eyes.png', x: 32, y: 64, height: 180 }, + { src: 'mouth.png', right: 32, bottom: 0, width: 128, height: 180 } + ].map(image => fixtures.getImage(image.src).then(src => { + image.src = src; + return image; + }))); + const b64 = await mergeImages(images, { Canvas, Image }); + + const expectedB64 = await fixtures.getDataURI('face-custom-height-width.png'); + + t.true(b64 === expectedB64); +}); + test('mergeImages uses custom jpeg quality', async t => { t.plan(1); const image = await fixtures.getImage('face.png'); From c8ad5f5c126ab663ac44f77b9dcd3f3b82204db3 Mon Sep 17 00:00:00 2001 From: Danial Date: Fri, 25 Nov 2022 09:40:57 +1300 Subject: [PATCH 2/2] Fixup linting --- src/index.js | 10 ++++++---- test/errors.js | 2 +- test/types.js | 2 +- test/unit.js | 16 ++++++++-------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/index.js b/src/index.js index 4fe8aa4..ce46355 100644 --- a/src/index.js +++ b/src/index.js @@ -9,16 +9,18 @@ const defaultOptions = { }; const getX = (image, width) => { - if (image.right != undefined) + if (image.right !== undefined) { return width - (image.right + (image.width || image.img.width)); + } return image.left || image.x || 0; -} +}; const getY = (image, height) => { - if (image.bottom != undefined) + if (image.bottom !== undefined) { return height - (image.bottom + (image.height || image.img.height)); + } return image.top || image.y || 0; -} +}; // Return Promise const mergeImages = (sources = [], options = {}) => new Promise(resolve => { diff --git a/test/errors.js b/test/errors.js index e6515a7..9ec69fb 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,6 +1,6 @@ +import mergeImages from '../'; import test from 'ava'; import { Canvas, Image } from 'canvas'; -import mergeImages from '../'; test('mergeImages rejects Promise if node-canvas instance isn\'t passed in', async t => { t.plan(1); diff --git a/test/types.js b/test/types.js index b12cc0c..b78050a 100644 --- a/test/types.js +++ b/test/types.js @@ -1,6 +1,6 @@ +import mergeImages from '../'; import test from 'ava'; import { Canvas, Image } from 'canvas'; -import mergeImages from '../'; test('mergeImages is a function', t => { t.is(typeof mergeImages, 'function'); diff --git a/test/unit.js b/test/unit.js index 5b000a3..eb66380 100644 --- a/test/unit.js +++ b/test/unit.js @@ -1,7 +1,7 @@ -import test from 'ava'; -import { Canvas, Image } from 'canvas'; import mergeImages from '../'; import fixtures from './fixtures'; +import { Canvas, Image } from 'canvas'; +import test from 'ava'; test('mergeImages returns empty b64 string if nothing is passed in', async t => { t.plan(1); @@ -62,9 +62,9 @@ test('mergeImages uses custom dimensions', async t => { test('mergeImages uses custom positions', async t => { t.plan(1); const images = await Promise.all([ - { src: 'body.png', left: 0, top: 0 }, - { src: 'eyes.png', x: 32, y: 0 }, - { src: 'mouth.png', right: -16, bottom: 0 } + { src: 'body.png', left: 0, top: 0 }, + { src: 'eyes.png', x: 32, y: 0 }, + { src: 'mouth.png', right: -16, bottom: 0 } ].map(image => fixtures.getImage(image.src).then(src => { image.src = src; return image; @@ -79,9 +79,9 @@ test('mergeImages uses custom positions', async t => { test('mergeImages uses custom sizing', async t => { t.plan(1); const images = await Promise.all([ - { src: 'body.png', left: 0, top: 0 }, - { src: 'eyes.png', x: 32, y: 64, height: 180 }, - { src: 'mouth.png', right: 32, bottom: 0, width: 128, height: 180 } + { src: 'body.png', left: 0, top: 0 }, + { src: 'eyes.png', x: 32, y: 64, height: 180 }, + { src: 'mouth.png', right: 32, bottom: 0, width: 128, height: 180 } ].map(image => fixtures.getImage(image.src).then(src => { image.src = src; return image;