Ändern Sie die Bildgröße mit Javascript-Leinwand (reibungslos)


88

Ich versuche, die Größe einiger Bilder mit Leinwand zu ändern, aber ich weiß nicht, wie ich sie glätten soll. In Photoshop, Browsern usw. werden einige Algorithmen verwendet (z. B. bikubisch, bilinear), aber ich weiß nicht, ob diese in Canvas integriert sind oder nicht.

Hier ist meine Geige: http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

Das erste ist ein normal verkleinertes Bild-Tag und das zweite ist Leinwand. Beachten Sie, dass die Leinwand nicht so glatt ist. Wie kann ich "Glätte" erreichen?

Antworten:


134

Sie können das Heruntertreten verwenden, um bessere Ergebnisse zu erzielen. Die meisten Browser scheinen beim Ändern der Bildgröße eher eine lineare Interpolation als eine bikubische zu verwenden.

( Update Den Spezifikationen wurde eine Qualitätseigenschaft hinzugefügt, imageSmoothingQualitydie derzeit nur in Chrome verfügbar ist.)

Sofern keine Glättung oder kein nächster Nachbar gewählt wird, interpoliert der Browser das Bild nach dem Herunterskalieren immer, da dies als Tiefpassfilter fungiert, um Aliasing zu vermeiden.

Bi-Linear verwendet 2x2 Pixel für die Interpolation, während Bi-Cubic 4x4 verwendet. Wenn Sie dies in Schritten tun, können Sie dem Bi-Cubic-Ergebnis nahe kommen, während Sie die Bi-Linear-Interpolation verwenden, wie in den resultierenden Bildern gezeigt.

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d');

    oc.width = img.width * 0.5;
    oc.height = img.height * 0.5;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

Abhängig davon, wie drastisch Ihre Größenänderung ist, können Sie Schritt 2 überspringen, wenn der Unterschied geringer ist.

In der Demo können Sie sehen, dass das neue Ergebnis jetzt dem Bildelement sehr ähnlich ist.


1
@steve heh, manchmal passieren diese Dinge :) Für Bilder können Sie dies normalerweise überschreiben, indem Sie ein CSS BTW einstellen.

Ken, das erste Ergebnis hat großartig funktioniert, aber wenn ich die Bilder ändere, kann man sehen, dass es zu verschwommen ist. Jsfiddle.net/kcHLG Was kann in diesem und anderen Fällen getan werden?
Steve

@steve Sie können die Anzahl der Schritte auf nur 1 oder keine reduzieren (für einige Bilder funktioniert dies einwandfrei). Siehe auch diese Antwort, die dieser ähnelt, aber hier habe ich eine Schärfungsfaltung hinzugefügt, damit Sie das resultierende Bild schärfer machen können, nachdem es verkleinert wurde.

1
@steve hier ist eine modifizierte Geige mit Bill mit nur einem zusätzlichen Schritt: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic Der Code ist eine Fortsetzung des OP-Codes. Wenn Sie die Geige öffnen, wird ctx definiert. Ich habe es hier eingefügt, um Missverständnisse zu vermeiden.

26

Da die Geige von Trung Le Nguyen Nhat überhaupt nicht korrekt ist (sie verwendet nur das Originalbild im letzten Schritt), habe
ich meine eigene allgemeine Geige mit Leistungsvergleich geschrieben:

GEIGE

Im Grunde ist es:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

Die am meisten unterschätzte Antwort, die jemals gesehen wurde.
Amsakanna

17

Ich habe einen wiederverwendbaren Angular-Dienst erstellt, um die Größe von Bildern / Leinwänden in hoher Qualität für alle Interessierten zu ändern : https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Der Service umfasst zwei Lösungen, da 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
    })
})

Entschuldigung - ich hatte meinen Github-Benutzernamen geändert. Gerade aktualisiert den Link gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
Ich sah das Wort eckig, ich hatte das komische Gefühl
SuperUberDuper

8

Während einige dieser Code-Schnipsel kurz sind und funktionieren, sind sie nicht trivial zu befolgen und zu verstehen.

Da ich kein Fan von "Kopieren und Einfügen" aus dem Stapelüberlauf bin, möchte ich, dass Entwickler den Code verstehen, den sie in ihre Software übertragen. Ich hoffe, Sie finden das Folgende nützlich.

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

Möglicherweise finden Sie drei verschiedene Methoden, um die Größe zu ändern. Diese helfen Ihnen zu verstehen, wie der Code funktioniert und warum.

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

Der vollständige Code der Demo und der 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 dize 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
        }
    };
}}

4

Ich habe eine Bibliothek erstellt, mit der Sie jeden Prozentsatz reduzieren können, während alle Farbdaten erhalten bleiben.

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

Diese Datei können Sie in den Browser aufnehmen. Die Ergebnisse sehen aus wie Photoshop oder Bildmagie, wobei alle Farbdaten erhalten bleiben und Pixel gemittelt werden, anstatt in der Nähe befindliche zu nehmen und andere zu löschen. Es wird keine Formel verwendet, um die Durchschnittswerte zu erraten, sondern der genaue Durchschnitt.


Ich würde wahrscheinlich webgl verwenden, um die Größe jetzt zu ändern
Funkodebat

4

Basierend auf der K3N-Antwort schreibe ich Code generell für jeden neu

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

UPDATE JSFIDDLE DEMO

Hier ist meine ONLINE-DEMO


2
Dies funktioniert nicht: Jedes Mal, wenn Sie die Größe der Zeichenfläche ändern, wird der Kontext gelöscht. Sie benötigen 2 Leinwände. Hier ist es genauso, als würde man drawImage mit den endgültigen Dimensionen direkt aufrufen.
Kaiido

2

Ich verstehe nicht, warum niemand vorschlägt createImageBitmap.

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

funktioniert wunderbar (vorausgesetzt, Sie legen IDs für Bild und Leinwand fest).


Weil es nicht weit verbreitet ist caniuse.com/#search=createImageBitmap
Matt

createImageBitmap wird von 73% aller Benutzer unterstützt. Abhängig von Ihrem Anwendungsfall kann es gut genug sein. Es ist nur Safari, die sich weigert, Unterstützung dafür hinzuzufügen. Ich denke, es ist erwähnenswert als mögliche Lösung.
cagdas_ucar

1

Ich habe ein kleines js-Dienstprogramm geschrieben, um das Bild im Frontend zuzuschneiden und seine Größe zu ändern. Hier ist der Link zum GitHub-Projekt. Sie können auch Blob vom endgültigen Bild erhalten, um es zu senden.

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.