developer tip

브라우저에서 자바 스크립트를 통해 이미지를 압축하는 방법은 무엇입니까?

copycodes 2020. 11. 7. 10:15
반응형

브라우저에서 자바 스크립트를 통해 이미지를 압축하는 방법은 무엇입니까?


TL; DR;

이미지를 업로드하기 전에 브라우저에서 직접 이미지 (대부분 jpeg, png 및 gif)를 압축하는 방법이 있습니까? JavaScript가 이것을 할 수 있다고 확신하지만 그것을 달성하는 방법을 찾을 수 없습니다.


구현하려는 전체 시나리오는 다음과 같습니다.

  • 사용자가 내 웹 사이트로 이동하여 input type="file"요소 를 통해 이미지를 선택합니다 .
  • 이 이미지는 JavaScript를 통해 검색되며 올바른 파일 형식, 최대 파일 크기 등과 같은 몇 가지 확인을 수행합니다.
  • 모든 것이 정상이면 페이지에 이미지 미리보기가 표시됩니다.
  • 사용자는 이미지를 90 ° / -90 ° 회전, 미리 정의 된 비율에 따라 자르기 등과 같은 몇 가지 기본 작업을 수행하거나 다른 이미지를 업로드하고 1 단계로 돌아갈 수 있습니다.
  • 사용자가 만족하면 편집 된 이미지가 압축되고 로컬로 "저장"됩니다 (파일에 저장되지 않고 브라우저 메모리 / 페이지에 저장 됨).
  • 사용자는 이름, 나이 등과 같은 데이터로 양식을 채 웁니다.
  • 사용자가 "마침"버튼을 클릭하면 데이터 + 압축 이미지가 포함 된 양식이 서버로 전송됩니다 (AJAX 제외).

마지막 단계까지의 전체 프로세스는 클라이언트 측에서 수행되어야하며 최신 Chrome 및 Firefox, Safari 5+ 및 IE 8+에서 호환되어야합니다 . 가능하다면 자바 스크립트 만 사용해야합니다 (하지만 이것이 불가능하다고 확신합니다).

지금은 아무것도 코딩하지 않았지만 이미 생각했습니다. 로컬에서 파일 읽기는 File API 를 통해 가능 하며 이미지 미리보기 및 편집은 Canvas 요소를 사용하여 수행 할 수 있지만 이미지 압축 부분을 수행하는 방법을 찾을 수 없습니다 .

html5please.comcaniuse.com 에 따르면 이러한 브라우저를 지원하는 것은 상당히 어렵지만 (IE 덕분에) FlashCanvasFileReader 와 같은 폴리 필을 사용하여 수행 할 수 있습니다 .

실제로 목표는 파일 크기를 줄이는 것이므로 이미지 압축을 해결책으로 봅니다. 하지만 업로드 된 이미지가 내 웹 사이트에 매번 같은 위치에 표시 될 것임을 알고 있으며이 표시 영역의 크기 (예 : 200x400)도 알고 있습니다. 따라서 해당 크기에 맞게 이미지 크기를 조정하여 파일 크기를 줄일 수 있습니다. 이 기술의 압축 비율이 얼마인지 모르겠습니다.

어떻게 생각해 ? 나에게 말해 줄 조언이 있습니까? JavaScript에서 이미지 브라우저 측을 압축하는 방법을 알고 있습니까? 답장 해 주셔서 감사합니다.


요컨대 :

  • .readAsArrayBuffer와 함께 HTML5 FileReader API를 사용하여 파일 읽기
  • 파일 데이터로 e Blob을 만들고 window.URL.createObjectURL (blob)을 사용 하여 URL을 가져옵니다.
  • 새 이미지 요소를 만들고 src를 파일 blob URL로 설정합니다.
  • 캔버스로 이미지를 보냅니다. 캔버스 크기는 원하는 출력 크기로 설정됩니다.
  • canvas.toDataURL ( "image / jpeg", 0.7)을 통해 캔버스에서 축소 된 데이터를 다시 가져옵니다 (자신의 출력 형식 및 품질 설정).
  • 숨겨진 새 입력을 원본 양식에 연결하고 dataURI 이미지를 기본적으로 일반 텍스트로 전송
  • 백엔드에서 dataURI를 읽고 Base64에서 디코딩 한 다음 저장합니다.

출처 : code .


@PsychoWoods의 대답은 좋습니다. 나만의 솔루션을 제공하고 싶습니다. 이 자바 스크립트 함수는 이미지 데이터 URL과 너비를 가져 와서 새 너비로 조정하고 새 데이터 URL을 반환합니다.

// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
    "use strict";
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;

    // Provide default values
    imageType = imageType || "image/jpeg";
    imageArguments = imageArguments || 0.7;

    // Create a temporary image so that we can compute the height of the downscaled image.
    image = new Image();
    image.src = dataUrl;
    oldWidth = image.width;
    oldHeight = image.height;
    newHeight = Math.floor(oldHeight / oldWidth * newWidth)

    // Create a temporary canvas to draw the downscaled image on.
    canvas = document.createElement("canvas");
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the downscaled image on the canvas and return the new data URL.
    ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    newDataUrl = canvas.toDataURL(imageType, imageArguments);
    return newDataUrl;
}

이 코드는 데이터 URL이 있고 축소 된 이미지에 대한 데이터 URL이 필요한 모든 곳에서 사용할 수 있습니다.


다른 답변에서 누락 된 두 가지가 있습니다.

  • canvas.toBlob(사용 가능한 경우)는보다 성능이 canvas.toDataURL뛰어나고 비동기식입니다.
  • 파일-> 이미지-> 캔버스-> 파일 변환으로 인해 EXIF ​​데이터가 손실됩니다. 특히 최신 휴대폰 / 태블릿에서 일반적으로 설정되는 이미지 회전에 대한 데이터입니다.

다음 스크립트는 두 가지 사항을 모두 다룹니다.

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value: function(callback, type, quality) {

            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                len = binStr.length,
                arr = new Uint8Array(len);

            for (var i = 0; i < len; i++) {
                arr[i] = binStr.charCodeAt(i);
            }

            callback(new Blob([arr], {type: type || 'image/png'}));
        }
    });
}

window.URL = window.URL || window.webkitURL;

// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
    if (file.slice) {
        file = file.slice(0, 131072);
    } else if (file.webkitSlice) {
        file = file.webkitSlice(0, 131072);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            callback(-2);
            return;
        }
        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) {
                    callback(-1);
                    return;
                }
                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) {
                        callback(view.getUint16(offset + (i * 12) + 8, little));
                        return;
                    }
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
    var canvas = document.createElement('canvas');
    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");
    }

    var ctx = canvas.getContext('2d');
    switch (orientation) {
        case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
        case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
        case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
        case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
        case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
        case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
    }
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
    return canvas;
}

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
    if (file.size <= acceptFileSize) {
        callback(file);
        return;
    }
    var img = new Image();
    img.onerror = function() {
        URL.revokeObjectURL(this.src);
        callback(file);
    };
    img.onload = function() {
        URL.revokeObjectURL(this.src);
        getExifOrientation(file, function(orientation) {
            var w = img.width, h = img.height;
            var 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);

            var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
            canvas.toBlob(function(blob) {
                console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
                callback(blob);
            }, 'image/jpeg', quality);
        });
    };
    img.src = URL.createObjectURL(file);
}

사용 예 :

inputfile.onchange = function() {
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
        let body = new FormData();
        body.set('file', blob, blob.name || "file.jpg");
        fetch('/upload-image', {method: 'POST', body}).then(...);
    });
};

이미지 변환을 볼 수 있습니다. 여기에서 시도하십시오-> 데모 페이지

여기에 이미지 설명 입력


편집 :이 답변에 대한 Mr Me 의견에 따라 이제 JPG / WebP 형식에 대해 압축을 사용할 수있는 것 같습니다 ( https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL 참조 ) .

As far as I know, you cannot compress images using canvas, instead, you can resize it. Using canvas.toDataURL will not let you choose the compression ratio to use. You can take a look at canimage that does exactly what you want : https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

In fact, it's often sufficient to just resize the image to decrease it's size but if you want to go further, you'll have to use newly introduced method file.readAsArrayBuffer to get a buffer containing the image data.

Then, just use a DataView to read it's content according to the image format specification (http://en.wikipedia.org/wiki/JPEG or http://en.wikipedia.org/wiki/Portable_Network_Graphics).

It'll be hard to deal with image data compression, but it is worse a try. On the other hand, you can try to delete the PNG headers or the JPEG exif data to make your image smaller, it should be easier to do so.

You'll have to create another DataWiew on another buffer and fill it with the filtered image content. Then, you'll just have to encode you're image content to DataURI using window.btoa.

Let me know if you implement something similar, will be interesting to go through the code.


I had an issue with the downscaleImage() function posted above by @daniel-allen-langdon in that the image.width and image.height properties are not available immediately because the image load is asynchronous.

이를 고려하고 async기능을 사용 하며 너비가 아닌 가장 긴 치수를 기준으로 이미지 크기를 조정하는 아래 업데이트 된 TypeScript 예제를 참조하십시오.

function getImage(dataUrl: string): Promise<HTMLImageElement> 
{
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = () => {
            resolve(image);
        };
        image.onerror = (el: any, err: ErrorEvent) => {
            reject(err.error);
        };
    });
}

export async function downscaleImage(
        dataUrl: string,  
        imageType: string,  // e.g. 'image/jpeg'
        resolution: number,  // max width/height in pixels
        quality: number   // e.g. 0.9 = 90% quality
    ): Promise<string> {

    // Create a temporary image so that we can compute the height of the image.
    const image = await getImage(dataUrl);
    const oldWidth = image.naturalWidth;
    const oldHeight = image.naturalHeight;
    console.log('dims', oldWidth, oldHeight);

    const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
    const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
    console.log('longest dim', longestDimension, currentRes);

    if (currentRes > resolution) {
        console.log('need to resize...');

        // Calculate new dimensions
        const newSize = longestDimension == 'width'
            ? Math.floor(oldHeight / oldWidth * resolution)
            : Math.floor(oldWidth / oldHeight * resolution);
        const newWidth = longestDimension == 'width' ? resolution : newSize;
        const newHeight = longestDimension == 'height' ? resolution : newSize;
        console.log('new width / height', newWidth, newHeight);

        // Create a temporary canvas to draw the downscaled image on.
        const canvas = document.createElement('canvas');
        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw the downscaled image on the canvas and return the new data URL.
        const ctx = canvas.getContext('2d')!;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        const newDataUrl = canvas.toDataURL(imageType, quality);
        return newDataUrl;
    }
    else {
        return dataUrl;
    }

}

JPG 이미지 압축의 경우 JIC (Javascript Image Compression)라는 최고의 압축 기술을 사용할 수 있습니다. 이것은 확실히 도움이 될 것입니다-> https://github.com/brunobar79/JIC

참고 URL : https://stackoverflow.com/questions/14672746/how-to-compress-an-image-via-javascript-in-the-browser

반응형