Yüklemeden önce bir resmi yeniden boyutlandırmak için HTML5 kullanın


102

Bu soruyu yanıtlayan stackoverflow hakkında birkaç farklı gönderi ve hatta soru buldum. Temelde bu yazı ile aynı şeyi uyguluyorum .

İşte benim sorunum. Fotoğrafı yüklediğimde, formun geri kalanını da göndermem gerekiyor. İşte html'im:

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

Önceden, resmi yeniden boyutlandırmam gerekmiyordu, bu yüzden javascript şuna benziyordu:

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

Bunların hepsi harika çalıştı ... şimdi resimleri yeniden boyutlandırmam gerektiğine göre ... formdaki resmi nasıl değiştirebilirim, böylece yeniden boyutlandırılan resim yayınlanır, yüklenen resim değil?

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

Dosya girişini formun dışına çıkarmayı ve değerini yeniden boyutlandırılan görüntünün değerine ayarladığım biçimde gizli bir girişe sahip olmayı düşündüm ... Ama merak ediyorum ki, zaten formda.


Herhangi bir sunucu tarafı dili ile mi yoksa sadece html5 ve javascript ile mi çalışıyorsunuz?
luke2012

1
@ luke2012 java sunucu tarafı
ferics2

Belki jCrop gibi bir şey kullanarak istemci tarafında görüntüyü kırpın, ardından koordinatları sunucu tarafına gönderin ve kırpın. ieBufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height);
luke2012

4
@ luke2012 Burada önemli olan, görüntü boyutunu sunucuya göndermeden ÖNCE küçültmektir.
ferics2

Pandamatak.com/people/anand/test/crop adresinin js kaynağına bir göz atın benzer görünüyor ..
luke2012

Yanıtlar:


161

İşte sonunda yaptığım şey ve harika çalıştı.

Önce dosya girişini formun dışına taşıdım, böylece gönderilmesin:

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

Ardından uploadPhotosişlevi yalnızca yeniden boyutlandırmayı işleyecek şekilde değiştirdim :

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

Gördüğünüz gibi canvas.toDataURL('image/jpeg');, yeniden boyutlandırılan resmi bir dataUrl adn olarak değiştirmek için kullanıyorum , ardından dataURLToBlob(dataUrl);dataUrl'yi daha sonra forma ekleyebileceğim bir blob'a dönüştürmek için işlevi çağırıyorum. Blob oluşturulduğunda özel bir olay tetiklerim. İşte blob oluşturma işlevi:

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

Son olarak, blob'u özel olaydan alan, formu ekleyen ve sonra gönderen olay işleyicim.

/* 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...
            }
        });
    }
});

1
Harika kod, biraz ince ayar yaptıktan ve bazı hataları düzelttikten sonra (tam olarak ne ve hangisini hatırlamıyorum) onu çalıştırdım. Bu arada, yazdığın width *= max_size / width;zaman gerçekten kastettiğini düşünüyorum width *= max_size / height;.
user1111929

2
Bu kod mobil cihazlarda çalışıyor mu? İOS ve Android altında mı?
planewalker

4
@planewalker Aslında kodu özellikle mobil cihazlar için yazdım. Veri kullanımını azaltmak için.
ferics2

2
@planewalker, bunu yazarken iOS üzerinde test yapıyordum. Umarım sizin için çalışır.
ferics2

3
Aferin ve paylaştığınız için teşekkürler! (Başkalarının bahsettiği ancak tanımlanmayan küçük hata, görüntü yeniden boyutlandırma olay tetikleyicisindedir. "Url: url", "url: dataUrl" olmalıdır)
Seoras

44

ilgilenen varsa bir typcript sürümü yaptım:

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

ve işte javascript sonucu:

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

kullanım şuna benzer:

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

veya ( async/ await):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")

Gerçekten güzel bir çözüm, sonucun base64 olarak döndürülmesini sağlamak için ek bir ayar eklenebilir (sonunda dataURIToBlob çağrısını atlayın).
Chen

Bu hatayı alıyorum:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined
Owen M

1
Güzel. Unescape'i (<any> pencere) olarak değiştirmek zorunda kaldım .unescape Ayrıca minSize ekledim.
robert king

maxSizebayt mı yoksa kb mi?
Jagruti

1
Cevabınız bu soruyu cevaplamama yardımcı oldu , teşekkürler.
Syed

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.