@webex/helper-image 3.0.0-beta.14 → 3.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detect-filetype.js.map +1 -1
- package/dist/index.js +17 -18
- package/dist/index.js.map +1 -1
- package/dist/process-image.browser.js.map +1 -1
- package/dist/process-image.js +2 -1
- package/dist/process-image.js.map +1 -1
- package/package.json +6 -6
- package/src/detect-filetype.js +8 -9
- package/src/index.js +31 -37
- package/src/process-image.browser.js +77 -69
- package/src/process-image.js +38 -31
- package/test/unit/spec/index.js +58 -59
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["detectFileType","file","logger","type","info","resolve","mimeType","detect","then","getType","name"],"sources":["detect-filetype.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {detect} from '@webex/http-core';\nimport {getType} from 'mime';\n\n/**\n * Determines the file type of the specified file\n * @param {FileLike} file\n * @param {Object} logger\n * @returns {Promise<string>}\n */\nexport default function detectFileType(file, logger) {\n if (file.type) {\n logger.info(`file already has type ${file.type}. using existing file.type.`);\n\n return Promise.resolve(file.type);\n }\n\n if (file.mimeType) {\n logger.info(`file already has mimeType ${file.type}. using existing file.mimeType.`);\n\n return Promise.resolve(file.mimeType);\n }\n\n // This kinda belongs in http core, but since we have no guarantee that\n // buffers are expected to have names there, it'll stay here for now.\n return detect(file)
|
|
1
|
+
{"version":3,"names":["detectFileType","file","logger","type","info","resolve","mimeType","detect","then","getType","name"],"sources":["detect-filetype.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {detect} from '@webex/http-core';\nimport {getType} from 'mime';\n\n/**\n * Determines the file type of the specified file\n * @param {FileLike} file\n * @param {Object} logger\n * @returns {Promise<string>}\n */\nexport default function detectFileType(file, logger) {\n if (file.type) {\n logger.info(`file already has type ${file.type}. using existing file.type.`);\n\n return Promise.resolve(file.type);\n }\n\n if (file.mimeType) {\n logger.info(`file already has mimeType ${file.type}. using existing file.mimeType.`);\n\n return Promise.resolve(file.mimeType);\n }\n\n // This kinda belongs in http core, but since we have no guarantee that\n // buffers are expected to have names there, it'll stay here for now.\n return detect(file).then((type) => {\n if (type === 'application/x-msi' || type === 'application/octet-stream') {\n logger.info(`detected filetype to be ${type}. Falling back to mime.lookup`);\n\n return getType(file.name);\n }\n\n logger.info(`detected filetype to be ${type}. returning it`);\n\n return type;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAIA;;AACA;;AALA;AACA;AACA;;AAKA;AACA;AACA;AACA;AACA;AACA;AACe,SAASA,cAAT,CAAwBC,IAAxB,EAA8BC,MAA9B,EAAsC;EACnD,IAAID,IAAI,CAACE,IAAT,EAAe;IACbD,MAAM,CAACE,IAAP,iCAAqCH,IAAI,CAACE,IAA1C;IAEA,OAAO,iBAAQE,OAAR,CAAgBJ,IAAI,CAACE,IAArB,CAAP;EACD;;EAED,IAAIF,IAAI,CAACK,QAAT,EAAmB;IACjBJ,MAAM,CAACE,IAAP,qCAAyCH,IAAI,CAACE,IAA9C;IAEA,OAAO,iBAAQE,OAAR,CAAgBJ,IAAI,CAACK,QAArB,CAAP;EACD,CAXkD,CAanD;EACA;;;EACA,OAAO,IAAAC,gBAAA,EAAON,IAAP,EAAaO,IAAb,CAAkB,UAACL,IAAD,EAAU;IACjC,IAAIA,IAAI,KAAK,mBAAT,IAAgCA,IAAI,KAAK,0BAA7C,EAAyE;MACvED,MAAM,CAACE,IAAP,mCAAuCD,IAAvC;MAEA,OAAO,IAAAM,aAAA,EAAQR,IAAI,CAACS,IAAb,CAAP;IACD;;IAEDR,MAAM,CAACE,IAAP,mCAAuCD,IAAvC;IAEA,OAAOA,IAAP;EACD,CAVM,CAAP;AAWD"}
|
package/dist/index.js
CHANGED
|
@@ -42,20 +42,19 @@ var _detectFiletype = _interopRequireDefault(require("./detect-filetype"));
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
/* eslint no-unused-vars: ["error", { "vars": "local" }] */
|
|
45
|
-
|
|
46
|
-
/* global FileReader */
|
|
45
|
+
// eslint-disable-next-line no-redeclare
|
|
47
46
|
var _require = require('safe-buffer'),
|
|
48
47
|
Buffer = _require.Buffer;
|
|
49
48
|
|
|
50
49
|
var _require2 = require('exifr/dist/lite.umd'),
|
|
51
50
|
parse = _require2.parse;
|
|
52
51
|
/**
|
|
53
|
-
* Updates the image file with exif information, required to correctly rotate the image activity
|
|
54
|
-
* @param {Object} file
|
|
55
|
-
* @param {Object} options
|
|
56
|
-
* @param {boolean} options.shouldNotAddExifData
|
|
57
|
-
* @returns {Promise<Object>}
|
|
58
|
-
*/
|
|
52
|
+
* Updates the image file with exif information, required to correctly rotate the image activity
|
|
53
|
+
* @param {Object} file
|
|
54
|
+
* @param {Object} options
|
|
55
|
+
* @param {boolean} options.shouldNotAddExifData
|
|
56
|
+
* @returns {Promise<Object>}
|
|
57
|
+
*/
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
function updateImageOrientation(file) {
|
|
@@ -78,11 +77,11 @@ function updateImageOrientation(file) {
|
|
|
78
77
|
});
|
|
79
78
|
}
|
|
80
79
|
/**
|
|
81
|
-
* Adds exif orientation information on the image file
|
|
82
|
-
* @param {Object} file
|
|
83
|
-
* @param {Object} buf
|
|
84
|
-
* @returns {Promise<ExifImage>}
|
|
85
|
-
*/
|
|
80
|
+
* Adds exif orientation information on the image file
|
|
81
|
+
* @param {Object} file
|
|
82
|
+
* @param {Object} buf
|
|
83
|
+
* @returns {Promise<ExifImage>}
|
|
84
|
+
*/
|
|
86
85
|
|
|
87
86
|
|
|
88
87
|
function readExifData(_x, _x2) {
|
|
@@ -91,11 +90,11 @@ function readExifData(_x, _x2) {
|
|
|
91
90
|
/* eslint-disable complexity */
|
|
92
91
|
|
|
93
92
|
/**
|
|
94
|
-
* Rotates/flips the image on the canvas as per exif information
|
|
95
|
-
* @param {Object} 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, ctx: canvas context)
|
|
96
|
-
* @param {Object} file
|
|
97
|
-
* @returns {Object}
|
|
98
|
-
*/
|
|
93
|
+
* Rotates/flips the image on the canvas as per exif information
|
|
94
|
+
* @param {Object} 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, ctx: canvas context)
|
|
95
|
+
* @param {Object} file
|
|
96
|
+
* @returns {Object}
|
|
97
|
+
*/
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
function _readExifData() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["require","Buffer","parse","updateImageOrientation","file","options","resolve","reader","FileReader","readAsArrayBuffer","onload","arrayBuffer","result","buf","from","then","shouldNotAddExifData","readExifData","type","mimeType","translateValues","exifData","Orientation","ExifImageHeight","ExifImageWidth","orientation","exifHeight","exifWidth","image","orient","width","height","ctx","img","x","y","transform","drawImage"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint no-unused-vars: [\"error\", { \"vars\": \"local\" }] */\n
|
|
1
|
+
{"version":3,"names":["require","Buffer","parse","updateImageOrientation","file","options","resolve","reader","FileReader","readAsArrayBuffer","onload","arrayBuffer","result","buf","from","then","shouldNotAddExifData","readExifData","type","mimeType","translateValues","exifData","Orientation","ExifImageHeight","ExifImageWidth","orientation","exifHeight","exifWidth","image","orient","width","height","ctx","img","x","y","transform","drawImage"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint no-unused-vars: [\"error\", { \"vars\": \"local\" }] */\n// eslint-disable-next-line no-redeclare\n\nconst {Buffer} = require('safe-buffer');\nconst {parse} = require('exifr/dist/lite.umd');\n\n/**\n * Updates the image file with exif information, required to correctly rotate the image activity\n * @param {Object} file\n * @param {Object} options\n * @param {boolean} options.shouldNotAddExifData\n * @returns {Promise<Object>}\n */\nexport function updateImageOrientation(file, options = {}) {\n return new Promise((resolve) => {\n const reader = new FileReader();\n\n reader.readAsArrayBuffer(file);\n reader.onload = function onload() {\n const arrayBuffer = reader.result;\n const buf = Buffer.from(arrayBuffer);\n\n resolve(buf);\n };\n }).then((buf) => {\n if (options.shouldNotAddExifData) {\n return buf;\n }\n\n return readExifData(file, buf);\n });\n}\n\n/**\n * Adds exif orientation information on the image file\n * @param {Object} file\n * @param {Object} buf\n * @returns {Promise<ExifImage>}\n */\nexport async function readExifData(file, buf) {\n // For avatar images the file.type is set as image/jpeg, however for images shared in an activity file.mimeType is set as image/jpeg. Handling both conditions.\n if (file && (file.type === 'image/jpeg' || file.mimeType === 'image/jpeg')) {\n const exifData = await parse(buf, {translateValues: false});\n\n if (exifData) {\n const {Orientation, ExifImageHeight, ExifImageWidth} = exifData;\n\n file.orientation = Orientation;\n file.exifHeight = ExifImageHeight;\n file.exifWidth = ExifImageWidth;\n\n if (file.image) {\n file.image.orientation = Orientation;\n }\n }\n }\n\n return buf;\n}\n\n/* eslint-disable complexity */\n/**\n * Rotates/flips the image on the canvas as per exif information\n * @param {Object} 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, ctx: canvas context)\n * @param {Object} file\n * @returns {Object}\n */\nexport function orient(options, file) {\n const {width, height, ctx, img, orientation, x, y} = options;\n\n if (file && file.orientation && file.orientation !== 1) {\n // explanation of orientation:\n // https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images\n switch (orientation) {\n case 2:\n // flip\n ctx.transform(-1, 0, 0, 1, width, 0);\n break;\n case 3:\n // rotateImage180\n ctx.transform(-1, 0, 0, -1, width, height);\n break;\n case 4:\n // rotate180AndFlipImage\n ctx.transform(1, 0, 0, -1, 0, height);\n break;\n case 5:\n // rotate90AndFlipImage\n ctx.transform(0, 1, 1, 0, 0, 0);\n break;\n case 6:\n // rotateImage90\n ctx.transform(0, 1, -1, 0, height, 0);\n break;\n case 7:\n // rotateNeg90AndFlipImage\n ctx.transform(0, -1, -1, 0, height, width);\n break;\n case 8:\n // rotateNeg90\n ctx.transform(0, -1, 1, 0, 0, width);\n break;\n default:\n break;\n }\n }\n ctx.drawImage(img, x, y, width, height);\n}\n/* eslint-enable complexity */\n\nexport {default as processImage} from './process-image';\nexport {default as detectFileType} from './detect-filetype';\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["computeDimensions","maxWidth","maxHeight","width","height","processImage","file","type","thumbnailMaxWidth","thumbnailMaxHeight","enableThumbnails","logger","isAvatar","startsWith","resolve","Blob","reject","img","Image","onload","onerror","src","URL","createObjectURL","then","fileDimensions","info","size","thumbnailDimensions","canvas","document","createElement","ctx","getContext","orientation","orient","x","y","parts","toDataURL","split","byteString","atob","buffer","ArrayBuffer","length","view","DataView","i","setUint8","charCodeAt"],"sources":["process-image.browser.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {pick} from 'lodash';\n\nimport {orient} from './index';\n/* eslint-env browser */\n\n/**\n * Determins the dimensions of an image\n * @param {Object} constraints\n * @param {Number} constraints.width\n * @param {Number} constraints.height\n * @param {Number} maxWidth\n * @param {Number} maxHeight\n * @returns {Object}\n */\nfunction computeDimensions({width, height}, maxWidth, maxHeight) {\n if (height > width) {\n if (height > maxHeight) {\n width = width * maxHeight / height;\n height = maxHeight;\n }\n\n if (width > maxWidth) {\n height = height * maxWidth / width;\n width = maxWidth;\n }\n }
|
|
1
|
+
{"version":3,"names":["computeDimensions","maxWidth","maxHeight","width","height","processImage","file","type","thumbnailMaxWidth","thumbnailMaxHeight","enableThumbnails","logger","isAvatar","startsWith","resolve","Blob","reject","img","Image","onload","onerror","src","URL","createObjectURL","then","fileDimensions","info","size","thumbnailDimensions","canvas","document","createElement","ctx","getContext","orientation","orient","x","y","parts","toDataURL","split","byteString","atob","buffer","ArrayBuffer","length","view","DataView","i","setUint8","charCodeAt"],"sources":["process-image.browser.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {pick} from 'lodash';\n\nimport {orient} from './index';\n/* eslint-env browser */\n\n/**\n * Determins the dimensions of an image\n * @param {Object} constraints\n * @param {Number} constraints.width\n * @param {Number} constraints.height\n * @param {Number} maxWidth\n * @param {Number} maxHeight\n * @returns {Object}\n */\nfunction computeDimensions({width, height}, maxWidth, maxHeight) {\n if (height > width) {\n if (height > maxHeight) {\n width = (width * maxHeight) / height;\n height = maxHeight;\n }\n\n if (width > maxWidth) {\n height = (height * maxWidth) / width;\n width = maxWidth;\n }\n } else {\n if (width > maxWidth) {\n height = (height * maxWidth) / width;\n width = maxWidth;\n }\n\n if (height > maxHeight) {\n width = (width * maxHeight) / height;\n height = maxHeight;\n }\n }\n\n return {height, width};\n}\n\n/**\n * Measures an image file and produces a thumbnail for it\n * @param {Object} options\n * @param {Blob|ArrayBuffer} options.file\n * @param {Number} options.thumbnailMaxWidth\n * @param {Number} options.thumbnailMaxHeight\n * @param {Boolean} options.enableThumbnails\n * @param {Object} options.logger\n * @param {Boolean} options.isAvatar\n * @returns {Promise<Array>} Buffer, Dimensions, thumbnailDimensions\n */\nexport default function processImage({\n file,\n type,\n thumbnailMaxWidth,\n thumbnailMaxHeight,\n enableThumbnails,\n logger,\n isAvatar,\n}) {\n if (!type || !type.startsWith('image')) {\n return Promise.resolve();\n }\n\n file = file instanceof Blob ? file : new Blob([file]);\n\n return new Promise((resolve, reject) => {\n const img = new Image();\n\n img.onload = function onload() {\n resolve(img);\n };\n img.onerror = reject;\n img.src = URL.createObjectURL(file);\n }).then((img) => {\n const fileDimensions = pick(img, 'height', 'width');\n\n if (isAvatar) {\n // only if image is a profile avatar\n logger.info('dimensions will be set for avatar image');\n const size =\n fileDimensions.height > fileDimensions.width ? fileDimensions.height : fileDimensions.width;\n\n fileDimensions.height = size;\n fileDimensions.width = size;\n }\n if (!enableThumbnails) {\n logger.info('thumbnails not enabled');\n\n return [null, fileDimensions, null];\n }\n const thumbnailDimensions = computeDimensions(\n fileDimensions,\n thumbnailMaxWidth,\n thumbnailMaxHeight\n );\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n const {width, height} = thumbnailDimensions;\n\n // explanation of orientation:\n // https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images\n if (file.orientation && file.orientation > 4) {\n canvas.width = height;\n canvas.height = width;\n thumbnailDimensions.width = height;\n thumbnailDimensions.height = width;\n } else {\n canvas.width = thumbnailDimensions.width;\n canvas.height = thumbnailDimensions.height;\n }\n\n orient(\n {\n orientation: file && file.orientation ? file.orientation : '',\n img,\n x: 0,\n y: 0,\n width,\n height,\n ctx,\n },\n file\n );\n\n const parts = canvas.toDataURL('image/png').split(',');\n // Thumbnail uploads were failing with common/base64 decoding\n const byteString = atob(parts[1]);\n\n const buffer = new ArrayBuffer(byteString.length);\n const view = new DataView(buffer);\n\n for (let i = 0; i < byteString.length; i += 1) {\n view.setUint8(i, byteString.charCodeAt(i));\n }\n\n return [buffer, fileDimensions, thumbnailDimensions];\n });\n}\n"],"mappings":"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"}
|
package/dist/process-image.js
CHANGED
|
@@ -53,7 +53,8 @@ function processImage(_ref) {
|
|
|
53
53
|
resolve((0, _pick2.default)(size, 'width', 'height'));
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
-
var thumbnail
|
|
56
|
+
var thumbnail;
|
|
57
|
+
var thumbnailDimensions;
|
|
57
58
|
|
|
58
59
|
if (enableThumbnails) {
|
|
59
60
|
thumbnail = new _promise.default(function (resolve, reject) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["processImage","file","type","thumbnailMaxWidth","thumbnailMaxHeight","enableThumbnails","logger","fileType","startsWith","resolve","fileDimensions","reject","gm","size","err","thumbnail","thumbnailDimensions","resize","autoOrient","toBuffer","buffer","then","all","catch","errorString","toString","includes","warn","debug"],"sources":["process-image.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport gm from 'gm';\nimport {pick} from 'lodash';\n\n/**\n * Measures an image file and produces a thumbnail for it\n * @param {Object} options\n * @param {Blob|ArrayBuffer} options.file\n * @param {Number} options.thumbnailMaxWidth\n * @param {Number} options.thumbnailMaxHeight\n * @param {Boolean} options.enableThumbnails\n * @param {Object} options.logger\n * @returns {Promise<Array>} Buffer, Dimensions, thumbnailDimensions\n */\nexport default function processImage({\n file
|
|
1
|
+
{"version":3,"names":["processImage","file","type","thumbnailMaxWidth","thumbnailMaxHeight","enableThumbnails","logger","fileType","startsWith","resolve","fileDimensions","reject","gm","size","err","thumbnail","thumbnailDimensions","resize","autoOrient","toBuffer","buffer","then","all","catch","errorString","toString","includes","warn","debug"],"sources":["process-image.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport gm from 'gm';\nimport {pick} from 'lodash';\n\n/**\n * Measures an image file and produces a thumbnail for it\n * @param {Object} options\n * @param {Blob|ArrayBuffer} options.file\n * @param {Number} options.thumbnailMaxWidth\n * @param {Number} options.thumbnailMaxHeight\n * @param {Boolean} options.enableThumbnails\n * @param {Object} options.logger\n * @returns {Promise<Array>} Buffer, Dimensions, thumbnailDimensions\n */\nexport default function processImage({\n file,\n type,\n thumbnailMaxWidth,\n thumbnailMaxHeight,\n enableThumbnails,\n logger,\n}) {\n const fileType = type || file.type;\n\n if (!fileType || !fileType.startsWith('image')) {\n return Promise.resolve();\n }\n\n const fileDimensions = new Promise((resolve, reject) => {\n gm(file).size((err, size) => {\n if (err) {\n reject(err);\n\n return;\n }\n\n resolve(pick(size, 'width', 'height'));\n });\n });\n\n let thumbnail;\n let thumbnailDimensions;\n\n if (enableThumbnails) {\n thumbnail = new Promise((resolve, reject) => {\n gm(file)\n .resize(thumbnailMaxWidth, thumbnailMaxHeight)\n .autoOrient()\n .toBuffer('PNG', (err, buffer) => {\n if (err) {\n reject(err);\n\n return;\n }\n\n resolve(buffer);\n });\n });\n\n thumbnailDimensions = thumbnail.then(\n (buffer) =>\n new Promise((resolve, reject) => {\n gm(buffer).size((err, size) => {\n if (err) {\n reject(err);\n\n return;\n }\n\n resolve(pick(size, 'width', 'height'));\n });\n })\n );\n }\n\n return Promise.all([thumbnail, fileDimensions, thumbnailDimensions]).catch((err) => {\n const errorString = err.toString();\n\n if (errorString.includes('EPIPE')) {\n logger.warn(err, 'Is GraphicsMagick installed?');\n\n return Promise.resolve();\n }\n\n if (errorString.includes('No decode delegate for this image format')) {\n logger.debug(err, 'File does not appear to be an image');\n\n return Promise.resolve();\n }\n\n if (errorString.includes('Stream yields empty buffer')) {\n logger.debug(err, 'File does not appear to be an image');\n\n return Promise.resolve();\n }\n\n return Promise.reject(err);\n });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/helper-image",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Saurabh Jain <saurjai3@cisco.com>",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"sinon": "^9.2.4"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@webex/helper-image": "3.0.0-beta.
|
|
32
|
-
"@webex/http-core": "3.0.0-beta.
|
|
33
|
-
"@webex/test-helper-chai": "3.0.0-beta.
|
|
34
|
-
"@webex/test-helper-file": "3.0.0-beta.
|
|
35
|
-
"@webex/test-helper-mocha": "3.0.0-beta.
|
|
31
|
+
"@webex/helper-image": "3.0.0-beta.15",
|
|
32
|
+
"@webex/http-core": "3.0.0-beta.15",
|
|
33
|
+
"@webex/test-helper-chai": "3.0.0-beta.15",
|
|
34
|
+
"@webex/test-helper-file": "3.0.0-beta.15",
|
|
35
|
+
"@webex/test-helper-mocha": "3.0.0-beta.15",
|
|
36
36
|
"exifr": "^5.0.3",
|
|
37
37
|
"gm": "^1.23.1",
|
|
38
38
|
"lodash": "^4.17.21",
|
package/src/detect-filetype.js
CHANGED
|
@@ -26,16 +26,15 @@ export default function detectFileType(file, logger) {
|
|
|
26
26
|
|
|
27
27
|
// This kinda belongs in http core, but since we have no guarantee that
|
|
28
28
|
// buffers are expected to have names there, it'll stay here for now.
|
|
29
|
-
return detect(file)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
logger.info(`detected filetype to be ${type}. Falling back to mime.lookup`);
|
|
29
|
+
return detect(file).then((type) => {
|
|
30
|
+
if (type === 'application/x-msi' || type === 'application/octet-stream') {
|
|
31
|
+
logger.info(`detected filetype to be ${type}. Falling back to mime.lookup`);
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
return getType(file.name);
|
|
34
|
+
}
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
logger.info(`detected filetype to be ${type}. returning it`);
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
return type;
|
|
39
|
+
});
|
|
41
40
|
}
|
package/src/index.js
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* eslint no-unused-vars: ["error", { "vars": "local" }] */
|
|
6
|
-
|
|
6
|
+
// eslint-disable-next-line no-redeclare
|
|
7
7
|
|
|
8
8
|
const {Buffer} = require('safe-buffer');
|
|
9
9
|
const {parse} = require('exifr/dist/lite.umd');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Updates the image file with exif information, required to correctly rotate the image activity
|
|
13
|
-
* @param {Object} file
|
|
14
|
-
* @param {Object} options
|
|
15
|
-
* @param {boolean} options.shouldNotAddExifData
|
|
16
|
-
* @returns {Promise<Object>}
|
|
17
|
-
*/
|
|
12
|
+
* Updates the image file with exif information, required to correctly rotate the image activity
|
|
13
|
+
* @param {Object} file
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {boolean} options.shouldNotAddExifData
|
|
16
|
+
* @returns {Promise<Object>}
|
|
17
|
+
*/
|
|
18
18
|
export function updateImageOrientation(file, options = {}) {
|
|
19
19
|
return new Promise((resolve) => {
|
|
20
20
|
const reader = new FileReader();
|
|
@@ -26,28 +26,24 @@ export function updateImageOrientation(file, options = {}) {
|
|
|
26
26
|
|
|
27
27
|
resolve(buf);
|
|
28
28
|
};
|
|
29
|
-
})
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
29
|
+
}).then((buf) => {
|
|
30
|
+
if (options.shouldNotAddExifData) {
|
|
31
|
+
return buf;
|
|
32
|
+
}
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
return readExifData(file, buf);
|
|
35
|
+
});
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
|
-
* Adds exif orientation information on the image file
|
|
41
|
-
* @param {Object} file
|
|
42
|
-
* @param {Object} buf
|
|
43
|
-
* @returns {Promise<ExifImage>}
|
|
44
|
-
*/
|
|
39
|
+
* Adds exif orientation information on the image file
|
|
40
|
+
* @param {Object} file
|
|
41
|
+
* @param {Object} buf
|
|
42
|
+
* @returns {Promise<ExifImage>}
|
|
43
|
+
*/
|
|
45
44
|
export async function readExifData(file, buf) {
|
|
46
45
|
// For avatar images the file.type is set as image/jpeg, however for images shared in an activity file.mimeType is set as image/jpeg. Handling both conditions.
|
|
47
|
-
if (
|
|
48
|
-
file &&
|
|
49
|
-
(file.type === 'image/jpeg' || file.mimeType === 'image/jpeg')
|
|
50
|
-
) {
|
|
46
|
+
if (file && (file.type === 'image/jpeg' || file.mimeType === 'image/jpeg')) {
|
|
51
47
|
const exifData = await parse(buf, {translateValues: false});
|
|
52
48
|
|
|
53
49
|
if (exifData) {
|
|
@@ -68,15 +64,13 @@ export async function readExifData(file, buf) {
|
|
|
68
64
|
|
|
69
65
|
/* eslint-disable complexity */
|
|
70
66
|
/**
|
|
71
|
-
* Rotates/flips the image on the canvas as per exif information
|
|
72
|
-
* @param {Object} 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, ctx: canvas context)
|
|
73
|
-
* @param {Object} file
|
|
74
|
-
* @returns {Object}
|
|
75
|
-
*/
|
|
67
|
+
* Rotates/flips the image on the canvas as per exif information
|
|
68
|
+
* @param {Object} 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, ctx: canvas context)
|
|
69
|
+
* @param {Object} file
|
|
70
|
+
* @returns {Object}
|
|
71
|
+
*/
|
|
76
72
|
export function orient(options, file) {
|
|
77
|
-
const {
|
|
78
|
-
width, height, ctx, img, orientation, x, y
|
|
79
|
-
} = options;
|
|
73
|
+
const {width, height, ctx, img, orientation, x, y} = options;
|
|
80
74
|
|
|
81
75
|
if (file && file.orientation && file.orientation !== 1) {
|
|
82
76
|
// explanation of orientation:
|
|
@@ -87,27 +81,27 @@ export function orient(options, file) {
|
|
|
87
81
|
ctx.transform(-1, 0, 0, 1, width, 0);
|
|
88
82
|
break;
|
|
89
83
|
case 3:
|
|
90
|
-
|
|
84
|
+
// rotateImage180
|
|
91
85
|
ctx.transform(-1, 0, 0, -1, width, height);
|
|
92
86
|
break;
|
|
93
87
|
case 4:
|
|
94
|
-
|
|
88
|
+
// rotate180AndFlipImage
|
|
95
89
|
ctx.transform(1, 0, 0, -1, 0, height);
|
|
96
90
|
break;
|
|
97
91
|
case 5:
|
|
98
|
-
|
|
92
|
+
// rotate90AndFlipImage
|
|
99
93
|
ctx.transform(0, 1, 1, 0, 0, 0);
|
|
100
94
|
break;
|
|
101
95
|
case 6:
|
|
102
|
-
|
|
96
|
+
// rotateImage90
|
|
103
97
|
ctx.transform(0, 1, -1, 0, height, 0);
|
|
104
98
|
break;
|
|
105
99
|
case 7:
|
|
106
|
-
|
|
100
|
+
// rotateNeg90AndFlipImage
|
|
107
101
|
ctx.transform(0, -1, -1, 0, height, width);
|
|
108
102
|
break;
|
|
109
103
|
case 8:
|
|
110
|
-
|
|
104
|
+
// rotateNeg90
|
|
111
105
|
ctx.transform(0, -1, 1, 0, 0, width);
|
|
112
106
|
break;
|
|
113
107
|
default:
|
|
@@ -19,23 +19,22 @@ import {orient} from './index';
|
|
|
19
19
|
function computeDimensions({width, height}, maxWidth, maxHeight) {
|
|
20
20
|
if (height > width) {
|
|
21
21
|
if (height > maxHeight) {
|
|
22
|
-
width = width * maxHeight / height;
|
|
22
|
+
width = (width * maxHeight) / height;
|
|
23
23
|
height = maxHeight;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (width > maxWidth) {
|
|
27
|
-
height = height * maxWidth / width;
|
|
27
|
+
height = (height * maxWidth) / width;
|
|
28
28
|
width = maxWidth;
|
|
29
29
|
}
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
30
|
+
} else {
|
|
32
31
|
if (width > maxWidth) {
|
|
33
|
-
height = height * maxWidth / width;
|
|
32
|
+
height = (height * maxWidth) / width;
|
|
34
33
|
width = maxWidth;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
if (height > maxHeight) {
|
|
38
|
-
width = width * maxHeight / height;
|
|
37
|
+
width = (width * maxHeight) / height;
|
|
39
38
|
height = maxHeight;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -55,7 +54,13 @@ function computeDimensions({width, height}, maxWidth, maxHeight) {
|
|
|
55
54
|
* @returns {Promise<Array>} Buffer, Dimensions, thumbnailDimensions
|
|
56
55
|
*/
|
|
57
56
|
export default function processImage({
|
|
58
|
-
file,
|
|
57
|
+
file,
|
|
58
|
+
type,
|
|
59
|
+
thumbnailMaxWidth,
|
|
60
|
+
thumbnailMaxHeight,
|
|
61
|
+
enableThumbnails,
|
|
62
|
+
logger,
|
|
63
|
+
isAvatar,
|
|
59
64
|
}) {
|
|
60
65
|
if (!type || !type.startsWith('image')) {
|
|
61
66
|
return Promise.resolve();
|
|
@@ -71,66 +76,69 @@ export default function processImage({
|
|
|
71
76
|
};
|
|
72
77
|
img.onerror = reject;
|
|
73
78
|
img.src = URL.createObjectURL(file);
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
79
|
+
}).then((img) => {
|
|
80
|
+
const fileDimensions = pick(img, 'height', 'width');
|
|
81
|
+
|
|
82
|
+
if (isAvatar) {
|
|
83
|
+
// only if image is a profile avatar
|
|
84
|
+
logger.info('dimensions will be set for avatar image');
|
|
85
|
+
const size =
|
|
86
|
+
fileDimensions.height > fileDimensions.width ? fileDimensions.height : fileDimensions.width;
|
|
87
|
+
|
|
88
|
+
fileDimensions.height = size;
|
|
89
|
+
fileDimensions.width = size;
|
|
90
|
+
}
|
|
91
|
+
if (!enableThumbnails) {
|
|
92
|
+
logger.info('thumbnails not enabled');
|
|
93
|
+
|
|
94
|
+
return [null, fileDimensions, null];
|
|
95
|
+
}
|
|
96
|
+
const thumbnailDimensions = computeDimensions(
|
|
97
|
+
fileDimensions,
|
|
98
|
+
thumbnailMaxWidth,
|
|
99
|
+
thumbnailMaxHeight
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const canvas = document.createElement('canvas');
|
|
103
|
+
const ctx = canvas.getContext('2d');
|
|
104
|
+
const {width, height} = thumbnailDimensions;
|
|
105
|
+
|
|
106
|
+
// explanation of orientation:
|
|
107
|
+
// https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
|
|
108
|
+
if (file.orientation && file.orientation > 4) {
|
|
109
|
+
canvas.width = height;
|
|
110
|
+
canvas.height = width;
|
|
111
|
+
thumbnailDimensions.width = height;
|
|
112
|
+
thumbnailDimensions.height = width;
|
|
113
|
+
} else {
|
|
114
|
+
canvas.width = thumbnailDimensions.width;
|
|
115
|
+
canvas.height = thumbnailDimensions.height;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
orient(
|
|
119
|
+
{
|
|
120
|
+
orientation: file && file.orientation ? file.orientation : '',
|
|
121
|
+
img,
|
|
122
|
+
x: 0,
|
|
123
|
+
y: 0,
|
|
124
|
+
width,
|
|
125
|
+
height,
|
|
126
|
+
ctx,
|
|
127
|
+
},
|
|
128
|
+
file
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const parts = canvas.toDataURL('image/png').split(',');
|
|
132
|
+
// Thumbnail uploads were failing with common/base64 decoding
|
|
133
|
+
const byteString = atob(parts[1]);
|
|
134
|
+
|
|
135
|
+
const buffer = new ArrayBuffer(byteString.length);
|
|
136
|
+
const view = new DataView(buffer);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < byteString.length; i += 1) {
|
|
139
|
+
view.setUint8(i, byteString.charCodeAt(i));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [buffer, fileDimensions, thumbnailDimensions];
|
|
143
|
+
});
|
|
136
144
|
}
|
package/src/process-image.js
CHANGED
|
@@ -16,7 +16,12 @@ import {pick} from 'lodash';
|
|
|
16
16
|
* @returns {Promise<Array>} Buffer, Dimensions, thumbnailDimensions
|
|
17
17
|
*/
|
|
18
18
|
export default function processImage({
|
|
19
|
-
file,
|
|
19
|
+
file,
|
|
20
|
+
type,
|
|
21
|
+
thumbnailMaxWidth,
|
|
22
|
+
thumbnailMaxHeight,
|
|
23
|
+
enableThumbnails,
|
|
24
|
+
logger,
|
|
20
25
|
}) {
|
|
21
26
|
const fileType = type || file.type;
|
|
22
27
|
|
|
@@ -36,11 +41,13 @@ export default function processImage({
|
|
|
36
41
|
});
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
let thumbnail
|
|
44
|
+
let thumbnail;
|
|
45
|
+
let thumbnailDimensions;
|
|
40
46
|
|
|
41
47
|
if (enableThumbnails) {
|
|
42
48
|
thumbnail = new Promise((resolve, reject) => {
|
|
43
|
-
gm(file)
|
|
49
|
+
gm(file)
|
|
50
|
+
.resize(thumbnailMaxWidth, thumbnailMaxHeight)
|
|
44
51
|
.autoOrient()
|
|
45
52
|
.toBuffer('PNG', (err, buffer) => {
|
|
46
53
|
if (err) {
|
|
@@ -53,43 +60,43 @@ export default function processImage({
|
|
|
53
60
|
});
|
|
54
61
|
});
|
|
55
62
|
|
|
56
|
-
thumbnailDimensions = thumbnail.then(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
thumbnailDimensions = thumbnail.then(
|
|
64
|
+
(buffer) =>
|
|
65
|
+
new Promise((resolve, reject) => {
|
|
66
|
+
gm(buffer).size((err, size) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
reject(err);
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
resolve(pick(size, 'width', 'height'));
|
|
74
|
+
});
|
|
75
|
+
})
|
|
76
|
+
);
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
return Promise.all([thumbnail, fileDimensions, thumbnailDimensions]).catch((err) => {
|
|
80
|
+
const errorString = err.toString();
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const errorString = err.toString();
|
|
82
|
+
if (errorString.includes('EPIPE')) {
|
|
83
|
+
logger.warn(err, 'Is GraphicsMagick installed?');
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
if (errorString.includes('No decode delegate for this image format')) {
|
|
89
|
+
logger.debug(err, 'File does not appear to be an image');
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
}
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (errorString.includes('Stream yields empty buffer')) {
|
|
88
|
-
logger.debug(err, 'File does not appear to be an image');
|
|
94
|
+
if (errorString.includes('Stream yields empty buffer')) {
|
|
95
|
+
logger.debug(err, 'File does not appear to be an image');
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
}
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
return Promise.reject(err);
|
|
101
|
+
});
|
|
95
102
|
}
|
package/test/unit/spec/index.js
CHANGED
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {assert} from '@webex/test-helper-chai';
|
|
6
|
-
import {
|
|
7
|
-
readExifData,
|
|
8
|
-
orient,
|
|
9
|
-
updateImageOrientation
|
|
10
|
-
} from '@webex/helper-image';
|
|
6
|
+
import {readExifData, orient, updateImageOrientation} from '@webex/helper-image';
|
|
11
7
|
import fileHelper from '@webex/test-helper-file';
|
|
12
8
|
import sinon from 'sinon';
|
|
13
9
|
import {browserOnly, nodeOnly} from '@webex/test-helper-mocha';
|
|
@@ -17,8 +13,8 @@ describe('helper-image', () => {
|
|
|
17
13
|
xdescribe('readExifData()', () => {
|
|
18
14
|
let buffer;
|
|
19
15
|
|
|
20
|
-
browserOnly(before)(() =>
|
|
21
|
-
.then((resFile) => {
|
|
16
|
+
browserOnly(before)(() =>
|
|
17
|
+
fileHelper.fetch('/Portrait_7.jpg').then((resFile) => {
|
|
22
18
|
/* global FileReader */
|
|
23
19
|
const fileReader = new FileReader();
|
|
24
20
|
|
|
@@ -30,12 +26,14 @@ describe('helper-image', () => {
|
|
|
30
26
|
resolve();
|
|
31
27
|
};
|
|
32
28
|
});
|
|
33
|
-
})
|
|
29
|
+
})
|
|
30
|
+
);
|
|
34
31
|
|
|
35
|
-
nodeOnly(before)(() =>
|
|
36
|
-
.then((resFile) => {
|
|
32
|
+
nodeOnly(before)(() =>
|
|
33
|
+
fileHelper.fetch('/Portrait_7.jpg').then((resFile) => {
|
|
37
34
|
buffer = resFile;
|
|
38
|
-
})
|
|
35
|
+
})
|
|
36
|
+
);
|
|
39
37
|
|
|
40
38
|
it('adds exif orientation information on the image file', () => {
|
|
41
39
|
const sampleFile = {
|
|
@@ -44,17 +42,16 @@ describe('helper-image', () => {
|
|
|
44
42
|
type: 'image/jpeg',
|
|
45
43
|
image: {
|
|
46
44
|
height: 300,
|
|
47
|
-
width: 362
|
|
45
|
+
width: 362,
|
|
48
46
|
},
|
|
49
47
|
mimeType: 'image/jpeg',
|
|
50
|
-
objectType: 'file'
|
|
48
|
+
objectType: 'file',
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
return readExifData(sampleFile, buffer)
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
51
|
+
return readExifData(sampleFile, buffer).then((res) => {
|
|
52
|
+
assert.equal(res, buffer);
|
|
53
|
+
assert.equal(sampleFile.orientation, 7);
|
|
54
|
+
});
|
|
58
55
|
});
|
|
59
56
|
|
|
60
57
|
it('adds replaces height/width with exif height/width information', () => {
|
|
@@ -64,47 +61,49 @@ describe('helper-image', () => {
|
|
|
64
61
|
type: 'image/jpeg',
|
|
65
62
|
image: {
|
|
66
63
|
height: 300,
|
|
67
|
-
width: 362
|
|
64
|
+
width: 362,
|
|
68
65
|
},
|
|
69
66
|
mimeType: 'image/jpeg',
|
|
70
|
-
objectType: 'file'
|
|
67
|
+
objectType: 'file',
|
|
71
68
|
};
|
|
72
69
|
|
|
73
|
-
return readExifData(sampleFile, buffer)
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
70
|
+
return readExifData(sampleFile, buffer).then((res) => {
|
|
71
|
+
assert.equal(res, buffer);
|
|
72
|
+
assert.equal(sampleFile.orientation, 7);
|
|
73
|
+
assert.equal(sampleFile.exifHeight, 450);
|
|
74
|
+
assert.equal(sampleFile.exifWidth, 600);
|
|
75
|
+
});
|
|
80
76
|
});
|
|
81
77
|
});
|
|
82
78
|
|
|
83
79
|
browserOnly(describe)('updateImageOrientation()', () => {
|
|
84
80
|
let file;
|
|
85
81
|
|
|
86
|
-
before(() =>
|
|
87
|
-
.then((resFile) => {
|
|
82
|
+
before(() =>
|
|
83
|
+
fileHelper.fetch('/Portrait_7.jpg').then((resFile) => {
|
|
88
84
|
file = resFile;
|
|
89
85
|
file.displayName = 'Portrait_7.jpg';
|
|
90
86
|
file.mimeType = 'image/jpeg';
|
|
91
|
-
})
|
|
87
|
+
})
|
|
88
|
+
);
|
|
92
89
|
|
|
93
|
-
it('does not add exif data on image file', () =>
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
it('does not add exif data on image file', () =>
|
|
91
|
+
updateImageOrientation(file, {shouldNotAddExifData: true})
|
|
92
|
+
.then((res) => {
|
|
93
|
+
assert.equal(file.orientation, undefined);
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
return fileHelper.isMatchingFile(res, file);
|
|
96
|
+
})
|
|
97
|
+
.then((result) => assert.isTrue(result)));
|
|
100
98
|
|
|
101
|
-
it('adds exif data on the image file', () =>
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
it('adds exif data on the image file', () =>
|
|
100
|
+
updateImageOrientation(file)
|
|
101
|
+
.then((res) => {
|
|
102
|
+
assert.equal(file.orientation, 7);
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
return fileHelper.isMatchingFile(res, file);
|
|
105
|
+
})
|
|
106
|
+
.then((result) => assert.isTrue(result)));
|
|
108
107
|
});
|
|
109
108
|
|
|
110
109
|
describe('orient()', () => {
|
|
@@ -114,10 +113,10 @@ describe('helper-image', () => {
|
|
|
114
113
|
type: 'image/jpeg',
|
|
115
114
|
image: {
|
|
116
115
|
height: 300,
|
|
117
|
-
width: 362
|
|
116
|
+
width: 362,
|
|
118
117
|
},
|
|
119
118
|
mimeType: 'image/jpeg',
|
|
120
|
-
objectType: 'file'
|
|
119
|
+
objectType: 'file',
|
|
121
120
|
};
|
|
122
121
|
const options = {
|
|
123
122
|
img: 'Portrait_7.jpg',
|
|
@@ -132,58 +131,56 @@ describe('helper-image', () => {
|
|
|
132
131
|
transform: sinon.stub().returns(() => true),
|
|
133
132
|
scale: sinon.stub().returns(() => true),
|
|
134
133
|
drawImage: sinon.stub().returns(() => true),
|
|
135
|
-
restore: sinon.stub().returns(() => true)
|
|
136
|
-
}
|
|
134
|
+
restore: sinon.stub().returns(() => true),
|
|
135
|
+
},
|
|
137
136
|
};
|
|
138
137
|
const {height, width} = options;
|
|
139
138
|
const events = [
|
|
140
139
|
{
|
|
141
|
-
orientation: 1
|
|
140
|
+
orientation: 1,
|
|
142
141
|
},
|
|
143
142
|
{
|
|
144
143
|
orientation: 2,
|
|
145
144
|
flip: true,
|
|
146
|
-
transform: [-1, 0, 0, 1, width, 0]
|
|
145
|
+
transform: [-1, 0, 0, 1, width, 0],
|
|
147
146
|
},
|
|
148
147
|
{
|
|
149
148
|
orientation: 3,
|
|
150
149
|
rotate: '180',
|
|
151
|
-
transform: [-1, 0, 0, -1, width, height]
|
|
150
|
+
transform: [-1, 0, 0, -1, width, height],
|
|
152
151
|
},
|
|
153
152
|
{
|
|
154
153
|
orientation: 4,
|
|
155
154
|
flip: true,
|
|
156
155
|
rotate: '180',
|
|
157
|
-
transform: [1, 0, 0, -1, 0, height]
|
|
156
|
+
transform: [1, 0, 0, -1, 0, height],
|
|
158
157
|
},
|
|
159
158
|
{
|
|
160
159
|
orientation: 5,
|
|
161
160
|
flip: true,
|
|
162
161
|
rotate: '270',
|
|
163
|
-
transform: [0, 1, 1, 0, 0, 0]
|
|
162
|
+
transform: [0, 1, 1, 0, 0, 0],
|
|
164
163
|
},
|
|
165
164
|
{
|
|
166
165
|
orientation: 6,
|
|
167
166
|
rotate: '270',
|
|
168
|
-
transform: [0, 1, -1, 0, height, 0]
|
|
167
|
+
transform: [0, 1, -1, 0, height, 0],
|
|
169
168
|
},
|
|
170
169
|
{
|
|
171
170
|
orientation: 7,
|
|
172
171
|
flip: true,
|
|
173
172
|
rotate: '90',
|
|
174
|
-
transform: [0, -1, -1, 0, height, width]
|
|
173
|
+
transform: [0, -1, -1, 0, height, width],
|
|
175
174
|
},
|
|
176
175
|
{
|
|
177
176
|
orientation: 8,
|
|
178
177
|
rotate: '90',
|
|
179
|
-
transform: [0, -1, 1, 0, 0, width]
|
|
180
|
-
}
|
|
178
|
+
transform: [0, -1, 1, 0, 0, width],
|
|
179
|
+
},
|
|
181
180
|
];
|
|
182
181
|
|
|
183
182
|
events.forEach((def) => {
|
|
184
|
-
const {
|
|
185
|
-
flip, orientation, rotate, transform
|
|
186
|
-
} = def;
|
|
183
|
+
const {flip, orientation, rotate, transform} = def;
|
|
187
184
|
|
|
188
185
|
describe(`when an image file is received with orientation as ${orientation}`, () => {
|
|
189
186
|
options.orientation = orientation;
|
|
@@ -197,7 +194,9 @@ describe('helper-image', () => {
|
|
|
197
194
|
if (transform) {
|
|
198
195
|
assert.isTrue(options.ctx.transform.calledWith(...transform));
|
|
199
196
|
}
|
|
200
|
-
assert.isTrue(
|
|
197
|
+
assert.isTrue(
|
|
198
|
+
options.ctx.drawImage.calledWith(options.img, options.x, options.y, width, height)
|
|
199
|
+
);
|
|
201
200
|
});
|
|
202
201
|
});
|
|
203
202
|
});
|