Ridimensiona l'immagine con javascript canvas (senza problemi)


91

Sto cercando di ridimensionare alcune immagini con la tela ma non ho idea di come levigarle. Su Photoshop, browser ecc. Ci sono alcuni algoritmi che usano (es. Bicubico, bilineare) ma non so se questi siano incorporati nella tela o meno.

Ecco il mio violino: 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);

Il primo è un normale tag di immagine ridimensionato e il secondo è canvas. Nota come quello della tela non è così liscio. Come posso ottenere la "levigatezza"?

Risposte:


136

Puoi utilizzare lo step down per ottenere risultati migliori. La maggior parte dei browser sembra utilizzare l'interpolazione lineare anziché il bi-cubico durante il ridimensionamento delle immagini.

( Aggiorna È stata aggiunta una proprietà di qualità alle specifiche, imageSmoothingQualityche è attualmente disponibile solo in Chrome.)

A meno che non si scelga nessun livellamento o il vicino più vicino, il browser interpolerà sempre l'immagine dopo averla ridotta in quanto questa funzione come un filtro passa-basso per evitare l'aliasing.

Bi-lineare utilizza 2x2 pixel per eseguire l'interpolazione mentre bi-cubico utilizza 4x4, quindi eseguendolo in passaggi è possibile avvicinarsi al risultato bi-cubico mentre si utilizza l'interpolazione bi-lineare come si vede nelle immagini risultanti.

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>

A seconda di quanto sia drastico il tuo ridimensionamento, puoi saltare il passaggio 2 se la differenza è inferiore.

Nella demo puoi vedere il nuovo risultato ora molto simile all'elemento immagine.


1
@steve heh, a volte succedono queste cose :) Per le immagini di solito puoi sovrascriverlo impostando un css BTW.

Ken, il primo risultato ha funzionato alla grande ma quando cambio le immagini, puoi vedere che è troppo sfocato jsfiddle.net/kcHLG Cosa si può fare in questo caso e in altri?
steve

@steve puoi ridurre il numero di passaggi a 1 o nessuno (per alcune immagini funziona bene). Vedi anche questa risposta che è simile a questa, ma qui ho aggiunto una convoluzione di nitidezza in modo da poter rendere l'immagine risultante più nitida dopo che è stata ridimensionata.

1
@steve ecco un violino modificato con Bill che utilizza solo un passaggio aggiuntivo: jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusic il codice è una continuazione del codice OP. Se apri il violino vedrai che ctx viene definito. L'ho inserito qui per evitare malintesi.

29

Dal momento che il violino di Trung Le Nguyen Nhat non è affatto corretto (usa solo l'immagine originale nell'ultimo passaggio)
ho scritto il mio violino generale con il confronto delle prestazioni:

VIOLINO

Fondamentalmente è:

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

La risposta più sottovalutata mai vista.
Amsakanna

17

Ho creato un servizio angolare riutilizzabile per gestire il ridimensionamento di alta qualità di immagini / tele per chiunque sia interessato: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983

Il servizio include due soluzioni perché entrambe hanno i loro pro / contro. L'approccio convoluzione di Lanczos è di qualità superiore al costo di essere più lento, mentre l'approccio di downscaling graduale produce risultati ragionevolmente antialias ed è significativamente più veloce.

Utilizzo di esempio:

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

Mi dispiace - avevo cambiato il mio nome utente GitHub. Ho appena aggiornato il link gist.github.com/transitive-bullshit/37bac5e741eaec60e983
fisch2

3
Ho visto la parola angolare, ho avuto quella strana sensazione
SuperUberDuper

8

Sebbene alcuni di questi frammenti di codice siano brevi e funzionanti, non sono banali da seguire e comprendere.

Dato che non sono un fan del "copia-incolla" dallo stack-overflow, vorrei che gli sviluppatori comprendessero il codice che stanno inserendo nel loro software, spero che troverai utile quanto segue.

DEMO : ridimensionamento delle immagini con JS e HTML Canvas Demo fiddler.

Potresti trovare 3 diversi metodi per eseguire questo ridimensionamento, che ti aiuteranno a capire come funziona il codice e perché.

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

Il codice completo della demo e del metodo TypeScript che potresti voler utilizzare nel tuo codice può essere trovato nel progetto GitHub.

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

Questo è il codice finale:

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

Ho creato una libreria che ti consente di ridurre qualsiasi percentuale mantenendo tutti i dati sul colore.

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

Quel file puoi includere nel browser. I risultati assomigliano a Photoshop o alla magia dell'immagine, preservando tutti i dati di colore, calcolando la media dei pixel, piuttosto che prendere quelli vicini e rilasciarne altri. Non usa una formula per indovinare le medie, prende la media esatta.


1
Probabilmente userei webgl per ridimensionare ora
Funkodebat

4

Sulla base della risposta K3N, riscrivo il codice generalmente per chiunque lo desideri

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

AGGIORNA LA DEMO DI JSFIDDLE

Ecco la mia DEMO ONLINE


2
Questo non funzionerà: ogni volta che ridimensionerai la tela, ne cancellerà il contesto. Hai bisogno di 2 tele. Qui è come chiamare direttamente drawImage con le dimensioni finali.
Kaiido

4

Non capisco perché nessuno lo suggerisce createImageBitmap.

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

funziona magnificamente (supponendo che tu abbia impostato gli ID per l'immagine e la tela).


1
Perché non è ampiamente supportato caniuse.com/#search=createImageBitmap
Matt

2
createImageBitmap è supportato per il 73% di tutti gli utenti. A seconda del tuo caso d'uso, potrebbe essere abbastanza buono. È solo Safari che si rifiuta di aggiungere il supporto per esso. Penso che valga la pena menzionarlo come possibile soluzione.
cagdas_ucar

Bella soluzione, ma sfortunatamente non funziona su Firefox
vcarel

1

Ho scritto una piccola utility js per ritagliare e ridimensionare l'immagine sul front-end. Ecco il collegamento al progetto GitHub. Inoltre puoi ottenere il blob dall'immagine finale per inviarlo.

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.