HTML5 Ändern Sie die Größe von Bildern vor dem Hochladen


181

Hier ist ein Nudelkratzer.

Denken Sie daran, wir haben HTML5 lokalen Speicher und xhr v2 und was nicht. Ich habe mich gefragt, ob jemand ein funktionierendes Beispiel finden oder mir einfach ein Ja oder Nein für diese Frage geben könnte:

Ist es möglich, die Größe eines Bilds mithilfe des neuen lokalen Speichers (oder was auch immer) vorab zu ändern, sodass ein Benutzer, der keine Ahnung hat, wie die Größe eines Bildes geändert werden kann, sein 10-MB-Bild auf meine Website ziehen kann, um die Größe mithilfe des neuen lokalen Speichers und zu ändern DANN lade es in der kleineren Größe hoch.

Ich weiß genau, dass Sie es mit Flash, Java-Applets, aktivem X tun können ... Die Frage ist, ob Sie es mit Javascript + Html5 tun können.

Ich freue mich auf die Antwort auf diese Frage.

Ta für jetzt.

Antworten:


181

Ja, verwenden Sie die Datei-API , dann können Sie die Bilder mit dem Canvas-Element verarbeiten .

Dieser Mozilla Hacks-Blogbeitrag führt Sie durch den größten Teil des Prozesses. Als Referenz hier der zusammengestellte Quellcode aus dem Blog-Beitrag:

// 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
Warum danke ich Ihnen, mein Herr? Ich werde heute Abend ein Stück spielen ... Mit der Datei-API also. Ich habe den Drag & Drop-Upload zum Laufen gebracht und festgestellt, dass dies auch eine wirklich nette Funktion ist. Hurra.
Jimmyt1988

3
Müssen wir nicht mycanvas.toDataURL ("image / jpeg", 0.5) verwenden, um sicherzustellen, dass die Qualität erreicht wird, um die Dateigröße zu verringern. Ich bin auf diesen Kommentar im Mozilla-Blog-Beitrag gestoßen
sbose

33
Sie sollten onloadauf das Bild warten (Handler vor dem Einstellen anhängen src), da der Browser asynchron dekodieren darf und Pixel möglicherweise vorher nicht verfügbar sind drawImage.
Kornel

6
Möglicherweise möchten Sie den Canvas-Code in die Onload-Funktion des Filereaders einfügen. Es besteht die Möglichkeit, dass gegen ctx.drawImage()Ende keine großen Bilder geladen werden .
Greg


106

Ich habe dieses Problem vor einigen Jahren angegangen und meine Lösung als https://github.com/rossturner/HTML5-ImageUploader auf github hochgeladen

Die Antwort von robertc verwendet die im Blog-Beitrag von Mozilla Hacks vorgeschlagene Lösung. Ich fand jedoch, dass dies eine wirklich schlechte Bildqualität ergab, wenn die Größe auf einen Maßstab geändert wurde, der nicht 2: 1 (oder ein Vielfaches davon) war. Ich fing an, mit verschiedenen Algorithmen zur Größenänderung von Bildern zu experimentieren, obwohl die meisten ziemlich langsam waren oder auch keine gute Qualität hatten.

Schließlich habe ich eine Lösung gefunden, die meiner Meinung nach schnell ausgeführt werden kann und auch eine ziemlich gute Leistung aufweist - da die Mozilla-Lösung zum Kopieren von einer Leinwand auf eine andere schnell und ohne Verlust der Bildqualität im Verhältnis 2: 1 bei einem Ziel von x funktioniert Pixel breit und y Pixel hoch. Ich verwende diese Methode zur Größenänderung der Leinwand, bis das Bild zwischen x und 2 x und y und 2 y liegt . An dieser Stelle wende ich mich dann der algorithmischen Bildgröße für den letzten "Schritt" der Größenänderung auf die Zielgröße zu. Nachdem ich verschiedene Algorithmen ausprobiert hatte, entschied ich mich für eine bilineare Interpolation aus einem Blog, das nicht mehr online ist, aber über das Internetarchiv zugänglich istHier ist der zutreffende Code:

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

Dadurch wird ein Bild auf eine Breite von verkleinert config.maxWidth, wobei das ursprüngliche Seitenverhältnis beibehalten wird. Zum Zeitpunkt der Entwicklung funktionierte dies auf iPad / iPhone Safari zusätzlich zu den wichtigsten Desktop-Browsern (IE9 +, Firefox, Chrome), sodass ich davon ausgehe, dass es angesichts der breiteren Verbreitung von HTML5 heute noch kompatibel sein wird. Beachten Sie, dass der Aufruf canvas.toDataURL () einen MIME-Typ und eine Bildqualität verwendet, mit denen Sie die Qualität und das Ausgabedateiformat steuern können (möglicherweise anders als die Eingabe, wenn Sie dies wünschen).

Der einzige Punkt, der hier nicht behandelt wird, ist die Beibehaltung der Orientierungsinformationen. Ohne Kenntnis dieser Metadaten wird die Größe des Bilds geändert und unverändert gespeichert. Dabei gehen alle Metadaten im Bild zur Orientierung verloren, was bedeutet, dass Bilder, die auf einem Tablet-Gerät "verkehrt herum" aufgenommen wurden als solche gerendert, obwohl sie im Kamerasucher des Geräts umgedreht worden wären. Wenn dies ein Problem darstellt, enthält dieser Blog-Beitrag eine gute Anleitung und Codebeispiele, wie dies erreicht werden kann. Ich bin sicher, dass dies in den obigen Code integriert werden könnte.


4
Ich habe dir das Kopfgeld verliehen, weil ich denke, dass es der Antwort eine Menge hinzufügt, aber ich muss das Orientierungsmaterial integrieren :)
Sam Saffron

3
Eine sehr hilfreiche Seele hat jetzt einige Funktionen für Orientierungsmetadaten zusammengeführt :)
Ross Taylor-Turner

26

Korrektur nach oben:

<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
Wie gehen Sie mit dem PHP-Teil um, nachdem Sie ihn zu FormData () hinzugefügt haben? Sie würden zum Beispiel nicht nach $ _FILES ['name'] ['tmp_name'] [$ i] suchen? Ich versuche if (isset ($ _ POST ['image'])) ... aber das ist dataurlnicht da.
Denikov

2
Es funktioniert nicht mit Firefox, wenn Sie zum ersten Mal versuchen, ein Bild hochzuladen. Wenn Sie es das nächste Mal versuchen, funktioniert es. Wie man es repariert?
Fred

Rückgabe, wenn die Datei Array ist null oder leer.
Sheelpriy

2
In Chrome beim Versuch zuzugreifen img.widthund img.heightzunächst sind sie 0. Sie sollten den Rest der Funktion inimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo

2
@ Justin können Sie "das Obige" klarstellen, wie in, was ist dieser Beitrag eine Korrektur von? Prost
Martin

16

Änderung der Antwort von Justin , die für mich funktioniert:

  1. Img.onload hinzugefügt
  2. Erweitern Sie die POST-Anforderung um ein reales Beispiel

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
Orientierungsinfo (exif) geht verloren
Konstantin Vahrushev

8

Wenn Sie das Rad nicht neu erfinden möchten, können Sie plupload.com ausprobieren


3
Ich verwende plupload auch für die clientseitige Größenänderung. Das Beste daran ist, dass Flash, HTML5, Silverlight usw. verwendet werden können. Was auch immer der Benutzer zur Verfügung hat, da jeder Benutzer ein anderes Setup hat. Wenn Sie nur HTML5 wollen, dann denke ich, dass es auf einigen alten Browsern und Opera nicht funktioniert.
jnl

6

Typoskript

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
Jeder, der auf diesen Beitrag stößt, diese vielversprechende Antwort ist meiner Meinung nach viel zuverlässiger. Ich habe zu normalem JS konvertiert und es funktioniert wie ein Zauber.
gbland777

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

Dies wird nicht funktionieren. Auf der PHP-Seite können Sie damit keine Datei speichern.

Verwenden Sie stattdessen diesen Code:

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

Das Ändern der Größe von Bildern in einem Canvas-Element ist im Allgemeinen eine schlechte Idee, da die billigste Box-Interpolation verwendet wird. Das resultierende Bild nimmt merklich an Qualität ab. Ich würde empfehlen, http://nodeca.github.io/pica/demo/ zu verwenden, um stattdessen eine Lanczos-Transformation durchzuführen. Die Demo-Seite oben zeigt den Unterschied zwischen Canvas- und Lanczos-Ansätzen.

Außerdem werden Web-Worker verwendet, um die Größe von Bildern parallel zu ändern. Es gibt auch eine WEBGL-Implementierung.

Es gibt einige Online-Bildveränderer, die Pica für die Arbeit verwenden, z. B. https://myimageresizer.com


5

Die akzeptierte Antwort funktioniert gut, aber die Größenänderungslogik ignoriert den Fall, in dem das Bild nur in einer der Achsen größer als das Maximum ist (z. B. Höhe> maxHöhe, aber Breite <= maxBreite).

Ich denke, der folgende Code kümmert sich einfacher und funktionaler um alle Fälle (ignorieren Sie die Anmerkungen zum Typoskripttyp, wenn Sie einfaches Javascript verwenden):

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

Sie können dropzone.js verwenden, wenn Sie einen einfachen Upload-Manager mit Größenänderung vor dem Hochladen verwenden möchten.

Es verfügt über integrierte Größenänderungsfunktionen, aber Sie können Ihre eigenen bereitstellen, wenn Sie möchten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.