immagini - javascript e canvas




Ridimensionamento di un'immagine in un'area di disegno HTML5 (12)

Sto cercando di creare un'immagine di anteprima 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 set di ricampionamento su "Nearest Neighbor" invece di Bicubic. So che è possibile farlo sembrare corretto, perché questo sito può farlo bene anche usando una tela. Ho provato ad usare lo stesso codice che fanno come 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 a posto quando ridimensionato.

Ecco il codice rilevante:

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 sia sbagliato, il sito web collegato non stava facendo meglio di un lavoro di ridimensionamento dell'immagine. Ho provato gli altri metodi suggeriti e nessuno di loro ha un aspetto migliore. Questo è quello che hanno portato i diversi metodi:

Photoshop:

Tela:

Immagine con rendering delle immagini: ottimizzazione della qualità impostata e ridimensionata con larghezza / altezza:

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

Ridimensiona tela su pixastic:

Immagino che questo significhi che firefox non sta usando il campionamento bicubico come dovrebbe. Dovrò solo aspettare finché non lo aggiungeranno.

Edit3:

Immagine originale


Algoritmo di ridimensionamento / ricampionamento rapido dell'immagine utilizzando il filtro Hermite con JavaScript. Supporta la trasparenza, dà buona qualità. Anteprima:


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

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! :)


Ho appena eseguito una pagina di confronti fianco a fianco e, a meno che qualcosa non sia cambiato di recente, non ho potuto vedere un ridimensionamento (ridimensionamento) migliore utilizzando canvas rispetto a semplici css. Ho provato su FF6 Mac OSX 10.7. Ancora leggermente morbido rispetto all'originale.

Tuttavia, mi sono imbattuto in qualcosa che ha fatto un'enorme differenza e che utilizzava filtri di immagini nei browser che supportano la tela. In Photoshop è possibile manipolare le immagini in modo molto simile a sfocatura, nitidezza, saturazione, ripple, scala di grigi, ecc.

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

Applico semplicemente il filtro sharpen subito dopo il ridimensionamento dell'immagine, che dovrebbe darti l'effetto desiderato. Non ho nemmeno dovuto usare un elemento canvas.


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

Ho incluso entrambe le soluzioni perché entrambi hanno i loro pro / contro. L'approccio alla 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.

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

Ho la sensazione che il modulo che ho scritto produrrà risultati simili a Photoshop, poiché conserva i dati dei colori calcolandoli in base alla media, non applicando un algoritmo. È un po 'lento, ma per me è il migliore, perché conserva tutti i dati dei colori.

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

Non prende il vicino più vicino e rilascia altri pixel, o assaggia un gruppo e prende una media casuale. Prende la proporzione esatta che ciascun pixel sorgente deve produrre nel pixel di destinazione. Il colore medio del pixel nella sorgente sarà il colore medio del pixel nella destinazione, che queste altre formule, penso che non saranno.

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

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


Il problema con alcune di queste soluzioni è che accedono direttamente ai dati dei pixel e passano attraverso di essi per eseguire il downsampling. A seconda della dimensione dell'immagine, questo può richiedere molto risorse e sarebbe meglio utilizzare gli algoritmi interni del browser.

La funzione drawImage () utilizza un metodo di ricampionamento lineare-interpolazione più vicino-vicino. Funziona bene quando non ridimensiona più della metà della dimensione originale .

Se esegui il ciclo solo per ridimensionare al massimo un mezzo alla volta, i risultati saranno abbastanza buoni e molto più rapidi dell'accesso ai dati dei pixel.

Questa funzione esegue il downsampling 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 a questo post


Prova pica : si tratta di un resizer altamente ottimizzato con algoritmi selezionabili. Guarda la demo .

Ad esempio, l'immagine originale del primo post viene ridimensionata in 120 ms con filtro Lanczos e finestra 3px o 60 ms con filtro Box e finestra 0.5 px. Per un'immagine enorme di 17mb, il ridimensionamento di 5000x3000 pixel richiede circa 1 secondo sul desktop e 3 su un dispositivo mobile.

Tutti i principi di ridimensionamento sono stati descritti molto bene in questo thread, e pica non aggiunge scienza missilistica. Ma è ottimizzato molto bene per i moderni JIT-s, ed è pronto all'uso fuori dalla scatola (via npm o bower). Inoltre, utilizza i webworkers quando è disponibile per evitare il blocco dell'interfaccia.

Ho anche in programma di aggiungere presto il supporto per le maschere di contrasto, perché è molto utile dopo il downscale.


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

}

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

Per ridimensionare il controllo del canvas da solo, è necessario utilizzare gli attributi height="" e width="" (o canvas.width / canvas.height ). Se si utilizza il CSS per ridimensionare l'area, verrà effettivamente allungato (ovvero ridimensionato) il contenuto della tela per adattarsi all'intero telo (anziché semplicemente aumentare o diminuire l'area della tela.

Vale la pena provare a disegnare l'immagine in un controllo canvas con gli attributi height e width impostati sulla dimensione dell'immagine e quindi utilizzare CSS per ridimensionare l'area di disegno alla dimensione che si sta cercando. Forse questo userebbe un diverso algoritmo di ridimensionamento.

Va anche notato che la tela ha effetti diversi nei vari browser (e anche nelle diverse versioni di diversi browser). È probabile che gli algoritmi e le tecniche utilizzate nei browser cambino nel tempo (specialmente con Firefox 4 e Chrome 6 che usciranno così presto, il che metterà molto l'accento sulle prestazioni del rendering della tela).

Inoltre, si consiglia di dare un tiro a SVG, in quanto probabilmente utilizza anche un algoritmo diverso.

Buona fortuna!


Resizer di immagini Javascript rapido e semplice:

https://github.com/calvintwr/Hermite-resize

Uso:

h.resize({
    source: document.getElementById('image'), // any canvas or image elements, jQuery or native
    width: 400,
    height: 600,
    output: 'image', // [optional] `image` or `canvas`. If not entered output is same as input element.
    quality: 0.7, // [optional] applicable for `image` output only
}, function(output) {
    //your callback
});

Storia

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

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

Forks 1 worker per eseguire il ridimensionamento in modo che non blocchi il browser durante il ridimensionamento, diversamente da tutti gli altri resizer JS in circolazione.


So che questo è un thread vecchio, ma potrebbe essere utile per alcune persone come me che mesi dopo hanno riscontrato 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 concettuale.

Inoltre, mi dispiace per l'utilizzo di jQuery per i 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 sul Mac. Questa "tecnica" mangia processore come se fosse un gelato in estate, ma fa il trucco.


Ti consiglio caldamente di controllare questo link e assicurarti che sia impostato su true.

Controllo del comportamento di ridimensionamento dell'immagine

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 saranno livellate quando ridimensionate. Questa proprietà è true per impostazione predefinita. visualizza la stampa?

  1. cx.mozImageSmoothingEnabled = false;




image-resizing