【问题标题】:A Javascript Canvas Loop to Check If Images Have a White Background用于检查图像是否具有白色背景的 Javascript 画布循环
【发布时间】:2021-01-24 06:15:33
【问题描述】:

我想在 HTML 中循环大约 50 个图像,从每个图像中提取 src,检查图像背景的主要颜色是否为白色,然后根据结果添加一些 css 样式(例如填充)。

到目前为止,我有这段代码,但是由于某种原因它不起作用。代码单独工作,但放在 for 循环中时不起作用。通常,这个循环要么根本不工作,要么只工作到某个时间点,然后只输出“#FFFFFF”的默认结果,因为画布本身被填充为白色。

我不确定为什么它不起作用。我一直在尝试修复它,但无济于事。

DEMO HERE(请查看):https://jsbin.com/tucegomemi/1/edit?html,js,console,output

此处为 JAVASCRIPT:

var i;
var GlobalVariable;
for (i=0; i < document.querySelectorAll('img').length ; i++) {
  
let canvas = document.getElementById("canvas"),
              canvasWidth = canvas.width,
        canvasHeight = canvas.height,
        c = canvas.getContext("2d"),
        img = new Image();
        img.crossOrigin="anonymous";
img.src = document.querySelectorAll('img')[i].src


      // Prepare the canvas
      var ptrn = c.createPattern(img, 'repeat'); 
      c.fillStyle = "white";
      c.fillRect(0,0,canvasWidth,canvasHeight);
      c.fillStyle = ptrn;
      c.fillRect(0,0,canvasWidth,canvasHeight);
      
      // Get img data
      var imgData = c.getImageData(0, 0, canvasWidth, canvasHeight),
          data = imgData.data,
          colours = {};

      
      // Build an object with colour data.
      for (var y = 0; y < canvasHeight; ++y) {
        for (var x = 0; x < canvasWidth; ++x) {
          var index = (y * canvasWidth + x) * 4,
              r = data[index],
              g = data[++index],
              b = data[++index],
              rgb = rgbToHex(r,g,b);
          
          if(colours[rgb]){
            colours[rgb]++;
          }else{
            colours[rgb] = 1;
          }
        }
      }
      
      // Determine what colour occurs most.
      var most = {
        colour:'',
        amount:0
      };
      for(var colour in colours){
        if(colours[colour] > most.amount){
          most.amount = colours[colour];
          most.colour = colour;
        }
      }

     GlobalVariable =  most.colour; 
     console.log(i);  
     console.log(GlobalVariable);  

  if (GlobalVariable !== "#ffffff") {document.querySelectorAll('img')[i].style.padding = "50px" ;}  

}
    
    function rgbToHex(r, g, b) {
      return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    }





【问题讨论】:

    标签: javascript html image loops canvas


    【解决方案1】:

    等待图片加载

    由于图像在页面上,您可以等待page load event 触发。仅当所有图像已加载(或加载失败)时才会触发。请阅读链接,因为使用加载事件有一些注意事项。

    由于图像在页面上,因此无需使用new Image 创建图像的副本您可以直接从页面使用图像。

    我还假设所有图像都会加载。如果图片不加载会有问题

    查看您的代码效率非常低,因此该示例是一个完全重写的示例,试图运行得更快并消耗更少的功率。

    注意:该示例使用仅在内存中的临时画布。它不需要页面上的画布。 注意:如果一个像素的计数大于图像中像素数的一半,它将停止计数。

    addEventListener("load",() => {         // wait for page (and images to load) 
        const toHex = val => (val & 0xFF).toString(16).padStart(2,"0");  // mask the to hex and pad with 0 if needed
        const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
        const images = document.querySelectorAll('img');
        const canvas = document.createElement("canvas"); // Only need one canvas
        const ctx = canvas.getContext("2d");             // and only one context
        for (const image of images) {                    // do each image in turn
            const w = canvas.width = image.naturalWidth; // size to fit image
            const h = canvas.height = image.naturalHeight;
            ctx.fillStyle = "#FFF";
            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(image, 0, 0);
            const imgData = ctx.getImageData(0, 0, w, h);
            const pixels = new Uint32Array(imgData.data.buffer); // get a pixel view of data (RGBA as one number)
            const counts = {};                                   // a map of color counts
            var idx = pixels.length, maxPx, maxCount = 0;        // track the most frequent pixel count and type
            while (idx-- > 0) {
                const pixel = pixels[idx];  // get pixel
                const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
                if (count > maxCount) {
                    maxCount = count;
                    maxPx = pixel;
                    if (count > pixels.length / 2) { break }
                }
            }
            image._FOUND_DOMINATE_COLOR = pixel2CSScolor(maxPx);
        }
    }); 
    

    每张图片都附加了一个名为_FOUND_DOMINATE_COLOR 的新属性,其中包含一个颜色为 CSS 十六进制颜色的字符串

    更好的方法

    由于我不确定图像格式和图像内容,以上示例是涵盖所有解决方案。

    如果图像有大面积的相似颜色,或者图像有很多噪点,您可以使用 GPU 渲染来为您完成大部分计数。这是通过以越来越小的比例绘制图像来完成的。 drawImage 函数将按原样平均像素值。

    这意味着,当您的代码查看像素数据时,图像大小减少了一半,内存和 CPU 负载减少了 4 倍,大小减少了四分之一,工作量减少了 16 倍。

    下一个示例将图像缩小到其自然大小的 1/4,然后使用平均像素值来查找颜色。请注意,为获得最佳效果,图像的宽度和高度至少应大于 16 像素

    addEventListener("load",() => {        
        const toHex = val => (val & 0xFF).toString(16).padStart(2,"0");  
        const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
        const reduceImage = image => {
            const w = canvas.width = image.naturalWidth;         
            const h = canvas.height = image.naturalHeight;
            ctx.globalCompositeOperation = "source-over";
            ctx.fillStyle = "#FFF";
            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(image, 0, 0);
            ctx.globalCompositeOperation = "copy";
            ctx.drawImage(canvas, 0, 0, w / 2, h / 2);
            ctx.drawImage(canvas, 0, 0, w / 2, h / 2,  0, 0, w / 4, h / 4);
            return new Uint32Array(ctx.getImageData(0, 0, w / 4 | 0, h / 4 | 0).data.buffer);
        }
        const images = document.querySelectorAll('img');
        const canvas = document.createElement("canvas"); 
        const ctx = canvas.getContext("2d");             
        for (const image of images) {                    
            const pixels = reduceImage(image), counts = {};                                   
            var idx = pixels.length, maxPx, maxCount = 0;       
            while (idx-- > 0) {
                const pixel = pixels[idx];  // get pixel
                const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
                if (count > maxCount) {
                    maxCount = count;
                    maxPx = pixel;
                    if (count > pixels.length / 2) { break }
                }
            }
            image._FOUND_DOMINATE_COLOR = pixel2CSScolor(maxPx);
        }
    }); 
    

    更新

    由于 cmets 中有一些问题,接下来的 sn-p 是检查以确保一切正常。

    除了我在 cmets 中详细说明的更正之外,我找不到代码有任何问题。

    出于下一个标题中所述的原因,我确实更改了一些名称并进一步增加了图像缩小步骤

    色频不等于主色

    下面的示例显示了两个图像,加载时填充设置为找到的颜色。您会注意到右边的图像似乎没有正确的颜色。

    这是因为棕色有很多,但没有一种棕色是最常见的。

    在我的回答 Finding dominant hue. 中,我解决了这个问题,并找到了一个更符合人类感知的解决方案。

    工作示例

    。低端设备的警告。其中一张图片是 ~9Mpx 。

    addEventListener("load",() => { geMostFrequentColor() },{once: true});
    const downScaleSteps = 4;
    function geMostFrequentColor() {
        const toHex = val => (val & 0xFF).toString(16).padStart(2,"0");  
        const pixel2CSScolor = px => `#${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}`;
        const reduceImage = image => {
            var w = canvas.width = image.naturalWidth, h = canvas.height = image.naturalHeight, step = 0;
            ctx.globalCompositeOperation = "source-over";
            ctx.fillStyle = "#FFF";
            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(image, 0, 0);
            ctx.globalCompositeOperation = "copy";
            while (step++ < downScaleSteps) {
                ctx.drawImage(canvas, 0, 0, w, h, 0, 0, w /= 2, h /= 2);
            }
            return new Uint32Array(ctx.getImageData(0, 0, w | 0, h | 0).data.buffer);
        }
        const images = document.querySelectorAll('img');
        const canvas = document.createElement("canvas"); 
        const ctx = canvas.getContext("2d");  
        var imgCount = 0;           
        for (const image of images) {           
            info.textContent = "Processing image: " + imgCount++;
            const pixels = reduceImage(image), counts = {};    
            let idx = pixels.length, maxPx, maxCount = 0;      
            while (idx-- > 0) {
                const pixel = pixels[idx];  // get pixel
                const count = counts[pixel] = counts[pixel] ? counts[pixel] + 1 : 1;
                if (count > maxCount) {
                    maxCount = count;
                    maxPx = pixel;
                    if (count > pixels.length / 2) { break }
                }
            }
            image._MOST_FREQUENT_COLOR = pixel2CSScolor(maxPx);
            image.style.background = image._MOST_FREQUENT_COLOR;
        }
        info.textContent = "All Done!";
    }
    img {
       height: 160px;
       padding: 20px;
    }
    <div id="info">Loading...</div>
    <img src="https://upload.wikimedia.org/wikipedia/commons/d/dd/Olympus-BX61-fluorescence_microscope.jpg"  crossorigin="anonymous">
    <img src="https://upload.wikimedia.org/wikipedia/commons/a/a5/Compound_Microscope_(cropped).JPG" alt="Compound Microscope (cropped).JPG"  crossorigin="anonymous"><br>
     Images from wiki no attribution required.

    【讨论】:

    • 太棒了!非常感谢你。你做得很好!这对于电子商务商店弄清楚他们的产品图像背景是否真的很有帮助。我面临的唯一问题是我得到的结果是这个字符串 '#{toHex(px >> 16)}{toHex(px >> 8)}{toHex(px)}' 或 'Failed to execute'绘制图像'错误。 (DEMO-> jsbin.com/xipapuledu/1/edit?html,js,console,output) 我不知道为什么.. 有没有快速解决这个问题的方法? :)
    • @Ani 对不起,我的错。模板字符串中应为const toHex = val =&gt; (val &amp; 0xFF).toString(16).padStart(2,"0"); const pixel2CSScolor = px =&gt; #${toHex(px >> 16)}${toHex(px >> 8)}${toHex(px)}; Forgot the $`,十六进制应为0x第一行的值。现在将修复答案。 `
    • 现在以 HEX 代码列出!!伟大的!这里-> jsbin.com/zapodavowo/1/edit?html,js,console,output ^_^ .. 只是现在所有的值都是#FFFFFF,这不应该是这种情况。抱歉打扰了。这个问题没有很多堆栈流答案,如果另一个小企业主遇到这个问题,我希望它也能帮助他们:) 谢谢!
    • @Ani 好的,我没有测试代码,所以一有机会我就会让它 100% 工作。
    • 保佑!!这将是惊人的!谢谢!只要有机会^_^
    【解决方案2】:

    您当前正在绘制一个空图像。图片需要一些时间来加载,因此您必须等待加载。

    一旦图像完成加载,使用onload 回调将图像绘制到画布上。在此事件之后,其他所有流程都应继续。

    img = new Image();
    img.crossOrigin = "anonymous";
    img.src = document.querySelectorAll('img')[i].src;
    img.onload = function() {
      c.clearRect(0, 0, canvasWidth, canvasHeight);
      c.drawImage(img, 0, 0, canvasWidth, canvasHeight); 
      // Continue here
    }
    

    【讨论】:

      猜你喜欢
      • 2013-10-02
      • 1970-01-01
      • 2017-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-16
      • 2017-02-23
      • 2012-02-07
      相关资源
      最近更新 更多