Skip to content

Commit

Permalink
feat: add retainExif option to retain Exif (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
zcf0508 authored and fengyuanchen committed Feb 12, 2023
1 parent 2ddf07d commit c2e5f40
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 8 deletions.
9 changes: 9 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ <h4 class="card-header">Options</h4>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputRetainExif" class="col-5 col-form-label">retainExif</label>
<div class="col-7">
<select class="form-control" name="retainExif" id="inputRetainExif" v-model.number="options.retainExif">
<option :value="false">false</option>
<option :value="true">true</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputMimeType" class="col-5 col-form-label">mimeType</label>
<div class="col-7">
Expand Down
1 change: 1 addition & 0 deletions docs/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ window.addEventListener('DOMContentLoaded', function () {
height: undefined,
resize: 'none',
quality: 0.8,
retainExif: false,
mimeType: '',
convertTypes: 'image/png',
convertSize: 5000000,
Expand Down
6 changes: 6 additions & 0 deletions src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 33 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import {
normalizeDecimalNumber,
parseOrientation,
resetAndGetOrientation,
getEXIF,
insertEXIF,
base64ToArrayBuffer,
blobToBase64,
dataURItoBlob,
} from './utilities';

const { ArrayBuffer, FileReader } = WINDOW;
Expand All @@ -37,6 +42,7 @@ export default class Compressor {
...DEFAULTS,
...options,
};
this.exif = [];
this.aborted = false;
this.result = null;
this.init();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
});
}
}
};

Expand All @@ -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);
Expand Down
79 changes: 79 additions & 0 deletions src/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
20 changes: 20 additions & 0 deletions test/specs/Compressor.spec.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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();
});
},
});
});
});
});
});
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare namespace Compressor {
height?: number;
resize?: 'contain' | 'cover' | 'none';
quality?: number;
retainExif?: boolean;
mimeType?: string;
convertTypes?: string | string[];
convertSize?: number;
Expand Down

0 comments on commit c2e5f40

Please sign in to comment.