HTML5 предварительно изменить размер изображения перед загрузкой
вот лапша скребок.
имея в виду, что у нас есть локальное хранилище HTML5 и xhr v2, а что нет. Мне было интересно, может ли кто-нибудь найти рабочий пример или даже просто дать мне да или нет для этого вопроса:
можно ли предварительно изменить размер изображения с помощью нового локального хранилища (или что-то еще), чтобы пользователь, который не имеет понятия о изменении размера изображения, мог перетащить свое изображение 10 Мб на мой сайт, он изменил его размер с помощью нового localStorage, а затем загрузить его в меньшем размере.
Я прекрасно знаю, что вы можете сделать это с помощью Flash, Java-апплетов, active X... Вопрос в том, если вы можете сделать с Javascript + Html5.
с нетерпением ждем ответа на этот вопрос.
Ta на данный момент.
10 ответов:
да, используйте File API, то вы можете обрабатывать изображения с Canvas элемент.
это Mozilla хаки блог пост проведет вас через процесс. Для справки вот собранный исходный код из сообщения в блоге:
// from an input element var filesToUpload = input.files; var file = filesToUpload[0]; var img = document.createElement("img"); var reader = new FileReader(); reader.onload = function(e) {img.src = e.target.result} reader.readAsDataURL(file); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); var MAX_WIDTH = 800; var MAX_HEIGHT = 600; var width = img.width; var height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); var dataurl = canvas.toDataURL("image/png"); //Post dataurl to the server with AJAX
Я решил эту проблему несколько лет назад и загрузил свое решение в github как https://github.com/rossturner/HTML5-ImageUploader
ответ robertc использует решение, предложенное в Mozilla хаки сообщение в блоге, однако я обнаружил, что это дало действительно плохое качество изображения при изменении размера до масштаба, который не был 2:1 (или несколько из них). Я начал экспериментировать с различными алгоритмами изменения размера изображения, хотя большинство из них оказались довольно медленно или еще не были большими по качеству также.
наконец, я придумал решение, которое, как я считаю, выполняется быстро и имеет довольно хорошую производительность - так как решение Mozilla для копирования с 1 холста на другой работает быстро и без потери качества изображения в соотношении 2:1, учитывая цель x пикселей в ширину и y пикселей в высоту, я использую этот метод изменения размера холста, пока изображение не находится между x и 2 x, и y и 2 y. На этом этапе я затем перейду к алгоритмическому изменению размера изображения для последнего "шага" изменения размера до целевого размера. После попытки нескольких различных алгоритмов я остановился на билинейной интерполяции, взятой из блога, который больше не находится в сети, но доступ через интернет-архив, что дает хорошие результаты, вот применимый код:
ImageUploader.prototype.scaleImage = function(img, completionCallback) { var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); while (canvas.width >= (2 * this.config.maxWidth)) { canvas = this.getHalfScaleCanvas(canvas); } if (canvas.width > this.config.maxWidth) { canvas = this.scaleCanvasWithAlgorithm(canvas); } var imageData = canvas.toDataURL('image/jpeg', this.config.quality); this.performUpload(imageData, completionCallback); }; ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) { var scaledCanvas = document.createElement('canvas'); var scale = this.config.maxWidth / canvas.width; scaledCanvas.width = canvas.width * scale; scaledCanvas.height = canvas.height * scale; var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height); this.applyBilinearInterpolation(srcImgData, destImgData, scale); scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0); return scaledCanvas; }; ImageUploader.prototype.getHalfScaleCanvas = function(canvas) { var halfCanvas = document.createElement('canvas'); halfCanvas.width = canvas.width / 2; halfCanvas.height = canvas.height / 2; halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height); return halfCanvas; }; ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) { function inner(f00, f10, f01, f11, x, y) { var un_x = 1.0 - x; var un_y = 1.0 - y; return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y); } var i, j; var iyv, iy0, iy1, ixv, ix0, ix1; var idxD, idxS00, idxS10, idxS01, idxS11; var dx, dy; var r, g, b, a; for (i = 0; i < destCanvasData.height; ++i) { iyv = i / scale; iy0 = Math.floor(iyv); // Math.ceil can go over bounds iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv)); for (j = 0; j < destCanvasData.width; ++j) { ixv = j / scale; ix0 = Math.floor(ixv); // Math.ceil can go over bounds ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv)); idxD = (j + destCanvasData.width * i) * 4; // matrix to vector indices idxS00 = (ix0 + srcCanvasData.width * iy0) * 4; idxS10 = (ix1 + srcCanvasData.width * iy0) * 4; idxS01 = (ix0 + srcCanvasData.width * iy1) * 4; idxS11 = (ix1 + srcCanvasData.width * iy1) * 4; // overall coordinates to unit square dx = ixv - ix0; dy = iyv - iy0; // I let the r, g, b, a on purpose for debugging r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy); destCanvasData.data[idxD] = r; g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy); destCanvasData.data[idxD + 1] = g; b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy); destCanvasData.data[idxD + 2] = b; a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy); destCanvasData.data[idxD + 3] = a; } } };
это масштабирует изображение до ширины
config.maxWidth
сохранение исходные пропорции. Во время разработки это работало на iPad/iPhone Safari в дополнение к основным настольным браузерам (IE9+, Firefox, Chrome), поэтому я ожидаю, что он все равно будет совместим с учетом более широкого использования HTML5 сегодня. Обратите внимание, что холст.вызов toDataURL () принимает тип mime и качество изображения, что позволит вам контролировать качество и формат выходного файла (потенциально отличается от ввода, если вы хотите).единственная точка, которую это не покрывает, - это сохранение ориентации информация, без знания этих метаданных изображение изменяется и сохраняется как есть, теряя любые метаданные в изображении для ориентации, что означает, что изображения, сделанные на планшетном устройстве "вверх ногами", были визуализированы как таковые, хотя они были бы перевернуты в видоискателе камеры устройства. Если это вызывает беспокойство,этот блог имеет хорошее руководство и примеры кода о том, как это сделать, которые, я уверен, могут быть интегрированы в приведенный выше код.
коррекция выше:
<img src="" id="image"> <input id="input" type="file" onchange="handleFiles()"> <script> function handleFiles() { var filesToUpload = document.getElementById('input').files; var file = filesToUpload[0]; // Create an image var img = document.createElement("img"); // Create a file reader var reader = new FileReader(); // Set the image once loaded into file reader reader.onload = function(e) { img.src = e.target.result; var canvas = document.createElement("canvas"); //var canvas = $("<canvas>", {"id":"testing"})[0]; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); var MAX_WIDTH = 400; var MAX_HEIGHT = 300; var width = img.width; var height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); var dataurl = canvas.toDataURL("image/png"); document.getElementById('image').src = dataurl; } // Load files into file reader reader.readAsDataURL(file); // Post the data /* var fd = new FormData(); fd.append("name", "some_filename.jpg"); fd.append("image", dataurl); fd.append("info", "lah_de_dah"); */ }</script>
модификации ответ Джастина это работает для меня:
- добавлено img.onload
- разверните запрос POST с реальным примером
function handleFiles() { var dataurl = null; var filesToUpload = document.getElementById('photo').files; var file = filesToUpload[0]; // Create an image var img = document.createElement("img"); // Create a file reader var reader = new FileReader(); // Set the image once loaded into file reader reader.onload = function(e) { img.src = e.target.result; img.onload = function () { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); var MAX_WIDTH = 800; var MAX_HEIGHT = 600; var width = img.width; var height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); dataurl = canvas.toDataURL("image/jpeg"); // Post the data var fd = new FormData(); fd.append("name", "some_filename.jpg"); fd.append("image", dataurl); fd.append("info", "lah_de_dah"); $.ajax({ url: '/ajax_photo', data: fd, cache: false, contentType: false, processData: false, type: 'POST', success: function(data){ $('#form_photo')[0].reset(); location.reload(); } }); } // img.onload } // Load files into file reader reader.readAsDataURL(file); }
Если вы не хотите изобретать велосипед, вы можете попробовать plupload.com
fd.append("image", dataurl);
это не сработает. На стороне PHP вы не можете сохранить файл с этим.
используйте этот код вместо:
var blobBin = atob(dataurl.split(',')[1]); var array = []; for(var i = 0; i < blobBin.length; i++) { array.push(blobBin.charCodeAt(i)); } var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"}); fd.append("image", file); // blob file
принятый ответ Отлично работает, но логика изменения размера игнорирует случай, когда изображение больше максимума только в одной из осей (например, height > maxHeight но width
Я думаю, что следующий код заботится обо всех случаях более прямолинейным и функциональным способом (игнорируйте аннотации типа typescript при использовании простого javascript):
private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} { if (width <= maxWidth && height <= maxHeight) return { width, height }; else if (width / maxWidth > height / maxHeight) return { width: maxWidth, height: height * maxWidth / width}; else return { width: width * maxHeight / height, height: maxHeight }; }
изменение размера изображений в элементе холста, как правило, плохая идея, так как он использует самую дешевую интерполяцию коробки. Полученное изображение заметно ухудшается в качестве. Я бы рекомендовал использовать http://nodeca.github.io/pica/demo/ который может выполнять преобразование Ланцоса вместо этого. Демонстрационная страница выше показывает разницу между подходами canvas и Lanczos.
Он также использует веб-работников для изменения размера изображений в параллельном режиме. Существует также реализация WEBGL.
там некоторые онлайн-размеры изображений, которые используют pica для выполнения этой работы, например https://myimageresizer.com
можно использовать dropzone.js Если вы хотите использовать простой и легкий менеджер загрузки с изменением размера перед загрузкой функций.
Он имеет встроенные функции изменения размера, но вы можете предоставить свой, Если вы хотите.
Typescript
async resizeImg(file: Blob): Promise<Blob> { let img = document.createElement("img"); img.src = await new Promise<any>(resolve => { let reader = new FileReader(); reader.onload = (e: any) => resolve(e.target.result); reader.readAsDataURL(file); }); await new Promise(resolve => img.onload = resolve) let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); let MAX_WIDTH = 1000; let MAX_HEIGHT = 1000; let width = img.naturalWidth; let height = img.naturalHeight; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); let result = await new Promise<Blob>(resolve => { canvas.toBlob(resolve, 'image/jpeg', 0.95); }); return result; }