Skip to content

Instantly share code, notes, and snippets.

@scf37
Last active September 5, 2023 16:43

Revisions

  1. scf37 revised this gist Feb 27, 2023. 1 changed file with 32 additions and 9 deletions.
    41 changes: 32 additions & 9 deletions imageOrientation.ts
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,39 @@
    // 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 => applyRotation(file, orientation || 1, maxWidth || 999999));
    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)
    */
    @@ -97,18 +122,17 @@ const applyRotation = (file: File, orientation: number, maxWidth: number) => new
    canvas.width = wh;
    canvas.height = wh;

    // for some transformations output image will be aligned to the right or bottom of square canvas
    // for some transformations output image will be aligned to the right of square canvas
    let rightAligned = false;
    let bottomAligned = 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; bottomAligned = true; break;
    case 4: context.transform(1, 0, 0, -1, 0, wh); bottomAligned = 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; bottomAligned = true; break;
    case 8: context.transform(0, -1, 1, 0, 0, wh); bottomAligned = 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;
    }

    @@ -121,8 +145,7 @@ const applyRotation = (file: File, orientation: number, maxWidth: number) => new
    canvas2.height = Math.floor(outputHeight * scale);
    const ctx2 = canvas2.getContext("2d");
    const sx = rightAligned ? canvas.width - canvas2.width : 0;
    const sy = bottomAligned ? canvas.height - canvas2.height : 0;
    ctx2.drawImage(canvas, sx, sy, canvas2.width, canvas2.height, 0, 0, canvas2.width, canvas2.height);
    ctx2.drawImage(canvas, sx, 0, canvas2.width, canvas2.height, 0, 0, canvas2.width, canvas2.height);

    // export base64
    resolve(canvas2.toDataURL("image/jpeg"));
  2. scf37 revised this gist Mar 3, 2020. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions imageOrientation.ts
    Original file line number Diff line number Diff line change
    @@ -97,17 +97,18 @@ const applyRotation = (file: File, orientation: number, maxWidth: number) => new
    canvas.width = wh;
    canvas.height = wh;

    // for some transformations output image will be aligned to the right of square canvas
    // for some transformations output image will be aligned to the right or bottom of square canvas
    let rightAligned = false;
    let bottomAligned = 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 3: context.transform(-1, 0, 0, -1, wh, wh); rightAligned = true; bottomAligned = true; break;
    case 4: context.transform(1, 0, 0, -1, 0, wh); bottomAligned = true; 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;
    case 7: context.transform(0, -1, -1, 0, wh, wh); rightAligned = true; bottomAligned = true; break;
    case 8: context.transform(0, -1, 1, 0, 0, wh); bottomAligned = true; break;
    default: break;
    }

    @@ -120,7 +121,8 @@ const applyRotation = (file: File, orientation: number, maxWidth: number) => new
    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);
    const sy = bottomAligned ? canvas.height - canvas2.height : 0;
    ctx2.drawImage(canvas, sx, sy, canvas2.width, canvas2.height, 0, 0, canvas2.width, canvas2.height);

    // export base64
    resolve(canvas2.toDataURL("image/jpeg"));
  3. scf37 created this gist Apr 15, 2019.
    133 changes: 133 additions & 0 deletions imageOrientation.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    // 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 => applyRotation(file, orientation || 1, maxWidth || 999999));
    }

    /**
    * @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);
    });