[javascript] HTML5 Canvas Resize (Downscale) Bild Hohe Qualität?


4 Answers

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

Update: Version 2.0 (schneller, Web Worker + ü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);
}
Question

Ich benutze html5 canvas Elemente, um Bilder in meinem Browser zu skalieren. Es stellt sich heraus, dass die Qualität sehr niedrig ist. Ich habe Folgendes gefunden: Deaktivieren Sie die Interpolation, wenn Sie eine <Leinwand> skalieren, aber das erhöht nicht die Qualität.

Unten ist mein CSS- und JS-Code sowie das mit Photoshop skalierte und in der Canvas-API skalierte Bild.

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

Hinweis: Ich möchte ein großes Bild auf ein kleines Bild verkleinern, die Farbe in einem Zeichenbereich ändern und das Ergebnis vom Zeichenbereich 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 skaliert:

Das Bild wurde auf der Leinwand skaliert:

Bearbeiten:

Ich habe versucht, das Downscaling in mehr als einer Stufe durchzuführen, wie es vorgeschlagen wurde in:

Ändern der Größe eines Bildes in einem HTML5- Zeichenbereich und einem Html5-Zeichenbereich drawImage: Anwenden von Antialiasing

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-Step-Down-Sizing verwende:

Hier ist das Ergebnis, wenn ich ein 3-Step-Down-Sizing verwende:

Hier ist das Ergebnis, wenn ich ein 4-Step-Down-Sizing verwende:

Hier ist das Ergebnis, wenn ich eine 20-Step-Down-Sizing verwende:

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

Gibt es eine Möglichkeit, das Problem zu lösen, dass das Bild unscharfer wird, je mehr Schritte du hinzufügst?

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

FotoShop Bild:

Algorithmus von GameAlchemist:




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

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

Nicht so bekannt ist, dass die Leinwand (in den meisten Browsern 2017) die Qualität von JPEG verändert

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

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

Schau hier




Hier ist ein wiederverwendbarer Angular-Service für qualitativ hochwertige Bild / Leinwand-Größenanpassung: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Der Dienst unterstützt Lanczos-Convolution und schrittweises Downscaling. Der Faltungsansatz ist eine höhere Qualität auf Kosten dessen, dass er langsamer ist, während der schrittweise Herunterskalierungsansatz relativ antialiasierte Ergebnisse erzeugt und wesentlich schneller ist.

Beispielverwendung:

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



context.scale(xScale, yScale)

<canvas id="c"></canvas>
<hr/>
<img id="i" />

<script>
var i = document.getElementById('i');

i.onload = function(){
    var width = this.naturalWidth,
        height = this.naturalHeight,
        canvas = document.getElementById('c'),
        ctx = canvas.getContext('2d');

    canvas.width = Math.floor(width / 2);
    canvas.height = Math.floor(height / 2);

    ctx.scale(0.5, 0.5);
    ctx.drawImage(this, 0, 0);
    ctx.rect(0,0,500,500);
    ctx.stroke();

    // restore original 1x1 scale
    ctx.scale(2, 2);
    ctx.rect(0,0,500,500);
    ctx.stroke();
};

i.src = 'https://static.md/b70a511140758c63f07b618da5137b5d.png';
</script>



Warum verwenden Sie die Leinwand, um die Größe von Bildern zu ändern? Moderne Browser verwenden alle eine bikubische Interpolation - den gleichen Prozess, den Photoshop verwendet (wenn Sie es richtig machen) - und das 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 einschließlich der späteren Versionen von IE unterstützt. Frühere Versionen benötigen möglicherweise browserspezifisches CSS .

Eine einfache Funktion (mit jQuery), um die Größe eines Bildes zu ändern, wäre wie folgt:

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

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

BEARBEITEN Geändert image zu img , um Funktion Args zu entsprechen. ^) ^

Verwenden Sie dann den zurückgegebenen Wert, um das Bild in einer oder beiden Dimensionen zu skalieren.

Offensichtlich gibt es verschiedene Verfeinerungen, die Sie machen könnten, aber das erledigt die Arbeit.

NACHTRAG

Füge den folgenden Code in die Konsole dieser Seite ein und beobachte, was mit den Gravataren passiert:

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



Related