it-swarm.com.de

Wie komprimiere ich ein Bild über Javascript im Browser?

TL; DR;

Gibt es eine Möglichkeit, ein Bild (meistens JPEG, PNG und GIF) direkt auf der Browserseite zu komprimieren, bevor Sie es hochladen? Ich bin mir ziemlich sicher, dass JavaScript dies kann, aber ich finde keinen Weg, um dies zu erreichen.


Hier ist das vollständige Szenario, das ich implementieren möchte:

  • der Benutzer geht auf meine Website und wählt ein Bild über ein input type="file"-Element aus.
  • dieses Bild wird über JavaScript abgerufen. Wir führen einige Überprüfungen durch, z. B. das richtige Dateiformat, die maximale Dateigröße usw.
  • wenn alles in Ordnung ist, wird eine Vorschau des Bildes auf der Seite angezeigt.
  • der Benutzer kann einige grundlegende Vorgänge ausführen, z. B. das Bild um 90 °/-90 ° drehen, in einem vordefinierten Verhältnis beschneiden usw., oder der Benutzer kann ein anderes Bild hochladen und zu Schritt 1 zurückkehren.
  • wenn der Benutzer zufrieden ist, wird das bearbeitete Bild komprimiert und lokal "gespeichert" (nicht in einer Datei gespeichert, sondern im Browserspeicher/auf der Seite).
  • der Benutzer füllt ein Formular mit Daten wie Name, Alter usw. aus.
  • der Benutzer klickt auf die Schaltfläche "Fertig stellen". Das Formular mit den Daten und dem komprimierten Bild wird an den Server gesendet (ohne AJAX).

Der gesamte Prozess bis zum letzten Schritt sollte clientseitig ausgeführt werden und sollte mit den neuesten Versionen von Chrome und Firefox, Safari 5+ und IE 8+ kompatibel sein. Wenn möglich sollte nur JavaScript verwendet werden (aber ich bin mir ziemlich sicher, dass dies nicht möglich ist).

Ich habe im Moment noch nichts programmiert, aber ich habe bereits darüber nachgedacht. Das lokale Lesen von Dateien ist über Datei-API möglich. Die Bildvorschau und -bearbeitung kann mit dem Element Canvas durchgeführt werden, aber Ich kann keine Möglichkeit finden, den Bildkomprimierungsabschnitt auszuführen. .

Laut html5please.com und caniuse.com ist die Unterstützung dieser Browser ziemlich schwierig (dank IE), könnte aber auch mit polyfill wie FlashCanvas und ausgeführt werden FileReader .

Eigentlich ist es das Ziel, die Dateigröße zu reduzieren, daher sehe ich die Bildkomprimierung als Lösung. Ich weiß jedoch, dass hochgeladene Bilder auf meiner Website immer an derselben Stelle angezeigt werden, und ich kenne die Größe dieses Anzeigebereichs (z. B. 200x400). So konnte ich die Größe des Bildes an diese Abmessungen anpassen und somit die Dateigröße reduzieren. Ich habe keine Ahnung, wie das Kompressionsverhältnis für diese Technik aussehen würde.

Was denkst du ? Hast du einen Rat, mir das zu sagen? Kennen Sie eine Möglichkeit, eine Bildbrowserseite in JavaScript zu komprimieren? Danke für deine Antworten.

58
pomeh

Zusamenfassend:

  • Lesen Sie die Dateien mithilfe der HTML5-FileReader-API mit .readAsArrayBuffer
  • Erstellen Sie einen e-Blob mit den Dateidaten und rufen Sie seine URL mit window.URL.createObjectURL (Blob) ab.
  • Erstellen Sie ein neues Image-Element, und legen Sie für die Datei blob-URL als Quelle src fest 
  • Senden Sie das Bild an die Leinwand. Die Leinwandgröße wird auf die gewünschte Ausgabegröße eingestellt
  • Holen Sie die verkleinerten Daten über canvas.toDataURL ("image/jpeg", 0.7) aus der Leinwand zurück (legen Sie Ihr eigenes Ausgabeformat und die Qualität fest)
  • Hängen Sie neue verborgene Eingaben an das Originalformular an und übertragen Sie die dataURI-Bilder grundsätzlich als normalen Text
  • Lesen Sie im Backend die dataURI, dekodieren Sie sie von Base64 und speichern Sie sie

Quelle: Code .

108
psychowood

Die Antwort von @PsychoWoods ist gut. Ich möchte meine eigene Lösung anbieten. Diese Javascript-Funktion nimmt eine Bilddaten-URL und eine Breite an, skaliert sie auf die neue Breite und gibt eine neue Daten-URL zurück.

// 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;
}

Dieser Code kann überall dort verwendet werden, wo Sie über eine Daten-URL verfügen und eine Daten-URL für ein verkleinertes Bild wünschen.

11

In den anderen Antworten fehlen zwei Dinge:

  • canvas.toBlob (falls verfügbar) ist performanter als canvas.toDataURL und auch asynchron.
  • die Datei -> Bild -> Leinwand -> Dateikonvertierung verliert EXIF-Daten; insbesondere Daten über die Bildrotation, die üblicherweise von modernen Telefonen/Tablets festgelegt werden.

Das folgende Skript behandelt beide Punkte:

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

Verwendungsbeispiel:

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(...);
    });
};
9
Simon Lindholm

Bearbeiten: Laut dem Kommentar von Herrn Me zu dieser Antwort scheint die Komprimierung für JPG/WebP-Formate verfügbar zu sein (siehe https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/ toDataURL ).

Soweit ich weiß, können Sie Bilder nicht mit Canvas komprimieren, sondern die Größe ändern. Bei der Verwendung von canvas.toDataURL können Sie nicht das zu verwendende Kompressionsverhältnis auswählen. Sie können einen Blick auf Canimage werfen, der genau das tut, was Sie möchten: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

In der Tat reicht es oft aus, das Bild nur zu verkleinern, um es zu verkleinern. Wenn Sie jedoch weiter vorgehen möchten, müssen Sie die neu eingeführte Methode file.readAsArrayBuffer verwenden, um einen Puffer mit den Bilddaten zu erhalten.

Verwenden Sie dann einfach eine DataView, um den Inhalt gemäß den Bildformatspezifikationen zu lesen ( http://en.wikipedia.org/wiki/JPEG oder http://en.wikipedia.org/wiki/Portable_Network_Graphics ).

Es wird schwierig sein, mit der Komprimierung von Bilddaten umzugehen, aber es ist ein schlechterer Versuch. Auf der anderen Seite können Sie versuchen, die PNG-Header oder die JPEG-Exif-Daten zu löschen, um das Bild zu verkleinern. Dies sollte jedoch einfacher sein.

Sie müssen einen anderen DataWiew in einem anderen Puffer erstellen und den gefilterten Bildinhalt füllen. Dann müssen Sie Ihren Image-Inhalt mit window.btoa in DataURI codieren.

Lassen Sie mich wissen, wenn Sie etwas Ähnliches implementieren, wird es interessant sein, den Code durchzugehen.

3
nfroidure

Ich hatte ein Problem mit der Funktion downscaleImage(), die von @ daniel-allen-langdon veröffentlicht wurde. Die Eigenschaften image.width und image.height sind nicht sofort verfügbar, da das Image asynchronous ist.

Im nachstehenden aktualisierten TypeScript-Beispiel wird dies berücksichtigt, async-Funktionen verwendet und das Bild basierend auf der längsten Bemaßung und nicht nur der Breite angepasst

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;
    }

}
1
Russell Briggs

Sie können einen Blick auf Bildkonvertierung werfen, versuchen Sie es hier -> Demo-Seite

 enter image description here

1
王玉略

Für die JPG-Bildkomprimierung können Sie die beste Komprimierungstechnik namens JIC (Javascript Image Compression) verwenden. Dies wird auf jeden Fall helfen -> https://github.com/brunobar79/J-I-C

0
Aravind Bhat K