Ridimensionamento di un'immagine in un'area HTML5


314

Sto cercando di creare una miniatura sul lato client usando javascript e un elemento canvas, ma quando rimpicciolisco l'immagine, sembra terribile. Sembra che sia stato ridimensionato in Photoshop con il ricampionamento impostato su "Prossimo prossimo" anziché Bicubic. So che è possibile ottenere questo aspetto giusto, perché questo sito può farlo bene anche usando una tela. Ho provato a usare lo stesso codice che hanno mostrato nel link "[Source]", ma sembra ancora terribile. C'è qualcosa che mi manca, qualche impostazione che deve essere impostata o qualcosa del genere?

MODIFICARE:

Sto cercando di ridimensionare un jpg. Ho provato a ridimensionare lo stesso jpg sul sito collegato e in Photoshop e sembra ridimensionato.

Ecco il codice pertinente:

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:

Sembra che mi sbagliassi, il sito web collegato non stava facendo un lavoro migliore nel ridimensionare l'immagine. Ho provato gli altri metodi suggeriti e nessuno di loro ha un aspetto migliore. Questo è il risultato dei diversi metodi:

Photoshop:

testo alternativo

Tela:

testo alternativo

Immagine con rendering dell'immagine: ottimizzare la qualità impostata e ridimensionata con larghezza / altezza:

testo alternativo

Immagine con rendering delle immagini: ottimizzare la qualità impostata e ridimensionata con -moz-transform:

testo alternativo

Ridimensionamento tela su pixastico:

testo alternativo

Immagino che questo significhi che Firefox non sta usando il campionamento bicubico come dovrebbe. Dovrò solo aspettare fino a quando non lo aggiungeranno effettivamente.

Edit3:

Immagine originale


Puoi pubblicare il codice che stai utilizzando per ridimensionare l'immagine?
Xavi,

Stai cercando di ridimensionare un'immagine GIF o simile con una tavolozza limitata? Anche in Photoshop queste immagini non si ridimensionano bene se non le converti in RGB.
Leepowers,

Puoi pubblicare una copia dell'immagine originale?
Xavi,

Ridimensionare l'immagine usando javascript è un po 'complicato - non solo stai usando la potenza di elaborazione del client per ridimensionare l'immagine, ma lo stai facendo ad ogni singolo caricamento della pagina. Perché non salvare semplicemente una versione ridotta di Photoshop e servirla invece / in tandem con l'immagine originale?
definisce il

29
Perché sto realizzando un uploader di immagini con la possibilità di ridimensionare e ritagliare le immagini prima di caricarle.
Telanor

Risposte:


393

Quindi cosa fai se tutti i browser (in realtà, Chrome 5 mi hanno dato uno abbastanza buono) non ti daranno una qualità di ricampionamento abbastanza buona? Li implementi tu stesso allora! Oh andiamo, stiamo entrando nella nuova era del Web 3.0, browser compatibili con HTML5, compilatori javascript JIT super ottimizzati, macchine multi-core (†), con tonnellate di memoria, di cosa hai paura? Ehi, c'è la parola java in javascript, quindi dovrebbe garantire le prestazioni, giusto? Ecco, il codice che genera l'anteprima:

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

... con cui puoi produrre risultati come questi!

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

quindi comunque, ecco una versione 'fissa' del tuo esempio:

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

Ora è il momento di mettere i tuoi migliori browser là fuori e vedere quale probabilmente aumenterà la pressione sanguigna del tuo cliente!

Umm, dov'è il mio tag sarcasmo?

(poiché molte parti del codice si basano su Anrieff Gallery Generator è anche coperto da GPL2? Non lo so)

† in realtà a causa della limitazione di javascript, il multi-core non è supportato.


2
In realtà avevo provato a implementarlo da solo, facendo come hai fatto tu, copiando il codice da un editor di immagini open source. Dato che non sono riuscito a trovare alcuna documentazione solida sull'algoritmo, ho avuto difficoltà a ottimizzarlo. Alla fine, il mio era un po 'lento (ci sono voluti alcuni secondi per ridimensionare l'immagine). Quando ne avrò la possibilità, proverò il tuo e vedrò se è più veloce. E penso che i webworker rendano javascript multi-core ora possibile. Avrei provato a usarli per accelerarlo, ma avevo difficoltà a capire come trasformarlo in un algoritmo multithread
Telanor,

3
Mi dispiace, l'ho dimenticato! Ho modificato la risposta. Non sarà comunque veloce, il bicubico dovrebbe essere più veloce. Per non parlare dell'algoritmo che ho usato non è il solito ridimensionamento a 2 vie (che è linea per linea, orizzontale quindi verticale), quindi è un bottino più lento.
Syockit,

5
Sei fantastico e meriti tonnellate di meraviglia.
Rocklan,

5
Questo produce risultati decenti, ma richiede 7,4 secondi per un'immagine da 1,8 MP nell'ultima versione di Chrome ...
mpen

2
Come metodi come questo riescono a raggiungere un punteggio così alto ?? La soluzione mostrata non riesce a tenere conto della scala logaritmica utilizzata per memorizzare le informazioni sul colore. Un RGB di 127.127.127 è un quarto della luminosità di 255, 255, 255 e non metà. Il down campionamento nella soluzione si traduce in un'immagine scura. Peccato che questo sia chiuso in quanto esiste un metodo molto semplice e veloce per ridimensionare le dimensioni che produce risultati ancora migliori rispetto al campione Photoshop (OP deve aver impostato le preferenze sbagliate)
Blindman67

37

Algoritmo di ridimensionamento / ricampionamento rapido delle immagini utilizzando il filtro Hermite con JavaScript. Supportare la trasparenza, offre una buona qualità. Anteprima:

inserisci qui la descrizione dell'immagine

Aggiornamento : versione 2.0 aggiunta su GitHub (più veloce, web worker + oggetti trasferibili). Finalmente l'ho fatto funzionare!

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

Forse puoi includere collegamenti alla tua demo miniPaint e repository Github?
Syockit,

1
Condividerai anche la versione dei webworker? Probabilmente a causa del sovraccarico di installazione, è più lento per le immagini di piccole dimensioni, ma potrebbe essere utile per immagini di origine più grandi.
Syockit,

aggiunta demo, collegamenti git, anche versione multi-core. A proposito non ho speso troppo tempo a ottimizzare la versione multicore ... Versione singola credo sia ottimizzata bene.
ViliusL

Enorme differenza e prestazioni decenti. Grazie mille! prima e dopo
KevBurnsJr

2
@ViliusL Ah ora mi sono ricordato del perché i lavoratori del web non hanno funzionato così bene. Non avevano mai condiviso la memoria prima, e ancora non ce l'hanno adesso! Forse un giorno quando riusciranno a risolverlo, il tuo codice verrà utilizzato (che, o forse le persone usano invece PNaCl)
syockit

26

Prova pica : è un resizer altamente ottimizzato con algoritmi selezionabili. Vedi la demo .

Ad esempio, l'immagine originale del primo post viene ridimensionata in 120ms con filtro Lanczos e finestra 3px o 60ms con filtro Box e finestra 0,5px. Per enormi immagini da 17 MB, il ridimensionamento di 5000x3000px richiede circa 1 secondo sul desktop e 3 secondi sul cellulare.

Tutti i principi di ridimensionamento sono stati descritti molto bene in questo thread e la pica non aggiunge scienza missilistica. Ma è ottimizzato molto bene per i moderni JIT ed è pronto per l'uso (via npm o bower). Inoltre, utilizza i webworker quando disponibili per evitare blocchi dell'interfaccia.

Ho anche intenzione di aggiungere presto il supporto per la maschera di contrasto, perché è molto utile dopo il downscale.


14

So che questo è un vecchio thread, ma potrebbe essere utile per alcune persone come me che mesi dopo stanno affrontando questo problema per la prima volta.

Ecco un codice che ridimensiona l'immagine ogni volta che ricarichi l'immagine. Sono consapevole che questo non è affatto ottimale, ma lo fornisco come prova di concetto.

Inoltre, mi dispiace per l'utilizzo di jQuery per selettori semplici ma mi sento troppo a mio agio con la sintassi.

$(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>

La mia funzione createImage viene chiamata una volta quando il documento viene caricato e successivamente viene chiamato ogni volta che la finestra riceve un evento di ridimensionamento.

L'ho provato in Chrome 6 e Firefox 3.6, entrambi su Mac. Questa "tecnica" mangia processore come se fosse gelato in estate, ma fa il trucco.


9

Ho messo su alcuni algoritmi per eseguire l'interpolazione di immagini su array di pixel di tela html che potrebbero essere utili qui:

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

Questi possono essere copiati / incollati e possono essere utilizzati all'interno dei web worker per ridimensionare le immagini (o qualsiasi altra operazione che richiede interpolazione - le sto usando per sfidare le immagini al momento).

Non ho aggiunto le cose di Lanczos sopra, quindi sentiti libero di aggiungerlo come confronto se vuoi.


6

Questa è una funzione javascript adattata dal codice di @ Telanor. Quando si passa un'immagine base64 come primo argomento alla funzione, restituisce la base64 dell'immagine ridimensionata. maxWidth e maxHeight sono opzionali.

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

}

il tuo approccio è molto veloce ma produce un'immagine sfocata, come potete vedere qui: stackoverflow.com/questions/18922880/...
confile

6

Consiglio vivamente di dare un'occhiata a questo link e assicurarsi che sia impostato su true.

Controllo del comportamento del ridimensionamento delle immagini

Introdotto in Gecko 1.9.2 (Firefox 3.6 / Thunderbird 3.1 / Fennec 1.0)

Gecko 1.9.2 ha introdotto la proprietà mozImageSmoothingEnabled nell'elemento canvas; se questo valore booleano è falso, le immagini non verranno uniformate quando ridimensionate. Questa proprietà è vera per impostazione predefinita. visualizzare il testo in chiaro?

  1. cx.mozImageSmoothingEnabled = false;

5

Se stai semplicemente cercando di ridimensionare un'immagine, ti consiglio di impostare widthe heightdell'immagine con CSS. Ecco un breve esempio:

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

Si noti che heighte widthpuò anche essere impostato tramite JavaScript. Ecco un esempio di codice rapido:

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

Inoltre, per garantire che l'immagine ridimensionata abbia un bell'aspetto, aggiungi le seguenti regole css al selettore di immagini:

Per quanto ne so, tutti i browser tranne IE utilizzano un algoritmo bicubico per ridimensionare le immagini per impostazione predefinita, quindi le immagini ridimensionate dovrebbero apparire bene in Firefox e Chrome.

Se l'impostazione del CSS widthe heightnon funziona, potresti voler giocare con un CSS transform:

Se per qualsiasi motivo si ha bisogno di usare una tela, si prega di notare che ci sono due modi di un'immagine può essere ridimensionamento: ridimensionando la tela con CSS o disegnando l'immagine in un formato più piccolo.

Vedi questa domanda per maggiori dettagli.


5
Né il ridimensionamento della tela né il disegno dell'immagine in dimensioni inferiori risolvono il problema (in Chrome), purtroppo.
Nestor

1
Chrome 27 produce una bella immagine ridimensionata, ma non puoi copiare il risultato su una tela; tentando di farlo, verrà invece copiata l'immagine originale.
Syockit,

4

Per ridimensionare l'immagine con una larghezza inferiore a quella originale, utilizzo:

    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, .... )

e funziona =).


4

ho ottenuto questa immagine facendo clic con il tasto destro del mouse sull'elemento canvas in Firefox e salvando come.

testo alternativo

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

quindi comunque, ecco una versione 'fissa' del tuo esempio:

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

Ho provato a fare quello che hai fatto e non è bello come il tuo. A meno che non mi sia sfuggito qualcosa, l'unica modifica che hai fatto è stata quella di utilizzare la firma del metodo di ridimensionamento anziché quella del taglio, giusto? Per qualche motivo non funziona per me.
Telanor,

3

Il problema con alcune di queste soluzioni è che accedono direttamente ai dati dei pixel e lo attraversano per eseguire il downsampling. A seconda della dimensione dell'immagine, ciò può richiedere molte risorse e sarebbe meglio usare gli algoritmi interni del browser.

La funzione drawImage () utilizza un metodo di ricampionamento a interpolazione lineare, il più vicino vicino. Questo funziona bene quando non si sta ridimensionando in calo di oltre la metà delle dimensioni originali .

Se esegui il loop per ridimensionare solo la metà alla volta, i risultati sarebbero abbastanza buoni e molto più veloci dell'accesso ai dati pixel.

Questa funzione riduce a metà alla volta fino a raggiungere la dimensione desiderata:

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

Crediti per questo post


2

Quindi qualcosa di interessante che ho trovato qualche tempo fa mentre lavoravo con la tela che potrebbe essere utile:

Per ridimensionare il controllo canvas da solo, è necessario utilizzare gli attributi height=""e width=""(o canvas.width/ canvas.heightelements). Se usi CSS per ridimensionare la tela, in realtà allungherà (cioè ridimensionerà) il contenuto della tela per adattarla alla tela intera (piuttosto che semplicemente aumentare o diminuire l'area della tela.

Vale la pena provare a disegnare l'immagine in un controllo canvas con gli attributi di altezza e larghezza impostati sulla dimensione dell'immagine e quindi utilizzare CSS per ridimensionare la tela alla dimensione che stai cercando. Forse questo userebbe un diverso algoritmo di ridimensionamento.

Va anche notato che la tela ha effetti diversi in browser diversi (e persino versioni diverse di browser diversi). È probabile che gli algoritmi e le tecniche utilizzati nei browser cambino nel tempo (soprattutto con Firefox 4 e Chrome 6 che verranno pubblicati così presto, il che porrà grande enfasi sulle prestazioni del rendering su tela).

Inoltre, potresti voler dare una possibilità anche a SVG, poiché probabilmente utilizza anche un algoritmo diverso.

Buona fortuna!


1
L'impostazione della larghezza o dell'altezza di una tela tramite gli attributi HTML provoca la cancellazione della tela, quindi non è possibile eseguire alcun ridimensionamento con quel metodo. Inoltre, SVG è pensato per gestire immagini matematiche. Devo essere in grado di disegnare PNG e simili, in modo che non mi aiuti.
Telanor,

Ho trovato (in Chrome) l'impostazione dell'altezza e della larghezza della tela e il ridimensionamento tramite CSS. Anche fare il ridimensionamento usando -webkit-transform piuttosto che CSS width / height non fa andare avanti l'interpolazione.
Nestor

2

Ho la sensazione che il modulo che ho scritto produrrà risultati simili a Photoshop, in quanto preserva i dati cromatici calcolandone la media, non applicando un algoritmo. È un po 'lento, ma per me è il migliore, perché conserva tutti i dati di colore.

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

Non prende il vicino più vicino e rilascia altri pixel, né campiona un gruppo e prende una media casuale. Prende la proporzione esatta che ogni pixel di origine dovrebbe generare nel pixel di destinazione. Il colore medio dei pixel nella sorgente sarà il colore medio dei pixel nella destinazione, che secondo queste altre formule, penso che non lo saranno.

un esempio di come utilizzare è in fondo a https://github.com/danschumann/limby-resize

AGGIORNAMENTO OTTOBRE 2018 : In questi giorni il mio esempio è più accademico di ogni altra cosa. Webgl è praticamente al 100%, quindi è meglio ridimensionarlo per produrre risultati simili, ma più velocemente. PICA.js lo fa, credo. -


1

Ho convertito la risposta di @ syockit e l'approccio step-down in un servizio angolare riutilizzabile per chiunque sia interessato: https://gist.github.com/fisch0920/37bac5e741eaec60e983

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

Esempio di utilizzo:

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

1

Resizer di immagini Javascript semplice e veloce:

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

Storia

Questo è davvero dopo molti round di ricerca, lettura e prova.

L'algoritmo di resizer utilizza lo script Hermite di @ ViliusL (il resizer di Hermite è davvero il più veloce e offre un output ragionevolmente buono). Esteso con le funzionalità di cui hai bisogno.

Forks 1 lavoratore esegue il ridimensionamento in modo che non blocchi il browser durante il ridimensionamento, a differenza di tutti gli altri ridimensionatori JS disponibili.


0

Grazie @syockit per una risposta fantastica. tuttavia, ho dovuto riformattare un po 'come segue per farlo funzionare. Forse a causa di problemi di scansione DOM:

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

-1

Ho appena eseguito una pagina di confronti affiancati e, a meno che qualcosa non sia cambiato di recente, non potrei vedere un ridimensionamento (ridimensionamento) migliore usando canvas vs semplice CSS. Ho testato in FF6 Mac OSX 10.7. Ancora leggermente morbido rispetto all'originale.

Tuttavia mi sono imbattuto in qualcosa che ha fatto una grande differenza e che stava usando i filtri di immagine nei browser che supportano la tela. Puoi effettivamente manipolare le immagini come fai in Photoshop con sfocatura, nitidezza, saturazione, ondulazione, scala di grigi, ecc.

Ho quindi trovato un fantastico plug-in jQuery che semplifica l'applicazione di questi filtri: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

Applico semplicemente il filtro di nitidezza subito dopo aver ridimensionato l'immagine che dovrebbe darti l'effetto desiderato. Non ho nemmeno dovuto usare un elemento canvas.


-1

Alla ricerca di un'altra grande soluzione semplice?

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

Questa soluzione utilizzerà l'algoritmo di ridimensionamento del browser! :)


La domanda riguarda il downsampling dell'immagine, non solo il ridimensionamento.
Jesús Carrera,

[...] Sto cercando di ridimensionare un jpg. Ho provato a ridimensionare lo stesso jpg sul sito collegato e in Photoshop e sembra a posto se ridimensionato. [...] perché non puoi usare <img> Jesus Carrera?
ale500,
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.