업로드하기 전에 HTML5 사전 크기 조정 이미지


181

여기에 국수 스크래퍼가 있습니다.

HTML5 로컬 저장소와 xhr v2가 있습니다. 누군가가 실제 사례를 찾거 나이 질문에 대해 예 또는 아니오를 줄 수 있는지 궁금합니다.

새로운 로컬 저장소 (또는 기타)를 사용하여 이미지 크기를 미리 조정할 수 있습니까? 이미지 크기 조정에 대한 단서가없는 사용자가 10MB 이미지를 내 웹 사이트로 드래그 할 수 있으며 새로운 로컬 저장소를 사용하여 크기를 조정할 수 있습니다. 그런 다음 더 작은 크기로 업로드하십시오.

나는 당신이 플래시, 자바 애플릿, 액티브 X로 그것을 할 수 있다는 것을 잘 알고 있습니다 ... 문제는 자바 스크립트 + Html5로 할 수 있는지입니다.

이에 대한 답변을 기대합니다.

지금 당장.

답변:


181

예, File API를 사용 하면 canvas 요소를 사용 하여 이미지를 처리 ​​할 수 ​​있습니다 .

이 Mozilla Hacks 블로그 게시물 은 대부분의 프로세스를 안내합니다. 블로그 게시물의 소스 코드는 다음과 같습니다.

// from an input element
var filesToUpload = input.files;
var file = filesToUpload[0];

var img = document.createElement("img");
var reader = new FileReader();  
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);

var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);

var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;

if (width > height) {
  if (width > MAX_WIDTH) {
    height *= MAX_WIDTH / width;
    width = MAX_WIDTH;
  }
} else {
  if (height > MAX_HEIGHT) {
    width *= MAX_HEIGHT / height;
    height = MAX_HEIGHT;
  }
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);

var dataurl = canvas.toDataURL("image/png");

//Post dataurl to the server with AJAX

6
왜 좋은 선생님 감사합니다. 나는 오늘 밤에 연극을 할 것입니다 ... 파일 api로. 드래그 앤 드롭 업로드가 작동하고 이것이 포함하기에 정말 좋은 기능이라는 것을 깨달았습니다. 야.
Jimmyt1988

3
파일 크기를 줄이기 위해 품질을 보장하기 위해이 mycanvas.toDataURL ( "image / jpeg", 0.5)을 사용할 필요는 없습니다. 나는 mozilla 블로그 게시물 에서이 의견을 발견하고 여기에서 버그 해결을 보았습니다 bugzilla.mozilla.org/show_bug.cgi?id=564388
sbose

33
브라우저에서 비동기 적으로 디코딩을 수행 할 수 있고 이전에 픽셀을 사용할 수 없기 때문에 onload이미지를 기다려야합니다 ( 설정 하기 전에 처리기 연결 src) drawImage.
Kornel

6
캔버스 코드를 파일 리더의 onload 함수에 넣을 수 있습니다 ctx.drawImage(). 끝 부분에 도달하기 전에 큰 이미지가로드되지 않을 수 있습니다 .
Greg

4
조심하십시오, IOS에서 작동하게하려면 추가 마법이 필요합니다 : stackoverflow.com/questions/11929099/…
flo850

107

몇 년 전에이 문제를 해결하고 https://github.com/rossturner/HTML5-ImageUploader 로 솔루션을 github에 업로드했습니다.

robertc의 답변은 Mozilla Hacks 블로그 게시물 에서 제안한 솔루션을 사용 하지만 2 : 1 (또는 그 배수)이 아닌 스케일로 크기를 조정할 때 이미지 품질이 실제로 좋지 않은 것으로 나타났습니다. 다른 이미지 크기 조정 알고리즘을 실험하기 시작했지만 대부분 속도가 느리거나 품질이 좋지 않았습니다.

마지막으로 x를 목표로 2 : 1 비율로 이미지 품질을 잃지 않고 1 캔버스에서 다른 캔버스로 복사하는 Mozilla 솔루션이 신속하게 작동하기 때문에 신속하게 실행되고 성능이 매우 좋은 솔루션을 생각해 냈습니다. 너비와 너비가 y 픽셀 인 경우 이미지가 x 와 2 x 사이 , y 와 2 y 사이가 될 때까지이 캔버스 크기 조정 방법을 사용합니다 . 이 시점에서 목표 크기로 크기를 조정하는 최종 "단계"에 대한 알고리즘 이미지 크기 조정으로 돌아갑니다. 몇 가지 다른 알고리즘을 시도한 후 더 이상 온라인이 아니지만 인터넷 아카이브를 통해 액세스 할 수 있는 블로그에서 가져온 이중 선형 보간에 정착했습니다.좋은 결과를 제공합니다. 적용 가능한 코드는 다음과 같습니다.

ImageUploader.prototype.scaleImage = function(img, completionCallback) {
    var canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);

    while (canvas.width >= (2 * this.config.maxWidth)) {
        canvas = this.getHalfScaleCanvas(canvas);
    }

    if (canvas.width > this.config.maxWidth) {
        canvas = this.scaleCanvasWithAlgorithm(canvas);
    }

    var imageData = canvas.toDataURL('image/jpeg', this.config.quality);
    this.performUpload(imageData, completionCallback);
};

ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) {
    var scaledCanvas = document.createElement('canvas');

    var scale = this.config.maxWidth / canvas.width;

    scaledCanvas.width = canvas.width * scale;
    scaledCanvas.height = canvas.height * scale;

    var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
    var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height);

    this.applyBilinearInterpolation(srcImgData, destImgData, scale);

    scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0);

    return scaledCanvas;
};

ImageUploader.prototype.getHalfScaleCanvas = function(canvas) {
    var halfCanvas = document.createElement('canvas');
    halfCanvas.width = canvas.width / 2;
    halfCanvas.height = canvas.height / 2;

    halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height);

    return halfCanvas;
};

ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) {
    function inner(f00, f10, f01, f11, x, y) {
        var un_x = 1.0 - x;
        var un_y = 1.0 - y;
        return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y);
    }
    var i, j;
    var iyv, iy0, iy1, ixv, ix0, ix1;
    var idxD, idxS00, idxS10, idxS01, idxS11;
    var dx, dy;
    var r, g, b, a;
    for (i = 0; i < destCanvasData.height; ++i) {
        iyv = i / scale;
        iy0 = Math.floor(iyv);
        // Math.ceil can go over bounds
        iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv));
        for (j = 0; j < destCanvasData.width; ++j) {
            ixv = j / scale;
            ix0 = Math.floor(ixv);
            // Math.ceil can go over bounds
            ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv));
            idxD = (j + destCanvasData.width * i) * 4;
            // matrix to vector indices
            idxS00 = (ix0 + srcCanvasData.width * iy0) * 4;
            idxS10 = (ix1 + srcCanvasData.width * iy0) * 4;
            idxS01 = (ix0 + srcCanvasData.width * iy1) * 4;
            idxS11 = (ix1 + srcCanvasData.width * iy1) * 4;
            // overall coordinates to unit square
            dx = ixv - ix0;
            dy = iyv - iy0;
            // I let the r, g, b, a on purpose for debugging
            r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy);
            destCanvasData.data[idxD] = r;

            g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy);
            destCanvasData.data[idxD + 1] = g;

            b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy);
            destCanvasData.data[idxD + 2] = b;

            a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy);
            destCanvasData.data[idxD + 3] = a;
        }
    }
};

config.maxWidth원래의 종횡비를 유지하면서 이미지를 너비로 축소합니다 . 개발 당시 이것은 주요 데스크탑 브라우저 (IE9 +, Firefox, Chrome) 외에도 iPad / iPhone Safari에서 작동 했으므로 오늘날 HTML5가 더 광범위하게 채택되면 여전히 호환 될 것으로 기대합니다. canvas.toDataURL () 호출은 MIME 유형과 이미지 품질을 사용하므로 품질 및 출력 파일 형식을 제어 할 수 있습니다 (원하는 경우 입력과 다를 수 있음).

여기서 다루지 않는 유일한 점은이 메타 데이터에 대한 지식이 없어도 방향 정보를 유지하는 것입니다. 이미지의 크기는 그대로있는 그대로 저장되며 방향에 대한 이미지 내 메타 데이터는 손실됩니다. 즉, 태블릿 장치에서 "거꾸로"찍은 이미지는 장치의 카메라 뷰 파인더에서 뒤집어 졌을지라도 렌더링됩니다. 이것이 우려되는 경우이 블로그 게시물 에는이를 수행하는 방법에 대한 좋은 가이드와 코드 예제가 있으며 위 코드에 통합 될 수 있습니다.


4
내가 답변에 무리를 추가한다고 생각하는 현상금 원인을 수여, 그 오리엔테이션 자료를 통합해야 함 :)
Sam Saffron

3
매우 유용한 영혼이 이제 방향 메타 데이터에 대한 일부 기능에 통합되었습니다.)
Ross Taylor-Turner

26

위의 수정 :

<img src="" id="image">
<input id="input" type="file" onchange="handleFiles()">
<script>

function handleFiles()
{
    var filesToUpload = document.getElementById('input').files;
    var file = filesToUpload[0];

    // Create an image
    var img = document.createElement("img");
    // Create a file reader
    var reader = new FileReader();
    // Set the image once loaded into file reader
    reader.onload = function(e)
    {
        img.src = e.target.result;

        var canvas = document.createElement("canvas");
        //var canvas = $("<canvas>", {"id":"testing"})[0];
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);

        var MAX_WIDTH = 400;
        var MAX_HEIGHT = 300;
        var width = img.width;
        var height = img.height;

        if (width > height) {
          if (width > MAX_WIDTH) {
            height *= MAX_WIDTH / width;
            width = MAX_WIDTH;
          }
        } else {
          if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
          }
        }
        canvas.width = width;
        canvas.height = height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, width, height);

        var dataurl = canvas.toDataURL("image/png");
        document.getElementById('image').src = dataurl;     
    }
    // Load files into file reader
    reader.readAsDataURL(file);


    // Post the data
    /*
    var fd = new FormData();
    fd.append("name", "some_filename.jpg");
    fd.append("image", dataurl);
    fd.append("info", "lah_de_dah");
    */
}</script>

2
PHP를 FormData ()에 추가 한 후 PHP 부분을 어떻게 처리합니까? 예를 들어 $ _FILES [ 'name'] [ 'tmp_name'] [$ i]을 찾고 있지 않습니까? if (isset ($ _ POST [ 'image'])) ... 시도하고 있지만 dataurl그렇지 않습니다.
denikov

2
이미지를 처음 업로드하려고 할 때 firefox에서 작동하지 않습니다. 다음에 시도하면 작동합니다. 고치는 방법?
Fred

파일이 배열이 null이거나 비어있는 경우 반환
Sheelpriy

2
크롬에 액세스하려고 할 때 img.width그리고 img.height처음에 그들이 있습니다 0. 나머지 기능은 내부에 넣어야합니다img.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo

2
@Justin이 게시물의 수정 내용과 같이 "위"를 명확히 할 수 있습니까? 건배
마틴

16

저에게 도움이 되는 저스틴답변 수정 :

  1. img.onload 추가
  2. 실제 예제로 POST 요청을 확장하십시오.

function handleFiles()
{
    var dataurl = null;
    var filesToUpload = document.getElementById('photo').files;
    var file = filesToUpload[0];

    // Create an image
    var img = document.createElement("img");
    // Create a file reader
    var reader = new FileReader();
    // Set the image once loaded into file reader
    reader.onload = function(e)
    {
        img.src = e.target.result;

        img.onload = function () {
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0);

            var MAX_WIDTH = 800;
            var MAX_HEIGHT = 600;
            var width = img.width;
            var height = img.height;

            if (width > height) {
              if (width > MAX_WIDTH) {
                height *= MAX_WIDTH / width;
                width = MAX_WIDTH;
              }
            } else {
              if (height > MAX_HEIGHT) {
                width *= MAX_HEIGHT / height;
                height = MAX_HEIGHT;
              }
            }
            canvas.width = width;
            canvas.height = height;
            var ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0, width, height);

            dataurl = canvas.toDataURL("image/jpeg");

            // Post the data
            var fd = new FormData();
            fd.append("name", "some_filename.jpg");
            fd.append("image", dataurl);
            fd.append("info", "lah_de_dah");
            $.ajax({
                url: '/ajax_photo',
                data: fd,
                cache: false,
                contentType: false,
                processData: false,
                type: 'POST',
                success: function(data){
                    $('#form_photo')[0].reset();
                    location.reload();
                }
            });
        } // img.onload
    }
    // Load files into file reader
    reader.readAsDataURL(file);
}

2
방향 정보는 (EXIF) 분실
콘스탄틴 Vahrushev을

8

휠을 재발 명하지 않으려면 plupload.com을 사용해보십시오 .


3
또한 클라이언트 측 크기 조정에 plupload를 사용합니다. 가장 좋은 점은 플래시, html5, silverlight 등을 사용할 수 있다는 것입니다. 모든 사용자가 설정이 다르기 때문에 사용자가 사용할 수있는 것은 무엇이든 가능합니다. html5 만 원한다면 일부 오래된 브라우저와 Opera에서 작동하지 않을 것이라고 생각합니다.
jnl

6

타이프 스크립트

async resizeImg(file: Blob): Promise<Blob> {
    let img = document.createElement("img");
    img.src = await new Promise<any>(resolve => {
        let reader = new FileReader();
        reader.onload = (e: any) => resolve(e.target.result);
        reader.readAsDataURL(file);
    });
    await new Promise(resolve => img.onload = resolve)
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    let MAX_WIDTH = 1000;
    let MAX_HEIGHT = 1000;
    let width = img.naturalWidth;
    let height = img.naturalHeight;
    if (width > height) {
        if (width > MAX_WIDTH) {
            height *= MAX_WIDTH / width;
            width = MAX_WIDTH;
        }
    } else {
        if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
        }
    }
    canvas.width = width;
    canvas.height = height;
    ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0, width, height);
    let result = await new Promise<Blob>(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); });
    return result;
}

1
이 게시물을 발견 할 수있는 사람이라면 누구나이 약속 기반 답변이 훨씬 신뢰할 만하다고 생각합니다. 나는 일반적인 JS로 변환했으며 매력처럼 작동합니다.
gbland777

5
fd.append("image", dataurl);

작동하지 않습니다. PHP 쪽에서는 이것으로 파일을 저장할 수 없습니다.

대신이 코드를 사용하십시오.

var blobBin = atob(dataurl.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
  array.push(blobBin.charCodeAt(i));
}
var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"});

fd.append("image", file); // blob file

5

캔버스 요소에서 이미지 크기를 조정하는 것은 가장 저렴한 상자 보간을 사용하기 때문에 일반적으로 좋지 않습니다. 결과 이미지가 눈에 띄게 저하됩니다. Lanczos 변환을 대신 수행 할 수있는 http://nodeca.github.io/pica/demo/ 를 사용 하는 것이 좋습니다 . 위의 데모 페이지는 캔버스와 Lanczos 접근 방식의 차이점을 보여줍니다.

또한 웹 워커를 사용하여 이미지 크기를 병렬로 조정합니다. WEBGL 구현도 있습니다.

https://myimageresizer.com 과 같이 작업을 위해 pica를 사용하는 일부 온라인 이미지 리사이 저가 있습니다.


5

허용되는 대답은 훌륭하지만 크기 조정 논리는 이미지가 축 중 하나에서만 최대 값보다 큰 경우를 무시합니다 (예 : 높이> maxHeight이지만 너비 <= maxWidth).

다음 코드는 모든 경우를보다 간단하고 기능적으로 처리한다고 생각합니다 (일반 자바 스크립트를 사용하는 경우 typescript 유형 주석은 무시하십시오).

private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} {
    if (width <= maxWidth && height <= maxHeight)
      return { width, height };
    else if (width / maxWidth > height / maxHeight)
      return { width: maxWidth, height: height * maxWidth / width};
    else
      return { width: width * maxHeight / height, height: maxHeight };
  }

0

업로드 전 크기 조정 기능을 사용하여 간단하고 쉬운 업로드 관리자를 사용 하려면 dropzone.js 를 사용할 수 있습니다 .

내장 크기 조정 기능이 있지만 원하는 경우 직접 제공 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.