Skip to content

Instantly share code, notes, and snippets.

@mrharel
Last active September 27, 2017 06:32
Show Gist options
  • Save mrharel/7ee4aa7c3ef3ce7a44643ffe827a3b8a to your computer and use it in GitHub Desktop.
Save mrharel/7ee4aa7c3ef3ce7a44643ffe827a3b8a to your computer and use it in GitHub Desktop.
Resize and flip the file (using the exif lib) if needed
import EXIF from "exif-js";
const DEFAULT_MAX_SIZE = 1600;
const JPEG_QUALITY = 0.9;
const FILE_READER_STATE_DONE = 2;
const JPEG_FILETYPE = "image/jpeg";
/**
* Set up operations and degrees to rotate for each EXIF orientation (index).
*/
const ExifOrientations = [
{ op: "none", degrees: 0 },
{ op: "flip-x", degrees: 0 },
{ op: "none", degrees: 180 },
{ op: "flip-y", degrees: 0 },
{ op: "flip-x", degrees: 90 },
{ op: "none", degrees: 90 },
{ op: "flip-x", degrees: -90 },
{ op: "none", degrees: -90 },
];
const calculateImageResolution = (img, maxSize) => {
const size = { width: img.width, height: img.height };
if (img.width > maxSize || img.height > maxSize) {
const ratio = img.width / img.height;
if (img.width >= img.height) {
size.width = maxSize;
size.height = maxSize / ratio;
} else {
size.height = maxSize;
size.width = maxSize * ratio;
}
}
return size;
};
const isImageDimChanged = (orientation) => !! ~[5, 6, 7, 8].indexOf(orientation);
const setDimensions = (canvas, size, orientation) => {
if (isImageDimChanged(orientation)) {
canvas.setAttribute("height", `${size.width}`);
canvas.setAttribute("width", `${size.height}`);
} else {
canvas.setAttribute("height", `${size.height}`);
canvas.setAttribute("width", `${size.width}`);
}
};
const flipContext = (ctx, canvas, x, y) => {
ctx.translate(x ? canvas.width : 0, y ? canvas.height : 0);
ctx.scale(x ? -1 : 1, y ? -1 : 1);
};
const rotateContext = (ctx, attr) => {
const x = attr.x || 0;
const y = attr.y || 0;
let radians = 0;
if (attr.degrees) {
radians = attr.degrees * (Math.PI / 180);
}
ctx.translate(x, y);
ctx.rotate(radians);
ctx.translate(-x, -y);
};
const getExifOrientations = (orientation) => {
let orientationIndex = orientation;
if (orientationIndex < 1 || orientationIndex > 8) {
orientationIndex = 1;
}
return ExifOrientations[orientationIndex - 1];
};
const drawFileToCanvas = (file) => {
const {
resized: {
meta: {
tags: {
Orientation: orientation = 1,
} = {},
img,
maxSize,
} = {},
} = {},
} = file;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
console.error("failed to get canvas context");
return null;
}
const exifOrientation = getExifOrientations(orientation);
const size = calculateImageResolution(img, maxSize);
setDimensions(canvas, size, orientation);
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Flip vertically or horizontally
if (exifOrientation.op === "flip-x") flipContext(ctx, canvas, true, false);
if (exifOrientation.op === "flip-y") flipContext(ctx, canvas, false, true);
// Rotate image
if (exifOrientation.degrees) {
rotateContext(ctx, {
degrees: exifOrientation.degrees,
x: canvas.width / 2,
y: canvas.height / 2,
});
if (isImageDimChanged(orientation)) {
const diff = canvas.width - canvas.height;
ctx.translate(diff / 2, -diff / 2);
}
}
ctx.drawImage(img, 0, 0, img.width, img.height, // Source rectangle
0, 0, size.width, size.height);
return canvas;
};
const base64toBlob = (base64Data, contentType = "", sliceSize = 512) => {
const byteCharacters = atob(base64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
};
const rotateAndResizeByMeta = (file, callback) => {
const canvas = drawFileToCanvas(file);
if (!canvas) {
callback({ err: "Failed to draw into the canvas", preview: null, imageData: null, type: null });
return;
}
const dataUrl = canvas.toDataURL(JPEG_FILETYPE, JPEG_QUALITY);
const base64 = dataUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, "");
const imageData = base64toBlob(base64, JPEG_FILETYPE);
callback({ preview: dataUrl, imageData, type: JPEG_FILETYPE, err: null });
};
const readFileAndCreateImage = (file, callback) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === FILE_READER_STATE_DONE) {
const img = new Image();
img.onload = () => {
callback({ data: reader.result, img, error: null });
};
img.src = file.preview;
}
};
reader.onerror = () => {
callback({ data: null, img: null, error: "Failed to read file as array buffer" });
};
reader.readAsArrayBuffer(file);
};
const rotateAndResize = (file, maxSize, callback) => readFileAndCreateImage(file, ({ data, img, error }) => {
file.resized = { // eslint-disable-line no-param-reassign
error,
data,
preview: null,
type: null,
meta: {
img,
tags: null,
maxSize,
},
};
if (error) {
callback();
return;
}
file.resized.meta.tags = EXIF.readFromBinaryFile(data); // eslint-disable-line no-param-reassign
rotateAndResizeByMeta(file, ({ err, preview, imageData, type }) => {
delete file.resized.meta; // eslint-disable-line no-param-reassign
if (err) {
file.resized.error = err; // eslint-disable-line no-param-reassign
} else {
file.resized.preview = preview; // eslint-disable-line no-param-reassign
file.resized.type = type; // eslint-disable-line no-param-reassign
file.resized.data = imageData; // eslint-disable-line no-param-reassign
}
callback();
});
});
/**
* This function gets a File array and add the following property to the file:
* resized {Object}:
* data {Array<UInt8>}
* type {String}
* preview {String}
* error {String}
* @param files
* @param maxSize
* @param callback
*/
export default ({
files,
maxSize = DEFAULT_MAX_SIZE,
callback }) => {
let completedJobs = 0;
files.forEach(file => rotateAndResize(file, maxSize, () => {
completedJobs++;
if (file.resized && file.resized.error) {
log.error(`Error in resize file : ${file.resized.error}`);
}
if (completedJobs === files.length) {
callback();
}
}));
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment