TL; DR;
Existe uma maneira de compactar uma imagem (principalmente jpeg, png e gif) diretamente do lado do navegador, antes de enviá-la? Tenho certeza de que o JavaScript pode fazer isso, mas não consigo encontrar uma maneira de fazer isso.
Aqui está o cenário completo que eu gostaria de implementar:
- o usuário vai ao meu site e escolhe uma imagem por meio de um
input type="file"
elemento, - esta imagem é recuperada via JavaScript, fazemos algumas verificações, como formato de arquivo correto, tamanho máximo do arquivo, etc.
- se tudo estiver OK, uma prévia da imagem é exibida na página,
- o usuário pode fazer algumas operações básicas, como girar a imagem em 90 ° / -90 °, recortá-la seguindo uma proporção pré-definida, etc, ou o usuário pode carregar outra imagem e retornar à etapa 1,
- quando o usuário está satisfeito, a imagem editada é então compactada e "salva" localmente (não salva em um arquivo, mas na memória / página do navegador), -
- o usuário preenche um formulário com dados como nome, idade etc,
- o usuário clica no botão "Terminar", então o formulário contendo dados + imagem compactada é enviado para o servidor (sem AJAX),
O processo completo até a última etapa deve ser feito do lado do cliente e deve ser compatível com o Chrome e Firefox mais recentes, Safari 5+ e IE 8+ . Se possível, apenas JavaScript deve ser usado (mas tenho certeza de que isso não é possível).
Não codifiquei nada agora, mas já pensei sobre isso. A leitura de arquivos localmente é possível através da API File , a visualização e edição da imagem podem ser feitas usando o elemento Canvas , mas não consigo encontrar uma maneira de fazer a parte de compressão da imagem .
De acordo com html5please.com e caniuse.com , suportar esses navegadores é bastante difícil (graças ao IE), mas pode ser feito usando polyfill como FlashCanvas e FileReader .
Na verdade, o objetivo é reduzir o tamanho do arquivo, então vejo a compactação de imagens como uma solução. Mas, eu sei que as imagens carregadas serão exibidas no meu site, sempre no mesmo lugar, e eu sei a dimensão dessa área de exibição (por exemplo, 200x400). Então, eu poderia redimensionar a imagem para caber nessas dimensões, reduzindo assim o tamanho do arquivo. Não tenho ideia de qual seria a taxa de compressão para essa técnica.
O que você acha ? Você tem algum conselho para me dizer? Você conhece alguma maneira de compactar uma imagem do lado do navegador em JavaScript? Obrigado por suas respostas.
canvas.toDataURL("image/jpeg",0.7)
efetivamente o comprime, ele salva JPEG com qualidade 70 (ao contrário do padrão, qualidade 100).URL.createObjectUrl()
sem transformá-lo em um blob; o arquivo conta como um blob.Vejo duas coisas faltando nas outras respostas:
canvas.toBlob
(quando disponível) tem mais desempenho do quecanvas.toDataURL
, e também assíncrono.O script a seguir trata de ambos os pontos:
// 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); }
Exemplo de uso:
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(...); }); };
fonte
A resposta de @PsychoWoods é boa. Eu gostaria de oferecer minha própria solução. Esta função Javascript pega um URL de dados de imagem e uma largura, dimensiona para a nova largura e retorna um novo URL de dados.
// 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; }
Esse código pode ser usado em qualquer lugar em que você tenha um URL de dados e queira um URL de dados para uma imagem em escala reduzida.
fonte
Você pode dar uma olhada na conversão de imagem , Experimente aqui -> página de demonstração
fonte
Tive um problema com a
downscaleImage()
função postada acima por @ daniel-allen-langdon em que as propriedadesimage.width
eimage.height
não estão disponíveis imediatamente porque o carregamento da imagem é assíncrono .Consulte o exemplo TypeScript atualizado abaixo que leva isso em consideração, usa
async
funções e redimensiona a imagem com base na dimensão mais longa, em vez de apenas na largurafunction 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; } }
fonte
quality
,resolution
eimageType
(formato disso)Editar: de acordo com o comentário do Sr. Me sobre esta resposta, parece que a compactação agora está disponível para formatos JPG / WebP (consulte https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ) .
Até onde eu sei, você não pode compactar imagens usando canvas, em vez disso, você pode redimensioná-las. Usar canvas.toDataURL não permitirá que você escolha a taxa de compactação a ser usada. Você pode dar uma olhada em canimage que faz exatamente o que você deseja: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
Na verdade, geralmente é suficiente apenas redimensionar a imagem para diminuir seu tamanho, mas se você quiser ir além, terá que usar o método file.readAsArrayBuffer recém-introduzido para obter um buffer contendo os dados da imagem.
Em seguida, basta usar um DataView para ler seu conteúdo de acordo com a especificação do formato de imagem ( http://en.wikipedia.org/wiki/JPEG ou http://en.wikipedia.org/wiki/Portable_Network_Graphics ).
Será difícil lidar com a compactação de dados de imagem, mas é uma tentativa pior. Por outro lado, você pode tentar excluir os cabeçalhos PNG ou os dados JPEG exif para tornar a imagem menor; deve ser mais fácil fazer isso.
Você terá que criar outro DataWiew em outro buffer e preenchê-lo com o conteúdo da imagem filtrada. Em seguida, você apenas terá que codificar o conteúdo da imagem para DataURI usando window.btoa.
Deixe-me saber se você implementar algo semelhante, será interessante percorrer o código.
fonte
Acho que há uma solução mais simples em comparação com a resposta aceita.
De acordo com sua pergunta:
Minha solução:
Crie um blob com o arquivo diretamente com
URL.createObjectURL(inputFileElement.files[0])
.Igual à resposta aceita.
Igual à resposta aceita. Vale a pena mencionar que, o tamanho da tela é necessário e use
img.width
eimg.height
para definircanvas.width
ecanvas.height
. Nãoimg.clientWidth
.Obtenha a imagem em escala reduzida por
canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)
. A configuração'image/jpg'
não tem efeito.image/png
também é suportado. Faça um novoFile
objeto dentro docallbackfunction
corpo comlet compressedImageBlob = new File([blob])
.Adicione novas entradas ocultas ou envie via javascript . O servidor não precisa decodificar nada.
Verifique https://javascript.info/binary para todas as informações. Eu descobri a solução depois de ler este capítulo.
Código:
Este código parece muito menos assustador do que as outras respostas.
Atualizar:
É preciso colocar tudo dentro
img.onload
. Caso contráriocanvas
, não será possível obter a largura e a altura da imagem corretamente conforme o tempocanvas
é atribuído.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
teste de compressão de arquivo comimage/jpeg
conjunto de argumentos.|0.9| 777KB | |0.8| 383KB | |0.7| 301KB | |0.6| 251KB | |0.5| 219kB |
fonte
Para compactação de imagens JPG, você pode usar a melhor técnica de compactação chamada JIC (Javascript Image Compression). Isso certamente ajudará você -> https://github.com/brunobar79/JIC
fonte
Eu melhorei a função de cabeça para ser esta:
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); }); };
o uso dele:
apreciar ;)
fonte