【问题标题】:HTML5 Pre-resize images before uploadingHTML5 上传前调整图片大小
【发布时间】:2012-05-07 05:14:20
【问题描述】:

这是一个面条刮刀。

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

是否可以使用新的本地存储(或其他)预先调整图像大小,以便不知道调整图像大小的用户可以将他们的 10mb 图像拖到我的网站中,它使用新的本地存储,然后以较小的大小上传。

我很清楚你可以用 Flash、Java 小程序、active X 来做到这一点...问题是你是否可以用 Javascript + Html5 来做到这一点。

期待这个回复。

暂时是Ta。

【问题讨论】:

    标签: image html upload xmlhttprequest local-storage


    【解决方案1】:

    是的,使用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
    

    【讨论】:

    • 为什么谢谢你好先生。今晚我会玩... 文件 api 就是这样。我让拖放上传工作,我意识到这也是一个非常好的功能。伊皮。
    • 难道我们不需要使用这个 mycanvas.toDataURL("image/jpeg", 0.5) 来确保带来质量以减小文件大小。我在 mozilla 博客文章中看到了这条评论,并在这里看到了错误解决方案bugzilla.mozilla.org/show_bug.cgi?id=564388
    • 您应该在图像上等待onload(附加处理程序之前设置src),因为允许浏览器进行异步解码,并且像素可能在之前不可用drawImage.
    • 您可能希望将画布代码放入文件阅读器的 onload 函数中 - 有可能在您接近尾声时ctx.drawImage() 之前不会加载大图像。
    • 小心,你需要额外的魔法才能让它在 IOS 上运行:stackoverflow.com/questions/11929099/…
    【解决方案2】:

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

    【讨论】:

    • 我还使用 plupload 来调整客户端大小。最好的一点是它可以使用 flash、html5、silverlight 等。无论用户有什么可用的,因为每个用户都有不同的设置。如果你只想要 html5,那么我认为它不适用于一些旧的浏览器和 Opera。
    【解决方案3】:

    以上更正:

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

    【讨论】:

    • 将PHP部分添加到FormData()后如何处理?例如,您不会寻找 $_FILES['name']['tmp_name'][$i]?我正在尝试 if(isset($_POST['image']))... 但dataurl 不存在。
    • 第一次尝试上传图片时,它在 Firefox 上不起作用。下次你尝试它的工作原理。如何解决?
    • 如果文件是数组则返回 null 或空。
    • 在 Chrome 中尝试访问 img.widthimg.height 最初它们是 0。你应该把函数的其余部分放在img.addEventListener('load', function () { // now the image has loaded, continue... });
    • @Justin 你能澄清“上述”,比如,这篇文章是对什么的更正?干杯
    【解决方案4】:

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

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

    最后,我想出了一个我认为执行速度很快并且性能也相当不错的解决方案 - 因为 Mozilla 解决方案从一个画布复制到另一个画布可以快速工作,并且不会以 2:1 的比例损失图像质量,给定一个x 像素宽和 y 像素高的目标,我使用此画布调整大小方法,直到图像介于 x 和 2 x、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 类型和图像质量,这将允许您控制质量和输出文件格式(如果您愿意,可能与输入不同)。

    这里没有涉及的唯一一点是维护方向信息,在不知道此元数据的情况下,图像被调整大小并按原样保存,丢失图像中用于方向的任何元数据,这意味着在平板设备上拍摄的图像“向上” down”是这样渲染的,尽管它们会在设备的相机取景器中翻转。如果这是一个问题,this blog post 有一个很好的指南和代码示例来说明如何实现这一点,我确信可以将其集成到上述代码中。

    【讨论】:

    • 奖励你赏金,因为我觉得它为答案增加了一堆,但必须整合方向的东西:)
    • 一个非常有帮助的灵魂现在已合并到方向元数据的一些功能中:)
    • @RossTaylor-Turner 合并了方向元数据的代码在哪里?
    【解决方案5】:

    修改适合我的answer by Justin

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

    【讨论】:

    • 方向信息(exif)丢失
    【解决方案6】:
    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
    

    【讨论】:

      【解决方案7】:

      接受的答案效果很好,但调整大小逻辑忽略了图像仅在一个轴上大于最大值的情况(例如,高度 > maxHeight 但宽度

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

      【讨论】:

        【解决方案8】:

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

        它还使用网络工作者并行调整图像大小。还有WEBGL实现。

        有一些在线图像调整器使用 pica 来完成这项工作,例如 https://myimageresizer.com

        【讨论】:

          【解决方案9】:

          如果您想使用简单易用的上传管理器和上传前调整大小功能,您可以使用dropzone.js

          它具有内置的调整大小功能,但您可以根据需要提供自己的功能。

          【讨论】:

            【解决方案10】:

            打字稿

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

            【讨论】:

            • 任何可能遇到这篇文章的人,我认为这个基于承诺的答案要可靠得多。我转换为普通的 JS,它就像一个魅力。
            猜你喜欢
            • 2014-08-26
            • 2014-07-19
            • 1970-01-01
            • 2011-08-25
            • 2011-05-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多