Я вирішив цю проблему кілька років тому і завантажив своє рішення в github як https://github.com/rossturner/HTML5-ImageUploader
Відповідь robertc використовує рішення, запропоноване в публікації блогу Mozilla Hacks , однак я виявив, що це дало дійсно низьку якість зображення при зміні розміру, який не був 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 сьогодні. Зауважте, що виклик canvas.toDataURL () має тип mime та якість зображення, що дозволить вам контролювати якість та формат вихідного файлу (можливо, інший, якщо ви бажаєте).
Єдиний момент, який це не стосується, - це збереження інформації про орієнтацію, без знання цих метаданих зображення змінюється за розміром і зберігається як є, втрачаючи будь-які метадані в межах зображення для орієнтації, тобто зображення, зроблені на планшетному пристрої "догори дном", надані як такі, хоча вони були б перекинуті на видошукач камери пристрою. Якщо це викликає занепокоєння, у цій публікації в блозі є хороший посібник та приклади коду, як це досягти, і я впевнений, що він може бути інтегрований до наведеного вище коду.