programing

JS 클라이언트 측 Exif 방향:JPEG 이미지 회전 및 미러링

sourcejob 2023. 10. 6. 21:03
반응형

JS 클라이언트 측 Exif 방향:JPEG 이미지 회전 및 미러링

디지털 카메라 사진은 종종 EXIF "Orientation" 태그와 함께 JPEG로 저장됩니다.올바르게 표시하려면 설정된 방향에 따라 이미지를 회전/미러링해야 하지만 브라우저는 이미지를 렌더링하는 이 정보를 무시합니다.대형 상용 웹 앱에서도 EXIF 방향 지원이 두드러질 수 있습니다.동일한 소스는 또한 JPEG가 가질 수 있는 8가지 다른 방향에 대한 멋진 요약을 제공합니다.

Summary of EXIF Orientations

샘플 이미지는 4시에 제공됩니다.

문제는 이미지가 올바르게 표시되고 필요한 경우 추가 처리할 수 있도록 클라이언트 측에서 이미지를 회전/미러링하는 방법입니다.

방향 속성 2를 포함하여 EXIF 데이터를 구문 분석하는 데 사용할 수 있는 JS 라이브러리가 있습니다.Flickr은 큰 이미지를 파싱할 때 웹 워커 3을 사용해야 하는 성능 문제를 지적했습니다.

콘솔 도구는 이미지 5의 방향을 올바르게 바꿀 수 있습니다.문제를 해결하는 PHP 스크립트는 6시에 이용 가능합니다.

github 프로젝트 JavaScript-Load-Image는 EXIF 방향 문제에 대한 완벽한 해결책을 제공하며, 8개의 Exif 방향에 대해 이미지를 올바르게 회전/미러링합니다.javascript exifientation 온라인 데모 보기

이미지는 HTML5 캔버스에 그려집니다.올바른 렌더링은 캔버스 작업을 통해 js/load-image-orientation.js로 구현됩니다.

이것이 다른 누군가에게 시간을 절약하고, 이 오픈 소스 보석에 대해 검색 엔진에 가르쳐 주기를 바랍니다. :)

메더의 상황 변화는 완벽하게 작동합니다.방향만 추출해야 하는 경우 이 기능을 사용합니다. EXIF 판독 립은 필요 없습니다.아래는 베이스64 이미지에서 방향을 재설정하기 위한 기능입니다.여기 그것을 위한 바이올린이 있습니다.는 방향 추출 데모도 준비했습니다.

function resetOrientation(srcBase64, srcOrientation, callback) {
  var img = new Image();    

  img.onload = function() {
    var width = img.width,
        height = img.height,
        canvas = document.createElement('canvas'),
        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 base64
    callback(canvas.toDataURL());
  };

  img.src = srcBase64;
};

한다면

width = img.width;
height = img.height;
var ctx = canvas.getContext('2d');

그런 다음 이 변환을 사용하여 이미지를 방향 1로 전환할 수 있습니다.

방향:

  1. ctx.transform(1, 0, 0, 1, 0, 0);
  2. ctx.transform(-1, 0, 0, 1, width, 0);
  3. ctx.transform(-1, 0, 0, -1, width, height);
  4. ctx.transform(1, 0, 0, -1, 0, height);
  5. ctx.transform(0, 1, 1, 0, 0, 0);
  6. ctx.transform(0, 1, -1, 0, height, 0);
  7. ctx.transform(0, -1, -1, 0, height, width);
  8. ctx.transform(0, -1, 1, 0, 0, width);

ctx에 영상을 그리기 전에

ok @user3096626 답변 외에 누군가가 코드 예제를 제공한다면 더 도움이 될 것이라고 생각합니다. 다음 예제는 URL(원격 이미지)에서 이미지 방향을 수정하는 방법을 보여줍니다.


해결책 1: javascript 사용 (권장

  1. load-image library는 url image에서만 exif 태그를 추출하지 않기 때문에(file/blob) exif-jsload-image javascript library를 모두 사용하므로 먼저 다음과 같이 이 라이브러리를 페이지에 추가합니다.

    <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.1.0/exif.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-scale.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-orientation.min.js"></script>
    

    참고로 exif-js 버전 2.2는 문제가 있는 것 같아서 2.1을 사용했습니다.

  2. 그럼 기본적으로 저희가 할 일은

    - 합니다를 합니다.window.loadImage()

    - b - 하여 exif 태그를 window.EXIF.getData()

    - c - 합니다를 합니다.window.loadImage.scale()

    d - 캔버스를 문서에 넣습니다.

여기 있습니다 :)

window.loadImage("/your-image.jpg", function (img) {
  if (img.type === "error") {
    console.log("couldn't load image:", img);
  } else {
    window.EXIF.getData(img, function () {
        var orientation = EXIF.getTag(this, "Orientation");
        var canvas = window.loadImage.scale(img, {orientation: orientation || 0, canvas: true});
        document.getElementById("container").appendChild(canvas); 
        // or using jquery $("#container").append(canvas);

    });
  }
});

물론 canvas 객체에서 base64로 이미지를 가져와 img src 속성에 넣을 수도 있으므로 jQuery를 사용하면 가능합니다.

$("#my-image").attr("src",canvas.toDataURL());

다음은 github: https://github.com/digital-flowers/loadimage-exif-example 의 전체 코드입니다.


해결책 2: html 사용 (브라우저 해킹)

매우 빠르고 쉬운 해킹이 있는데, 대부분의 브라우저는 HTML 없이 새로운 탭 안에서 이미지를 직접 열면 이미지를 올바른 방향으로 표시합니다(LOL 나는 이유를 모릅니다). 따라서 기본적으로 iframes src 속성을 이미지 URL로 직접 입력하면 ifram을 사용하여 이미지를 표시할 수 있습니다.

<iframe src="/my-image.jpg"></iframe>

해결책 3: CSS 사용 (ios에서 파이어폭스와 사파리만 해당)

이미지 방향을 수정하는 CSS3 속성이 있지만 파이어폭스와 사파리/ios에서만 작동하는 문제는 곧 모든 브라우저에서 사용할 수 있기 때문에 여전히 언급할 가치가 있습니다(caniuse의 브라우저 지원 정보).

img {
   image-orientation: from-image;
}

입력 컨트롤의 파일을 가지고 있고 방향이 무엇인지 모르며 약간 게으르고 아래의 큰 라이브러리를 포함하고 싶지 않은 사람에게는 @WunderBart가 제공하는 코드가 방향을 찾는 @WunderBart(https://stackoverflow.com/a/32490603) 에 링크된 답변과 함께 제공됩니다.

function getDataUrl(file, callback2) {
        var callback = function (srcOrientation) {
            var reader2 = new FileReader();
            reader2.onload = function (e) {
                var srcBase64 = e.target.result;
                var img = new Image();

                img.onload = function () {
                    var width = img.width,
                        height = img.height,
                        canvas = document.createElement('canvas'),
                        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 base64
                    callback2(canvas.toDataURL());
                };

                img.src = srcBase64;
            }

            reader2.readAsDataURL(file);
        }

        var reader = new FileReader();
        reader.onload = function (e) {

            var view = new DataView(e.target.result);
            if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
            var length = view.byteLength, offset = 2;
            while (offset < length) {
                var marker = view.getUint16(offset, false);
                offset += 2;
                if (marker == 0xFFE1) {
                    if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1);
                    var little = view.getUint16(offset += 6, false) == 0x4949;
                    offset += view.getUint32(offset + 4, little);
                    var tags = view.getUint16(offset, little);
                    offset += 2;
                    for (var i = 0; i < tags; i++)
                        if (view.getUint16(offset + (i * 12), little) == 0x0112)
                            return callback(view.getUint16(offset + (i * 12) + 8, little));
                }
                else if ((marker & 0xFF00) != 0xFF00) break;
                else offset += view.getUint16(offset, false);
            }
            return callback(-1);
        };
        reader.readAsArrayBuffer(file);
    }

그렇게 부르기 쉬운

getDataUrl(input.files[0], function (imgBase64) {
      vm.user.BioPhoto = imgBase64;
});

한 대의 정기선은?

도서관에 대해 언급하는 사람을 본 적이 없습니다.여기에 딱 맞는 도우미 기능이 있습니다.

:const orientation = await imageCompression.getExifOrientation(file)

다른 많은 면에서도 유용한 도구입니다.

저는 원더바트의 대답이 최고였습니다.이미지가 올바른 방향인 경우에는 방향을 먼저 테스트하고 회전이 필요 없는 경우 코드의 나머지 부분을 우회하는 것만으로 속도를 크게 높일 수 있습니다.

원더바트의 모든 정보를 종합해보면, 이런 것들이 있습니다.

var handleTakePhoto = function () {
    let fileInput: HTMLInputElement = <HTMLInputElement>document.getElementById('photoInput');
    fileInput.addEventListener('change', (e: any) => handleInputUpdated(fileInput, e.target.files));
    fileInput.click();
}

var handleInputUpdated = function (fileInput: HTMLInputElement, fileList) {
    let file = null;

    if (fileList.length > 0 && fileList[0].type.match(/^image\//)) {
        isLoading(true);
        file = fileList[0];
        getOrientation(file, function (orientation) {
            if (orientation == 1) {
                imageBinary(URL.createObjectURL(file));
                isLoading(false);
            }
            else 
            {
                resetOrientation(URL.createObjectURL(file), orientation, function (resetBase64Image) {
                    imageBinary(resetBase64Image);
                    isLoading(false);
                });
            }
        });
    }

    fileInput.removeEventListener('change');
}


// from http://stackoverflow.com/a/32490603
export function getOrientation(file, callback) {
    var reader = new FileReader();

    reader.onload = function (event: any) {
        var view = new DataView(event.target.result);

        if (view.getUint16(0, false) != 0xFFD8) return callback(-2);

        var length = view.byteLength,
            offset = 2;

        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;

            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    return callback(-1);
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;

                for (var i = 0; i < tags; i++)
                    if (view.getUint16(offset + (i * 12), little) == 0x0112)
                        return callback(view.getUint16(offset + (i * 12) + 8, little));
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        return callback(-1);
    };

    reader.readAsArrayBuffer(file.slice(0, 64 * 1024));
};

export function resetOrientation(srcBase64, srcOrientation, callback) {
    var img = new Image();

    img.onload = function () {
        var width = img.width,
            height = img.height,
            canvas = document.createElement('canvas'),
            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 base64
        callback(canvas.toDataURL());
    };

    img.src = srcBase64;
}

이를 정확히 해결하는 ES6 모듈로 랩핑된 수업을 만들었습니다.

103개의 라인으로 종속성이 없으며 상당히 잘 구성되어 있고 문서화되어 있어 수정/재사용이 용이합니다.

가능한 8가지 방향을 모두 처리하며 약속 기반입니다.

여기 있습니다. 이것이 여전히 누군가에게 도움이 되기를 바랍니다: https://gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913

Wunderbart의 게시물은 Statler의 개선과 함께 저에게 효과가 있었습니다.코멘트와 구문 정리를 몇 개 더 추가하고 방향 값을 돌려주면 다음 코드를 자유롭게 사용할 수 있습니다.그냥 전화하세요.readImageFile()아래 기능을 수행하면 변환된 이미지와 원래 방향을 다시 얻을 수 있습니다.

const JpegOrientation = [
    "NOT_JPEG",
    "NORMAL",
    "FLIP-HORIZ",
    "ROT180",
    "FLIP-HORIZ-ROT180",
    "FLIP-HORIZ-ROT270",
    "ROT270",
    "FLIP-HORIZ-ROT90",
    "ROT90"
];


//Provided a image file, determines the orientation of the file based on the EXIF information.
//Calls the "callback" function with an index into the JpegOrientation array. 
//If the image is not a JPEG, returns 0. If  the orientation value cannot be read (corrupted file?) return -1.
function getOrientation(file, callback) {
    
    const reader = new FileReader();
    reader.onload = (e) => {

        const view = new DataView(e.target.result);
        
        if (view.getUint16(0, false) !== 0xFFD8) {
            return callback(0);  //NOT A JPEG FILE
        }
        
        const length = view.byteLength;
        let offset = 2;
        while (offset < length) {
            
            if (view.getUint16(offset+2, false) <= 8)   //unknown?
                return callback(-1);
            
            const marker = view.getUint16(offset, false);
            offset += 2;
            if (marker === 0xFFE1) {
                
                if (view.getUint32(offset += 2, false) !== 0x45786966) 
                    return callback(-1); //unknown?
                

                const little = view.getUint16(offset += 6, false) === 0x4949;
                offset += view.getUint32(offset + 4, little);
                const tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++) {
                    if (view.getUint16(offset + (i * 12), little) === 0x0112) {
                        return callback(view.getUint16(offset + (i * 12) + 8, little));   //found orientation code
                    }
                }
            }
            else if ((marker & 0xFF00) !== 0xFF00) {
                break;
            }
            else { 
                offset += view.getUint16(offset, false);
            }
        }
        
        return callback(-1); //unknown?
    };
    reader.readAsArrayBuffer(file);
}

//Takes a jpeg image file as base64 and transforms it back to original, providing the
//transformed image in callback.  If the image is not a jpeg or is already in normal orientation,
//just calls the callback directly with the source.
//Set type to the desired output type if transformed, default is image/jpeg for speed.
function resetOrientation(srcBase64, srcOrientation, callback, type = "image/jpeg") {
    
    if (srcOrientation <= 1) {  //no transform needed
        callback(srcBase64);
        return;
    }
    
    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 1: normal, no transform needed
              
              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 base64
        callback(canvas.toDataURL(type), srcOrientation);
    };

    img.src = srcBase64;
};


//Read an image file, providing the returned data to callback. If the image is jpeg
//and is transformed according to EXIF info, transform it first.
//The callback function receives the image data and the orientation value (index into JpegOrientation)
export function readImageFile(file, callback) {

    getOrientation(file, (orientation) => {

        console.log("Read file \"" + file.name + "\" with orientation: " + JpegOrientation[orientation]);

        const reader = new FileReader();
        reader.onload = () => {  //when reading complete

            const img = reader.result;
            resetOrientation(img, orientation, callback);
        };
        reader.readAsDataURL(file);  //start read
        
    });
}

혼합용액(php+css)을 사용하고 있습니다.

다음을 위해 용기가 필요합니다.

  • div.imgCont2회전에 필요한 컨테이너.
  • div.imgCont1축소하는 데 필요한 컨테이너 -width:150%;
  • div.imgCont이미지가 zoomOut일 때 스크롤 막대에 필요한 컨테이너.

.

<?php
    $image_url = 'your image url.jpg';
    $exif = @exif_read_data($image_url,0,true);
    $orientation = @$exif['IFD0']['Orientation'];
?>

<style>
.imgCont{
    width:100%;
    overflow:auto;
}
.imgCont2[data-orientation="8"]{
    transform:rotate(270deg);
    margin:15% 0;
}
.imgCont2[data-orientation="6"]{
    transform:rotate(90deg);
    margin:15% 0;
}
.imgCont2[data-orientation="3"]{
    transform:rotate(180deg);
}
img{
    width:100%;
}
</style>

<div class="imgCont">
  <div class="imgCont1">
    <div class="imgCont2" data-orientation="<?php echo($orientation) ?>">
      <img src="<?php echo($image_url) ?>">
    </div>
  </div>
</div>

저 같은 경우에는 exif-auto-rotate 라이브러리가 필요했습니다.

저는 base64 포맷 이미지를 백엔드에서 가져왔고 사용하기 전에 올바른 방향으로 돌려야 했습니다.로테이션을 변경한 후에 64번 베이스를 돌려주는데, 나에게도 좋은 점입니다.

다음은 해당 라이브러리의 npm 링크입니다. https://www.npmjs.com/package/exif-auto-rotate

@fareed namrouti의 답변 외에도,

파일 입력 요소에서 이미지를 탐색해야 하는 경우 사용해야 합니다.

<input type="file" name="file" id="file-input"><br/>
image after transform: <br/>
<div id="container"></div>

<script>
    document.getElementById('file-input').onchange = function (e) {
        var image = e.target.files[0];
        window.loadImage(image, function (img) {
            if (img.type === "error") {
                console.log("couldn't load image:", img);
            } else {
                window.EXIF.getData(image, function () {
                    console.log("load image done!");
                    var orientation = window.EXIF.getTag(this, "Orientation");
                    var canvas = window.loadImage.scale(img,
                        {orientation: orientation || 0, canvas: true, maxWidth: 200});
                    document.getElementById("container").appendChild(canvas);
                    // or using jquery $("#container").append(canvas);
                });
            }
        });
    };
</script>

이미지를 회전시키는 작은 php 스크립트를 작성했습니다.요청할 때마다 이미지를 다시 계산하기만 하면 됩니다.

<?php

header("Content-type: image/jpeg");
$img = 'IMG URL';

$exif = @exif_read_data($img,0,true);
$orientation = @$exif['IFD0']['Orientation'];
if($orientation == 7 || $orientation == 8) {
    $degrees = 90;
} elseif($orientation == 5 || $orientation == 6) {
    $degrees = 270;
} elseif($orientation == 3 || $orientation == 4) {
    $degrees = 180;
} else {
    $degrees = 0;
}
$rotate = imagerotate(imagecreatefromjpeg($img), $degrees, 0);
imagejpeg($rotate);
imagedestroy($rotate);

?>

건배.

이 답변은 Angular people을 위한 것으로, 여기 오리엔테이션을 찾는 데 도움이 되는 패키지가 있습니다.

아래 예제에서는 changeEvent를 입력 태그에 사용했지만 데이터가 있으면 사용할 수 있습니다Url

dataUrl이 없는 경우 파일 -> dataUrl 또는 blob -> dataUrl을 쉽게 변환할 수 있습니다.파일 첨부 -> 선택한 이미지를 dataUrl로 변환할 수 있도록 도와주는 변환기 기능이 있습니다

https://www.npmjs.com/package/ngx-image-compress 이것은 패키지입니다.npm i ngx-image-compress

import { DOC_ORIENTATION, NgxImageCompressService } from 'ngx-image-compress';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
})

export class Test {
     constructor(private imageCompress: NgxImageCompressService) {}

      async onFileSelected(event: any) {
         const file: File = event.target.files[0]
         const dataUrl : string = await this.fileToDataURL(file)
         const orientation : DOC_ORIENTATION = await this.imageCompress.getOrientation(file)

      }
      
     fileToDataURL(blob: Blob): Promise<string> {
        return new Promise<string>((resolve, reject) => {
           const reader = new FileReader();
           reader.onload = _e => resolve(reader.result as string);
           reader.onerror = _e => reject(reader.error);
           reader.onabort = _e => reject(new Error("Read aborted"));
           reader.readAsDataURL(blob);
    });
  }

}

언급URL : https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images

반응형