HTML5 попередньо зміни розміру зображень перед завантаженням


181

Ось подряпина з локшиною.

Маючи на увазі, у нас є локальне зберігання HTML5 та xhr v2, а що ні. Мені було цікаво, чи може хтось знайти робочий приклад чи навіть просто дати мені це чи так?

Чи можна попередньо розмістити зображення за допомогою нового локального сховища (або будь-якого іншого), щоб користувач, який не має поняття щодо зміни розміру зображення, міг перетягнути їх зображення на 10 Мб на мій веб-сайт, змінити його розмір за допомогою нового локального сховища та ЦЕ завантажте його меншим розміром.

Я добре знаю, що ви можете це зробити з Flash, апплетами Java, активним X ... Питання в тому, чи можна робити з Javascript + Html5.

З нетерпінням чекаю на цю відповідь.

Та поки що.

Відповіді:


181

Так, використовуйте API файлу , тоді ви можете обробляти зображення елементом "canvas" .

Ця публікація в блозі Mozilla Hacks проводить вас протягом більшої частини процесу. Для ознайомлення ось зібраний вихідний код із публікації блогу:

// 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

6
Чому дякую, добрий пане. Я буду мати гру сьогодні ввечері ... З файлом api, який є. Я отримав перетягування та завантаження на роботу, і я зрозумів, що це також буде дуже приємною функцією. Іппі.
Jimmyt1988

3
Чи не потрібно нам використовувати цей mycanvas.toDataURL ("image / jpeg", 0,5), щоб переконатися в якості якості, щоб зменшити розмір файлу. Я натрапив на цей коментар до публікації в блозі mozilla і побачив тут вирішення помилок bugzilla.mozilla.org/show_bug.cgi?id=564388
sbose

33
Вам слід зачекати onloadна зображенні (приєднайте обробник до налаштування src), оскільки браузер може виконувати декодування асинхронно, а пікселі можуть бути недоступними раніше drawImage.
Корнель

6
Ви можете поставити код канви у функцію завантаження файлового читця - є ймовірність, що великі зображення не будуть завантажені перед вами ctx.drawImage()найближчим кінцем.
Грег

4
будьте обережні, вам потрібно additionnal магію , щоб змусити його працювати на IOS: stackoverflow.com/questions/11929099 / ...
flo850

107

Я вирішив цю проблему кілька років тому і завантажив своє рішення в 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 та якість зображення, що дозволить вам контролювати якість та формат вихідного файлу (можливо, інший, якщо ви бажаєте).

Єдиний момент, який це не стосується, - це збереження інформації про орієнтацію, без знання цих метаданих зображення змінюється за розміром і зберігається як є, втрачаючи будь-які метадані в межах зображення для орієнтації, тобто зображення, зроблені на планшетному пристрої "догори дном", надані як такі, хоча вони були б перекинуті на видошукач камери пристрою. Якщо це викликає занепокоєння, у цій публікації в блозі є хороший посібник та приклади коду, як це досягти, і я впевнений, що він може бути інтегрований до наведеного вище коду.


4
Я нагородив вас щедротою, тому що я відчуваю, що це додає кучу відповідь, але я повинен інтегрувати ці орієнтаційні речі, хоча :)
Сем Шафрон

3
Дуже корисна душа тепер об’єдналася в деякій функціональності для метаданих орієнтації :)
Росс Тейлор-Тернер

26

Виправлення вище:

<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>

2
Як ви обробляєте її частину PHP після додавання її до FormData ()? Ви б не шукали, наприклад, $ _FILES ['name'] ['tmp_name'] [$ i]? Я намагаюся, якщо (isset ($ _ POST ['image'])) ..., але dataurlне там.
denikov

2
вона не працює на Firefox при першому спробі завантаження зображення. Наступного разу, коли ви спробуєте, це працює. Як це виправити?
Фред

повернути, якщо масив файлу є нульовим або порожнім.
Sheelpriy

2
У Chrome при спробі доступу img.widthта img.heightспочатку вони є 0. Ви повинні покласти решту функції всерединуimg.addEventListener('load', function () { // now the image has loaded, continue... });
Ініго

2
@Justin Ви можете уточнити "вищесказане", як у, що це за виправлення? Ура
Мартін

16

Модифікація відповіді Джастіна, яка працює на мене:

  1. Додано img.onload
  2. Розгорніть запит 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);
}

2
інформація про орієнтацію (exif) втрачається
Костянтин Варушев

8

Якщо ви не хочете винаходити колесо, ви можете спробувати plupload.com


3
Я також використовую plupload для зміни розміру на стороні клієнта. Найкраще в ньому - це використання flash, html5, silverlight тощо. Що б у користувача не було, тому що кожен користувач має різні налаштування. Якщо ви хочете лише html5, то я думаю, що це не буде працювати на деяких старих браузерах та Opera.
jnl

6

Машинопис

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;
}

1
Кожен, хто може натрапити на цю посаду, на цю обіцянку відповідь є набагато надійнішою на мою думку. Я перейшов у звичайний JS, і це працює як шарм.
gbland777

5
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

5

Зміна розміру зображень у елементі полотна, як правило, погана ідея, оскільки використовується найдешевша інтерполяція коробки. Отримане зображення помітно погіршує якість. Я рекомендую використовувати http://nodeca.github.io/pica/demo/, який може замість цього перетворити Lanczos. На демонстраційній сторінці вище показана різниця між підходом canvas та Lanczos.

Він також використовує веб-працівників для паралельного розміру зображень. Також є реалізація WEBGL.

Є кілька онлайн-резизерів зображень, які використовують pica для виконання своєї роботи, як-от https://myimageresizer.com


5

Прийнята відповідь чудово працює, але логіка зміни розміру ігнорує випадок, коли зображення є більшим за максимальне лише в одній з осей (наприклад, висота> maxHeight, але ширина <= maxWidth).

Я думаю, що наступний код забезпечує всі випадки більш прямого та функціонального способу (ігноруйте анотації типу машинопису, якщо використовуєте звичайний 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 };
  }

0

Ви можете використовувати dropzone.js, якщо хочете скористатися простим та легким менеджером завантаження із зміною розміру перед функціями завантаження.

Він має вбудовані функції зміни розміру, але ви можете надати свої, якщо хочете.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.