Ändern der Größe eines Bildes in einer HTML5-Zeichenfläche

314

Ich versuche, auf der Clientseite ein Miniaturbild mit Javascript und einem Canvas-Element zu erstellen, aber wenn ich das Bild verkleinere, sieht es schrecklich aus. Es sieht so aus, als wäre es in Photoshop verkleinert worden, wobei das Resampling auf "Nearest Neighbor" anstelle von Bicubic eingestellt war. Ich weiß, dass es möglich ist, dass dies richtig aussieht, da diese Seite es auch mit einer Leinwand gut machen kann. Ich habe versucht, denselben Code wie im Link "[Quelle]" zu verwenden, aber es sieht immer noch schrecklich aus. Fehlt mir etwas, eine Einstellung, die eingestellt werden muss, oder so?

BEARBEITEN:

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

Hier ist der relevante 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:

Ich habe mich wohl geirrt, die verlinkte Website hat es nicht besser gemacht, das Bild zu verkleinern. Ich habe die anderen vorgeschlagenen Methoden ausprobiert und keine davon sieht besser aus. Dies führte zu den verschiedenen Methoden:

Photoshop:

Alt-Text

Segeltuch:

Alt-Text

Bild mit Bildwiedergabe: OptimizeQuality festgelegt und skaliert mit Breite / Höhe:

Alt-Text

Bild mit Bildwiedergabe: OptimizeQuality festgelegt und skaliert mit -moz-transform:

Alt-Text

Leinwandgröße auf Pixastic ändern:

Alt-Text

Ich denke, dies bedeutet, dass Firefox keine bikubischen Proben verwendet, wie es soll. Ich muss nur warten, bis sie es tatsächlich hinzufügen.

EDIT3:

Original Bild

Telanor
quelle
Können Sie den Code veröffentlichen, mit dem Sie die Bildgröße ändern?
Xavi
Versuchen Sie, die Größe eines GIF-Bilds oder eines ähnlichen Bilds mit einer begrenzten Palette zu ändern? Selbst in Photoshop werden diese Bilder nur dann gut verkleinert, wenn Sie sie in RGB konvertieren.
Leepowers
Können Sie eine Kopie des Originalbildes veröffentlichen?
Xavi
Das Ändern der Bildgröße mithilfe von Javascript ist ein wenig umständlich. Sie verwenden nicht nur die Client-Verarbeitungsleistung, um die Bildgröße zu ändern, sondern tun dies auch bei jedem einzelnen Seitenaufruf. Warum nicht einfach eine verkleinerte Version aus Photoshop speichern und stattdessen / zusammen mit dem Originalbild bereitstellen?
definiert
29
Weil ich einen Bild-Uploader mit der Fähigkeit erstelle, die Größe der Bilder zu ändern und sie vor dem Hochladen zuzuschneiden.
Telanor

Antworten:

393

Was tun Sie also, wenn alle Browser (Chrome 5 hat mir einen recht guten gegeben) Ihnen keine ausreichend gute Resampling-Qualität bieten? Sie implementieren sie dann selbst! Ach komm schon, wir betreten das neue Zeitalter von Web 3.0, HTML5-kompatiblen Browsern, superoptimierten JIT-Javascript-Compilern, Multi-Core-Computern (†) und viel Speicher. Wovor haben Sie Angst? Hey, es gibt das Wort Java in Javascript, das sollte also die Leistung garantieren, oder? Siehe, das Miniaturbild generiert Code:

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

Hier ist eine 'feste' 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 Zeit, Ihre besten Browser da draußen zu platzieren und herauszufinden, welcher den Blutdruck Ihres Kunden am wenigsten erhöht!

Ähm, wo ist mein Sarkasmus-Tag?

(Da viele Teile des Codes auf dem Anrieff Gallery Generator basieren, wird er auch unter GPL2 behandelt? Ich weiß nicht)

Aufgrund der Einschränkung von Javascript wird Multi-Core nicht unterstützt.

Syockit
quelle
2
Ich hatte tatsächlich versucht, es selbst zu implementieren, indem ich Code aus einem Open-Source-Bildeditor kopierte. Da ich keine solide Dokumentation zum Algorithmus finden konnte, fiel es mir schwer, ihn zu optimieren. Am Ende war meine etwas langsam (es dauerte einige Sekunden, bis die Größe des Bildes geändert wurde). Wenn ich die Chance habe, werde ich deine ausprobieren und sehen, ob es schneller geht. Und ich denke, Webworker ermöglichen jetzt Multi-Core-Javascript. Ich wollte versuchen, sie zu verwenden, um es zu beschleunigen, aber ich hatte Probleme herauszufinden, wie ich daraus einen Multithread-Algorithmus machen kann
Telanor
3
Entschuldigung, das habe ich vergessen! Ich habe die Antwort bearbeitet. Es wird sowieso nicht schnell sein, Bikubic sollte schneller sein. Ganz zu schweigen davon, dass der von mir verwendete Algorithmus nicht die übliche 2-Wege-Größenänderung ist (zeilenweise, horizontal und vertikal), daher ist er langsamer.
Syockit
5
Du bist großartig und verdienst jede Menge Ehrfurcht.
Rocklan
5
Dies führt zu anständigen Ergebnissen, dauert jedoch 7,4 Sekunden für ein 1,8-MP-Bild in der neuesten Version von Chrome ...
mpen
2
Wie schaffen solche Methoden eine so hohe Punktzahl? Die gezeigte Lösung berücksichtigt die logarithmische Skala, die zum Speichern von Farbinformationen verwendet wird, nicht vollständig. Ein RGB von 127.127.127 ist ein Viertel der Helligkeit von 255, 255, 255 und nicht die Hälfte. Die Abwärtsabtastung in der Lösung führt zu einem abgedunkelten Bild. Schade, dass dies geschlossen ist, da es eine sehr einfache und schnelle Methode zum Verkleinern gibt, die noch bessere Ergebnisse liefert als das Beispiel in Photoshop (OP muss die Einstellungen falsch eingestellt haben)
Blindman67
37

Schneller Algorithmus zur Größenänderung / Neuabtastung von Bildern mithilfe des Hermite- Filters mit JavaScript. Unterstützung Transparenz, gibt gute Qualität. Vorschau:

Geben Sie hier die Bildbeschreibung ein

Update : Version 2.0 auf GitHub hinzugefügt (schneller, Web Worker + übertragbare Objekte). Endlich habe ich es geschafft!

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);
}
ViliusL
quelle
Vielleicht können Sie Links zu Ihrer miniPaint-Demo und Ihrem Github-Repo hinzufügen?
Syockit
1
Werden Sie auch die Webworker-Version teilen? Wahrscheinlich aufgrund des Einrichtungsaufwands ist es für kleine Bilder langsamer, aber es könnte für größere Quellbilder nützlich sein.
Syockit
Demo, Git-Links, auch Multi-Core-Version hinzugefügt. Übrigens habe ich nicht zu viel Zeit mit der Optimierung der Multicore-Version verbracht ... Die Single-Version ist meiner Meinung nach gut optimiert.
ViliusL
Großer Unterschied und anständige Leistung. Vielen Dank! vorher und nachher
KevBurnsJr
2
@ViliusL Ah, jetzt erinnerte ich mich, warum Web-Worker nicht so gut funktionierten. Sie hatten vorher keinen gemeinsamen Speicher und haben ihn jetzt noch nicht! Vielleicht wird eines Tages, wenn sie es schaffen, es zu klären, Ihr Code verwendet (das, oder vielleicht verwenden die Leute stattdessen PNaCl)
syockit
26

Probieren Sie pica aus - das ist ein hochoptimierter Resizer mit wählbaren Algorithmen. Siehe Demo .

Beispielsweise wird die Größe des Originalbilds aus dem ersten Beitrag in 120 ms mit Lanczos-Filter und 3px-Fenster oder in 60 ms mit Box-Filter und 0,5px-Fenster geändert. Für ein riesiges 17-MB-Bild dauert die Größenänderung von 5000 x 3000 Pixel ca. 1 Sekunde auf dem Desktop und 3 Sekunden auf dem Handy.

Alle Größenänderungsprinzipien wurden in diesem Thread sehr gut beschrieben, und Pica fügt keine Raketenwissenschaft hinzu. Aber es ist sehr gut für moderne JITs optimiert und sofort einsatzbereit (über npm oder bower). Außerdem werden Webworker verwendet, sofern verfügbar, um ein Einfrieren der Benutzeroberfläche zu vermeiden.

Ich habe auch vor, bald Unterstützung für unscharfe Masken hinzuzufügen, da dies nach dem Verkleinern sehr nützlich ist.

Vitaly
quelle
14

Ich weiß, dass dies ein alter Thread ist, 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 überhaupt nicht optimal ist, aber ich biete es als Proof of Concept an.

Es tut mir auch leid, dass ich jQuery für einfache Selektoren verwendet habe, aber ich fühle mich mit der Syntax einfach zu wohl.

$(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 Funktion createImage wird beim Laden des Dokuments einmal aufgerufen und danach jedes Mal aufgerufen, wenn das Fenster ein Größenänderungsereignis empfängt.

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

cesarsalazar
quelle
9

Ich habe einige Algorithmen für die Bildinterpolation auf HTML-Canvas-Pixel-Arrays entwickelt, 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 und innerhalb von Web-Workern verwendet werden, um die Größe von Bildern zu ändern (oder jede andere Operation, die eine Interpolation erfordert - ich verwende sie derzeit, um Bilder zu entschärfen).

Ich habe das lanczos-Zeug oben nicht hinzugefügt, also zögern Sie nicht, es als Vergleich hinzuzufügen, wenn Sie möchten.

Daniel
quelle
6

Dies ist eine Javascript-Funktion, die aus dem Code von @ Telanor angepasst wurde. Wenn Sie ein Image base64 als erstes Argument an die Funktion übergeben, wird das base64 des verkleinerten Images zurückgegeben. maxWidth und maxHeight sind optional.

function thumbnail(base64, maxWidth, maxHeight) {

  // Max size for thumbnail
  if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
  if(typeof(maxHeight) === 'undefined') var maxHeight = 500;

  // Create and initialize two canvas
  var canvas = document.createElement("canvas");
  var ctx = canvas.getContext("2d");
  var canvasCopy = document.createElement("canvas");
  var copyContext = canvasCopy.getContext("2d");

  // Create original image
  var img = new Image();
  img.src = base64;

  // Determine new ratio based on max size
  var ratio = 1;
  if(img.width > maxWidth)
    ratio = maxWidth / img.width;
  else if(img.height > maxHeight)
    ratio = maxHeight / img.height;

  // Draw original image in second canvas
  canvasCopy.width = img.width;
  canvasCopy.height = img.height;
  copyContext.drawImage(img, 0, 0);

  // Copy and resize second canvas to first canvas
  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);

  return canvas.toDataURL();

}
Christophe Marois
quelle
Ihr Ansatz ist sehr schnell, aber es erzeugt ein unscharfes Bild, wie Sie hier sehen können: stackoverflow.com/questions/18922880/…
confile
6

Ich würde Ihnen dringend empfehlen, diesen Link zu überprüfen und sicherzustellen, dass er auf true gesetzt ist.

Steuern des Bildskalierungsverhaltens

Eingeführt in Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 hat die Eigenschaft mozImageSmoothingEnabled in das Canvas-Element eingeführt. Wenn dieser boolesche Wert falsch ist, werden Bilder beim Skalieren nicht geglättet. Diese Eigenschaft ist standardmäßig wahr. Klartext anzeigen?

  1. cx.mozImageSmoothingEnabled = false;
Evan Carroll
quelle
5

Wenn Sie einfach nur du versuchen , ein Bild , um die Größe, würde ich Einstellung empfehlen widthund heightdes Bildes mit CSS. Hier ist ein kurzes Beispiel:

.small-image {
    width: 100px;
    height: 100px;
}

Beachten Sie, dass das heightund widthauch mit JavaScript eingestellt werden kann. Hier ist ein kurzes Codebeispiel:

var img = document.getElement("my-image");
img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE

Fügen Sie der Bildauswahl außerdem die folgenden CSS-Regeln hinzu, um sicherzustellen, dass das Bild mit geänderter Größe gut aussieht:

Soweit ich das beurteilen kann, verwenden alle Browser außer dem IE standardmäßig einen bikubischen Algorithmus, um die Größe von Bildern zu ändern, sodass die Größe Ihrer Bilder in Firefox und Chrome gut aussehen sollte.

Wenn das CSS eingestellt ist widthund heightnicht funktioniert, möchten Sie möglicherweise mit einem CSS spielen transform:

Wenn aus irgendeinem Grund Sie brauchen eine Leinwand zu verwenden, beachten Sie bitte , dass es zwei Möglichkeiten gibt ein Bild Resize sein: durch die Leinwand mit CSS Ändern der Größe oder durch die Bildzeichnungs bei einer kleineren Größe.

Weitere Informationen finden Sie in dieser Frage .

Xavi
quelle
5
Weder die Größenänderung der Leinwand noch das Zeichnen des Bildes in einer kleineren Größe lösen das Problem (in Chrome) leider.
Nestor
1
Chrome 27 erzeugt ein schönes Bild in der Größe, aber Sie können das Ergebnis nicht auf eine Leinwand kopieren. Wenn Sie dies versuchen, wird stattdessen das Originalbild kopiert.
Syockit
4

Für die Größenänderung auf ein Bild mit einer Breite, die kleiner als das Original ist, verwende ich:

    function resize2(i) {
      var cc = document.createElement("canvas");
      cc.width = i.width / 2;
      cc.height = i.height / 2;
      var ctx = cc.getContext("2d");
      ctx.drawImage(i, 0, 0, cc.width, cc.height);
      return cc;
    }
    var cc = img;
    while (cc.width > 64 * 2) {
      cc = resize2(cc);
    }
    // .. than drawImage(cc, .... )

und es funktioniert =).

Yaffle
quelle
4

Ich habe dieses Bild erhalten, indem ich mit der rechten Maustaste auf das Canvas-Element in Firefox geklickt und unter gespeichert habe.

Alt-Text

var img = new Image();
img.onload = function () {
    console.debug(this.width,this.height);
    var canvas = document.createElement('canvas'), ctx;
    canvas.width = 188;
    canvas.height = 150;
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';

Hier ist eine 'feste' Version Ihres Beispiels:

var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);

var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body

document.body.appendChild(canvasCopy);

var copyContext = canvasCopy.getContext("2d");

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

        // defining cause it wasnt
        var maxWidth = 188,
            maxHeight = 150;

        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;
        // the line to change
        // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        // the method signature you are using is for slicing
        ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};

// changed for example
img.src = 'original.jpg';
Robert
quelle
Ich habe versucht, das zu tun, was du getan hast, und es kommt nicht so gut heraus wie deins. Wenn ich nichts verpasst habe, haben Sie nur die Skalierungsmethoden-Signatur anstelle der Slicing-Signatur verwendet, oder? Aus irgendeinem Grund funktioniert es bei mir nicht.
Telanor
3

Das Problem bei einigen dieser Lösungen besteht darin, dass sie direkt auf die Pixeldaten zugreifen und diese durchlaufen, um das Downsampling durchzuführen. Abhängig von der 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 Resampling-Methode mit linearer Interpolation und nächstem Nachbarn. Das funktioniert gut, wenn Sie nicht mehr als die Hälfte der Originalgröße verkleinern .

Wenn Sie eine Schleife ausführen, um jeweils nur die Hälfte der Größe zu ändern, sind die Ergebnisse recht gut und viel schneller als der Zugriff auf Pixeldaten.

Diese Funktion wird jeweils auf die Hälfte heruntergesampelt, 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] );

Credits zu diesem Beitrag

Jesús Carrera
quelle
2

Also etwas Interessantes, das ich vor einiger Zeit bei der Arbeit mit Leinwand gefunden habe und das hilfreich sein könnte:

Um die Größe des Canvas-Steuerelements selbst zu ändern, müssen Sie die Attribute height=""und width=""(oder canvas.width/) verwendencanvas.height elements) verwenden. Wenn Sie CSS verwenden, um die Größe der Leinwand zu ändern, wird der Inhalt der Leinwand tatsächlich so gedehnt (dh verkleinert), dass er zur gesamten Leinwand passt (anstatt einfach den Bereich der Leinwand zu vergrößern oder zu verkleinern).

Es lohnt sich, das Bild in ein Canvas-Steuerelement zu zeichnen, dessen Höhen- und Breitenattribute auf die Größe des Bildes eingestellt sind, und dann die Größe der Leinwand mithilfe von CSS auf die gewünschte Größe zu ändern. Möglicherweise würde dies einen anderen Größenänderungsalgorithmus verwenden.

Es sollte auch beachtet werden, dass Canvas in verschiedenen Browsern (und sogar in verschiedenen Versionen verschiedener Browser) unterschiedliche Effekte hat. Die in den Browsern verwendeten Algorithmen und Techniken werden sich wahrscheinlich im Laufe der Zeit ändern (insbesondere wenn Firefox 4 und Chrome 6 so bald herauskommen, was einen starken Schwerpunkt auf die Leistung beim Rendern von Zeichenflächen legt).

Darüber hinaus möchten Sie möglicherweise auch SVG ausprobieren, da wahrscheinlich auch ein anderer Algorithmus verwendet wird.

Viel Glück!

Mattbasta
quelle
1
Wenn Sie die Breite oder Höhe einer Zeichenfläche über die HTML-Attribute festlegen, wird die Zeichenfläche gelöscht, sodass mit dieser Methode keine Größenänderung vorgenommen werden kann. SVG ist auch für den Umgang mit mathematischen Bildern gedacht. Ich muss in der Lage sein, PNGs und dergleichen zu zeichnen, damit mir das da draußen nicht hilft.
Telanor
Das Festlegen der Höhe und Breite der Leinwand und das Ändern der Größe mithilfe von CSS hilft nicht, wie ich festgestellt habe (in Chrome). Selbst wenn Sie die Größe mit -webkit-transform anstelle von CSS width / height ändern, wird die Interpolation nicht in Gang gebracht.
Nestor
2

Ich habe das Gefühl, dass das Modul, das ich geschrieben habe, ähnliche Ergebnisse wie Photoshop liefert, da es Farbdaten bewahrt, indem es sie mittelt und keinen Algorithmus anwendet. Es ist etwas langsam, aber für mich ist es das Beste, weil alle Farbdaten erhalten bleiben.

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

Es wird nicht der nächste Nachbar genommen und keine anderen Pixel gelöscht oder eine Gruppe abgetastet und ein zufälliger Durchschnitt ermittelt. Es wird genau das Verhältnis benötigt, das jedes Quellpixel in das Zielpixel ausgeben soll. Die durchschnittliche Pixelfarbe in der Quelle ist die durchschnittliche Pixelfarbe im Ziel, was diese anderen Formeln meiner Meinung nach nicht sein werden.

Ein Beispiel für die Verwendung finden Sie unten unter https://github.com/danschumann/limby-resize

UPDATE OKT 2018 : Heutzutage ist mein Beispiel akademischer als alles andere. Webgl ist fast zu 100%, daher ist es besser, die Größe zu ändern, um ähnliche Ergebnisse zu erzielen, jedoch schneller. PICA.js macht das, glaube ich. - -

Funkodebat
quelle
1

Ich habe die Antwort von @ syockit sowie den Step-Down-Ansatz in einen wiederverwendbaren Angular-Service für alle Interessierten umgewandelt: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Ich habe beide Lösungen aufgenommen, weil beide ihre eigenen Vor- und Nachteile haben. Der Lanczos-Faltungsansatz bietet eine höhere Qualität auf Kosten der Langsamkeit, während der schrittweise Downscaling-Ansatz einigermaßen antialiasierte Ergebnisse liefert und erheblich schneller ist.

Anwendungsbeispiel:

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
    })
})
fisch2
quelle
1

Schneller und einfacher Javascript Image Resizer:

https://github.com/calvintwr/blitz-hermite-resize

const blitz = Blitz.create()

/* Promise */
blitz({
    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
    width: 400,
    height: 600
}).then(output => {
    // handle output
})catch(error => {
    // handle error
})

/* Await */
let resized = await blizt({...})

/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
    // run your callback.
})

Geschichte

Dies ist wirklich nach vielen Runden des Forschens, Lesens und Versuchens.

Der Resizer-Algorithmus verwendet das Hermite-Skript von @ ViliusL (Hermite-Resizer ist wirklich der schnellste und liefert eine einigermaßen gute Ausgabe). Erweitert mit den Funktionen, die Sie benötigen.

Fordert einen Mitarbeiter auf, die Größenänderung vorzunehmen, damit Ihr Browser beim Ändern der Größe nicht einfriert, im Gegensatz zu allen anderen JS-Größenänderungen.

Calvintwr
quelle
0

Danke @syockit für eine tolle Antwort. Ich musste jedoch ein wenig wie folgt neu formatieren, damit es funktioniert. Möglicherweise aufgrund von DOM-Scanproblemen:

$(document).ready(function () {

$('img').on("load", clickA);
function clickA() {
    var img = this;
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 50, 3);
    document.body.appendChild(canvas);
}

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(process1, 0, this, 0);
}

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

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(process1, 0, self, u);
    else
        setTimeout(process2, 0, self);
};

process2 = function (self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0);
    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";
}
});
Manish
quelle
-1

Ich habe gerade eine Seite mit Nebeneinander-Vergleichen erstellt, und wenn sich in letzter Zeit nichts geändert hat, konnte ich keine bessere Verkleinerung (Skalierung) mit Canvas im Vergleich zu einfachem CSS feststellen. Ich habe in FF6 Mac OSX 10.7 getestet. Immer noch leicht weich im Vergleich zum Original.

Ich bin jedoch auf etwas gestoßen, das einen großen Unterschied gemacht hat und das Bildfilter in Browsern verwendet, die Canvas unterstützen. Sie können Bilder ähnlich wie in Photoshop mit Unschärfe, Schärfe, Sättigung, Welligkeit, Graustufen usw. bearbeiten.

Ich habe dann ein fantastisches jQuery-Plug-In gefunden, das die Anwendung dieser Filter zum Kinderspiel macht: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Ich wende einfach den Schärfefilter direkt nach dem Ändern der Bildgröße an, um den gewünschten Effekt zu erzielen. Ich musste nicht einmal ein Canvas-Element verwenden.

Julian Dormon
quelle
-1

Suchen Sie nach einer anderen großartigen einfachen Lösung?

var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);

Diese Lösung verwendet den Größenänderungsalgorithmus des Browsers! :) :)

ale500
quelle
Bei der Frage geht es darum, das Bild herunterzusampeln und nicht nur die Größe zu ändern.
Jesús Carrera
[...] Ich versuche, die Größe eines JPGs zu ändern. Ich habe versucht, die Größe desselben JPGs auf der verlinkten Website und in Photoshop zu ändern, und es sieht gut aus, wenn es verkleinert wird. [...] Warum können Sie das <img> Jesus Carrera nicht verwenden?
Ale500