Gunakan HTML5 untuk mengubah ukuran gambar sebelum mengupload

102

Saya telah menemukan beberapa posting berbeda dan bahkan pertanyaan tentang stackoverflow menjawab pertanyaan ini. Saya pada dasarnya menerapkan hal yang sama seperti posting ini .

Jadi inilah masalah saya. Saat saya mengupload foto, saya juga harus menyerahkan formulir lainnya. Ini html saya:

<form id="uploadImageForm" enctype="multipart/form-data">
  <input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
  <input id="name" value="#{name}" />
  ... a few more inputs ... 
</form>

Sebelumnya, saya tidak perlu mengubah ukuran gambar, jadi javascript saya terlihat seperti ini:

window.uploadPhotos = function(url){
    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

Ini semua bekerja dengan baik ... sekarang saya perlu mengubah ukuran gambar ... bagaimana saya bisa mengganti gambar di formulir sehingga yang diubah ukurannya diposting dan bukan gambar yang diunggah?

window.uploadPhotos = function(url){

    var resizedImage;

    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 1200,
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                resizedImage = canvas.toDataURL('image/jpeg');
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }


   // TODO: Need some logic here to switch out which photo is being posted...

    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

Saya telah berpikir untuk memindahkan input file keluar dari formulir dan memiliki input tersembunyi dalam bentuk yang saya setel nilainya ke nilai gambar yang diubah ukurannya ... Tapi saya bertanya-tanya apakah saya bisa mengganti gambar itu sudah dalam bentuk.

ferics2
sumber
Apakah Anda bekerja dengan bahasa sisi server atau hanya html5 dan javascript?
luke2012
1
@ luke2012 java server side
ferics2
Mungkin memotong gambar di sisi klien menggunakan sesuatu seperti jCrop kemudian mengirim koordinat ke sisi server dan memotongnya. yaituBufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height);
luke2012
4
@ luke2012 intinya adalah untuk mengurangi ukuran gambar SEBELUM mengirimnya ke server.
ferics2
Lihatlah sumber js dari pandamatak.com/people/anand/test/crop tampaknya serupa ..
luke2012

Jawaban:

161

Inilah yang akhirnya saya lakukan dan berhasil dengan baik.

Pertama saya memindahkan input file ke luar formulir sehingga tidak dikirimkan:

<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
    <input id="name" value="#{name}" />
    ... a few more inputs ... 
</form>

Kemudian saya mengubah uploadPhotosfungsi untuk hanya menangani pengubahan ukuran:

window.uploadPhotos = function(url){
    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 544,// TODO : pull max size from a site config
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                var dataUrl = canvas.toDataURL('image/jpeg');
                var resizedImage = dataURLToBlob(dataUrl);
                $.event.trigger({
                    type: "imageResized",
                    blob: resizedImage,
                    url: dataUrl
                });
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }
};

Seperti yang Anda lihat, saya menggunakan canvas.toDataURL('image/jpeg');untuk mengubah gambar yang diubah ukurannya menjadi dataUrl dan kemudian saya memanggil fungsi dataURLToBlob(dataUrl);untuk mengubah dataUrl menjadi gumpalan yang kemudian dapat saya tambahkan ke formulir. Saat blob dibuat, saya memicu peristiwa khusus. Berikut fungsi untuk membuat blob:

/* Utility function to convert a canvas to a BLOB */
var dataURLToBlob = function(dataURL) {
    var BASE64_MARKER = ';base64,';
    if (dataURL.indexOf(BASE64_MARKER) == -1) {
        var parts = dataURL.split(',');
        var contentType = parts[0].split(':')[1];
        var raw = parts[1];

        return new Blob([raw], {type: contentType});
    }

    var parts = dataURL.split(BASE64_MARKER);
    var contentType = parts[0].split(':')[1];
    var raw = window.atob(parts[1]);
    var rawLength = raw.length;

    var uInt8Array = new Uint8Array(rawLength);

    for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], {type: contentType});
}
/* End Utility function to convert a canvas to a BLOB      */

Terakhir, berikut adalah event handler saya yang mengambil blob dari event khusus, menambahkan formulir dan kemudian mengirimkannya.

/* Handle image resized events */
$(document).on("imageResized", function (event) {
    var data = new FormData($("form[id*='uploadImageForm']")[0]);
    if (event.blob && event.url) {
        data.append('image_data', event.blob);

        $.ajax({
            url: event.url,
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: function(data){
               //handle errors...
            }
        });
    }
});
ferics2
sumber
1
Kode yang bagus, setelah beberapa mengutak-atik dan memperbaiki beberapa kesalahan (saya tidak ingat persis apa dan yang mana) saya membuatnya berfungsi. Ngomong-ngomong, menurutku saat kamu menulis yang width *= max_size / width;kamu maksud sebenarnya width *= max_size / height;.
user1111929
2
Apakah kode ini berfungsi di perangkat seluler? Di bawah iOS dan Android?
planewalker
4
@planewalker Sebenarnya saya menulis kode khusus untuk perangkat seluler. Untuk mengurangi penggunaan data.
ferics2
2
@planewalker, saya menguji di iOS ketika saya menulis ini. Semoga berhasil untuk Anda.
ferics2
3
Selamat dan terima kasih telah berbagi! (Kesalahan kecil yang disebutkan orang lain tetapi tidak teridentifikasi ada di pemicu peristiwa pengubahan ukuran gambar. "Url: url" seharusnya menjadi "url: dataUrl")
Seoras
44

jika ada yang berminat saya sudah membuat versi skrip:

interface IResizeImageOptions {
  maxSize: number;
  file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
  const file = settings.file;
  const maxSize = settings.maxSize;
  const reader = new FileReader();
  const image = new Image();
  const canvas = document.createElement('canvas');
  const dataURItoBlob = (dataURI: string) => {
    const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
        atob(dataURI.split(',')[1]) :
        unescape(dataURI.split(',')[1]);
    const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    const max = bytes.length;
    const ia = new Uint8Array(max);
    for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
    return new Blob([ia], {type:mime});
  };
  const resize = () => {
    let width = image.width;
    let height = image.height;

    if (width > height) {
        if (width > maxSize) {
            height *= maxSize / width;
            width = maxSize;
        }
    } else {
        if (height > maxSize) {
            width *= maxSize / height;
            height = maxSize;
        }
    }

    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(image, 0, 0, width, height);
    let dataUrl = canvas.toDataURL('image/jpeg');
    return dataURItoBlob(dataUrl);
  };

  return new Promise((ok, no) => {
      if (!file.type.match(/image.*/)) {
        no(new Error("Not an image"));
        return;
      }

      reader.onload = (readerEvent: any) => {
        image.onload = () => ok(resize());
        image.src = readerEvent.target.result;
      };
      reader.readAsDataURL(file);
  })    
};

dan inilah hasil javascriptnya:

var resizeImage = function (settings) {
    var file = settings.file;
    var maxSize = settings.maxSize;
    var reader = new FileReader();
    var image = new Image();
    var canvas = document.createElement('canvas');
    var dataURItoBlob = function (dataURI) {
        var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
            atob(dataURI.split(',')[1]) :
            unescape(dataURI.split(',')[1]);
        var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var max = bytes.length;
        var ia = new Uint8Array(max);
        for (var i = 0; i < max; i++)
            ia[i] = bytes.charCodeAt(i);
        return new Blob([ia], { type: mime });
    };
    var resize = function () {
        var width = image.width;
        var height = image.height;
        if (width > height) {
            if (width > maxSize) {
                height *= maxSize / width;
                width = maxSize;
            }
        } else {
            if (height > maxSize) {
                width *= maxSize / height;
                height = maxSize;
            }
        }
        canvas.width = width;
        canvas.height = height;
        canvas.getContext('2d').drawImage(image, 0, 0, width, height);
        var dataUrl = canvas.toDataURL('image/jpeg');
        return dataURItoBlob(dataUrl);
    };
    return new Promise(function (ok, no) {
        if (!file.type.match(/image.*/)) {
            no(new Error("Not an image"));
            return;
        }
        reader.onload = function (readerEvent) {
            image.onload = function () { return ok(resize()); };
            image.src = readerEvent.target.result;
        };
        reader.readAsDataURL(file);
    });
};

penggunaan seperti:

resizeImage({
    file: $image.files[0],
    maxSize: 500
}).then(function (resizedImage) {
    console.log("upload resized image")
}).catch(function (err) {
    console.error(err);
});

atau ( async/ await):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
Santiago Hernández
sumber
Solusi yang sangat bagus, dapat menambahkan pengaturan tambahan untuk memungkinkan mengembalikan hasil sebagai base64 (melewati panggilan dataURIToBlob di akhir).
Chen
Saya mendapatkan kesalahan ini:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined
Owen M
1
Bagus. Saya harus mengubah unescape ke (<any> window] .unescape Saya juga menambahkan minSize.
raja robert
maxSizedalam byte atau kb?
Jagruti
1
Jawaban Anda membantu saya menjawab pertanyaan ini , terima kasih.
Syed