TL; DR;
Existe-t-il un moyen de compresser une image (principalement au format jpeg, png et gif) directement sur le navigateur, avant de la télécharger? Je suis sûr que JavaScript peut le faire, mais je ne trouve pas le moyen de le réaliser.
Voici le scénario complet que je voudrais implémenter:
input type="file"
,Le processus complet, jusqu’à la dernière étape, doit être effectué côté client et doit être compatible avec les versions les plus récentes de Chrome et Firefox, Safari 5+ et IE 8+. Si possible, seul JavaScript devrait être utilisé (mais je suis à peu près 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 fichier en local est possible via API de fichier , la prévisualisation et l'édition d'images peuvent être effectuées à l'aide de l'élément Canvas , mais je ne trouve pas le moyen de procéder à la compression d'image .
Selon (html5please.com et caniuse.com , supporter ces navigateurs est assez difficile (grâce à IE), mais pourrait être fait avec polyfill comme FlashCanvas et FileReader .
En réalité, l’objectif est de réduire la taille du fichier. La compression d’image est donc 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). Je pouvais donc redimensionner l'image pour l'adapter à ces dimensions, réduisant ainsi la taille du fichier. Je n'ai aucune idée de ce que serait le taux de compression pour 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.
En bref:
Source: code .
La réponse de @PsychoWoods est bonne. Je voudrais offrir ma propre solution. Cette fonction Javascript prend une URL de données d'image et une largeur, la redimensionne à 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.
Je vois deux choses qui manquent dans les autres réponses:
canvas.toBlob
(si disponible) est plus performant que canvas.toDataURL
et 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(...);
});
};
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 ).
Autant que je sache, vous ne pouvez pas compresser les images à l'aide de la toile, mais vous pouvez le redimensionner. Utiliser canvas.toDataURL ne vous laissera pas choisir le taux de compression à utiliser. Vous pouvez consulter 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 simplement redimensionner l’image pour la réduire, mais si vous voulez aller plus loin, vous devrez utiliser la méthode récemment introduite file.readAsArrayBuffer pour obtenir un tampon contenant les données de l’image.
Ensuite, utilisez simplement un DataView pour lire son contenu conformément à la spécification de 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 encore pire. D'autre part, vous pouvez essayer de supprimer les en-têtes PNG ou les données JPEG exif pour réduire la taille de votre image. Cela devrait être plus facile.
Vous devrez créer un autre DataWiew sur un autre tampon et le remplir avec le contenu de l'image filtrée. Ensuite, vous devrez simplement encoder le contenu de votre image dans DataURI en utilisant window.btoa.
Faites-moi savoir si vous implémentez quelque chose de similaire, il sera intéressant de parcourir le code.
J'ai eu un problème avec la fonction downscaleImage()
publiée ci-dessus par @ daniel-allen-langdon en ce que les propriétés image.width
et image.height
ne sont pas disponibles immédiatement car le chargement de l'image est asynchrone .
Veuillez consulter l'exemple de TypeScript mis à jour ci-dessous, qui prend cela en compte, utilise les fonctions async
et redimensionne l'image en fonction de la plus grande dimension plutôt que de la largeur.
function 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;
}
}
Vous pouvez jeter un oeil à conversion d'image , essayez-le ici -> page de démonstration
Pour la compression d’images JPG, vous pouvez utiliser la meilleure technique de compression appelée JIC (Compression d’images Javascript). Cela vous aidera certainement -> https://github.com/brunobar79/J-I-C