Last active
February 18, 2021 06:34
-
-
Save Hongyu-Zhuo/d9f2aa32f8082e2052fae115127b9121 to your computer and use it in GitHub Desktop.
Image orientation
This file contains hidden or 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
| export class ImageUtil { | |
| constructor() { | |
| } | |
| init() { | |
| window.URL = window.URL || webkitURL; | |
| } | |
| // https://www.media.mit.edu/pia/Research/deepview/exif.html | |
| // https://exiftool.org/TagNames/EXIF.html | |
| static async getExifOrientation(file: Blob): Promise<number> { | |
| return new Promise((resolve, reject) => { | |
| file = file.slice(0, 131072); | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const dataView = new DataView(e.target?.result as ArrayBuffer); | |
| // 是否是 jpeg | |
| if (dataView.getUint16(0, false) !== 0xffd8) { | |
| resolve(-2); | |
| return; | |
| } | |
| const length = dataView.byteLength; | |
| let offset = 2; | |
| while (offset < length) { | |
| const marker = dataView.getUint16(offset, false); | |
| offset += 2; | |
| // 是否是 EXIF(jpeg) | |
| if (marker === 0xffe1) { | |
| // 是否存在 EXIF metadata,`0x45786966` 是 EXIF 头的起始 ASCII 码; | |
| if (dataView.getUint16((offset += 2), false) !== 0x45786966) { | |
| resolve(-1); | |
| return; | |
| } | |
| // 是否是小字节序(little-endian); | |
| const little = dataView.getUint16((offset += 6), false) === 0x4949; | |
| offset += dataView.getUint32(offset + 4, little); | |
| const tags = dataView.getUint16(offset, little); | |
| offset += 2; | |
| for (let i = 0; i < tags; i++) { | |
| if (dataView.getUint16(offset + i * 12, little) == 0x0112) { | |
| resolve(dataView.getUint16(offset + i * 12 + 8, little)); | |
| return; | |
| } | |
| } | |
| } else if ((marker & 0xff00) !== 0xff00) { | |
| break; | |
| } else { | |
| offset += dataView.getUint16(offset, false); | |
| } | |
| resolve(-1); | |
| } | |
| reader.readAsArrayBuffer(file); | |
| } | |
| }) | |
| } | |
| // Derived from https://stackoverflow.com/a/40867559, cc by-sa | |
| private static imgToCanvasWithOrientation(img: HTMLImageElement, rawWidth: number, rawHeight: number, orientation: number) { | |
| const canvas = document.createElement("canvas"); | |
| // https://www.impulseadventure.com/photo/exif-orientation.html | |
| if (orientation > 4) { | |
| canvas.width = rawHeight; | |
| canvas.height = rawWidth; | |
| } else { | |
| canvas.width = rawWidth; | |
| canvas.height = rawHeight; | |
| } | |
| if (orientation > 1) { | |
| console.log("EXIF orientation = " + orientation + ", rotating picture"); | |
| } | |
| const context = canvas.getContext('2d'); | |
| switch (orientation) { | |
| case 2: | |
| context?.transform(-1, 0, 0, 1, rawWidth, 0); | |
| break; | |
| case 3: | |
| context?.transform(-1, 0, 0, -1, rawWidth, rawHeight); | |
| break; | |
| case 4: | |
| context?.transform(1, 0, 0, -1, 0, rawHeight); | |
| break; | |
| case 5: | |
| context?.transform(0, 1, 1, 0, 0, 0); | |
| break; | |
| case 6: | |
| context?.transform(0, 1, -1, 0, rawHeight, 0); | |
| break; | |
| case 7: | |
| context?.transform(0, -1, -1, 0, rawHeight, rawWidth); | |
| break; | |
| case 8: | |
| context?.transform(0, -1, 1, 0, 0, rawWidth); | |
| break; | |
| } | |
| context?.drawImage(img, 0, 0, rawWidth, rawHeight); | |
| return canvas; | |
| } | |
| static async reduceFileSize(file: File, acceptFileSize: number, maxWidth: 'auto', maxHeight: 'auto', quality: number): Promise<Blob>; | |
| static async reduceFileSize(file: File, acceptFileSize: number, maxWidth: number, maxHeight: number, quality: number): Promise<Blob>; | |
| /** | |
| * @function reduceFileSize | |
| * @param file 原始文件 | |
| * @param acceptFileSize 最大文件大小,传入的文件尺寸如果大于这个值,就会执行压缩 | |
| * @param maxWidth 压缩后的图片最大宽度 | |
| * @param maxHeight 最大高度 | |
| * @param quality 压缩质量,(0, 1] | |
| * @description | |
| * | |
| */ | |
| static async reduceFileSize(file: File, acceptFileSize: number, maxWidth: number | 'auto', maxHeight: number | 'auto', quality: number): Promise<Blob> { | |
| return new Promise((resolve, reject) =>{ | |
| if (file.size < acceptFileSize) { | |
| resolve(file); | |
| return; | |
| } | |
| const image = new Image(); | |
| image.onerror = function() { | |
| URL.revokeObjectURL(this.src); | |
| resolve(file); | |
| return; | |
| } | |
| image.onload = async function() { | |
| URL.revokeObjectURL((this as HTMLImageElement).src); | |
| const orientation = await ImageUtil.getExifOrientation(file); | |
| let w = image.width; | |
| let h = image.height; | |
| let scale = 1; | |
| if ( maxWidth !== 'auto' && maxHeight !== 'auto') { | |
| scale = orientation > 4 | |
| ? Math.min(maxHeight / w, maxWidth / h, 1) | |
| : Math.min(maxWidth / w, maxHeight / h, 1); | |
| } | |
| h = Math.round(h * scale); | |
| w = Math.round(w * scale); | |
| const canvas = ImageUtil.imgToCanvasWithOrientation(image, w, h, orientation); | |
| canvas.toBlob((blob: Blob | null) => { | |
| console.log( | |
| "Resized image to " + w + "x" + h + ", " + ((blob as Blob).size >> 10) + "kB" | |
| ); | |
| resolve(blob as Blob); | |
| return; | |
| }, "image/jpeg", quality); | |
| } | |
| image.src = URL.createObjectURL(file); | |
| }); | |
| } | |
| } | |
| //from: https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603 | |
| /** | |
| * Based on StackOverflow answer: https://stackoverflow.com/a/32490603 | |
| * | |
| * @param imageFile The image file to inspect | |
| * @param onRotationFound callback when the rotation is discovered. Will return 0 if if it fails, otherwise 0, 90, 180, or 270 | |
| * @example | |
| * ```ts | |
| * import { getOrientation } from './ImageUtils'; | |
| * onDrop = (pics: any) => { | |
| * getOrientation(pics[0], rotationInDegrees => { | |
| * this.setState({ image: pics[0], rotate: rotationInDegrees }); | |
| * }); | |
| * }; | |
| * ``` | |
| */ | |
| export function getOrientation(imageFile: File, onRotationFound: (rotationInDegrees: number) => void) { | |
| const reader = new FileReader(); | |
| reader.onload = (event: ProgressEvent) => { | |
| if (!event.target) { | |
| return; | |
| } | |
| const innerFile = event.target as FileReader; | |
| const view = new DataView(innerFile.result as ArrayBuffer); | |
| if (view.getUint16(0, false) !== 0xffd8) { | |
| return onRotationFound(convertRotationToDegrees(-2)); | |
| } | |
| const length = view.byteLength; | |
| let offset = 2; | |
| while (offset < length) { | |
| if (view.getUint16(offset + 2, false) <= 8) { | |
| return onRotationFound(convertRotationToDegrees(-1)); | |
| } | |
| const marker = view.getUint16(offset, false); | |
| offset += 2; | |
| if (marker === 0xffe1) { | |
| if (view.getUint32((offset += 2), false) !== 0x45786966) { | |
| return onRotationFound(convertRotationToDegrees(-1)); | |
| } | |
| 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 onRotationFound(convertRotationToDegrees(view.getUint16(offset + i * 12 + 8, little))); | |
| } | |
| } | |
| // tslint:disable-next-line:no-bitwise | |
| } else if ((marker & 0xff00) !== 0xff00) { | |
| break; | |
| } else { | |
| offset += view.getUint16(offset, false); | |
| } | |
| } | |
| return onRotationFound(convertRotationToDegrees(-1)); | |
| }; | |
| reader.readAsArrayBuffer(imageFile); | |
| } | |
| /** | |
| * Based off snippet here: https://github.com/mosch/react-avatar-editor/issues/123#issuecomment-354896008 | |
| * @param rotation converts the int into a degrees rotation. | |
| */ | |
| function convertRotationToDegrees(rotation: number): number { | |
| let rotationInDegrees = 0; | |
| switch (rotation) { | |
| case 8: | |
| rotationInDegrees = 270; | |
| break; | |
| case 6: | |
| rotationInDegrees = 90; | |
| break; | |
| case 3: | |
| rotationInDegrees = 180; | |
| break; | |
| default: | |
| rotationInDegrees = 0; | |
| } | |
| return rotationInDegrees; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment