HTML5 Pra-ubah ukuran gambar sebelum mengunggah

181

Ini penggaruk mie.

Mengingat kami memiliki penyimpanan lokal HTML5 dan xhr v2 dan apa yang tidak. Saya bertanya-tanya apakah ada yang bisa menemukan contoh kerja atau bahkan hanya memberi saya ya atau tidak untuk pertanyaan ini:

Apakah mungkin untuk pra-ukuran gambar menggunakan penyimpanan lokal baru (atau apa pun), sehingga pengguna yang tidak memiliki petunjuk tentang mengubah ukuran gambar dapat menyeret gambar 10mb mereka ke situs web saya, itu mengubah ukurannya menggunakan penyimpanan lokal baru dan MAKA unggah dengan ukuran lebih kecil.

Saya tahu betul Anda dapat melakukannya dengan Flash, applet Java, X aktif ... Pertanyaannya adalah apakah Anda dapat melakukannya dengan Javascript + Html5.

Menantikan tanggapan yang satu ini.

Ta untuk saat ini.

Jimmyt1988
sumber

Jawaban:

181

Ya, gunakan File API , maka Anda dapat memproses gambar dengan elemen kanvas .

Posting blog Mozilla Hacks ini memandu Anda melalui sebagian besar proses. Untuk referensi, inilah kode sumber yang dikumpulkan dari posting blog:

// 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
robertc
sumber
6
Kenapa terima kasih tuan yang baik. Saya akan bermain malam ini ... Dengan file api itu. Saya berhasil mengunggah drag and drop dan saya menyadari ini juga akan menjadi fitur yang sangat bagus untuk disertakan. Hura.
Jimmyt1988
3
Bukankah kita harus menggunakan mycanvas.toDataURL ini ("image / jpeg", 0,5) untuk memastikan kualitasnya dibawa untuk mengurangi ukuran file. Saya menemukan komentar ini di posting blog mozilla dan melihat resolusi bug di sini bugzilla.mozilla.org/show_bug.cgi?id=564388
sbose
33
Anda harus menunggu onloadpada gambar (lampirkan handler sebelum pengaturan src), karena browser diperbolehkan untuk melakukan decoding secara asinkron dan piksel mungkin tidak tersedia sebelumnya drawImage.
Kornel
6
Anda mungkin ingin memasukkan kode kanvas ke fungsi onload filereader - ada kemungkinan gambar besar tidak akan dimuat sebelum Anda ctx.drawImage()mendekati akhir.
Greg
4
hati-hati, Anda perlu sihir tambahan untuk membuatnya bekerja di iOS: stackoverflow.com/questions/11929099/…
flo850
107

Saya mengatasi masalah ini beberapa tahun yang lalu dan mengunggah solusi saya ke github sebagai https://github.com/rossturner/HTML5-ImageUploader

jawaban robertc menggunakan solusi yang diusulkan dalam posting blog Mozilla Hacks , namun saya menemukan ini memberikan kualitas gambar yang sangat buruk ketika mengubah ukuran ke skala yang bukan 2: 1 (atau beberapa darinya). Saya mulai bereksperimen dengan algoritme pengubahan ukuran gambar yang berbeda, meskipun sebagian besar pada akhirnya cukup lambat atau kualitasnya tidak terlalu bagus.

Akhirnya saya datang dengan solusi yang saya percaya dijalankan dengan cepat dan memiliki kinerja yang sangat bagus juga - sebagai solusi Mozilla menyalin dari 1 kanvas ke karya lain dengan cepat dan tanpa kehilangan kualitas gambar pada rasio 2: 1, diberikan target x piksel lebar dan tinggi piksel y , saya menggunakan metode pengubahan ukuran kanvas ini sampai gambar berada di antara x dan 2 x , dan y dan 2 y . Pada titik ini saya kemudian beralih ke pengubahan ukuran gambar algoritmik untuk "langkah" akhir pengubahan ukuran ke ukuran target. Setelah mencoba beberapa algoritma yang berbeda, saya memilih interpolasi bilinear yang diambil dari blog yang tidak online lagi tetapi dapat diakses melalui Internet Archive, yang memberikan hasil yang baik, inilah kode yang berlaku:

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

Ini skala gambar ke lebar config.maxWidth, mempertahankan rasio aspek asli. Pada saat pengembangan ini bekerja pada iPad / iPhone Safari selain browser desktop utama (IE9 +, Firefox, Chrome) jadi saya berharap ini akan tetap kompatibel mengingat penggunaan HTML5 yang lebih luas saat ini. Perhatikan bahwa panggilan canvas.toDataURL () menggunakan tipe mime dan kualitas gambar yang akan memungkinkan Anda untuk mengontrol kualitas dan format file output (berpotensi berbeda untuk input jika Anda mau).

Satu-satunya hal yang tidak dibahas adalah mempertahankan informasi orientasi, tanpa sepengetahuan metadata ini, gambar diubah ukurannya dan disimpan apa adanya, kehilangan metadata apa pun di dalam gambar untuk orientasi yang berarti bahwa gambar yang diambil pada perangkat tablet "terbalik" adalah ditampilkan seperti itu, meskipun mereka akan terbalik di jendela bidik kamera perangkat. Jika ini yang menjadi perhatian, posting blog ini memiliki panduan dan contoh kode yang baik tentang bagaimana mencapainya, yang saya yakin dapat diintegrasikan dengan kode di atas.

Ross Taylor-Turner
sumber
4
Memberi Anda hadiah karena saya merasa itu menambah banyak jawaban, harus mendapatkan hal-hal orientasi terintegrasi :)
Sam Saffron
3
Jiwa yang sangat membantu kini telah bergabung dalam beberapa fungsionalitas untuk metadata orientasi :)
Ross Taylor-Turner
26

Koreksi ke atas:

<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>
Justin Levene
sumber
2
Bagaimana Anda menangani bagian PHP setelah Anda menambahkannya ke FormData ()? Anda tidak akan mencari $ _FILES ['name'] ['tmp_name'] [$ i], misalnya? Saya mencoba if (isset ($ _ POST ['image']))) ... tetapi dataurltidak ada.
denikov
2
itu tidak berfungsi di firefox saat pertama kali Anda mencoba mengunggah gambar. Lain kali Anda mencobanya berfungsi. Bagaimana memperbaikinya?
Fred
kembali jika file array kosong atau kosong.
Sheelpriy
2
Di Chrome ketika mencoba mengakses img.widthdan img.heightawalnya mereka 0. Anda harus memasukkan sisa fungsi ke dalamimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo
2
@Justin dapatkah Anda mengklarifikasi "di atas", seperti, di mana posting ini merupakan koreksi? Cheers
Martin
16

Modifikasi ke jawaban oleh Justin yang bekerja untuk saya:

  1. Menambahkan img.onload
  2. Luaskan permintaan POST dengan contoh nyata

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);
}
Akan
sumber
2
info orientasi (exif) hilang
Konstantin Vahrushev
8

Jika Anda tidak ingin menemukan kembali roda Anda dapat mencoba plupload.com

nanospeck
sumber
3
Saya juga menggunakan plupload untuk mengubah ukuran sisi klien. Hal terbaik tentang itu adalah dapat menggunakan flash, html5, silverlight, dll. Apa pun yang tersedia, karena setiap pengguna memiliki pengaturan yang berbeda. Jika Anda hanya ingin HTML5, maka saya pikir itu tidak akan berfungsi pada beberapa browser lama dan Opera.
jnl
6

Naskah

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;
}
jales cardoso
sumber
1
Siapa pun yang menemukan posting ini, jawaban berdasarkan janji ini jauh lebih dapat diandalkan menurut saya. Saya mengonversi ke JS normal dan itu bekerja seperti pesona.
gbland777
5
fd.append("image", dataurl);

Ini tidak akan berfungsi. Di sisi PHP Anda tidak dapat menyimpan file dengan ini.

Gunakan kode ini sebagai gantinya:

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
robot
sumber
5

Mengubah ukuran gambar dalam elemen kanvas umumnya ide buruk karena menggunakan interpolasi kotak termurah. Gambar yang dihasilkan terlihat menurun kualitasnya. Saya akan merekomendasikan menggunakan http://nodeca.github.io/pica/demo/ yang dapat melakukan transformasi Lanczos sebagai gantinya. Halaman demo di atas menunjukkan perbedaan antara pendekatan kanvas dan Lanczos.

Ini juga menggunakan pekerja web untuk mengubah ukuran gambar secara paralel. Ada juga implementasi WEBGL.

Ada beberapa resizer gambar online yang menggunakan pica untuk melakukan pekerjaan itu, seperti https://myimageresizer.com

Ivan Nikitin
sumber
5

Jawaban yang diterima bekerja dengan baik, tetapi logika pengubahan ukuran mengabaikan kasus di mana gambar lebih besar daripada maksimum hanya dalam satu sumbu (misalnya, tinggi> maxHeight tetapi lebar <= maxWidth).

Saya pikir kode berikut menangani semua kasus dengan cara yang lebih mudah dan fungsional (abaikan anotasi jenis naskah jika menggunakan javascript biasa):

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 };
  }
Talas
sumber
0

Anda dapat menggunakan dropzone.js jika Anda ingin menggunakan pengelola unggahan yang sederhana dan mudah dengan pengubahan ukuran sebelum fungsi unggahan.

Ini memiliki fungsi pengubahan ukuran bawaan, tetapi Anda dapat menyediakannya sendiri jika diinginkan.

Michał Zalewski
sumber