it-swarm.com.de

HTML5 Canvas Größe ändern (Downscale) Bild Hohe Qualität?

Ich verwende html5-Canvas-Elemente, um die Größe von Bildern in meinem Browser zu ändern. Es stellt sich heraus, dass die Qualität sehr niedrig ist. Ich habe folgendes gefunden: Interpolation beim Skalieren einer <Leinwand> deaktivieren , aber es hilft nicht, die Qualität zu erhöhen.

Unten finden Sie meinen CSS- und JS-Code sowie das mit Photoshop erstellte und in der Canvas-API skalierte Bild. 

Was muss ich tun, um beim Skalieren eines Bildes im Browser eine optimale Qualität zu erhalten?

Hinweis: Ich möchte ein großes Bild auf ein kleines Bild verkleinern, die Farbe in einer Leinwand ändern und das Ergebnis von der Leinwand an den Server senden.

CSS: 

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS: 

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

Das Bild wurde mit Photoshop verkleinert:

enter image description here

Das Bild wurde auf Leinwand verkleinert:

enter image description here

Bearbeiten: 

Ich habe versucht, das Downscaling in mehreren Schritten durchzuführen, wie in vorgeschlagen:

Größe eines Bildes in einer HTML5-Zeichenfläche ändern und Html5-Zeichenfläche drawImage: So wird Antialiasing angewendet

Dies ist die Funktion, die ich verwendet habe: 

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

Hier ist das Ergebnis, wenn ich eine 2-stufige Größenanpassung verwende: 

enter image description here

Hier ist das Ergebnis, wenn ich eine 3-stufige Größenanpassung verwende: 

enter image description here

Hier ist das Ergebnis, wenn ich eine 4-stufige Größenanpassung verwende: 

enter image description here

Hier ist das Ergebnis, wenn ich eine 20-stufige Größenanpassung verwende: 

enter image description here

Hinweis: Es stellt sich heraus, dass sich die Bildqualität von 1 Schritt auf 2 Schritte erheblich verbessert. Je mehr Schritte Sie jedoch dem Prozess hinzufügen, desto unschärfer wird das Bild.

Gibt es eine Möglichkeit, das Problem zu lösen, dass das Bild umso unschärfer wird, je mehr Schritte Sie hinzufügen? 

Edit 2013-10-04: Ich habe den Algorithmus von GameAlchemist ausprobiert. Hier ist das Ergebnis im Vergleich zu Photoshop.

PhotoShop Bild: 

PhotoShop Image

GameAlchemist's Algorithmus:

GameAlchemist's Algorithm

143
confile

Da es Ihr Problem ist, Ihr Bild zu verkleinern, ist es sinnlos, über Interpolation zu sprechen, bei der es um das Erstellen von Pixeln geht. Das Problem hier ist das Downsampling. 

Um ein Bild herunterzusampeln, müssen wir jedes Quadrat der p * p-Pixel im Originalbild in ein Pixel im Zielbild umwandeln. 

Aus Leistungsgründen führen Browser eine sehr einfache Nachuntersuchung durch: Um ein kleineres Bild zu erstellen, wird nur ein Pixel in der Quelle ausgewählt und dessen Wert für das Ziel verwendet. was "vergisst" einige Details und fügt Lärm hinzu. 

Es gibt jedoch eine Ausnahme: Da das 2X-Bild-Downsampling sehr einfach zu berechnen ist (durchschnittlich 4 Pixel), und für Retina/HiDPI-Pixel verwendet wird, wird dieser Fall richtig behandelt - der Browser verwendet 4 Pixel ein-. 

ABER ... Wenn Sie mehrmals ein 2X-Downsampling verwenden, stehen Sie vor dem Problem, dass die aufeinander folgenden Rundungsfehler zu viel Rauschen hinzufügen.
Was noch schlimmer ist, Sie werden nicht immer um eine Zweierpotenz angepasst, und die Größenänderung auf die nächste Stärke + eine letzte Größenänderung ist sehr laut.

Was Sie suchen, ist ein pixelgenaues Downsampling, das heißt: eine erneute Abtastung des Bildes, bei der alle Eingangspixel berücksichtigt werden, unabhängig von der Skalierung.
Um dies zu tun, müssen wir für jedes Eingangspixel seinen Beitrag zu einem, zwei oder vier Zielpixeln berechnen, abhängig davon, ob sich die skalierte Projektion der Eingangspixel direkt innerhalb eines Zielpixels befindet, ein X-Rand, ein Y-Rand, überlappt , oder beides.
(Ein Schema wäre hier nett, aber ich habe keinen.) 

Hier ist ein Beispiel für eine Leinwandskala im Vergleich zu meiner pixelgenauen Skala im Maßstab 1/3 eines Zombats.

Beachten Sie, dass das Bild möglicherweise in Ihrem Browser skaliert wird und von S.O. .Jpegized wird.
Aber wir sehen, dass es viel weniger Lärm gibt, besonders im Gras hinter dem Wombat und den Ästen rechts davon. Das Geräusch im Fell macht es kontrastreicher, aber es sieht so aus, als hätte er weiße Haare - anders als das Ausgangsbild -.
Das rechte Bild ist weniger eingängig, aber definitiv schöner.

enter image description here

Hier ist der Code, um das Pixel-Perfect-Downscaling durchzuführen:

fiddle-Ergebnis: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
Geige selbst: http://jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel's y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 |  tx;    // rounded : target pixel's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

Es ist recht speichergierig, da zum Speichern der Zwischenwerte des Zielbilds ein Float-Puffer benötigt wird (-> Wenn der Ergebnisbereich gezählt wird, verwenden wir in diesem Algorithmus den 6-fachen Speicher des Quellbilds).
Es ist auch ziemlich teuer, da jedes Quellpixel unabhängig von der Zielgröße verwendet wird, und wir müssen für das getImageData/putImageDate-Paket auch ziemlich langsam zahlen.
Aber es gibt keinen Weg, um schneller als jeden Quellwert zu verarbeiten, und die Situation ist nicht so schlecht: Für mein 740 * 556-Image eines Wombats dauert die Verarbeitung zwischen 30 und 40 ms.

160
GameAlchemist

Schnelle Leinwandwiederherstellung mit guter Qualität: http://jsfiddle.net/9g9Nv/442/

Update: Version 2.0 (schneller, Webworker + übertragbare Objekte) - https://github.com/viliusle/Hermite-resize

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}
45
ViliusL

Vorschlag 1 - Erweitern Sie die Prozessleitung

Sie können Step-Down verwenden, wie ich es in den Links beschrieben habe, auf die Sie verweisen, aber Sie scheinen sie falsch zu verwenden.

Zum Verkleinern von Bildern auf ein Verhältnis von mehr als 1: 2 ist keine erneute Einstellung erforderlich (normalerweise, jedoch nicht beschränkt auf). An dieser Stelle müssen Sie eine drastischeAbwärtsskalierung durchführen, die Sie in zwei (und selten mehr) Stufen aufteilen müssen, je nach dem Inhalt des Bildes (insbesondere bei hohen Frequenzen wie dünnen Linien) auftreten).

Jedes Mal, wenn Sie ein Bild herunterladen, verlieren Sie Details und Informationen. Sie können nicht erwarten, dass das resultierende Bild so klar ist wie das Original.

Wenn Sie die Bilder dann in vielen Schritten verkleinern, verlieren Sie eine Menge Informationen und das Ergebnis ist schlecht, wie Sie es bereits bemerkt haben.

Versuchen Sie es mit nur einem zusätzlichen Schritt oder bei den oberen zwei.

Windungen

Beachten Sie bei Photoshop, dass eine Faltung angewendet wird, nachdem das Bild erneut aufgenommen wurde, z. B. Schärfen. Es erfolgt nicht nur eine bi-kubische Interpolation. Um Photoshop vollständig zu emulieren, müssen wir auch die Schritte hinzufügen, die Photoshop durchführt (mit der Standardeinstellung).

Für dieses Beispiel werde ich meine ursprüngliche Antwort verwenden, auf die Sie sich in Ihrem Beitrag beziehen, aber ich habe eine scharfe Faltung hinzugefügt, um die Qualität als Nachbearbeitungsprozess zu verbessern (siehe Demo unten).

Hier ist der Code für das Hinzufügen eines Schärfefilters (er basiert auf einem generischen Faltungsfilter - ich gebe die Gewichtungsmatrix für die Schärfe darin sowie einen Mischfaktor zum Anpassen der Aussprache des Effekts):

Verwendungszweck:

sharpen(context, width, height, mixFactor);

Die Variable mixFactor ist ein Wert zwischen [0.0, 1.0] und ermöglicht es Ihnen, den Schärfeffekt herunterzuspielen - Faustregel: Je weniger Größe, desto weniger Effekt ist erforderlich.

Funktion (basierend auf diesem Snippet ):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;

    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

Das Ergebnis dieser Kombination wird sein:

ONLINE-DEMO HIER

Result downsample and sharpen convolution

Je nachdem, wie viel der Schärfung Sie der Mischung hinzufügen möchten, können Sie das Ergebnis von "unscharf" zu sehr scharf bekommen:

Variations of sharpen

Vorschlag 2 - Implementierung eines Low-Level-Algorithmus

Wenn Sie das beste Ergebnis in Bezug auf die Qualität erzielen möchten, müssen Sie zunächst einen Low-Level-Modus nutzen und in Betracht ziehen, beispielsweise diesen brandneuen Algorithmus zu implementieren.

Siehe Interpolationsabhängiges Image-Downsampling (2011) von IEEE.
Hier ist ein Link zur vollständigen Veröffentlichung (PDF) .

Es gibt derzeit keine Implementierungen dieses Algorithmus in JavaScript AFAIK von. Daher haben Sie eine Handvoll, wenn Sie sich dieser Aufgabe stellen wollen.

Die Essenz ist (Auszüge aus dem Papier):

Abstrakt

Vorgeschlagen wird ein auf Interpolation ausgerichteter adaptiver Downsampling-Algorithmus. für die Bildkodierung mit niedriger Bitrate in diesem Dokument. Bei einem Bild wird der Der vorgeschlagene Algorithmus kann ein Bild mit niedriger Auflösung von .__ erhalten. was ein qualitativ hochwertiges Bild mit der gleichen Auflösung wie die Eingabe ist Bild kann interpoliert werden. Anders als das traditionelle Downsampling-Algorithmen, die unabhängig von der .__ sind. Interpolationsprozess, hängt der vorgeschlagene Downsampling-Algorithmus die Downsampling auf den Interpolationsprozess. Folglich ist der Der vorgeschlagene Downsampling-Algorithmus kann das ursprüngliche .__ beibehalten. Information des Eingabebildes in größtmöglichem Umfang. Das heruntergetastete Das Bild wird dann in JPEG eingespeist. Eine Gesamtvariation (TV) basierend Die Verarbeitung wird dann auf das dekomprimierte Bild mit niedriger Auflösung angewendet. Letztendlich wird das verarbeitete Bild interpoliert, um das .__ beizubehalten. Originalauflösung des Eingabebildes. Experimentelle Ergebnisse bestätigen dass unter Verwendung des heruntergetasteten Bildes durch den vorgeschlagenen Algorithmus ein Es kann ein interpoliertes Bild mit viel höherer Qualität erzielt werden. Außerdem, Der vorgeschlagene Algorithmus kann eine bessere Leistung als .__ erzielen. JPEG für die Bildkodierung mit niedriger Bitrate.

Snapshot from paper

(Siehe den bereitgestellten Link für alle Details, Formeln usw.)

28
user1693593

Wenn Sie nur die Leinwand verwenden möchten, erzielen Sie mit mehreren Schritten das beste Ergebnis. Aber das ist noch nicht gut. Für eine bessere Qualität benötigen Sie eine reine js-Implementierung. Wir haben gerade pica - High-Speed-Downscaler mit variabler Qualität/Geschwindigkeit veröffentlicht. Kurz gesagt, verkleinert es 1280 * 1024px in ~ 0,1s und 5000 * 3000px Bilder in 1s mit höchster Qualität (lanczos-Filter mit 3 Keulen). Pica hat Demo , wo Sie mit Ihren Bildern, Qualitätsstufen spielen und es sogar auf mobilen Geräten ausprobieren können.

Pica hat noch keine unscharfe Maske, aber das wird sehr bald hinzugefügt. Das ist viel einfacher als die Implementierung eines Hochgeschwindigkeits-Faltungsfilters für die Größenänderung.

18
Vitaly

Warum die Leinwand verwenden, um die Größe der Bilder zu ändern? Moderne Browser verwenden alle bikubische Interpolation - den gleichen Prozess, den Photoshop verwendet (wenn Sie es richtig machen) - und sie sind schneller als der Canvas-Prozess. Geben Sie einfach die gewünschte Bildgröße an (verwenden Sie nur eine Dimension, Höhe oder Breite, um die Größe proportional zu ändern).

Dies wird von den meisten Browsern unterstützt, einschließlich späterer IE-Versionen. Frühere Versionen erfordern browserspezifisches CSS .

Eine einfache Funktion (mit jQuery) zum Ändern der Bildgröße würde folgendermaßen aussehen:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

Verwenden Sie dann den zurückgegebenen Wert, um die Größe des Bildes in einer oder beiden Dimensionen zu ändern.

Natürlich gibt es verschiedene Verfeinerungen, die Sie vornehmen könnten, aber dies erledigt die Arbeit.

Fügen Sie den folgenden Code in die Konsole dieser Seite ein und beobachten Sie, was mit den Gravataren geschieht:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});
17
Robusto

Nicht die richtige Antwort für Leute, die das Bild selbst wirklich verkleinern müssen, , aber nur um die Dateigröße zu verringern.

Ich hatte ein Problem mit "direkt von der Kamera" aufgenommenen Bildern, die meine Kunden oft in "unkomprimiertem" JPEG hochgeladen haben. 

Nicht so bekannt ist, dass die Leinwand (in den meisten Browsern 2017) die JPEG-Qualität ändern kann

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

Mit diesem Trick könnte ich 4k x 3k Bilder mit> 10Mb auf 1 oder 2Mb reduzieren, sicher hängt es von Ihren Bedürfnissen ab.

Schau hier

6
halfbit

Dies ist der verbesserte Hermite-Größenänderungsfilter, der einen Arbeiter verwendet, damit das Fenster nicht einfriert.

https://github.com/calvintwr/Hermite-resize

4
Calvintwr

Hier ist ein wiederverwendbarer Angular-Service für die Bildgrößenanpassung von hoher Qualität: https://Gist.github.com/fisch0920/37bac5e741eaec60e983

Der Dienst unterstützt die lanczos-Faltung und das schrittweise Downscaling. Der Faltungsansatz ist von höherer Qualität und kostet langsamer, wohingegen der schrittweise Herunterskalierungsansatz einigermaßen antialiasive Ergebnisse liefert und deutlich schneller ist.

Verwendungsbeispiel:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})
4
fisch2

Ich habe eine Lösung gefunden, die nicht direkt auf die Pixeldaten zugreifen und diese durchlaufen muss, um das Downsampling durchzuführen. Je nach Größe des Bildes kann dies sehr ressourcenintensiv sein, und es ist besser, die internen Algorithmen des Browsers zu verwenden.

Die Funktion drawImage () verwendet eine Methode der linearen Interpolation und der nächstgelegenen Nachbarn. Das funktioniert gut, wenn Sie nicht mehr als die Hälfte der Originalgröße verkleinern}.

Wenn Sie eine Schleife in der Größenordnung von jeweils maximal einer Hälfte erstellen, sind die Ergebnisse ziemlich gut und viel schneller als der Zugriff auf Pixeldaten.

Diese Funktion wird auf die Hälfte heruntergerechnet, bis die gewünschte Größe erreicht ist:

  function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );

Kredite an diesen Beitrag

3
Jesús Carrera

Vielleicht kann man das ausprobieren, was ich in meinem Projekt immer benutze. Auf diese Weise können Sie nicht nur ein qualitativ hochwertiges Bild erhalten, sondern auch jedes andere Element auf Ihrer Leinwand.

/* 
 * @parame canvas => canvas object
 * @parame rate => the pixel quality
 */
function setCanvasSize(canvas, rate) {
    const scaleRate = rate;
    canvas.width = window.innerWidth * scaleRate;
    canvas.height = window.innerHeight * scaleRate;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    canvas.getContext('2d').scale(scaleRate, scaleRate);
}
0
DeCoder

DEMO: Ändern der Größe von Bildern mit JS und HTML Canvas Demo-Fiddler.

Es gibt 3 verschiedene Methoden, um diese Größe zu ändern. So können Sie besser verstehen, wie der Code funktioniert und warum.

https://jsfiddle.net/1b68eLdr/93089/

Den vollständigen Code der Demo- und TypeScript-Methode, die Sie möglicherweise in Ihrem Code verwenden möchten, finden Sie im GitHub-Projekt.

https://github.com/eyalc4/ts-image-resizer

Dies ist der endgültige Code:

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the size by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}
0
Eyal c

anstelle von . 85, wenn wir 1. hinzufügen. Sie erhalten eine genaue Antwort.

data=canvas.toDataURL('image/jpeg', 1.0);

Sie können ein klares und helles Bild erhalten. Bitte prüfe

0
Phoenix