From d4b5bfb70d8929cce455b0080d6db7691ebfd8c5 Mon Sep 17 00:00:00 2001 From: aimma Date: Tue, 21 Apr 2020 14:02:34 -0700 Subject: [PATCH] fix(helper-image): rotate/create thumbnail image correctly --- .../@webex/helper-image/README.md | 4 - .../@webex/helper-image/src/index.js | 78 ++++----------- .../helper-image/src/process-image.browser.js | 24 +++-- .../helper-image/test/unit/spec/index.js | 96 ++++++++++--------- 4 files changed, 85 insertions(+), 117 deletions(-) diff --git a/packages/node_modules/@webex/helper-image/README.md b/packages/node_modules/@webex/helper-image/README.md index 404566a24b9..7b4b44805cc 100644 --- a/packages/node_modules/@webex/helper-image/README.md +++ b/packages/node_modules/@webex/helper-image/README.md @@ -18,10 +18,6 @@ npm install --save @webex/helper-image ## Usage -### `drawImage(options)` - -Draws the image on the canvas so that the thumbnail could be generated. - ### `updateImageOrientation(file)` Updates the image file with exif information, required to correctly rotate the image activity diff --git a/packages/node_modules/@webex/helper-image/src/index.js b/packages/node_modules/@webex/helper-image/src/index.js index 24a40a2f3eb..8fc2978a6a7 100644 --- a/packages/node_modules/@webex/helper-image/src/index.js +++ b/packages/node_modules/@webex/helper-image/src/index.js @@ -8,38 +8,6 @@ const {Buffer} = require('safe-buffer'); const {ExifImage} = require('exif'); -/** -* Draws the image on the canvas so that the thumbnail -* could be generated -* @param {Object} options(options(orientation: image exif orientation range from 1-8, img: Image object, x: start x-axis, y: start y-axis, width: width of the thumbnail, height: height of the thumbnail, deg: counterclockwise degree rotation, flip: flip Image, ctx: canvas context)) -* @returns {Object} -*/ -export function drawImage(options) { - // save current context before applying transformations - options.ctx.save(); - let rad; - - // convert degrees to radians - if (options.flip) { - rad = options.deg * Math.PI / 180; - } - else { - rad = 2 * Math.PI - options.deg * Math.PI / 180; - } - // set the origin to the center of the image - options.ctx.translate(options.x + options.width / 2, options.y + options.height / 2); - // rotate the canvas around the origin - options.ctx.rotate(rad); - if (options.flip) { - // flip the canvas - options.ctx.scale(-1, 1); - } - // draw the image - options.ctx.drawImage(options.img, -options.width / 2, -options.height / 2, options.width, options.height); - // restore the canvas - options.ctx.restore(); -} - /** * Updates the image file with exif information, required to correctly rotate the image activity * @param {Object} file @@ -97,57 +65,47 @@ export function readExifData(file, buf) { * @returns {Object} */ export function orient(options, file) { - if (file && file.orientation && file.orientation !== 1) { - const image = { - img: options.img, - x: options.x, - y: options.y, - width: options.width, - height: options.height, - deg: 0, - flip: true, - ctx: options.ctx - }; + const { + width, height, ctx, img, orientation, x, y + } = options; - switch (options && options.orientation) { + if (file && file.orientation && file.orientation !== 1) { + // explanation of orientation: + // https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images + switch (orientation) { + case 2: + // flip + ctx.transform(-1, 0, 0, 1, width, 0); + break; case 3: // rotateImage180 - image.deg = 180; - image.flip = false; + ctx.transform(-1, 0, 0, -1, width, height); break; case 4: // rotate180AndFlipImage - image.deg = 180; - image.flip = true; + ctx.transform(1, 0, 0, -1, 0, height); break; case 5: // rotate90AndFlipImage - image.deg = 270; - image.flip = true; + ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: // rotateImage90 - image.deg = 270; - image.flip = false; + ctx.transform(0, 1, -1, 0, height, 0); break; case 7: // rotateNeg90AndFlipImage - image.deg = 90; - image.flip = true; + ctx.transform(0, -1, -1, 0, height, width); break; case 8: // rotateNeg90 - image.deg = 90; - image.flip = false; + ctx.transform(0, -1, 1, 0, 0, width); break; default: break; } - drawImage(image); - } - else { - options.ctx.drawImage(options.img, options.x, options.y, options.width, options.height); } + ctx.drawImage(img, x, y, width, height); } /* eslint-enable complexity */ diff --git a/packages/node_modules/@webex/helper-image/src/process-image.browser.js b/packages/node_modules/@webex/helper-image/src/process-image.browser.js index 9d8816c9fe7..092362a6fa2 100644 --- a/packages/node_modules/@webex/helper-image/src/process-image.browser.js +++ b/packages/node_modules/@webex/helper-image/src/process-image.browser.js @@ -90,11 +90,22 @@ export default function processImage({ const thumbnailDimensions = computeDimensions(fileDimensions, thumbnailMaxWidth, thumbnailMaxHeight); const canvas = document.createElement('canvas'); - - canvas.width = thumbnailDimensions.width; - canvas.height = thumbnailDimensions.height; - const ctx = canvas.getContext('2d'); + const {width, height} = thumbnailDimensions; + + // explanation of orientation: + // https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images + if (file.orientation && file.orientation > 4) { + canvas.width = height; + canvas.height = width; + thumbnailDimensions.width = height; + thumbnailDimensions.height = width; + } + else { + canvas.width = thumbnailDimensions.width; + canvas.height = thumbnailDimensions.height; + } + orient( { @@ -102,12 +113,13 @@ export default function processImage({ img, x: 0, y: 0, - width: thumbnailDimensions.width, - height: thumbnailDimensions.height, + width, + height, ctx }, file ); + const parts = canvas.toDataURL('image/png').split(','); // Thumbnail uploads were failing with common/base64 decoding const byteString = atob(parts[1]); diff --git a/packages/node_modules/@webex/helper-image/test/unit/spec/index.js b/packages/node_modules/@webex/helper-image/test/unit/spec/index.js index 9cdc3fa9e82..c1d15f83ccb 100644 --- a/packages/node_modules/@webex/helper-image/test/unit/spec/index.js +++ b/packages/node_modules/@webex/helper-image/test/unit/spec/index.js @@ -76,94 +76,96 @@ describe('helper-image', () => { }); describe('orient()', () => { + const file = { + displayName: 'Portrait_7.jpg', + fileSize: 405822, + type: 'image/jpeg', + image: { + height: 300, + width: 362 + }, + mimeType: 'image/jpeg', + objectType: 'file' + }; + const options = { + img: 'Portrait_7.jpg', + x: 0, + y: 0, + width: 362, + height: 300, + ctx: { + save: sinon.stub().returns(() => true), + translate: sinon.stub().returns(() => true), + rotate: sinon.stub().returns(() => true), + transform: sinon.stub().returns(() => true), + scale: sinon.stub().returns(() => true), + drawImage: sinon.stub().returns(() => true), + restore: sinon.stub().returns(() => true) + } + }; + const {height, width} = options; const events = [ { orientation: 1 }, { orientation: 2, - flip: true + flip: true, + transform: [-1, 0, 0, 1, width, 0] }, { orientation: 3, - rotate: '180' + rotate: '180', + transform: [-1, 0, 0, -1, width, height] }, { orientation: 4, flip: true, - rotate: '180' + rotate: '180', + transform: [1, 0, 0, -1, 0, height] }, { orientation: 5, flip: true, - rotate: '270' + rotate: '270', + transform: [0, 1, 1, 0, 0, 0] }, { orientation: 6, - rotate: '270' + rotate: '270', + transform: [0, 1, -1, 0, height, 0] }, { orientation: 7, flip: true, - rotate: '90' + rotate: '90', + transform: [0, -1, -1, 0, height, width] }, { orientation: 8, - rotate: '90' + rotate: '90', + transform: [0, -1, 1, 0, 0, width] } ]; - const file = { - displayName: 'Portrait_7.jpg', - fileSize: 405822, - type: 'image/jpeg', - image: { - height: 300, - width: 362 - }, - mimeType: 'image/jpeg', - objectType: 'file' - }; - const options = { - img: 'Portrait_7.jpg', - x: 0, - y: 0, - width: 362, - height: 300, - ctx: { - save: sinon.stub().returns(() => true), - translate: sinon.stub().returns(() => true), - rotate: sinon.stub().returns(() => true), - scale: sinon.stub().returns(() => true), - drawImage: sinon.stub().returns(() => true), - restore: sinon.stub().returns(() => true) - } - }; events.forEach((def) => { - const {flip, orientation, rotate} = def; + const { + flip, orientation, rotate, transform + } = def; describe(`when an image file is received with orientation as ${orientation}`, () => { options.orientation = orientation; - file.image.orientation = orientation; + file.orientation = orientation; orient(options, file); let message = flip ? 'flips ' : ''; message += rotate ? `rotates ${rotate} deg` : ''; message = message ? `image on the canvas ${message}` : 'does nothing '; it(`${message}`, () => { - assert.isTrue(options.ctx.save.called); - assert.isTrue(options.ctx.translate.calledWith(options.x + options.width / 2, options.y + options.height / 2)); - assert.isTrue(options.ctx.drawImage.calledWith(options.img, -options.width / 2, -options.height / 2, options.width, options.height)); - assert.isTrue(options.ctx.restore.called); - if (flip) { - assert.isTrue(options.ctx.scale.calledWith(-1, 1)); - if (rotate) { - assert.isTrue(options.ctx.rotate.calledWith(rotate * Math.PI / 180)); - } - } - else if (rotate) { - assert.isTrue(options.ctx.rotate.calledWith(2 * Math.PI - 90 * Math.PI / 180)); + if (transform) { + assert.isTrue(options.ctx.transform.calledWith(...transform)); } + assert.isTrue(options.ctx.drawImage.calledWith(options.img, options.x, options.y, width, height)); }); }); });