Utilisez HTML5 pour redimensionner une image avant la mise en ligne

102

J'ai trouvé quelques articles différents et même des questions sur stackoverflow répondant à cette question. J'implémente fondamentalement la même chose que ce post .

Voici donc mon problème. Lorsque je télécharge la photo, je dois également soumettre le reste du formulaire. Voici mon html:

<form id="uploadImageForm" enctype="multipart/form-data">
  <input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
  <input id="name" value="#{name}" />
  ... a few more inputs ... 
</form>

Auparavant, je n'avais pas besoin de redimensionner l'image, donc mon javascript ressemblait à ceci:

window.uploadPhotos = function(url){
    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

Tout cela a très bien fonctionné ... maintenant que j'ai besoin de redimensionner les images ... comment puis-je remplacer l'image dans le formulaire afin que l'image redimensionnée soit publiée et non l'image téléchargée?

window.uploadPhotos = function(url){

    var resizedImage;

    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 1200,
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                resizedImage = canvas.toDataURL('image/jpeg');
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }


   // TODO: Need some logic here to switch out which photo is being posted...

    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

J'ai pensé à déplacer l'entrée de fichier hors du formulaire et à avoir une entrée masquée sous la forme dont j'ai défini la valeur sur la valeur de l'image redimensionnée ... Mais je me demande si je peux simplement remplacer l'image qui est déjà sous la forme.

ferics2
la source
Travaillez-vous avec n'importe quel langage côté serveur ou seulement html5 et javascript?
luke2012
1
@ luke2012 côté serveur Java
ferics2
Peut-être recadrez l'image côté client en utilisant quelque chose comme jCrop, puis envoyez les coordonnées côté serveur et recadrez-la. ieBufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height);
luke2012
4
@ luke2012 le but est de réduire la taille de l'image AVANT de l'envoyer au serveur.
ferics2
Jetez un œil à la source js de pandamatak.com/people/anand/test/crop semble être similaire.
luke2012

Réponses:

161

Voici ce que j'ai fini par faire et cela a très bien fonctionné.

J'ai d'abord déplacé l'entrée du fichier en dehors du formulaire afin qu'elle ne soit pas soumise:

<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
    <input id="name" value="#{name}" />
    ... a few more inputs ... 
</form>

Ensuite, j'ai changé la uploadPhotosfonction pour gérer uniquement le redimensionnement:

window.uploadPhotos = function(url){
    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 544,// TODO : pull max size from a site config
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                var dataUrl = canvas.toDataURL('image/jpeg');
                var resizedImage = dataURLToBlob(dataUrl);
                $.event.trigger({
                    type: "imageResized",
                    blob: resizedImage,
                    url: dataUrl
                });
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }
};

Comme vous pouvez le voir, j'utilise canvas.toDataURL('image/jpeg');pour changer l'image redimensionnée en dataUrl adn, puis j'appelle la fonction dataURLToBlob(dataUrl);pour transformer dataUrl en un blob que je peux ensuite ajouter au formulaire. Lorsque l'objet blob est créé, je déclenche un événement personnalisé. Voici la fonction pour créer le blob:

/* Utility function to convert a canvas to a BLOB */
var dataURLToBlob = function(dataURL) {
    var BASE64_MARKER = ';base64,';
    if (dataURL.indexOf(BASE64_MARKER) == -1) {
        var parts = dataURL.split(',');
        var contentType = parts[0].split(':')[1];
        var raw = parts[1];

        return new Blob([raw], {type: contentType});
    }

    var parts = dataURL.split(BASE64_MARKER);
    var contentType = parts[0].split(':')[1];
    var raw = window.atob(parts[1]);
    var rawLength = raw.length;

    var uInt8Array = new Uint8Array(rawLength);

    for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], {type: contentType});
}
/* End Utility function to convert a canvas to a BLOB      */

Enfin, voici mon gestionnaire d'événements qui prend l'objet blob de l'événement personnalisé, ajoute le formulaire, puis le soumet.

/* Handle image resized events */
$(document).on("imageResized", function (event) {
    var data = new FormData($("form[id*='uploadImageForm']")[0]);
    if (event.blob && event.url) {
        data.append('image_data', event.blob);

        $.ajax({
            url: event.url,
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: function(data){
               //handle errors...
            }
        });
    }
});
ferics2
la source
1
Magnifique code, après quelques ajustements et corrections d'erreurs (je ne me souviens pas exactement quoi et lesquelles) je l'ai fait fonctionner. Au fait, je pense que lorsque vous avez écrit, width *= max_size / width;vous vouliez vraiment dire width *= max_size / height;.
user1111929
2
Ce code fonctionne-t-il sur les appareils mobiles? Sous iOS et Android?
planewalker le
4
@planewalker J'ai en fait écrit le code spécifiquement pour les appareils mobiles. Pour réduire l'utilisation des données.
ferics2
2
@planewalker, je testais sur iOS quand j'ai écrit ceci. J'espère que ça marche pour toi.
ferics2
3
Bravo et Merci pour le partage! (L'erreur mineure que d'autres ont mentionnée mais non identifiée se trouve dans le déclencheur de l'événement de redimensionnement d'image. "Url: url" doit être "url: dataUrl")
Seoras
44

si vous êtes intéressé, j'ai fait une version dactylographiée:

interface IResizeImageOptions {
  maxSize: number;
  file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
  const file = settings.file;
  const maxSize = settings.maxSize;
  const reader = new FileReader();
  const image = new Image();
  const canvas = document.createElement('canvas');
  const dataURItoBlob = (dataURI: string) => {
    const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
        atob(dataURI.split(',')[1]) :
        unescape(dataURI.split(',')[1]);
    const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    const max = bytes.length;
    const ia = new Uint8Array(max);
    for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
    return new Blob([ia], {type:mime});
  };
  const resize = () => {
    let width = image.width;
    let height = image.height;

    if (width > height) {
        if (width > maxSize) {
            height *= maxSize / width;
            width = maxSize;
        }
    } else {
        if (height > maxSize) {
            width *= maxSize / height;
            height = maxSize;
        }
    }

    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(image, 0, 0, width, height);
    let dataUrl = canvas.toDataURL('image/jpeg');
    return dataURItoBlob(dataUrl);
  };

  return new Promise((ok, no) => {
      if (!file.type.match(/image.*/)) {
        no(new Error("Not an image"));
        return;
      }

      reader.onload = (readerEvent: any) => {
        image.onload = () => ok(resize());
        image.src = readerEvent.target.result;
      };
      reader.readAsDataURL(file);
  })    
};

et voici le résultat javascript:

var resizeImage = function (settings) {
    var file = settings.file;
    var maxSize = settings.maxSize;
    var reader = new FileReader();
    var image = new Image();
    var canvas = document.createElement('canvas');
    var dataURItoBlob = function (dataURI) {
        var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
            atob(dataURI.split(',')[1]) :
            unescape(dataURI.split(',')[1]);
        var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var max = bytes.length;
        var ia = new Uint8Array(max);
        for (var i = 0; i < max; i++)
            ia[i] = bytes.charCodeAt(i);
        return new Blob([ia], { type: mime });
    };
    var resize = function () {
        var width = image.width;
        var height = image.height;
        if (width > height) {
            if (width > maxSize) {
                height *= maxSize / width;
                width = maxSize;
            }
        } else {
            if (height > maxSize) {
                width *= maxSize / height;
                height = maxSize;
            }
        }
        canvas.width = width;
        canvas.height = height;
        canvas.getContext('2d').drawImage(image, 0, 0, width, height);
        var dataUrl = canvas.toDataURL('image/jpeg');
        return dataURItoBlob(dataUrl);
    };
    return new Promise(function (ok, no) {
        if (!file.type.match(/image.*/)) {
            no(new Error("Not an image"));
            return;
        }
        reader.onload = function (readerEvent) {
            image.onload = function () { return ok(resize()); };
            image.src = readerEvent.target.result;
        };
        reader.readAsDataURL(file);
    });
};

l'utilisation est comme:

resizeImage({
    file: $image.files[0],
    maxSize: 500
}).then(function (resizedImage) {
    console.log("upload resized image")
}).catch(function (err) {
    console.error(err);
});

ou ( async/ await):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
Santiago Hernández
la source
Solution vraiment sympa, peut être d'ajouter un paramètre supplémentaire pour permettre de renvoyer le résultat en base64 (contourner l'appel dataURIToBlob à la fin).
Chen
J'obtiens cette erreur:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined
Owen M
1
Agréable. J'ai dû changer unescape en (<any> window) .unescape J'ai également ajouté minSize.
robert king
maxSizeest en octets ou en ko?
Jagruti
1
Votre réponse m'a aidé à répondre à cette question , merci.
Syed