Skip to content

Instantly share code, notes, and snippets.

@selbekk
Created August 24, 2018 09:42
Show Gist options
  • Save selbekk/c6c6185f239e1d2fc914c75a8575fa23 to your computer and use it in GitHub Desktop.
Save selbekk/c6c6185f239e1d2fc914c75a8575fa23 to your computer and use it in GitHub Desktop.
An image optimizer in JS
import Pica from 'pica';
const getImageFromFile = file =>
new Promise(resolve => {
const reader = new FileReader();
const image = new Image();
reader.onload = async fileReaderEvent => {
image.onload = () => resolve(image);
image.src = await fileReaderEvent.target.result;
};
reader.readAsDataURL(file);
});
// Adapted from https://stackoverflow.com/a/40867559/1064572
// No idea about orientation?
// https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603
const FALLBACK_ORIENTATION = 1;
const getOrientation = file =>
new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => {
const view = new DataView(e.target.result);
if (view.getUint16(0, false) !== 0xffd8) {
return resolve(FALLBACK_ORIENTATION);
}
const length = view.byteLength;
let offset = 2;
while (offset < length) {
if (view.getUint16(offset + 2, false) <= 8) {
return resolve(FALLBACK_ORIENTATION);
}
const marker = view.getUint16(offset, false);
offset += 2;
if (marker === 0xffe1) {
if (view.getUint32((offset += 2), false) !== 0x45786966) {
return resolve(FALLBACK_ORIENTATION);
}
const little = view.getUint16((offset += 6), false) === 0x4949;
offset += view.getUint32(offset + 4, little);
const tags = view.getUint16(offset, little);
offset += 2;
for (let i = 0; i < tags; i++) {
if (view.getUint16(offset + i * 12, little) === 0x0112) {
return resolve(view.getUint16(offset + i * 12 + 8, little));
}
}
} else if ((marker & 0xff00) !== 0xff00) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
return resolve(FALLBACK_ORIENTATION);
};
reader.readAsArrayBuffer(file);
});
const resetOrientation = (srcBase64, srcOrientation) =>
new Promise(resolve => {
const img = new Image();
img.onload = () => {
const width = img.width;
const height = img.height;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// set proper canvas dimensions before transform & export
if (4 < srcOrientation && srcOrientation < 9) {
canvas.width = height;
canvas.height = width;
} else {
canvas.width = width;
canvas.height = height;
}
// transform context before drawing image
switch (srcOrientation) {
case 2:
ctx.transform(-1, 0, 0, 1, width, 0);
break;
case 3:
ctx.transform(-1, 0, 0, -1, width, height);
break;
case 4:
ctx.transform(1, 0, 0, -1, 0, height);
break;
case 5:
ctx.transform(0, 1, 1, 0, 0, 0);
break;
case 6:
ctx.transform(0, 1, -1, 0, height, 0);
break;
case 7:
ctx.transform(0, -1, -1, 0, height, width);
break;
case 8:
ctx.transform(0, -1, 1, 0, 0, width);
break;
default:
break;
}
// draw image
ctx.drawImage(img, 0, 0);
// export canvas
resolve(canvas);
};
img.src = srcBase64;
});
const getRotatedCanvasFromFile = async file => {
const orientation = await getOrientation(file);
const image = await getImageFromFile(file);
return await resetOrientation(image.src, orientation);
};
const photoOptimizer = Pica();
export const processPhoto = async file => {
const fromCanvas = await getRotatedCanvasFromFile(file);
const toCanvas = document.createElement('canvas');
const MAX_DIMENSION = 1500;
const ratio = fromCanvas.width / fromCanvas.height;
if (ratio > 1) {
// landscape
toCanvas.width = MAX_DIMENSION;
toCanvas.height = MAX_DIMENSION / ratio;
} else {
// portrait
toCanvas.width = MAX_DIMENSION * ratio;
toCanvas.height = MAX_DIMENSION;
}
const resized = await photoOptimizer.resize(fromCanvas, toCanvas, {});
return photoOptimizer.toBlob(resized, 'image/jpeg', 0.9);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment