HTML5 Pre-ridimensionare le immagini prima del caricamento


181

Ecco un gratta e Vinci.

Tenendo presente che abbiamo l'archiviazione locale HTML5 e xhr v2 e cosa no. Mi chiedevo se qualcuno potesse trovare un esempio funzionante o anche solo darmi un sì o no per questa domanda:

È possibile ridimensionare un'immagine usando il nuovo archivio locale (o qualsiasi altra cosa), in modo che un utente che non abbia la minima idea di ridimensionare un'immagine possa trascinare la sua immagine da 10 MB nel mio sito Web, ridimensionandola usando il nuovo archivio locale e POI caricalo a dimensioni inferiori.

So benissimo che puoi farlo con Flash, applet Java, X attivo ... La domanda è se puoi farlo con Javascript + Html5.

In attesa di una risposta su questo.

Ta per ora.

Risposte:


181

Sì, utilizza l' API File , quindi puoi elaborare le immagini con l' elemento canvas .

Questo post sul blog di Mozilla Hacks ti guida attraverso la maggior parte del processo. Per riferimento ecco il codice sorgente assemblato dal post del blog:

// from an input element
var filesToUpload = input.files;
var file = filesToUpload[0];

var img = document.createElement("img");
var reader = new FileReader();  
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);

var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);

var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;

if (width > height) {
  if (width > MAX_WIDTH) {
    height *= MAX_WIDTH / width;
    width = MAX_WIDTH;
  }
} else {
  if (height > MAX_HEIGHT) {
    width *= MAX_HEIGHT / height;
    height = MAX_HEIGHT;
  }
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);

var dataurl = canvas.toDataURL("image/png");

//Post dataurl to the server with AJAX

6
Perché grazie, signore. Stasera farò un gioco ... Con il file API che è. Ho fatto in modo che il caricamento tramite trascinamento della selezione funzionasse e mi sono reso conto che anche questa sarebbe stata una caratteristica davvero interessante da includere. Yippee.
Jimmyt1988,

3
Non abbiamo bisogno di usare questo mycanvas.toDataURL ("image / jpeg", 0.5) per assicurarci che la qualità sia portata al fine di ridurre le dimensioni del file. Mi sono imbattuto in questo commento sul post del blog di mozilla e ho visto la risoluzione dei bug qui bugzilla.mozilla.org/show_bug.cgi?id=564388
sbose

33
Dovresti aspettare onloadsull'immagine (collega il gestore prima di impostare src), perché il browser può eseguire la decodifica in modo asincrono e i pixel potrebbero non essere disponibili prima drawImage.
Kornel,

6
Potresti voler inserire il codice canvas nella funzione onload del filereader - c'è la possibilità che le immagini di grandi dimensioni non vengano caricate prima ctx.drawImage()della fine.
Greg

4
fate attenzione, è necessario magia additionnal per farlo funzionare su IOS: stackoverflow.com/questions/11929099/...
flo850

107

Ho affrontato questo problema alcuni anni fa e ho caricato la mia soluzione su github come https://github.com/rossturner/HTML5-ImageUploader

La risposta di robertc utilizza la soluzione proposta nel post sul blog di Mozilla Hacks , tuttavia ho scoperto che ciò ha dato una qualità dell'immagine davvero scadente quando si ridimensionava su una scala che non era 2: 1 (o un suo multiplo). Ho iniziato a sperimentare diversi algoritmi di ridimensionamento delle immagini, sebbene la maggior parte finisse per essere piuttosto lenta o altrimenti non era eccezionale in termini di qualità.

Alla fine ho trovato una soluzione che credo si esegua rapidamente e abbia anche prestazioni abbastanza buone - poiché la soluzione Mozilla di copiare da una tela all'altra funziona rapidamente e senza perdita di qualità dell'immagine con un rapporto di 2: 1, dato un obiettivo di x pixel di larghezza e y altezza pixel, che utilizzano questo metodo di ridimensionamento tela finché l'immagine è tra x e 2 x ed y e 2 y . A questo punto passo quindi al ridimensionamento algoritmico dell'immagine per il "passo" finale del ridimensionamento fino alla dimensione target. Dopo aver provato diversi algoritmi ho optato per l'interpolazione bilineare presa da un blog che non è più online ma accessibile tramite Internet Archive, che fornisce buoni risultati, ecco il codice applicabile:

ImageUploader.prototype.scaleImage = function(img, completionCallback) {
    var canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);

    while (canvas.width >= (2 * this.config.maxWidth)) {
        canvas = this.getHalfScaleCanvas(canvas);
    }

    if (canvas.width > this.config.maxWidth) {
        canvas = this.scaleCanvasWithAlgorithm(canvas);
    }

    var imageData = canvas.toDataURL('image/jpeg', this.config.quality);
    this.performUpload(imageData, completionCallback);
};

ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) {
    var scaledCanvas = document.createElement('canvas');

    var scale = this.config.maxWidth / canvas.width;

    scaledCanvas.width = canvas.width * scale;
    scaledCanvas.height = canvas.height * scale;

    var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
    var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height);

    this.applyBilinearInterpolation(srcImgData, destImgData, scale);

    scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0);

    return scaledCanvas;
};

ImageUploader.prototype.getHalfScaleCanvas = function(canvas) {
    var halfCanvas = document.createElement('canvas');
    halfCanvas.width = canvas.width / 2;
    halfCanvas.height = canvas.height / 2;

    halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height);

    return halfCanvas;
};

ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) {
    function inner(f00, f10, f01, f11, x, y) {
        var un_x = 1.0 - x;
        var un_y = 1.0 - y;
        return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y);
    }
    var i, j;
    var iyv, iy0, iy1, ixv, ix0, ix1;
    var idxD, idxS00, idxS10, idxS01, idxS11;
    var dx, dy;
    var r, g, b, a;
    for (i = 0; i < destCanvasData.height; ++i) {
        iyv = i / scale;
        iy0 = Math.floor(iyv);
        // Math.ceil can go over bounds
        iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv));
        for (j = 0; j < destCanvasData.width; ++j) {
            ixv = j / scale;
            ix0 = Math.floor(ixv);
            // Math.ceil can go over bounds
            ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv));
            idxD = (j + destCanvasData.width * i) * 4;
            // matrix to vector indices
            idxS00 = (ix0 + srcCanvasData.width * iy0) * 4;
            idxS10 = (ix1 + srcCanvasData.width * iy0) * 4;
            idxS01 = (ix0 + srcCanvasData.width * iy1) * 4;
            idxS11 = (ix1 + srcCanvasData.width * iy1) * 4;
            // overall coordinates to unit square
            dx = ixv - ix0;
            dy = iyv - iy0;
            // I let the r, g, b, a on purpose for debugging
            r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy);
            destCanvasData.data[idxD] = r;

            g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy);
            destCanvasData.data[idxD + 1] = g;

            b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy);
            destCanvasData.data[idxD + 2] = b;

            a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy);
            destCanvasData.data[idxD + 3] = a;
        }
    }
};

Questo ridimensiona un'immagine fino a una larghezza di config.maxWidth, mantenendo le proporzioni originali. Al momento dello sviluppo questo funzionava su iPad / iPhone Safari oltre ai principali browser desktop (IE9 +, Firefox, Chrome), quindi mi aspetto che sarà ancora compatibile dato il più ampio utilizzo di HTML5 oggi. Nota che la chiamata canvas.toDataURL () accetta un tipo mime e una qualità dell'immagine che ti permetteranno di controllare la qualità e il formato del file di output (potenzialmente diverso da inserire se lo desideri).

L'unico punto che non copre è il mantenimento delle informazioni sull'orientamento, senza la conoscenza di questi metadati l'immagine viene ridimensionata e salvata così com'è, perdendo qualsiasi metadata all'interno dell'immagine per orientamento, il che significa che le immagini scattate su un dispositivo tablet "sottosopra" erano renderizzato come tale, anche se sarebbero stati capovolti nel mirino della fotocamera del dispositivo. Se questo è un problema, questo post sul blog ha una buona guida ed esempi di codice su come realizzare questo, che sono sicuro che potrebbe essere integrato con il codice sopra.


4
Ti ho assegnato la generosità perché ritengo che aggiunga un sacco alla risposta, ma devo integrare quella roba di orientamento :)
Sam Saffron,

3
Un'anima molto utile si è ora unita ad alcune funzionalità per i metadati di orientamento :)
Ross Taylor-Turner,

26

Correzione sopra:

<img src="" id="image">
<input id="input" type="file" onchange="handleFiles()">
<script>

function handleFiles()
{
    var filesToUpload = document.getElementById('input').files;
    var file = filesToUpload[0];

    // Create an image
    var img = document.createElement("img");
    // Create a file reader
    var reader = new FileReader();
    // Set the image once loaded into file reader
    reader.onload = function(e)
    {
        img.src = e.target.result;

        var canvas = document.createElement("canvas");
        //var canvas = $("<canvas>", {"id":"testing"})[0];
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);

        var MAX_WIDTH = 400;
        var MAX_HEIGHT = 300;
        var width = img.width;
        var height = img.height;

        if (width > height) {
          if (width > MAX_WIDTH) {
            height *= MAX_WIDTH / width;
            width = MAX_WIDTH;
          }
        } else {
          if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
          }
        }
        canvas.width = width;
        canvas.height = height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, width, height);

        var dataurl = canvas.toDataURL("image/png");
        document.getElementById('image').src = dataurl;     
    }
    // Load files into file reader
    reader.readAsDataURL(file);


    // Post the data
    /*
    var fd = new FormData();
    fd.append("name", "some_filename.jpg");
    fd.append("image", dataurl);
    fd.append("info", "lah_de_dah");
    */
}</script>

2
Come gestite la parte PHP dopo averla aggiunta a FormData ()? Ad esempio, non dovresti cercare $ _FILES ['name'] ['tmp_name'] [$ i]? Sto provando if (isset ($ _ POST ['image'])) ... ma dataurlnon c'è.
Denikov,

2
non funziona su Firefox la prima volta che provi a caricare un'immagine. La prossima volta che provi funziona. Come sistemarlo?
Fred,

ritorna se il file è array è nullo o vuoto.
Sheelpriy,

2
In Chrome quando si tenta di accedere img.widthe img.heightinizialmente lo sono 0. Dovresti inserire il resto della funzioneimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo,

2
@Justin puoi chiarire "quanto sopra", come in, di cosa tratta questo post una correzione? Saluti
Martin,

16

Modifica alla risposta di Justin che funziona per me:

  1. Aggiunto img.onload
  2. Espandi la richiesta POST con un esempio reale

function handleFiles()
{
    var dataurl = null;
    var filesToUpload = document.getElementById('photo').files;
    var file = filesToUpload[0];

    // Create an image
    var img = document.createElement("img");
    // Create a file reader
    var reader = new FileReader();
    // Set the image once loaded into file reader
    reader.onload = function(e)
    {
        img.src = e.target.result;

        img.onload = function () {
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0);

            var MAX_WIDTH = 800;
            var MAX_HEIGHT = 600;
            var width = img.width;
            var height = img.height;

            if (width > height) {
              if (width > MAX_WIDTH) {
                height *= MAX_WIDTH / width;
                width = MAX_WIDTH;
              }
            } else {
              if (height > MAX_HEIGHT) {
                width *= MAX_HEIGHT / height;
                height = MAX_HEIGHT;
              }
            }
            canvas.width = width;
            canvas.height = height;
            var ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0, width, height);

            dataurl = canvas.toDataURL("image/jpeg");

            // Post the data
            var fd = new FormData();
            fd.append("name", "some_filename.jpg");
            fd.append("image", dataurl);
            fd.append("info", "lah_de_dah");
            $.ajax({
                url: '/ajax_photo',
                data: fd,
                cache: false,
                contentType: false,
                processData: false,
                type: 'POST',
                success: function(data){
                    $('#form_photo')[0].reset();
                    location.reload();
                }
            });
        } // img.onload
    }
    // Load files into file reader
    reader.readAsDataURL(file);
}

2
informazioni sull'orientamento (exif) si perde
Konstantin Vahrushev,

8

Se non vuoi reinventare la ruota, puoi provare plupload.com


3
Uso anche il plupload per il ridimensionamento lato client. La cosa migliore è che può usare flash, html5, silverlight, ecc. Qualunque cosa l'utente abbia a disposizione, perché ogni utente ha una configurazione diversa. Se vuoi solo html5, penso che non funzionerà su alcuni vecchi browser e Opera.
jnl

6

Dattiloscritto

async resizeImg(file: Blob): Promise<Blob> {
    let img = document.createElement("img");
    img.src = await new Promise<any>(resolve => {
        let reader = new FileReader();
        reader.onload = (e: any) => resolve(e.target.result);
        reader.readAsDataURL(file);
    });
    await new Promise(resolve => img.onload = resolve)
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    let MAX_WIDTH = 1000;
    let MAX_HEIGHT = 1000;
    let width = img.naturalWidth;
    let height = img.naturalHeight;
    if (width > height) {
        if (width > MAX_WIDTH) {
            height *= MAX_WIDTH / width;
            width = MAX_WIDTH;
        }
    } else {
        if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
        }
    }
    canvas.width = width;
    canvas.height = height;
    ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0, width, height);
    let result = await new Promise<Blob>(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); });
    return result;
}

1
Chiunque possa imbattersi in questo post, questa risposta basata sulla promessa è molto più affidabile secondo me. Mi sono convertito in JS normale e funziona come un fascino.
gbland777,

5
fd.append("image", dataurl);

Questo non funzionerà. Sul lato PHP non puoi salvare file con questo.

Usa invece questo codice:

var blobBin = atob(dataurl.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
  array.push(blobBin.charCodeAt(i));
}
var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"});

fd.append("image", file); // blob file

5

Il ridimensionamento delle immagini in un elemento canvas è generalmente una cattiva idea poiché utilizza l'interpolazione di box più economica. L'immagine risultante si degrada in termini di qualità. Consiglierei di usare http://nodeca.github.io/pica/demo/ che può invece eseguire la trasformazione di Lanczos. La pagina demo sopra mostra la differenza tra gli approcci canvas e Lanczos.

Utilizza anche web worker per ridimensionare le immagini in parallelo. C'è anche l'implementazione di WEBGL.

Ci sono alcuni ridimensionatori di immagini online che usano pica per fare il lavoro, come https://myimageresizer.com


5

La risposta accettata funziona alla grande, ma la logica di ridimensionamento ignora il caso in cui l'immagine è più grande del massimo in uno solo degli assi (ad esempio, altezza> maxHeight ma larghezza <= maxWidth).

Penso che il seguente codice si occupi di tutti i casi in un modo più diretto e funzionale (ignora le annotazioni del tipo dattiloscritto se usi javascript semplice):

private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} {
    if (width <= maxWidth && height <= maxHeight)
      return { width, height };
    else if (width / maxWidth > height / maxHeight)
      return { width: maxWidth, height: height * maxWidth / width};
    else
      return { width: width * maxHeight / height, height: maxHeight };
  }

0

È possibile utilizzare dropzone.js se si desidera utilizzare il gestore caricamento semplice e facile con il ridimensionamento prima delle funzioni di caricamento.

Ha funzioni di ridimensionamento integrate, ma puoi fornire le tue se lo desideri.

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.