diff --git a/docs/index.html b/docs/index.html index b2d331d..c843fab 100755 --- a/docs/index.html +++ b/docs/index.html @@ -153,6 +153,15 @@

Options

+
+ +
+ +
+
diff --git a/docs/js/main.js b/docs/js/main.js index 3fb2821..8bce7c7 100755 --- a/docs/js/main.js +++ b/docs/js/main.js @@ -24,6 +24,7 @@ window.addEventListener('DOMContentLoaded', function () { height: undefined, resize: 'none', quality: 0.8, + retainExif: false, mimeType: '', convertTypes: 'image/png', convertSize: 5000000, diff --git a/lint-staged.config.js b/lint-staged.config.js index 9812d43..6d8524d 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,4 +1,4 @@ module.exports = { - '{src,test}/**/*.js|*.conf*.js': 'vue-cli-service lint', + '{src,test}/**/*.js|*.conf*.js': 'eslint --fix', '{src,docs}/**/*.{css,scss,html}': 'stylelint --fix', }; diff --git a/src/defaults.js b/src/defaults.js index a49fac2..e560cdf 100755 --- a/src/defaults.js +++ b/src/defaults.js @@ -67,6 +67,12 @@ export default { */ quality: 0.8, + /** + * If set `true`, the compressed image will retain exif. + * @type {boolean} + */ + retainExif: false, + /** * The mime type of the output image. * By default, the original mime type of the source image file will be used. diff --git a/src/index.js b/src/index.js index 176da6b..f18c180 100755 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,11 @@ import { normalizeDecimalNumber, parseOrientation, resetAndGetOrientation, + getEXIF, + insertEXIF, + base64ToArrayBuffer, + blobToBase64, + dataURItoBlob, } from './utilities'; const { ArrayBuffer, FileReader } = WINDOW; @@ -37,6 +42,7 @@ export default class Compressor { ...DEFAULTS, ...options, }; + this.exif = []; this.aborted = false; this.result = null; this.init(); @@ -79,6 +85,9 @@ export default class Compressor { const { result } = target; const data = {}; + if (options.retainExif) { + this.exif = getEXIF(result); + } if (checkOrientation) { // Reset the orientation value to its default value 1 // as some iOS browsers will render image with its orientation @@ -279,14 +288,29 @@ export default class Compressor { if (this.aborted) { return; } - + const that = this; const done = (result) => { if (!this.aborted) { - this.done({ - naturalWidth, - naturalHeight, - result, - }); + if (options.retainExif && result) { + blobToBase64(result, (base64) => { + that.done({ + naturalWidth, + naturalHeight, + result: dataURItoBlob( + arrayBufferToDataURL( + insertEXIF(base64ToArrayBuffer(base64, options.mimeType), that.exif), + options.mimeType, + ), + ), + }, that); + }); + } else { + this.done({ + naturalWidth, + naturalHeight, + result, + }); + } } }; @@ -301,8 +325,9 @@ export default class Compressor { naturalWidth, naturalHeight, result, - }) { - const { file, image, options } = this; + }, _this = undefined) { + const that = _this || this; + const { file, image, options } = that; if (URL && !options.checkOrientation) { URL.revokeObjectURL(image.src); diff --git a/src/utilities.js b/src/utilities.js index 3aef019..72293d1 100755 --- a/src/utilities.js +++ b/src/utilities.js @@ -279,3 +279,82 @@ export function getAdjustedSizes( height, }; } + +export function getEXIF(arrayBuffer) { + let head = 0; + const segments = []; + let length; + let endPoint; + let seg; + const arr = [].slice.call(new Uint8Array(arrayBuffer), 0); + + while (true) { + // SOS(Start of Scan) + if (arr[head] === 0xff && arr[head + 1] === 0xda) { break; } + // SOI(Start of Image) + if (arr[head] === 0xff && arr[head + 1] === 0xd8) { + head += 2; + } else { + length = arr[head + 2] * 256 + arr[head + 3]; + endPoint = head + length + 2; + seg = arr.slice(head, endPoint); + head = endPoint; + segments.push(seg); + } + if (head > arr.length) { + break; + } + } + if (!segments.length) { return []; } + let res = []; + for (let x = 0; x < segments.length; x += 1) { + const s = segments[x]; + if (s[0] === 0xff && s[1] === 0xe1) { + res = res.concat(s); + } + } + return res; +} + +export function insertEXIF(resizedImg, exifArr) { + const arr = [].slice.call(new Uint8Array(resizedImg), 0); + if (arr[2] !== 0xff || arr[3] !== 0xe0) { + return resizedImg; + } + const app0Length = arr[4] * 256 + arr[5]; + + const newImg = [0xff, 0xd8].concat(exifArr, arr.slice(4 + app0Length)); + return new Uint8Array(newImg); +} + +export function dataURItoBlob(dataURI, mimeType = null) { + const mimeString = mimeType || dataURI.split(',')[0].split(':')[1].split(';')[0]; + const byteString = atob(dataURI.split(',')[1]); + const arrayBuffer = new ArrayBuffer(byteString.length); + const intArray = new Uint8Array(arrayBuffer); + + for (let i = 0; i < byteString.length; i += 1) { + intArray[i] = byteString.charCodeAt(i); + } + return new Blob([intArray], { type: mimeString }); +} + +export function base64ToArrayBuffer(base64) { + base64 = base64.replace(/^data:([^;]+);base64,/gim, ''); + const binary = atob(base64); + const len = binary.length; + const buffer = new ArrayBuffer(len); + const view = new Uint8Array(buffer); + for (let i = 0; i < len; i += 1) { + view[i] = binary.charCodeAt(i); + } + return buffer; +} + +export function blobToBase64(blob, callback) { + const reader = new FileReader(); + reader.onloadend = () => { + callback(reader.result); + }; + reader.readAsDataURL(blob); +} diff --git a/test/specs/Compressor.spec.js b/test/specs/Compressor.spec.js index a7bc6fc..9a90da4 100644 --- a/test/specs/Compressor.spec.js +++ b/test/specs/Compressor.spec.js @@ -1,3 +1,5 @@ +import { getEXIF, blobToBase64, base64ToArrayBuffer } from '../../src/utilities'; + describe('Compressor', () => { it('should be a class (function)', () => { expect(Compressor).to.be.a('function'); @@ -81,4 +83,22 @@ describe('Compressor', () => { }); }); }); + + it('should same between the original image`s exif and the compressed image`s exif', (done) => { + window.loadImageAsBlob('/base/docs/images/picture.jpg', (image) => { + blobToBase64(image, (oBase64) => { + const exif = getEXIF(base64ToArrayBuffer(oBase64)); + + new Compressor(image, { + retainExif: true, + success(result) { + blobToBase64(result, (cBase64) => { + expect(getEXIF(base64ToArrayBuffer(cBase64)).sort()).to.deep.equal(exif.sort()); + done(); + }); + }, + }); + }); + }); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index c058f1a..34d036a 100755 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -10,6 +10,7 @@ declare namespace Compressor { height?: number; resize?: 'contain' | 'cover' | 'none'; quality?: number; + retainExif?: boolean; mimeType?: string; convertTypes?: string | string[]; convertSize?: number;