338 Stimmen

Größenänderung eines Bildes in einem HTML5-Canvas

Ich versuche, ein Thumbnail-Bild auf der Client-Seite mit Javascript und ein Canvas-Element zu erstellen, aber wenn ich das Bild zu verkleinern, sieht es schrecklich. Es sieht aus, als ob es in Photoshop mit dem Resampling auf "Nearest Neighbor" anstelle von Bicubic verkleinert wurde. Ich weiß, dass es möglich ist, dies richtig aussehen zu lassen, weil diese Seite kann das auch mit einer Leinwand gut machen. Ich habe versucht, den gleichen Code zu verwenden, wie er im Link "[Quelle]" gezeigt wird, aber es sieht immer noch schrecklich aus. Gibt es etwas, das ich vermisse, eine Einstellung, die festgelegt werden muss oder etwas?

EDIT :

Ich versuche, die Größe eines jpg zu ändern. Ich habe versucht, die Größe desselben jpg auf der verlinkten Website und in Photoshop zu ändern, und es sieht gut aus, wenn es verkleinert wird.

Hier ist der entsprechende Code:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

Anscheinend habe ich mich geirrt, denn die verlinkte Website hat das Bild auch nicht besser verkleinert. Ich habe die anderen vorgeschlagenen Methoden ausprobiert, und keine von ihnen sieht besser aus. So sahen die verschiedenen Methoden aus:

Photoshop:

alt text

Segeltuch:

alt text

Bild mit image-rendering: optimizeQuality eingestellt und mit Breite/Höhe skaliert:

alt text

Bild mit image-rendering: optimizeQuality eingestellt und mit -moz-transform skaliert:

alt text

Größenänderung der Leinwand bei pixastic:

alt text

Ich schätze, das bedeutet, dass Firefox kein bikubisches Sampling verwendet, wie es eigentlich sein sollte. Ich werde einfach warten müssen, bis sie es tatsächlich hinzufügen.

EDIT3:

Originalbild

404voto

syockit Punkte 5661

Also, was tun Sie, wenn alle Browser (eigentlich, Chrome 5 gab mir ganz gut ein) wird nicht geben Sie gut genug Resampling-Qualität? Sie implementieren sie dann selbst! Ach komm, wir betreten das neue Zeitalter von Web 3.0, HTML5-kompatiblen Browsern, super-optimierten JIT-Javascript-Compilern, Multi-Core(†)-Maschinen, mit Tonnen von Speicher, wovor hast du Angst? Hey, da ist das Wort Java in Javascript, also sollte das die Leistung garantieren, oder? Siehe, der Code zur Erzeugung der Miniaturansicht:

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

...mit denen Sie solche Ergebnisse erzielen können!

img717.imageshack.us/img717/8910/lanczos358.png

Wie auch immer, hier ist eine "korrigierte" Version Ihres Beispiels:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

Jetzt ist es an der Zeit, die besten Browser auszuspielen und herauszufinden, welcher davon den Blutdruck Ihres Kunden am wenigsten erhöht!

Hm, wo ist mein Sarkasmus-Tag?

(da viele Teile des Codes auf Anrieff Galerie Generator steht es auch unter der GPL2? Ich weiß es nicht)

Aufgrund von Javascript-Beschränkungen wird Multi-Core nicht unterstützt.

43voto

ViliusL Punkte 4268

Schneller Algorithmus zur Größenänderung/Resampling von Bildern mit Hermite Filter mit JavaScript. Unterstützt Transparenz, gibt gute Qualität. Vorschau:

enter image description here

Update : Version 2.0 auf GitHub hinzugefügt (schneller, Webworker + übertragbare Objekte). Endlich habe ich es zum Laufen gebracht!

Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/

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

27voto

Vitaly Punkte 3122

Versuchen Sie pica - das ist ein hoch optimierter Resizer mit wählbaren Algorithmen. Siehe Demo .

Zum Beispiel wird das Originalbild aus dem ersten Beitrag in 120ms mit Lanczos-Filter und 3px-Fenster oder in 60ms mit Box-Filter und 0,5px-Fenster verkleinert. Für riesige 17mb Bild 5000x3000px Größe dauert ~1s auf dem Desktop und 3s auf dem Handy.

Alle Prinzipien der Größenanpassung wurden in diesem Thread sehr gut beschrieben, und Pica ist keine Raketenwissenschaft. Aber es ist sehr gut für moderne JIT-s optimiert, und ist bereit, out of box (über npm oder bower) zu verwenden. Auch, es verwenden Webworker, wenn verfügbar, um Schnittstelle friert zu vermeiden.

Ich habe auch vor, bald die Unterstützung für Unschärfemasken hinzuzufügen, da diese nach der Verkleinerung sehr nützlich sind.

15voto

cesarsalazar Punkte 698

Ich weiß, dies ist ein altes Thema, aber es könnte für einige Leute wie mich nützlich sein, die Monate später zum ersten Mal auf dieses Problem stoßen.

Hier ist ein Code, der die Größe des Bildes jedes Mal ändert, wenn Sie das Bild neu laden. Ich bin mir bewusst, dass dies nicht optimal ist, aber ich biete es als Beweis für das Konzept.

Auch, sorry für die Verwendung von jQuery für einfache Selektoren, aber ich fühle mich einfach zu bequem mit der Syntax.

$(document).on('ready', createImage);
$(window).on('resize', createImage);

var createImage = function(){
  var canvas = document.getElementById('myCanvas');
  canvas.width = window.innerWidth || $(window).width();
  canvas.height = window.innerHeight || $(window).height();
  var ctx = canvas.getContext('2d');
  img = new Image();
  img.addEventListener('load', function () {
    ctx.drawImage(this, 0, 0, w, h);
  });
  img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};

html, body{
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
canvas{
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Canvas Resize</title>
  </head>
  <body>
    <canvas id="myCanvas"></canvas>
  </body>
</html>

Meine createImage-Funktion wird einmal aufgerufen, wenn das Dokument geladen wird, und danach wird sie jedes Mal aufgerufen, wenn das Fenster ein Größenänderungsereignis erhält.

Ich habe es in Chrome 6 und Firefox 3.6 getestet, beide auf dem Mac. Diese "Technik" frisst Prozessor, als ob es Eiscreme im Sommer wäre, aber es tut den Trick.

10voto

Daniel Punkte 7397

Ich habe einige Algorithmen für die Bildinterpolation auf HTML-Canvas-Pixelarrays erstellt, die hier nützlich sein könnten:

https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

Diese können kopiert/eingefügt werden und können innerhalb von Web Workern verwendet werden, um die Größe von Bildern zu ändern (oder jede andere Operation, die Interpolation erfordert - ich verwende sie im Moment, um Bilder zu verzerren).

Ich habe die Lanczos-Sachen oben nicht hinzugefügt, Sie können sie also gerne als Vergleich hinzufügen, wenn Sie möchten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X