web-dev-qa-db-fra.com

HTML5 Pré-redimensionner les images avant de les télécharger

Voici un scratcher de nouilles.

Gardant à l'esprit, nous avons le stockage local HTML5 et xhr v2 et ce qui ne l'est pas. Je me demandais si quelqu'un pourrait trouver un exemple de travail ou même simplement me donner un oui ou un non à cette question:

Est-il possible de prédimensionner une image en utilisant le nouveau stockage local (ou autre), de sorte qu'un utilisateur qui n'a pas la moindre idée de redimensionner une image puisse faire glisser son image de 10 Mo sur mon site Web, il la redimensionnera en utilisant le nouveau stockage local et ALORS le télécharger à la taille la plus petite.

Je sais très bien que vous pouvez le faire avec Flash, applets Java, X actif ... La question est de savoir si vous pouvez le faire avec Javascript + Html5.

Dans l'attente de la réponse sur celui-ci.

Ta pour l'instant.

170
Jimmyt1988

Oui, utilisez le fichier API , vous pouvez ensuite traiter les images avec l’élément canvas .

Cet article de blog de Mozilla Hacks vous guide tout au long du processus. Pour référence, voici le code source assemblé à partir de l'article de 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
168
robertc

J'ai abordé ce problème il y a quelques années et j'ai téléchargé ma solution sur github en tant que https://github.com/rossturner/HTML5-ImageUploader

la réponse de robertc utilise la solution proposée dans le billet de blog Mozilla Hacks , mais j’ai trouvé que la qualité d’image était vraiment médiocre lors du redimensionnement à une échelle qui n’était pas 2: 1 (ou un multiple de celle-ci). J'ai commencé à expérimenter différents algorithmes de redimensionnement d'image, bien que la plupart aient été plutôt lents ou d'une qualité médiocre. 

Enfin, j’ai trouvé une solution qui, à mon avis, s’exécute rapidement et offre également de très bonnes performances: la solution Mozilla consistant à copier d’un canevas à un autre fonctionne rapidement et sans perte de qualité d’image avec un rapport de 2: 1, avec un objectif de x pixels de large et y pixels de hauteur, j'utilise cette méthode de redimensionnement de la toile jusqu'à ce que l'image se situe entre x et 2 x, et y et 2 y À ce stade, je passe maintenant au redimensionnement d'image algorithmique pour la dernière étape de redimensionnement à la taille cible. Après avoir essayé plusieurs algorithmes différents, j'ai opté pour une interpolation bilinéaire tirée d'un blog qui n'est plus en ligne mais et accessible via Internet Archive , qui donne de bons résultats. Voici le code applicable:

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

Cela réduit une image à une largeur de config.maxWidth, en conservant les proportions d'origine. Au moment du développement, cela fonctionnait sur Safari pour iPad/iPhone ainsi que sur les principaux navigateurs de bureau (IE9 +, Firefox, Chrome) et je pense donc qu'il sera toujours compatible compte tenu de l'utilisation plus large de HTML5 aujourd'hui. Notez que l'appel canvas.toDataURL () prend un type mime et une qualité d'image qui vous permettront de contrôler la qualité et le format du fichier de sortie (potentiellement différent de l'entrée si vous le souhaitez).

Le seul point que cela ne couvre pas est la gestion des informations d'orientation. Sans la connaissance de ces métadonnées, l'image est redimensionnée et enregistrée telle quelle, perdant ainsi toutes les métadonnées de l'image, ce qui signifie que les images prises sur une tablette ont été "à l'envers". restitués tels quels, bien qu'ils auraient été retournés dans le viseur de l'appareil photo de l'appareil. Si cela vous pose un problème, cet article de blog contient un bon guide et des exemples de code expliquant comment procéder, ce qui, j'en suis sûr, pourrait être intégré au code ci-dessus.

101
Ross Taylor-Turner

Correction ci-dessus:

<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>
24
Justin Levene

Modification du réponse de Justin ça marche pour moi:

  1. Ajouté img.onload
  2. Développez la demande POST avec un exemple réel

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

Si vous ne voulez pas réinventer la roue, vous pouvez essayer plupload.com

8
nanospeck
fd.append("image", dataurl);

Cela ne fonctionnera pas. Du côté PHP, vous ne pouvez pas enregistrer de fichier avec cela.

Utilisez ce code à la place:

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
robot

La réponse acceptée fonctionne bien, mais la logique de redimensionnement ignore le cas où l'image est plus grande que le maximum dans un seul des axes (par exemple, height> maxHeight mais width <= maxWidth).

Je pense que le code suivant prend en charge tous les cas de manière plus simple et fonctionnelle (ignorez les annotations de type TypeScript si vous utilisez du javascript simple):

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 };
  }
5
lautaro.dragan

Redimensionner des images dans un élément de la toile est généralement une mauvaise idée, car il utilise l’interpolation de case la moins chère. L'image résultante se dégrade notablement en qualité. Je recommanderais d'utiliser http://nodeca.github.io/pica/demo/ qui peut effectuer la transformation Lanczos à la place. La page de démonstration ci-dessus montre la différence entre les approches canvas et Lanczos.

Il utilise également des travailleurs Web pour redimensionner les images en parallèle. Il y a aussi l'implémentation WEBGL.

Il existe des outils de redimensionnement d'images en ligne qui utilisent pica pour effectuer le travail, par exemple https://myimageresizer.com

3
Ivan Nikitin

Manuscrit

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;
}
2
jales cardoso

Vous pouvez utiliser dropzone.js si vous souhaitez utiliser un gestionnaire de téléchargement simple et facile avec le redimensionnement avant les fonctions de téléchargement.

Il a des fonctions de redimensionnement intégrées, mais vous pouvez en fournir si vous le souhaitez.

0
Michał Zalewski