【问题标题】:Crop image white space automatically using jQuery使用 jQuery 自动裁剪图像空白
【发布时间】:2012-08-23 22:46:02
【问题描述】:

我有 100,000 张不受我控制的图像。其中一些图像非常出色,因为图像可以延伸到边界,而有些则具有过多的空白。

当有过多的空白时,它会使页面看起来很糟糕,并且意味着屏幕上的图像看起来都是不同的大小。

你可以在这里看到我的意思:

http://www.fitness-saver.com/uk/shop/mountain-bikes/

我一直在寻找的是一种裁剪图像并自动删除空白的 jQuery 方法。

1) 每张图片的空白数量不同 2)图像的比例不同 3) 我想使用 javascript 而不是预处理图像。

希望你能帮上忙!

编辑:这是一个示例图像 - http://images.productserve.com/preview/3395/128554505.jpg。请注意,这些图片来自不同的附属网站,并且肯定来自不同的域。

【问题讨论】:

  • 我可以建议您将出现问题的图像之一添加到您的问题中。它会阻止投票结束,因为您正在有效地宣传您的网站。
  • 您可以在画布上绘制图像并删除整个空白列和行,并将图像重新缩放为“相同”大小(保持纵横比)。
  • “我想使用 javascript 而不是预处理图像” - 为什么?为什么您希望用户浏览器在每次加载图片时都必须删除空白,而不是只在服务器端执行一次,然后保存没有空白的图片以供将来使用?
  • @h2ooooooo 因为 OP 无法控制图像。他从images.productserve.com获取图像
  • ...并且该图像主机允许远程链接到图像但不能远程下载?您可以只编写一个服务器端脚本来下载图像,对其进行处理并保存远程文件名,以便它知道不保存 duklicates。

标签: javascript jquery image


【解决方案1】:

基于Jose Rui Santos 提供的出色答案,我已将他的代码更改为仅使用image 对象而无需加载jQuery 库。

这个函数的返回是裁剪后的图片数据URL,直接在图片元素中使用。

/*
    Source: http://jsfiddle.net/ruisoftware/ddZfV/7/
    Updated by: Mohammad M. AlBanna
    Website: MBanna.info 
    Facebook: FB.com/MBanna.info
*/

var myImage = new Image();
myImage.crossOrigin = "Anonymous";
myImage.onload = function(){
    var imageData = removeImageBlanks(myImage); //Will return cropped image data
}
myImage.src = "IMAGE SOURCE";



//-----------------------------------------//
function removeImageBlanks(imageObject) {
    imgWidth = imageObject.width;
    imgHeight = imageObject.height;
    var canvas = document.createElement('canvas');
    canvas.setAttribute("width", imgWidth);
    canvas.setAttribute("height", imgHeight);
    var context = canvas.getContext('2d');
    context.drawImage(imageObject, 0, 0);

    var imageData = context.getImageData(0, 0, imgWidth, imgHeight),
        data = imageData.data,
        getRBG = function(x, y) {
            var offset = imgWidth * y + x;
            return {
                red:     data[offset * 4],
                green:   data[offset * 4 + 1],
                blue:    data[offset * 4 + 2],
                opacity: data[offset * 4 + 3]
            };
        },
        isWhite = function (rgb) {
            // many images contain noise, as the white is not a pure #fff white
            return rgb.red > 200 && rgb.green > 200 && rgb.blue > 200;
        },
                scanY = function (fromTop) {
        var offset = fromTop ? 1 : -1;

        // loop through each row
        for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {

            // loop through each column
            for(var x = 0; x < imgWidth; x++) {
                var rgb = getRBG(x, y);
                if (!isWhite(rgb)) {
                    if (fromTop) {
                        return y;
                    } else {
                        return Math.min(y + 1, imgHeight);
                    }
                }
            }
        }
        return null; // all image is white
    },
    scanX = function (fromLeft) {
        var offset = fromLeft? 1 : -1;

        // loop through each column
        for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {

            // loop through each row
            for(var y = 0; y < imgHeight; y++) {
                var rgb = getRBG(x, y);
                if (!isWhite(rgb)) {
                    if (fromLeft) {
                        return x;
                    } else {
                        return Math.min(x + 1, imgWidth);
                    }
                }      
            }
        }
        return null; // all image is white
    };

    var cropTop = scanY(true),
        cropBottom = scanY(false),
        cropLeft = scanX(true),
        cropRight = scanX(false),
        cropWidth = cropRight - cropLeft,
        cropHeight = cropBottom - cropTop;

    canvas.setAttribute("width", cropWidth);
    canvas.setAttribute("height", cropHeight);
    // finally crop the guy
    canvas.getContext("2d").drawImage(imageObject,
        cropLeft, cropTop, cropWidth, cropHeight,
        0, 0, cropWidth, cropHeight);

    return canvas.toDataURL();
}

【讨论】:

  • 为什么加+10;毫无意义,你让我浪费了很多时间来寻找错误
  • @IvanCastellanos 感谢您的编辑!我在代码中添加了它,因为裁剪后的图像有一个文本,我需要在底部和右侧留出一点空间。很抱歉。
  • 谢谢。你的代码真的帮助了我。我已对您的代码发布了一个小的错误更正:它总是从右侧和底部裁剪 1px 太多。 (复制:多次重复裁剪将在这些网站上缩小 1 个额外像素。)
  • @MohammadAlBanna 这是否会在图像的边缘或任何地方移除?发现边缘后是否停止扫描?伊万在此答案的代码中提到的建议是否在这里?
  • 这似乎也不遵守css宽度高度限制。
【解决方案2】:

要分析图像中的空白,我知道的唯一方法是将该图像加载到canvas

var img = new Image(),
    $canvas = $("<canvas>"), // create an offscreen canvas
    canvas = $canvas[0],
    context = canvas.getContext("2d");

img.onload = function () {
   context.drawImage(this, 0, 0); // put the image in the canvas
   $("body").append($canvas);
   removeBlanks(this.width, this.height);
};

// test image
img.src = 'http://images.productserve.com/preview/1302/218680281.jpg';

接下来,使用getImageData() 方法。此方法返回一个 ImageData 对象,您可以使用该对象检查每个像素数据(颜色)。

var removeBlanks = function (imgWidth, imgHeight) {
    var imageData = context.getImageData(0, 0, canvas.width, canvas.height),
             data = imageData.data,
           getRBG = function(x, y) {
                      return {
                        red:   data[(imgWidth*y + x) * 4],
                        green: data[(imgWidth*y + x) * 4 + 1],
                        blue:  data[(imgWidth*y + x) * 4 + 2]
                      };
                    },
          isWhite = function (rgb) {
                      return rgb.red == 255 && rgb.green == 255 && rgb.blue == 255;
                    },
            scanY = function (fromTop) {
                      var offset = fromTop ? 1 : -1;

                      // loop through each row
                      for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {

                        // loop through each column
                        for(var x = 0; x < imgWidth; x++) {
                            if (!isWhite(getRBG(x, y))) {
                                return y;                        
                            }      
                        }
                    }
                    return null; // all image is white
                },
            scanX = function (fromLeft) {
                      var offset = fromLeft? 1 : -1;

                      // loop through each column
                      for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {

                        // loop through each row
                        for(var y = 0; y < imgHeight; y++) {
                            if (!isWhite(getRBG(x, y))) {
                                return x;                        
                            }      
                        }
                    }
                    return null; // all image is white
                };


        var cropTop = scanY(true),
            cropBottom = scanY(false),
            cropLeft = scanX(true),
            cropRight = scanX(false);
    // cropTop is the last topmost white row. Above this row all is white
    // cropBottom is the last bottommost white row. Below this row all is white
    // cropLeft is the last leftmost white column.
    // cropRight is the last rightmost white column.
};

坦率地说,我无法测试此代码是有充分理由的:我遇到了臭名昭著的“无法从画布获取图像数据,因为画布已被跨域数据污染。”安全性例外。

这不是错误,而是预期的功能。来自specs

toDataURL()、toDataURLHD()、toBlob()、getImageData() 和 getImageDataHD() 方法检查标志并抛出 SecurityError 异常而不是泄漏跨域数据。

drawImage() 从外部域加载文件时会发生这种情况,这会导致画布的 origin-clean 标志设置为 false,从而阻止进一步的数据操作。

恐怕你会遇到同样的问题,但无论如何,here is the code.

即使这在客户端有效,我可以想象在性能方面会有多悲惨。因此,正如 Jan 所说,如果您可以下载图像并在服务器端对其进行预处理,那就更好了。


编辑:我很好奇我的代码是否真的会裁剪图像,确实如此。

你可以去看看here

如前所述,它仅适用于您域中的图像。您可以选择自己的白色背景图像并更改最后一行:

// define here an image from your domain
img.src = 'http://localhost/strawberry2.jpg'; 

显然,您需要从您的域运行代码,而不是从 jsFiddle。


Edit2:如果您想裁剪和放大以保持相同的纵横比,请更改此设置

var $croppedCanvas = $("<canvas>").attr({ width: cropWidth, height: cropHeight });

// finally crop the guy
$croppedCanvas[0].getContext("2d").drawImage(canvas,
    cropLeft, cropTop, cropWidth, cropHeight,
    0, 0, cropWidth, cropHeight);

var $croppedCanvas = $("<canvas>").attr({ width: imgWidth, height: imgHeight });

// finally crop the guy
$croppedCanvas[0].getContext("2d").drawImage(canvas,
    cropLeft, cropTop, cropWidth, cropHeight,
    0, 0, imgWidth, imgHeight);

Edit3:在浏览器上裁剪图像的一种快速方法是通过使用 Web Workers 并行化工作负载,正如 excellent article 所解释的那样。 p>

【讨论】:

  • 感谢您的详细回复。没有像使用 Javascript 和 JSONP 一样的等效安全解决方法吗?
  • 如果附属网站以 JSON 格式返回图像 URL,您可能会尝试将 JSON 响应包装在 javascript 函数中,请参阅 en.wikipedia.org/wiki/JSONP 另外,我注意到新的 HTML5 crossorigin 属性允许读取来自外国域的图像,但服务器(您的关联公司)必须通过具有Access-Control-Allow-Origin 标头来允许它,请参阅developer.mozilla.org/en-US/docs/CORS_Enabled_Image 我在 jsFiddle 代码中引入了该新属性
  • @DaveHilditch Web workers 是另一种方式(见我的编辑3)
  • 在您的 img.onload 函数中,在上面的代码中您缺少 $("body").append($canvas); ,您在 jsfiddle 中拥有它
  • 如果这对您不起作用,请确保您还使用了 Edit2 中的代码,并且您的画布已正确设置 canvas.heightcanvas.width - 请注意它与 CSS 宽度/高度不同 - 这对我不起作用。
猜你喜欢
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 1970-01-01
  • 2012-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多