Created
August 24, 2018 09:42
-
-
Save selbekk/c6c6185f239e1d2fc914c75a8575fa23 to your computer and use it in GitHub Desktop.
An image optimizer in JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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