-
-
Save scf37/6b4bf47dce4d78be92216323b12f2d21 to your computer and use it in GitHub Desktop.
| // Based on: https://stackoverflow.com/a/46814952/283851 | |
| // Based on: https://gist.github.com/mindplay-dk/72f47c1a570e870a375bd3dbcb9328fb | |
| /** | |
| * Create a Base64 Image URL, with rotation applied to compensate for EXIF orientation, if needed. | |
| * | |
| * Optionally resize to a smaller maximum width - to improve performance for larger image thumbnails. | |
| */ | |
| export function getImageUrl(file: File, maxWidth: number|undefined): Promise<string> { | |
| return readOrientation(file).then(orientation => { | |
| if (browserSupportsAutoRotation) orientation = undefined; | |
| return applyRotation(file, orientation || 1, maxWidth || 999999) | |
| }); | |
| } | |
| let browserSupportsAutoRotation = null; | |
| (function () { | |
| // black 2x1 JPEG, with the following meta information set: | |
| // - EXIF Orientation: 6 (Rotated 90Β° CCW) | |
| var testImageURL = | |
| 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' + | |
| 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' + | |
| 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' + | |
| 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' + | |
| 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' + | |
| 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==' | |
| var img = document.createElement('img') | |
| img.onload = function () { | |
| // Check if browser supports automatic image orientation: | |
| browserSupportsAutoRotation = img.width === 1 && img.height === 2 | |
| } | |
| img.src = testImageURL | |
| })() | |
| /** | |
| * @returns EXIF orientation value (or undefined) | |
| */ | |
| const readOrientation = (file: File) => new Promise<number|undefined>(resolve => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve((() => { | |
| const view = new DataView(/** @type {ArrayBuffer} */ (reader.result) as ArrayBuffer); | |
| if (view.getUint16(0, false) != 0xFFD8) { | |
| return; | |
| } | |
| const length = view.byteLength; | |
| let offset = 2; | |
| while (offset < length) { | |
| const marker = view.getUint16(offset, false); | |
| offset += 2; | |
| if (marker == 0xFFE1) { | |
| offset += 2; | |
| if (view.getUint32(offset, false) != 0x45786966) { | |
| return; | |
| } | |
| offset += 6; | |
| const little = view.getUint16(offset, 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 view.getUint16(offset + (i * 12) + 8, little); | |
| } | |
| } | |
| } else if ((marker & 0xFF00) != 0xFF00) { | |
| break; | |
| } else { | |
| offset += view.getUint16(offset, false); | |
| } | |
| } | |
| })()); | |
| reader.readAsArrayBuffer(file.slice(0, 64 * 1024)); | |
| }); | |
| /** | |
| * @returns Base64 Image URL (with rotation applied to compensate for orientation, if any) | |
| */ | |
| const applyRotation = (file: File, orientation: number, maxWidth: number) => new Promise<string>(resolve => { | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| const url = reader.result as string; | |
| const image = new Image(); | |
| image.onload = () => { | |
| const canvas = document.createElement("canvas"); | |
| const context = canvas.getContext("2d")!; | |
| let { width, height } = image; | |
| const [outputWidth, outputHeight] = orientation >= 5 && orientation <= 8 | |
| ? [height, width] | |
| : [width, height]; | |
| const scale = outputWidth > maxWidth ? maxWidth / outputWidth : 1; | |
| width = Math.floor(width * scale); | |
| height = Math.floor(height * scale); | |
| // to rotate rectangular image, we need enough space so square canvas is used | |
| const wh = Math.max(width, height); | |
| // set proper canvas dimensions before transform & export | |
| canvas.width = wh; | |
| canvas.height = wh; | |
| // for some transformations output image will be aligned to the right of square canvas | |
| let rightAligned = false; | |
| // transform context before drawing image | |
| switch (orientation) { | |
| case 2: context.transform(-1, 0, 0, 1, wh, 0); rightAligned = true; break; | |
| case 3: context.transform(-1, 0, 0, -1, wh, wh); rightAligned = true; break; | |
| case 4: context.transform(1, 0, 0, -1, 0, wh); break; | |
| case 5: context.transform(0, 1, 1, 0, 0, 0); break; | |
| case 6: context.transform(0, 1, -1, 0, wh, 0); rightAligned = true; break; | |
| case 7: context.transform(0, -1, -1, 0, wh, wh); rightAligned = true; break; | |
| case 8: context.transform(0, -1, 1, 0, 0, wh); break; | |
| default: break; | |
| } | |
| // draw image | |
| context.drawImage(image, 0, 0, width, height); | |
| // copy rotated image to output dimensions and export it | |
| const canvas2 = document.createElement("canvas"); | |
| canvas2.width = Math.floor(outputWidth * scale); | |
| canvas2.height = Math.floor(outputHeight * scale); | |
| const ctx2 = canvas2.getContext("2d"); | |
| const sx = rightAligned ? canvas.width - canvas2.width : 0; | |
| ctx2.drawImage(canvas, sx, 0, canvas2.width, canvas2.height, 0, 0, canvas2.width, canvas2.height); | |
| // export base64 | |
| resolve(canvas2.toDataURL("image/jpeg")); | |
| }; | |
| image.src = url; | |
| }; | |
| reader.readAsDataURL(file); | |
| }); |
@sandstrom I'll look into this on weekend.
@scf37 Awesome, looking forward to hear your thoughts! π
@scf37 Just curious, did you have time to look at this? Would love to hear your thoughts
@scf37 Sorry for the constant pings on this! π
But if you find some space, I'm eager to hear your thinking on this.
Very clean code! I like it.
Unfortunately it doesnot work if I upload an image from my Iphone :S Did I miss something?
@sandstrom fixed!
Thank you for pointing out that bug :-)
@SaliZumberi can't really help you without details - javascript error (if any) and screenshot would be nice.
@scf37 Awesome! π₯
Glad I could help π
@SaliZumberi I've used the code for iOS uploads and it works really well. Perhaps there is something wrong with your implementation of the code?
@scf37 If you have time I'd love to hear your thoughts on this, and whether you think it would make sense to introduce something similar to 'right-aligned' (probably called 'bottom-aligned'), to rectify this issue.