Как сжать изображение с помощью Javascript в браузере?
TL; DR;
есть ли способ сжать изображение (в основном jpeg, png и gif) непосредственно на стороне браузера, прежде чем загружать его ? Я уверен, что JavaScript может это сделать, но я не могу найти способ достичь этого.
Вот полный сценарий, который я хотел бы реализовать:
- пользователь переходит на мой сайт, и выбрать изображение через
input type="file"элемент - это изображение извлекается с помощью JavaScript, мы делаем некоторые проверки, такие как правильный формат файла, максимальный размер файла и т. д.
- если все в порядке, предварительный просмотр изображения отображается на странице,
- пользователь может выполнить некоторые основные операции, такие как поворот изображения на 90°/-90°, обрезать его после заданного соотношения и т. д., или пользователь может загрузить другое изображение и вернуться к шагу 1,
- когда пользователь удовлетворен, отредактированное изображение затем сжимается и" сохраняется " локально (не сохраняется в файл, но в браузере память / страница), -
- пользователь заполняет форму с данными, как имя, возраст и т. д.
- пользователь нажимает на кнопку "Готово", после чего на сервер отправляется форма, содержащая данные + сжатый образ (без AJAX),
полный процесс до последнего шага должен быть выполнен на стороне клиента и должен быть совместим с последними Chrome и Firefox, Safari 5+ и IE 8+. Если возможно, следует использовать только JavaScript (но я уверен, что это не так вероятный.)
Я ничего не кодировал прямо сейчас, но я уже думал об этом. Чтение файлов локально возможно через File API, предварительный просмотр и редактирование изображений может быть сделано с помощью холст элемент, но Я не могу найти способ сделать сжатие изображений часть.
согласно html5please.com и caniuse.com, поддержка этих браузеров довольно сложно (благодаря IE), но может быть сделано с помощью полифилл, такие как FlashCanvas и FileReader.
на самом деле, цель состоит в том, чтобы уменьшить размер файла, поэтому я вижу сжатие изображения как решение. Но я знаю, что загруженные изображения будут отображаться на моем веб-сайте каждый раз в одном и том же месте, и я знаю размер этой области отображения (например. 200x400). Таким образом, я мог бы изменить размер изображения, чтобы соответствовать этим размерам, тем самым уменьшая размер файла. Я понятия не имею, какова будет степень сжатия для этого метод.
что вы думаете ? У вас есть какие-нибудь советы, чтобы сказать мне ? Вы знаете, как сжать изображение на стороне браузера в JavaScript ? Спасибо за ваши ответы.
6 ответов:
короче:
- читать файлы с помощью HTML5 FileReader API С.readAsArrayBuffer
- создать E Blob с данными файла и получить его url-адрес с
@PsychoWoods ' ответ хороший. Я хотел бы предложить свое собственное решение. Эта функция Javascript принимает URL-адрес данных изображения и ширину, масштабирует его до новой ширины и возвращает новый URL-адрес данных.
// 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; }этот код можно использовать везде, где у вас есть URL-адрес данных и требуется URL-адрес данных для уменьшенного изображения.
Я вижу две вещи, отсутствующие в других ответах:
canvas.toBlob(при наличии) является более производительным, чемcanvas.toDataURL, а также асинхронные.- файл -> изображение -> холст -> преобразование файлов теряет данные EXIF; в частности, данные о вращении изображения обычно устанавливаются современными телефонами / планшетами.
следующий скрипт имеет дело с обеими точками:
// 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); }пример использования:
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: согласно комментарию Mr Me на этот ответ, похоже, что сжатие теперь доступно для форматов JPG/WebP ( см. https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL).
насколько я знаю, вы не можете сжимать изображения с помощью холста, вместо этого вы можете изменить его размер. Использование холста.toDataURL не позволит вам выбрать степень сжатия для использования. Вы можете взглянуть на canimage, который делает именно то, что вы хотите : https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
на самом деле, часто достаточно просто изменить размер изображения, чтобы уменьшить его размер, но если вы хотите пойти дальше, вам придется использовать недавно введенный файл метода.readAsArrayBuffer, чтобы получить буфер, содержащий данные изображения.
затем просто используйте DataView для чтения его содержимого в соответствии со спецификацией формата изображения (http://en.wikipedia.org/wiki/JPEG или http://en.wikipedia.org/wiki/Portable_Network_Graphics).
Это будет трудно иметь дело со сжатием данных изображения, но это хуже попытка. С другой стороны, вы можете попытаться удалить заголовки PNG или данные jpeg exif, чтобы сделать ваше изображение меньше, это должно быть проще сделать.
вам нужно будет создать другой DataWiew на другом буфере и заполнить его отфильтрованным содержимым изображения. Затем вам просто нужно будет закодировать содержимое вашего изображения в DataURI, используя окно.онлайн.
Дайте мне знать, если вы реализуете что-то подобное, будет интересно пройти через код.
у меня была проблема с
downscaleImage()функция, опубликованная выше @daniel-allen-langdon в этомimage.widthиimage.heightсвойства не доступны сразу, потому что загрузка изображения асинхронные.пожалуйста, смотрите обновленный пример TypeScript ниже, который учитывает это, использует
asyncфункции, и изменяет размер изображения на основе самого длинного размера, а не только ширину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; } }
для сжатия изображений JPG вы можете использовать лучший метод сжатия, называемый JIC (Сжатие изображений Javascript)это определенно поможет вам -->https://github.com/brunobar79/J-I-C
Comments