HTML5在上传之前预先调整图像大小

时间:2012-04-26 13:01:44

标签: image html5 upload xmlhttprequest local-storage

这是面条刮痕。

请记住,我们有HTML5本地存储和xhr v2,还有什么不是。我想知道是否有人能找到一个有效的例子,甚至只是给我一个是或否的问题:

是否可以使用新的本地存储(或其他)预先调整图像大小,以便没有关于调整图像大小的线索的用户可以将他们的10mb图像拖到我的网站中,它会使用新的本地存储,然后以较小的尺寸上传它。

我完全知道你可以用Flash,Java小程序,活动X来做...问题是你是否可以使用Javascript + Html5。

期待对此的回应。

现在。

10 个答案:

答案 0 :(得分:172)

是的,使用File API,然后您可以使用canvas element处理图片。

This Mozilla Hacks blog post引导您完成整个过程。这里参考的是博客文章中汇编的源代码:

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

答案 1 :(得分:102)

几年前我解决了这个问题,并将我的解决方案上传到github https://github.com/rossturner/HTML5-ImageUploader

robertc的回答使用了Mozilla Hacks blog post中提出的解决方案,但是我发现当调整到不是2:1(或其倍数)的比例时,这给出了非常差的图像质量。我开始尝试不同的图像大小调整算法,虽然大多数最终都很慢,或者质量也不高。

最后,我提出了一个解决方案,我相信它能够快速执行并且具有相当不错的性能 - 因为Mozilla从1个画布复制到另一个画布的解决方案可以快速工作,并且不会以2:1的比例损失图像质量,给定一个 x 像素宽且 y 像素高的目标,我使用此画布调整大小方法,直到图像在 x 和2 x <之间/ em>, y 和2 y 。在这一点上,我转向算法图像大小调整,以调整大小到目标大小的最后“步骤”。在尝试了几种不同的算法之后,我决定采用不再在线的博客进行双线性插值,但是accessible via the Internet Archive,这给出了很好的结果,这是适用的代码:

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,保持原始高宽比。在开发时,除了主要的桌面浏览器(IE9 +,Firefox,Chrome)之外,它还可以在iPad / iPhone Safari上运行,因此我认为,鉴于今天更广泛地采用HTML5,它仍将兼容。请注意,canvas.toDataURL()调用采用mime类型和图像质量,这将允许您控制质量和输出文件格式(如果您愿意,可能与输入不同)。

唯一不包括的是维护方向信息,在不知道此元数据的情况下,图像被调整大小并按原样保存,丢失图像中的任何元数据以获得方向意味着在平板电脑设备上拍摄的图像具有“优势”虽然它们会在设备的相机取景器中翻转,但它们仍然会被渲染出来。如果这是一个问题,this blog post有一个很好的指南和代码示例如何实现这一点,我相信我可以将其集成到上面的代码中。

答案 2 :(得分:25)

以上更正:

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

答案 3 :(得分:14)

answer by Justin的修改对我有用:

  1. 添加了img.onload
  2. 使用实例扩展POST请求
  3. 
    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);
    }
    

答案 4 :(得分:8)

如果您不想重新发明轮子,可以试试plupload.com

答案 5 :(得分: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

答案 6 :(得分:5)

接受的答案效果很好,但调整大小逻辑忽略了图像大于仅一个轴中的最大值的情况(例如,高度> maxHeight但宽度&lt; = maxWidth)。

我认为以下代码以更直接和更实用的方式处理所有情况(如果使用普通的javascript,则忽略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 };
  }

答案 7 :(得分:3)

调整画布元素中的图像通常是个坏主意,因为它使用最便宜的盒子插值。产生的图像明显降低质量。我建议使用可以执行Lanczos转换的http://nodeca.github.io/pica/demo/。上面的演示页面显示了canvas和Lanczos方法之间的区别。

它还使用Web worker并行调整图像大小。还有WEBGL实现。

有些在线图像缩放器使用pica来完成工作,例如https://myimageresizer.com

答案 8 :(得分:2)

打字稿

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

答案 9 :(得分:0)

如果您希望在上传功能之前使用简单且简单的上传管理器并调整大小,则可以使用dropzone.js

它内置了调整大小功能,但如果需要,您可以提供自己的功能。