HTML5 Thay đổi kích thước hình ảnh trước khi tải lên


181

Đây là một dụng cụ cào mì.

Hãy nhớ rằng chúng tôi có bộ nhớ cục bộ HTML5 và xhr v2 và những gì không. Tôi đã tự hỏi nếu bất cứ ai có thể tìm thấy một ví dụ làm việc hoặc thậm chí chỉ cho tôi có hoặc không cho câu hỏi này:

Có thể định cỡ trước hình ảnh bằng cách sử dụng bộ nhớ cục bộ mới (hoặc bất cứ thứ gì) để người dùng không có đầu mối về việc thay đổi kích thước hình ảnh có thể kéo hình ảnh 10mb của họ vào trang web của tôi, nó thay đổi kích thước hình ảnh bằng cách sử dụng bộ lưu trữ cục bộ mới và THEN tải nó ở kích thước nhỏ hơn.

Tôi biết rất rõ bạn có thể làm điều đó với Flash, Java applet, active X ... Câu hỏi đặt ra là liệu bạn có thể làm với Javascript + Html5 không.

Mong chờ phản hồi về điều này.

Ta cho bây giờ.

Câu trả lời:


181

Có, sử dụng API tệp , sau đó bạn có thể xử lý hình ảnh bằng phần tử canvas .

Bài đăng trên blog Mozilla Hacks này sẽ đưa bạn qua hầu hết quá trình. Để tham khảo ở đây, mã nguồn được lắp ráp từ bài viết trên 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

6
Tại sao cảm ơn bạn tốt thưa ngài. Tôi sẽ có một vở kịch tối nay ... Với tập tin api đó là. Tôi đã kéo và thả tải lên để làm việc và tôi nhận ra đây cũng sẽ là một tính năng thực sự hay để đưa vào. Yippee.
Jimmyt1988

3
Chúng ta không cần sử dụng mycanvas.toDataURL ("image / jpeg", 0,5) này để đảm bảo chất lượng được mang theo để giảm kích thước tệp. Tôi đã xem qua nhận xét này trên bài đăng trên blog của mozilla và thấy độ phân giải lỗi ở đây bugzilla.mozilla.org/show_orms.cgi?id=564388
sbose

33
Bạn nên đợi onloadtrên hình ảnh (đính kèm trình xử lý trước khi cài đặt src), vì trình duyệt được phép thực hiện giải mã không đồng bộ và pixel có thể không khả dụng trước đó drawImage.
Kornel

6
Bạn có thể muốn đặt mã canvas vào chức năng tải của trình quay phim - có thể các hình ảnh lớn sẽ không được tải trước khi bạn ctx.drawImage()gần kết thúc.
Greg

4
hãy cẩn thận, bạn cần phép thuật bổ sung để làm cho nó hoạt động trên iOS: stackoverflow.com/questions/11929099/ trên
flo850

107

Tôi đã giải quyết vấn đề này vài năm trước và tải lên giải pháp của mình cho github là https://github.com/rossturner/HTML5-ImageUploader

Câu trả lời của robertc sử dụng giải pháp được đề xuất trong bài đăng trên blog của Mozilla Hacks , tuy nhiên tôi thấy điều này cho chất lượng hình ảnh thực sự kém khi thay đổi kích thước theo tỷ lệ không phải là 2: 1 (hoặc bội số của nó). Tôi bắt đầu thử nghiệm các thuật toán thay đổi kích thước hình ảnh khác nhau, mặc dù hầu hết kết thúc khá chậm hoặc chất lượng khác cũng không tốt.

Cuối cùng tôi đã đưa ra một giải pháp mà tôi tin rằng thực thi nhanh chóng và cũng có hiệu suất khá tốt - vì giải pháp Mozilla sao chép từ 1 canvas sang một công việc khác nhanh chóng và không làm giảm chất lượng hình ảnh theo tỷ lệ 2: 1, đưa ra mục tiêu là x pixel rộng và y pixel cao, tôi sử dụng phương pháp thay đổi kích thước canvas này cho đến khi hình ảnh nằm giữa x và 2 x , và y và 2 y . Lúc này tôi chuyển sang thay đổi kích thước hình ảnh thuật toán cho "bước" cuối cùng là thay đổi kích thước xuống kích thước đích. Sau khi thử một số thuật toán khác nhau, tôi đã giải quyết được phép nội suy song tuyến tính được lấy từ một blog không còn trực tuyến nữa nhưng có thể truy cập qua Lưu trữ Internet, cho kết quả tốt, đây là mã áp dụng:

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

Điều này chia tỷ lệ hình ảnh xuống chiều rộng config.maxWidth, duy trì tỷ lệ khung hình gốc. Tại thời điểm phát triển, nó hoạt động trên iPad / iPhone Safari ngoài các trình duyệt máy tính để bàn lớn (IE9 +, Firefox, Chrome), vì vậy tôi hy vọng nó vẫn sẽ tương thích với sự hấp thụ rộng rãi hơn của HTML5 ngày hôm nay. Lưu ý rằng lệnh gọi canvas.toDataURL () có loại mime và chất lượng hình ảnh sẽ cho phép bạn kiểm soát chất lượng và định dạng tệp đầu ra (có khả năng khác với đầu vào nếu bạn muốn).

Điểm duy nhất không bao gồm là duy trì thông tin định hướng, không có kiến ​​thức về siêu dữ liệu này, hình ảnh được thay đổi kích thước và lưu nguyên trạng, làm mất bất kỳ siêu dữ liệu nào trong hình ảnh cho định hướng có nghĩa là hình ảnh được chụp trên thiết bị máy tính bảng "lộn ngược" được hiển thị như vậy, mặc dù chúng sẽ được lật trong kính ngắm camera của thiết bị. Nếu đây là một mối quan tâm, bài đăng trên blog này có một hướng dẫn và ví dụ mã tốt về cách thực hiện điều này, mà tôi chắc chắn có thể được tích hợp vào mã trên.


4
Trao cho bạn tiền thưởng vì tôi cảm thấy nó thêm một bó vào câu trả lời, phải lấy công cụ định hướng đó tích hợp :)
Sam Saffron

3
Một linh hồn rất hữu ích giờ đã được hợp nhất trong một số chức năng cho siêu dữ liệu định hướng :)
Ross Taylor-Turner

26

Sửa lỗi ở trên:

<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
Làm thế nào để bạn xử lý phần PHP của nó sau khi bạn thêm nó vào FormData ()? Bạn sẽ không tìm kiếm $ _FILES ['name'] ['tmp_name'] [$ i], chẳng hạn? Tôi đang thử nếu (isset ($ _ POST ['image'])) ... nhưng dataurlkhông có.
denikov

2
nó không hoạt động trên firefox trong lần đầu tiên bạn thử tải lên một hình ảnh. Lần sau bạn thử nó hoạt động. Làm thế nào để khắc phục nó?
Fred

Trả về nếu tập tin là mảng rỗng hoặc trống.
Sheelpriy

2
Trong Chrome khi cố gắng truy cập img.widthimg.heightban đầu chúng là 0. Bạn nên đặt phần còn lại của chức năng vào bên trongimg.addEventListener('load', function () { // now the image has loaded, continue... });
Inigo

2
@Justin bạn có thể làm rõ "những điều trên", như trong, bài đăng này là gì? Chúc mừng
Martin

16

Sửa đổi câu trả lời của Justin phù hợp với tôi:

  1. Đã thêm img.onload
  2. Mở rộng yêu cầu POST bằng một ví dụ thực tế

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
thông tin định hướng (exif) bị mất
Konstantin Vahrushev

8

Nếu bạn không muốn phát minh lại bánh xe, bạn có thể thử plupload.com


3
Tôi cũng sử dụng plupload để thay đổi kích thước phía máy khách. Điều tốt nhất về nó là nó có thể sử dụng flash, html5, silverlight, v.v. Dù người dùng có sẵn điều gì, bởi vì mỗi người dùng có một thiết lập khác nhau. Nếu bạn chỉ muốn html5, thì tôi nghĩ nó không hoạt động trên một số trình duyệt và Opera cũ.
jnl

6

Bản đánh máy

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
Bất cứ ai có thể đi qua bài đăng này, theo tôi, câu trả lời dựa trên lời hứa này đáng tin cậy hơn nhiều. Tôi đã chuyển đổi sang JS bình thường và nó hoạt động như một bùa mê.
gbland777

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

Điều này sẽ không hoạt động. Về phía PHP, bạn không thể lưu tệp với điều này.

Sử dụng mã này thay thế:

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

Thay đổi kích thước hình ảnh trong một phần tử canvas thường là ý tưởng tồi vì nó sử dụng phép nội suy hộp rẻ nhất. Các hình ảnh kết quả suy giảm đáng chú ý về chất lượng. Tôi khuyên bạn nên sử dụng http://nodeca.github.io/pica/demo/ để có thể thực hiện chuyển đổi Lanczos thay thế. Trang demo ở trên cho thấy sự khác biệt giữa phương pháp canvas và Lanczos.

Nó cũng sử dụng các nhân viên web để thay đổi kích thước hình ảnh song song. Ngoài ra còn có triển khai WEBGL.

Có một số trình chỉnh sửa hình ảnh trực tuyến sử dụng pica để thực hiện công việc, như https://myimageresizer.com


5

Câu trả lời được chấp nhận hoạt động rất tốt, nhưng logic thay đổi kích thước bỏ qua trường hợp hình ảnh lớn hơn mức tối đa chỉ trong một trục (ví dụ: height> maxHeight nhưng width <= maxWidth).

Tôi nghĩ rằng đoạn mã sau xử lý tất cả các trường hợp theo cách đơn giản và chức năng hơn (bỏ qua các chú thích kiểu bản in nếu sử dụng javascript đơn giản):

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

Bạn có thể sử dụng dropzone.js nếu bạn muốn sử dụng trình quản lý tải lên đơn giản và dễ dàng với thay đổi kích thước trước khi tải lên các chức năng.

Nó có các chức năng thay đổi kích thước dựng sẵn, nhưng bạn có thể cung cấp riêng nếu bạn muốn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.