TL; DR;
¿Hay alguna forma de comprimir una imagen (principalmente jpeg, png y gif) directamente en el navegador antes de cargarla? Estoy bastante seguro de que JavaScript puede hacer esto, pero no puedo encontrar la manera de lograrlo.
Aquí está el escenario completo que me gustaría implementar:
- el usuario va a mi sitio web y elige una imagen a través de un
input type="file"
elemento, - esta imagen se recupera a través de JavaScript, realizamos algunas verificaciones, como el formato de archivo correcto, el tamaño máximo de archivo, etc.
- si todo está bien, se muestra una vista previa de la imagen en la página,
- el usuario puede realizar algunas operaciones básicas como rotar la imagen 90 ° / -90 °, recortarla siguiendo una proporción predefinida, etc., o el usuario puede cargar otra imagen y volver al paso 1,
- cuando el usuario está satisfecho, la imagen editada se comprime y "guarda" localmente (no se guarda en un archivo, sino en la memoria / página del navegador), -
- el usuario llena un formulario con datos como nombre, edad, etc.
- el usuario hace clic en el botón "Finalizar", luego el formulario que contiene los datos + imagen comprimida se envía al servidor (sin AJAX),
El proceso completo hasta el último paso debe realizarse del lado del cliente y debe ser compatible con los últimos Chrome y Firefox, Safari 5+ e IE 8+ . Si es posible, solo debe usarse JavaScript (pero estoy bastante seguro de que esto no es posible).
No he codificado nada en este momento, pero ya lo he pensado. La lectura de archivos localmente es posible a través de la API de archivos , la vista previa y la edición de imágenes se pueden realizar utilizando el elemento Canvas , pero no puedo encontrar una manera de hacer la parte de compresión de imágenes .
Según html5please.com y caniuse.com , admitir esos navegadores es bastante difícil (gracias a IE), pero se podría hacer usando polyfill como FlashCanvas y FileReader .
En realidad, el objetivo es reducir el tamaño del archivo, por lo que veo la compresión de imágenes como una solución. Pero sé que las imágenes cargadas se mostrarán en mi sitio web, siempre en el mismo lugar, y conozco la dimensión de esta área de visualización (por ejemplo, 200x400). Entonces, podría cambiar el tamaño de la imagen para que se ajuste a esas dimensiones, reduciendo así el tamaño del archivo. No tengo idea de cuál sería la relación de compresión para esta técnica.
Qué piensas ? ¿Tienes algún consejo que darme? ¿Conoce alguna forma de comprimir una imagen del lado del navegador en JavaScript? Gracias por tus respuestas.
canvas.toDataURL("image/jpeg",0.7)
efectivamente lo comprime, guarda JPEG con calidad 70 (en contraposición al valor predeterminado, calidad 100).URL.createObjectUrl()
sin convertir el archivo en un blob; el archivo cuenta como un blob.Veo dos cosas que faltan en las otras respuestas:
canvas.toBlob
(cuando esté disponible) es más eficaz quecanvas.toDataURL
, y también asincrónico.El siguiente guión trata de ambos puntos:
// 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); }
Uso de ejemplo:
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(...); }); };
fuente
La respuesta de @PsychoWoods es buena. Me gustaría ofrecer mi propia solución. Esta función de Javascript toma una URL de datos de imagen y un ancho, la escala al nuevo ancho y devuelve una nueva URL de datos.
// 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; }
Este código se puede usar en cualquier lugar donde tenga una URL de datos y desee una URL de datos para una imagen reducida.
fuente
Puede echar un vistazo a la conversión de imágenes , Pruébelo aquí -> página de demostración
fuente
Tuve un problema con la
downscaleImage()
función publicada anteriormente por @ daniel-allen-langdon porque las propiedadesimage.width
yimage.height
no están disponibles de inmediato porque la carga de la imagen es asincrónica .Consulte el ejemplo actualizado de TypeScript a continuación que tiene esto en cuenta, utiliza
async
funciones y cambia el tamaño de la imagen según la dimensión más larga en lugar de solo el ancho.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; } }
fuente
quality
,resolution
yimageType
(formato de este)Editar: según el comentario de Mr Me sobre esta respuesta, parece que la compresión ahora está disponible para formatos JPG / WebP (consulte https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ) .
Hasta donde yo sé, no puede comprimir imágenes usando lienzo, en su lugar, puede cambiar su tamaño. El uso de canvas.toDataURL no le permitirá elegir la relación de compresión a utilizar. Puedes echar un vistazo a canimage que hace exactamente lo que quieres: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
De hecho, a menudo es suficiente cambiar el tamaño de la imagen para disminuir su tamaño, pero si desea ir más allá, tendrá que usar el método file.readAsArrayBuffer recién introducido para obtener un búfer que contenga los datos de la imagen.
Luego, simplemente use un DataView para leer su contenido de acuerdo con la especificación del formato de imagen ( http://en.wikipedia.org/wiki/JPEG o http://en.wikipedia.org/wiki/Portable_Network_Graphics ).
Será difícil lidiar con la compresión de datos de imágenes, pero es peor intentarlo. Por otro lado, puede intentar eliminar los encabezados PNG o los datos exif JPEG para hacer su imagen más pequeña, debería ser más fácil hacerlo.
Tendrá que crear otro DataWiew en otro búfer y llenarlo con el contenido de la imagen filtrada. Luego, solo tendrá que codificar el contenido de su imagen en DataURI usando window.btoa.
Avíseme si implementa algo similar, será interesante revisar el código.
fuente
Encuentro que hay una solución más simple en comparación con la respuesta aceptada.
Según tu pregunta:
Mi solución:
Cree un blob con el archivo directamente con
URL.createObjectURL(inputFileElement.files[0])
.Igual que la respuesta aceptada.
Igual que la respuesta aceptada. Vale la pena mencionar que el tamaño del lienzo es necesario y usar
img.width
yimg.height
configurarcanvas.width
ycanvas.height
. Noimg.clientWidth
.Obtenga la imagen reducida por
canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)
. El ajuste'image/jpg'
no tiene ningún efecto.image/png
también es compatible. Haz un nuevoFile
objeto dentro delcallbackfunction
cuerpo conlet compressedImageBlob = new File([blob])
.Agregue nuevas entradas ocultas o envíe a través de javascript . El servidor no tiene que decodificar nada.
Consulte https://javascript.info/binary para obtener toda la información. Se me ocurrió la solución después de leer este capítulo.
Código:
Este código parece mucho menos aterrador que las otras respuestas.
Actualizar:
Hay que meterlo todo dentro
img.onload
. Decanvas
lo contrario , no podrá obtener el ancho y el alto de la imagen correctamente a medida quecanvas
se asigna el tiempo .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
prueba de compresión de archivos conimage/jpeg
conjunto de argumentos.|0.9| 777KB | |0.8| 383KB | |0.7| 301KB | |0.6| 251KB | |0.5| 219kB |
fuente
Para la compresión de imágenes JPG, puede usar la mejor técnica de compresión llamada JIC (Compresión de imágenes Javascript) Esto definitivamente lo ayudará -> https://github.com/brunobar79/JIC
fuente
Mejoré la función a head para que sea esto:
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); }); };
el uso de la misma:
disfrutar;)
fuente