TL, DR;
Existe-t-il un moyen de compresser une image (principalement jpeg, png et gif) directement côté navigateur, avant de la télécharger? Je suis presque sûr que JavaScript peut le faire, mais je ne trouve pas de moyen d'y parvenir.
Voici le scénario complet que je voudrais mettre en œuvre:
- l'utilisateur accède à mon site web, et choisit une image via un
input type="file"
élément, - cette image est récupérée via JavaScript, nous effectuons des vérifications telles que le format de fichier correct, la taille maximale du fichier, etc.
- si tout va bien, un aperçu de l'image s'affiche sur la page,
- l'utilisateur peut effectuer certaines opérations de base comme faire pivoter l'image de 90 ° / -90 °, la recadrer suivant un rapport prédéfini, etc., ou l'utilisateur peut télécharger une autre image et revenir à l'étape 1
- lorsque l'utilisateur est satisfait, l'image éditée est alors compressée et "enregistrée" localement (non enregistrée dans un fichier, mais dans la mémoire / page du navigateur), -
- l'utilisateur remplit un formulaire avec des données telles que son nom, son âge, etc.
- l'utilisateur clique sur le bouton "Terminer", puis le formulaire contenant les données + image compressée est envoyé au serveur (sans AJAX),
Le processus complet jusqu'à la dernière étape doit être effectué côté client et doit être compatible avec les derniers Chrome et Firefox, Safari 5+ et IE 8+ . Si possible, seul JavaScript doit être utilisé (mais je suis presque sûr que ce n'est pas possible).
Je n'ai rien codé pour le moment, mais j'y ai déjà pensé. La lecture de fichiers localement est possible via File API , la prévisualisation et l'édition d'image peuvent être effectuées à l'aide de l' élément Canvas , mais je ne trouve pas de moyen de faire la partie compression d'image .
Selon html5please.com et caniuse.com , la prise en charge de ces navigateurs est assez difficile (grâce à IE), mais pourrait être réalisée à l'aide de polyfill tels que FlashCanvas et FileReader .
En fait, l'objectif est de réduire la taille du fichier, donc je vois la compression d'image comme une solution. Mais, je sais que les images téléchargées vont être affichées sur mon site Web, à chaque fois au même endroit, et je connais la dimension de cette zone d'affichage (par exemple. 200x400). Ainsi, je pourrais redimensionner l'image pour l'adapter à ces dimensions, réduisant ainsi la taille du fichier. Je n'ai aucune idée du taux de compression de cette technique.
Qu'est-ce que tu penses ? Avez-vous des conseils à me dire? Connaissez-vous un moyen de compresser une image côté navigateur en JavaScript? Merci pour vos réponses.
canvas.toDataURL("image/jpeg",0.7)
le compresse efficacement, il enregistre JPEG avec une qualité 70 (par opposition à la qualité par défaut, 100).URL.createObjectUrl()
sans le transformer en un blob; le fichier compte comme un objet blob.Je vois deux choses qui manquent dans les autres réponses:
canvas.toBlob
(si disponible) est plus performant quecanvas.toDataURL
, et aussi asynchrone.Le script suivant traite des deux points:
// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari: if (!HTMLCanvasElement.prototype.toBlob) { Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { value: function(callback, type, quality) { var binStr = atob(this.toDataURL(type, quality).split(',')[1]), len = binStr.length, arr = new Uint8Array(len); for (var i = 0; i < len; i++) { arr[i] = binStr.charCodeAt(i); } callback(new Blob([arr], {type: type || 'image/png'})); } }); } window.URL = window.URL || window.webkitURL; // Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0 // -2 = not jpeg, -1 = no data, 1..8 = orientations function getExifOrientation(file, callback) { // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/: if (file.slice) { file = file.slice(0, 131072); } else if (file.webkitSlice) { file = file.webkitSlice(0, 131072); } var reader = new FileReader(); reader.onload = function(e) { var view = new DataView(e.target.result); if (view.getUint16(0, false) != 0xFFD8) { callback(-2); return; } var length = view.byteLength, offset = 2; while (offset < length) { var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { callback(-1); return; } var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) if (view.getUint16(offset + (i * 12), little) == 0x0112) { callback(view.getUint16(offset + (i * 12) + 8, little)); return; } } else if ((marker & 0xFF00) != 0xFF00) break; else offset += view.getUint16(offset, false); } callback(-1); }; reader.readAsArrayBuffer(file); } // Derived from https://stackoverflow.com/a/40867559, cc by-sa function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) { var canvas = document.createElement('canvas'); if (orientation > 4) { canvas.width = rawHeight; canvas.height = rawWidth; } else { canvas.width = rawWidth; canvas.height = rawHeight; } if (orientation > 1) { console.log("EXIF orientation = " + orientation + ", rotating picture"); } var ctx = canvas.getContext('2d'); switch (orientation) { case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break; case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break; case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break; case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break; case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break; } ctx.drawImage(img, 0, 0, rawWidth, rawHeight); return canvas; } function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) { if (file.size <= acceptFileSize) { callback(file); return; } var img = new Image(); img.onerror = function() { URL.revokeObjectURL(this.src); callback(file); }; img.onload = function() { URL.revokeObjectURL(this.src); getExifOrientation(file, function(orientation) { var w = img.width, h = img.height; var scale = (orientation > 4 ? Math.min(maxHeight / w, maxWidth / h, 1) : Math.min(maxWidth / w, maxHeight / h, 1)); h = Math.round(h * scale); w = Math.round(w * scale); var canvas = imgToCanvasWithOrientation(img, w, h, orientation); canvas.toBlob(function(blob) { console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB"); callback(blob); }, 'image/jpeg', quality); }); }; img.src = URL.createObjectURL(file); }
Exemple d'utilisation:
inputfile.onchange = function() { // If file size > 500kB, resize such that width <= 1000, quality = 0.9 reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => { let body = new FormData(); body.set('file', blob, blob.name || "file.jpg"); fetch('/upload-image', {method: 'POST', body}).then(...); }); };
la source
La réponse de @PsychoWoods est bonne. Je souhaite proposer ma propre solution. Cette fonction Javascript prend une URL de données d'image et une largeur, la met à l'échelle à la nouvelle largeur et renvoie une nouvelle URL de données.
// Take an image URL, downscale it to the given width, and return a new image URL. function downscaleImage(dataUrl, newWidth, imageType, imageArguments) { "use strict"; var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; // Provide default values imageType = imageType || "image/jpeg"; imageArguments = imageArguments || 0.7; // Create a temporary image so that we can compute the height of the downscaled image. image = new Image(); image.src = dataUrl; oldWidth = image.width; oldHeight = image.height; newHeight = Math.floor(oldHeight / oldWidth * newWidth) // Create a temporary canvas to draw the downscaled image on. canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; // Draw the downscaled image on the canvas and return the new data URL. ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, newWidth, newHeight); newDataUrl = canvas.toDataURL(imageType, imageArguments); return newDataUrl; }
Ce code peut être utilisé partout où vous avez une URL de données et souhaitez une URL de données pour une image réduite.
la source
Vous pouvez jeter un oeil à la conversion d'image , essayez-le ici -> page de démonstration
la source
J'ai eu un problème avec la
downscaleImage()
fonction publiée ci-dessus par @ daniel-allen-langdon en ce que les propriétésimage.width
etimage.height
ne sont pas disponibles immédiatement car le chargement de l'image est asynchrone .Veuillez consulter l'exemple TypeScript mis à jour ci-dessous qui prend cela en compte, utilise des
async
fonctions et redimensionne l'image en fonction de la dimension la plus longue plutôt que de la largeurfunction getImage(dataUrl: string): Promise<HTMLImageElement> { return new Promise((resolve, reject) => { const image = new Image(); image.src = dataUrl; image.onload = () => { resolve(image); }; image.onerror = (el: any, err: ErrorEvent) => { reject(err.error); }; }); } export async function downscaleImage( dataUrl: string, imageType: string, // e.g. 'image/jpeg' resolution: number, // max width/height in pixels quality: number // e.g. 0.9 = 90% quality ): Promise<string> { // Create a temporary image so that we can compute the height of the image. const image = await getImage(dataUrl); const oldWidth = image.naturalWidth; const oldHeight = image.naturalHeight; console.log('dims', oldWidth, oldHeight); const longestDimension = oldWidth > oldHeight ? 'width' : 'height'; const currentRes = longestDimension == 'width' ? oldWidth : oldHeight; console.log('longest dim', longestDimension, currentRes); if (currentRes > resolution) { console.log('need to resize...'); // Calculate new dimensions const newSize = longestDimension == 'width' ? Math.floor(oldHeight / oldWidth * resolution) : Math.floor(oldWidth / oldHeight * resolution); const newWidth = longestDimension == 'width' ? resolution : newSize; const newHeight = longestDimension == 'height' ? resolution : newSize; console.log('new width / height', newWidth, newHeight); // Create a temporary canvas to draw the downscaled image on. const canvas = document.createElement('canvas'); canvas.width = newWidth; canvas.height = newHeight; // Draw the downscaled image on the canvas and return the new data URL. const ctx = canvas.getContext('2d')!; ctx.drawImage(image, 0, 0, newWidth, newHeight); const newDataUrl = canvas.toDataURL(imageType, quality); return newDataUrl; } else { return dataUrl; } }
la source
quality
,resolution
etimageType
(format de ceci)Edit: Selon le commentaire de M. Me sur cette réponse, il semble que la compression soit maintenant disponible pour les formats JPG / WebP (voir https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ) .
Pour autant que je sache, vous ne pouvez pas compresser les images à l'aide de la toile, mais vous pouvez la redimensionner. L'utilisation de canvas.toDataURL ne vous permettra pas de choisir le taux de compression à utiliser. Vous pouvez jeter un œil à canimage qui fait exactement ce que vous voulez: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
En fait, il suffit souvent de redimensionner l'image pour diminuer sa taille, mais si vous voulez aller plus loin, vous devrez utiliser la méthode nouvellement introduite file.readAsArrayBuffer pour obtenir un tampon contenant les données d'image.
Ensuite, utilisez simplement un DataView pour lire son contenu conformément à la spécification du format d'image ( http://en.wikipedia.org/wiki/JPEG ou http://en.wikipedia.org/wiki/Portable_Network_Graphics ).
Il sera difficile de gérer la compression des données d'image, mais c'est pire à essayer. D'un autre côté, vous pouvez essayer de supprimer les en-têtes PNG ou les données JPEG exif pour rendre votre image plus petite, cela devrait être plus facile à faire.
Vous devrez créer un autre DataWiew sur un autre tampon et le remplir avec le contenu de l'image filtrée. Ensuite, il vous suffira d'encoder le contenu de votre image dans DataURI à l'aide de window.btoa.
Faites-moi savoir si vous implémentez quelque chose de similaire, il sera intéressant de parcourir le code.
la source
Je trouve qu'il existe une solution plus simple par rapport à la réponse acceptée.
Selon votre question:
Ma solution:
Créez un objet blob avec le fichier directement avec
URL.createObjectURL(inputFileElement.files[0])
.Identique à la réponse acceptée.
Identique à la réponse acceptée. Il convient de mentionner que la taille de la toile est nécessaire et à utiliser
img.width
etimg.height
à définircanvas.width
etcanvas.height
. Nonimg.clientWidth
.Obtenez l'image de réduction par
canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)
. Le réglage'image/jpg'
n'a aucun effet.image/png
est également pris en charge. Créez un nouvelFile
objet à l'intérieur ducallbackfunction
corps aveclet compressedImageBlob = new File([blob])
.Ajoutez de nouvelles entrées cachées ou envoyez via javascript . Le serveur n'a rien à décoder.
Consultez https://javascript.info/binary pour toutes les informations. J'ai trouvé la solution après avoir lu ce chapitre.
Code:
Ce code semble beaucoup moins effrayant que les autres réponses.
Mise à jour:
Il faut tout mettre à l'intérieur
img.onload
. Sinoncanvas
, vous ne pourrez pas obtenir correctement la largeur et la hauteur de l'image lorsque l'heurecanvas
est attribuée.function upload(){ var f = fileToUpload.files[0]; var fileName = f.name.split('.')[0]; var img = new Image(); img.src = URL.createObjectURL(f); img.onload = function(){ var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); canvas.toBlob(function(blob){ console.info(blob.size); var f2 = new File([blob], fileName + ".jpeg"); var xhr = new XMLHttpRequest(); var form = new FormData(); form.append("fileToUpload", f2); xhr.open("POST", "upload.php"); xhr.send(form); }, 'image/jpeg', 0.5); } }
3.4MB
.png
test de compression de fichier avecimage/jpeg
jeu d'arguments.|0.9| 777KB | |0.8| 383KB | |0.7| 301KB | |0.6| 251KB | |0.5| 219kB |
la source
Pour la compression d'image JPG, vous pouvez utiliser la meilleure technique de compression appelée JIC (Javascript Image Compression) Cela vous aidera certainement -> https://github.com/brunobar79/JIC
la source
J'ai amélioré la fonction d'une tête pour être ceci:
var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7){ var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; (new Promise(function(resolve){ image = new Image(); image.src = dataUrl; log(image); resolve('Done : '); })).then((d)=>{ oldWidth = image.width; oldHeight = image.height; log([oldWidth,oldHeight]); newHeight = Math.floor(oldHeight / oldWidth * newWidth); log(d+' '+newHeight); canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; log(canvas); ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, newWidth, newHeight); //log(ctx); newDataUrl = canvas.toDataURL(imageType, imageArguments); resolve(newDataUrl); }); };
son utilisation:
prendre plaisir ;)
la source