HTML5 ปรับขนาดภาพล่วงหน้าก่อนที่จะอัพโหลด


181

นี่คือเครื่องขูดเส้นก๋วยเตี๋ยว

โปรดจำไว้ว่าเรามีที่จัดเก็บในตัว HTML5 และ xhr v2 และสิ่งที่ไม่ ฉันสงสัยว่าใครสามารถหาตัวอย่างการทำงานหรือแม้แต่ให้ฉันใช่หรือไม่ใช่สำหรับคำถามนี้

เป็นไปได้หรือไม่ที่จะปรับขนาดภาพโดยใช้ที่จัดเก็บในตัวเครื่องใหม่ (หรืออะไรก็ตาม) เพื่อให้ผู้ใช้ที่ไม่มีเงื่อนงำเกี่ยวกับการปรับขนาดภาพสามารถลากภาพขนาด 10mb ไปยังเว็บไซต์ของฉันได้ จากนั้นอัปโหลดที่ขนาดเล็กกว่า

ฉันรู้ดีว่าคุณสามารถทำได้ด้วย Flash, Java applets, active X ... คำถามคือถ้าคุณสามารถทำได้ด้วย Javascript + Html5

คอยที่จะตอบสนองในนี้

ตาสำหรับตอนนี้

คำตอบ:


181

ใช่ใช้ไฟล์ APIแล้วคุณสามารถประมวลผลภาพที่มีองค์ประกอบผ้าใบ

โพสต์บล็อกของ 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เพราะเบราว์เซอร์ได้รับอนุญาตให้ทำถอดรหัสถ่ายทอดสดและพิกเซลอาจไม่สามารถใช้ได้ก่อน
Kornel

6
คุณอาจต้องการใส่รหัสแคนวาสลงในฟังก์ชั่นออนโหลดของฟิลเลอร์ - มีโอกาสที่ภาพใหญ่จะไม่ถูกโหลดก่อนที่คุณจะctx.drawImage()ถึงจุดจบ
เกร็ก

4
ระวังคุณต้องใช้เวทย์มนตร์เพิ่มเติมเพื่อให้มันทำงานบน IOS: stackoverflow.com/questions/11929099/ …
flo850

107

ฉันจัดการกับปัญหานี้เมื่อไม่กี่ปีที่ผ่านมาและอัปโหลดโซลูชันของฉันไปยัง github เป็นhttps://github.com/rossturner/HTML5-ImageUploader

คำตอบของ robertc ใช้วิธีแก้ปัญหาที่เสนอในโพสต์บล็อกของMozilla Hacksแต่ฉันพบว่าสิ่งนี้ให้คุณภาพของภาพที่แย่มากเมื่อปรับขนาดเป็นมาตราส่วนที่ไม่ใช่ 2: 1 (หรือหลายรายการ) ฉันเริ่มทำการทดลองด้วยอัลกอริธึมการปรับขนาดรูปภาพที่แตกต่างกันถึงแม้ว่าส่วนใหญ่แล้วจะค่อนข้างช้าหรืออย่างอื่นก็ไม่ได้คุณภาพดีเช่นกัน

ในที่สุดฉันก็มาพร้อมกับโซลูชันที่ฉันเชื่อว่ารันได้อย่างรวดเร็วและมีประสิทธิภาพที่ดีเช่นกันเนื่องจากโซลูชัน Mozilla ของการคัดลอกจากผ้าใบหนึ่งผืนไปยังอีกงานหนึ่งได้อย่างรวดเร็วและไม่สูญเสียคุณภาพของภาพที่อัตราส่วน 2: 1 ตามเป้าหมายของxพิกเซลกว้างและYพิกเซลสูงผมใช้ผ้าใบวิธีนี้เป็นวิธีการปรับขนาดจนกระทั่งภาพระหว่างx 2 xและy ที่ 2 ปีปีเมื่อมาถึงจุดนี้ฉันก็หันไปปรับขนาดภาพอัลกอริทึมสำหรับ "ขั้นตอน" สุดท้ายของการปรับขนาดลงเป็นขนาดเป้าหมาย หลังจากลองใช้อัลกอริทึมที่แตกต่างกันหลายอย่างฉันตัดสินการแก้ไขแบบสองทิศทางที่นำมาจากบล็อกซึ่งไม่ได้ออนไลน์อีกต่อไป แต่สามารถเข้าถึงได้ทางอินเทอร์เน็ตอาร์ไคว์ฟซึ่งจะช่วยให้ผลดีนี่คือรหัสที่ใช้บังคับ:

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
ที่ได้รับรางวัลที่คุณโปรดปรานทำให้ฉันรู้สึกว่ามันเพิ่มพวงให้คำตอบที่จำต้องได้รับสิ่งที่บูรณาการการวางแนวทางแม้ว่า :)
แซม Saffron

3
ตอนนี้วิญญาณที่เป็นประโยชน์มากได้รวมเข้ากับฟังก์ชั่นบางอย่างสำหรับเมทาดาทาการปฐมนิเทศ :)
Ross Taylor-Turner

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] ใช่ไหม? ฉันพยายามถ้า (ผู้ออก ($ _ POST ['ภาพ'])) ... แต่dataurlไม่ใช่
denikov

2
มันไม่ทำงานใน firefox ในครั้งแรกที่คุณพยายามอัปโหลดรูปภาพ ครั้งต่อไปที่คุณลองใช้งาน จะแก้ไขได้อย่างไร?
Fred

ส่งคืนถ้าไฟล์เป็นอาร์เรย์เป็นโมฆะหรือว่างเปล่า
Sheelpriy

2
ใน Chrome เมื่อพยายามที่จะเข้าถึงimg.widthและครั้งแรกที่พวกเขามีimg.height 0คุณควรวางส่วนที่เหลือของฟังก์ชั่นไว้ภายในimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo

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) สูญหาย
Konstantin Vahrushev

8

หากคุณไม่ต้องการที่จะบูรณาการล้อคุณอาจลองplupload.com


3
ฉันยังใช้ plupload เพื่อปรับขนาดฝั่งไคลเอ็นต์ สิ่งที่ดีที่สุดเกี่ยวกับมันคือสามารถใช้แฟลช, 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 แทน หน้าตัวอย่างข้างต้นแสดงให้เห็นถึงความแตกต่างระหว่างวิธีการวาดภาพกับ Lanczos

นอกจากนี้ยังใช้เจ้าหน้าที่เว็บเพื่อปรับขนาดรูปภาพในแบบคู่ขนาน นอกจากนี้ยังมีการติดตั้ง WEBGL

มีตัวแก้ไขรูปภาพออนไลน์ที่ใช้ pica ในการทำงานเช่นhttps://myimageresizer.com


5

คำตอบที่ได้รับการยอมรับนั้นใช้งานได้ดี แต่ตรรกะการปรับขนาดจะไม่สนใจเคสที่รูปภาพมีขนาดใหญ่กว่าค่าสูงสุดในหนึ่งแกนเท่านั้น (เช่นความสูง> maxHeight แต่ความกว้าง <= maxWidth)

ฉันคิดว่ารหัสต่อไปนี้จะดูแลทุกกรณีอย่างตรงไปตรงมาและใช้งานได้ดีกว่า (ละเว้นคำอธิบายประกอบประเภท typescript หากใช้จาวาสคริปต์ธรรมดา):

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หากคุณต้องการใช้ตัวจัดการการอัปโหลดที่ง่ายและสะดวกพร้อมการปรับขนาดก่อนใช้งานฟังก์ชั่นการอัพโหลด

มันมีฟังก์ชั่นปรับขนาดภายใน แต่คุณสามารถจัดเตรียมของคุณเองหากคุณต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.