HTML5 Yüklemeden önce görüntüleri yeniden boyutlandırın


181

İşte bir şehriye kaşıyıcı.

Unutmayın ki HTML5 yerel depolama ve xhr v2 var ve ne yok. Herkesin çalışan bir örnek bulabileceğini veya hatta bu soru için bana bir evet veya hayır verebileceğini merak ediyordum:

Bir görüntüyü yeniden boyutlandırma konusunda ipucu olmayan bir kullanıcının 10mb görüntüsünü web siteme sürükleyebilmesi için yeni yerel depoyu (veya her şeyi) kullanarak bir görüntüyü önceden boyutlandırmak mümkün mü, yeni localstorage ve SONRA daha küçük boyutta yükleyin.

Flash, Java uygulamaları, aktif X ile yapabileceğinizi iyi biliyorum ... Soru Javascript + Html5 ile yapabiliyorsanız.

Bu konuda yanıt bekliyorum.

Şimdilik Ta.

Yanıtlar:


181

Evet, Dosya API'sını kullanın , ardından tuval öğesiyle görüntüleri işleyebilirsiniz .

Bu Mozilla Hacks blog yazısı , sürecin çoğunda size yol gösterir. Referans olarak, blog gönderisinden toplanan kaynak kodu şöyledir:

// 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
Neden teşekkür ederim efendim. Bu gece oynayacağım ... api dosyasıyla. Sürükle ve bırak yüklemesini işe aldım ve bunun da dahil edilmesi gerçekten güzel bir özellik olacağını fark ettim. Yippee.
Jimmyt1988

3
Dosya boyutunu azaltmak için kalitenin getirildiğinden emin olmak için bu mycanvas.toDataURL ("image / jpeg", 0.5) kullanmamız gerekmez. Mozilla blog yazısında bu yoruma rastladım ve burada hata çözümünü gördüm bugzilla.mozilla.org/show_bug.cgi?id=564388
sbose

33
Tarayıcının senkronize olmayan kod çözme yapmasına izin verildiğinden ve pikseller daha önce kullanılamayabileceğinden onload, görüntüyü beklemelisiniz ( ayardan önce işleyiciyi ekleyin src) drawImage.
Kornel

6
Kanvas kodunu birleştiricinin yükleme fonksiyonuna koymak isteyebilirsiniz - sonuna yaklaşmadan büyük görüntülerin yüklenmemesi ihtimali vardır ctx.drawImage().
Greg

4
dikkatli olun, IOS üzerinde çalışmasını sağlamak için ek bir sihire ihtiyacınız var: stackoverflow.com/questions/11929099/…
flo850 13:14

107

Bu sorunu birkaç yıl önce ele aldım ve çözümümü https://github.com/rossturner/HTML5-ImageUploader olarak github'a yükledim

robertc'in cevabı Mozilla Hacks blog gönderisinde önerilen çözümü kullanıyor , ancak bunun 2: 1 olmayan bir ölçeğe (veya bunların çoğuna) yeniden boyutlandırırken gerçekten kötü görüntü kalitesi verdiğini buldum. Farklı görüntü yeniden boyutlandırma algoritmalarıyla denemeye başladım, ancak çoğu oldukça yavaştı ya da kalitesi çok iyi değildi.

Sonunda hızlı bir şekilde yürütüldüğüne ve oldukça iyi bir performansa sahip olduğuna inandığım bir çözüm buldum - çünkü 1 tuvalden diğerine kopyalamanın Mozilla çözümü hızlı bir şekilde ve 2: 1 oranında görüntü kalitesi kaybı olmadan x hedefi piksel genişliğinde ve y piksel yüksekliğinde, resim x ile 2 x ve y ile 2 y arasında olana kadar bu tuval yeniden boyutlandırma yöntemini kullanıyorum . Bu noktada, hedef boyuta yeniden boyutlandırmanın son "adımı" için algoritmik görüntüyü yeniden boyutlandırmaya dönüyorum. Birkaç farklı algoritmayı denedikten sonra artık çevrimiçi olmayan ama bir blogdan alınan bilinear enterpolasyona yerleştim İnternet Arşivinden erişilebilen, bu da iyi sonuçlar verir, işte geçerli kod:

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

Bu, görüntüyü config.maxWidthorijinal en boy oranını koruyarak genişliğine kadar ölçeklendirir . Geliştirme sırasında bu, büyük masaüstü tarayıcılarına (IE9 +, Firefox, Chrome) ek olarak iPad / iPhone Safari'de çalıştı, bu yüzden bugün HTML5'in daha geniş bir şekilde kullanılması göz önüne alındığında hala uyumlu olacağını umuyorum. Canvas.toDataURL () çağrısının, kalite ve çıktı dosya biçimini (isterseniz giriş yapmak için potansiyel olarak farklı) kontrol etmenizi sağlayacak bir mime türü ve görüntü kalitesi aldığını unutmayın.

Bunun kapsamadığı tek nokta yönlendirme bilgisini korumaktır, bu meta veri hakkında bilgi sahibi olmadan görüntü yeniden boyutlandırılır ve olduğu gibi kaydedilir, yönlendirme için görüntüdeki herhangi bir meta veri kaybedilir, yani bir tablet cihazında "baş aşağı" çekilen görüntüler cihazın kamera vizörüne çevrilmiş olsa da, bu şekilde oluşturulur. Bu bir endişe ise, bu blog yazısı iyi bir rehber ve bunu başarmak için kod örnekleri vardır, eminim yukarıdaki koda entegre olabilir.


4
Sana ödül ödülü verdim çünkü cevaba bir demet eklediğini hissediyorum, o yönelim şeylerini entegre etmeliyim :)
Sam Saffron

3
Çok yardımcı bir ruh, yönlendirme meta verileri için bazı işlevlerde birleşti :)
Ross Taylor-Turner

26

Yukarıdakilere düzeltme:

<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
FormData () ekledikten sonra PHP bölümünü nasıl ele alırsınız? Örneğin, $ _FILES ['name'] ['tmp_name'] [$ i] için arama yapmaz mıydınız? Eğer deniyorum (isset ($ _ POST ['image'])) ... ama dataurlorada değil.
denikov

2
ilk kez resim yüklemeye çalıştığınızda firefox'ta çalışmaz. Bir daha denediğinizde çalışır. Nasıl düzeltebilirim?
Fred

dosya dizi ise null veya boşsa döndürür.
17'de Sheelpriy

2
Chrome'da erişmeye çalışırken img.widthve img.heightbaşlangıçta bunlar 0. Fonksiyonun geri kalanını içine koymalısınızimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo

2
@ Justin, "yukarıdaki" yi açıklayabilir misiniz, olduğu gibi, bu yazı ne düzeltiyor? Şerefe
Martin

16

Justin'in benim için çalışan cevabında değişiklik :

  1. İmg.onload eklendi
  2. POST isteğini gerçek bir örnekle genişletin

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
yönlendirme bilgisi (exif) kayboluyor
Konstantin Vahrushev

8

Tekerleği yeniden icat etmek istemiyorsanız plupload.com'u deneyebilirsiniz


3
Ben de istemci tarafı yeniden boyutlandırma için plupload kullanın. Bu konuda en iyi şey, flaş, html5, silverlight vb. Sadece html5 istiyorsanız, o zaman bazı eski tarayıcılarda ve Opera'da çalışmaz.
jnl

6

daktilo ile yazılmış yazı

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
Bu yazıyla karşılaşabilecek herkes, bu söz tabanlı cevap bence çok daha güvenilir. Normal JS'ye dönüştürdüm ve bir cazibe gibi çalışıyor.
gbland777

5
fd.append("image", dataurl);

Bu işe yaramayacak. PHP tarafında bununla dosya kaydedemezsiniz.

Bunun yerine bu kodu kullanın:

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

Bir tuval öğesindeki görüntüleri yeniden boyutlandırmak, en ucuz kutu enterpolasyonunu kullandığından genellikle kötü bir fikirdir. Ortaya çıkan görüntü kalitesi düşer. Bunun yerine Lanczos dönüşümünü gerçekleştirebilecek http://nodeca.github.io/pica/demo/ kullanmanızı öneririm . Yukarıdaki demo sayfası kanvas ve Lanczos yaklaşımları arasındaki farkı göstermektedir.

Ayrıca görüntüleri paralel olarak yeniden boyutlandırmak için web çalışanlarını kullanır. Ayrıca WEBGL uygulaması da vardır.

Gibi, işini yaptığı için pika kullanan bazı çevrimiçi resim resizers vardır https://myimageresizer.com


5

Kabul edilen cevap harika çalışıyor, ancak yeniden boyutlandırma mantığı, görüntünün eksenlerden yalnızca birindeki maksimum değerden daha büyük olduğu durumu gözardı ediyor (örneğin, height> maxHeight ancak width <= maxWidth).

Aşağıdaki kod (düz javascript kullanıyorsanız, typescript türü ek açıklamaları yok say) daha düz-ileri ve işlevsel bir şekilde tüm durumlarda halleder düşünüyorum:

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

Sen kullanabilirsiniz dropzone.js yüklediğiniz fonksiyonları önce yeniden boyutlandırma ile basit ve kolay yükleme yöneticisi kullanmak istiyorsanız.

Yerleşik yeniden boyutlandırma işlevleri vardır, ancak isterseniz kendiniz de sağlayabilirsiniz.

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.