【问题标题】:Get average color of image via Javascript通过Javascript获取图像的平均颜色
【发布时间】:2011-02-02 05:52:41
【问题描述】:

不确定这是否可行,但希望编写一个脚本来返回图像的平均 hexrgb 值。我知道它可以在 AS 中完成,但希望在 JavaScript 中完成。

【问题讨论】:

  • 我的朋友 Boaz 以前也曾走过这条路(希望他能加入进来),但一般情况下,您通常会留下呕吐般的颜色。你真正想要的是“主色”。有几种方法可以获得它(带有动态分桶的色调直方图等),但我认为这对于您的预期结果可能更精确。

标签: javascript image-manipulation


【解决方案1】:

首先:它可以在没有 HTML5 Canvas 或 SVG 的情况下完成。
实际上,有人只是设法使用data URI schemegenerate client-side PNG files using JavaScript没有画布或 SVG。

编辑:您可以简单地使用 document.getElementById("canvas").toDataURL("image/png", 1.0)

创建一个 PNG

第二:您实际上可能根本不需要 Canvas、SVG 或上述任何一种。
如果您只需要在客户端处理图像,修改它们,这一切都不需要。

您可以从页面上的 img 标记获取源地址,对其发出 XHR 请求 - 它很可能来自浏览器缓存 - 并将其作为 Javascript 的字节流处理。
您需要对图像格式有很好的理解。 (上面的生成器部分基于 libpng 源,可能会提供一个很好的起点。)

【讨论】:

  • +1 用于字节流解决方案。如果唯一的选择是在客户端应用程序上制作它,这是一个很好的选择。
【解决方案2】:

这是@350D 的答案,但异步(因为某些图像可能需要时间加载)和打字稿

async function get_average_rgb(src: string): Promise<Uint8ClampedArray> {
/* https://stackoverflow.com/questions/2541481/get-average-color-of-image-via-javascript */
    return new Promise(resolve => {
        let context = document.createElement('canvas').getContext('2d');
        context!.imageSmoothingEnabled = true;

        let img = new Image;
        img.src = src;
        img.crossOrigin = "";

        img.onload = () => {
            context!.drawImage(img, 0, 0, 1, 1);
            resolve(context!.getImageData(0, 0, 1, 1).data.slice(0,3));
        };
    });
}

【讨论】:

    【解决方案3】:

    编辑:只有在发布此内容后,我才意识到@350D 的答案完全相同。

    令人惊讶的是,这只需 4 行代码即可完成:

    const canvas = document.getElementById("canvas"),
      preview = document.getElementById("preview"),
      ctx = canvas.getContext("2d");
    
    canvas.width = 1;
    canvas.height = 1;
    
    preview.width = 400;
    preview.height = 400;
    
    function getDominantColor(imageObject) {
      //draw the image to one pixel and let the browser find the dominant color
      ctx.drawImage(imageObject, 0, 0, 1, 1);
    
      //get pixel color
      const i = ctx.getImageData(0, 0, 1, 1).data;
    
      console.log(`rgba(${i[0]},${i[1]},${i[2]},${i[3]})`);
    
      console.log("#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1));
    }
    
    
    
    // vvv all of this is to just get the uploaded image vvv
    const input = document.getElementById("input");
    input.type = "file";
    input.accept = "image/*";
    
    input.onchange = event => {
      const file = event.target.files[0];
      const reader = new FileReader();
    
      reader.onload = readerEvent => {
        const image = new Image();
        image.onload = function() {
          //shows preview of uploaded image
          preview.getContext("2d").drawImage(
            image,
            0,
            0,
            preview.width,
            preview.height,
          );
          getDominantColor(image);
        };
        image.src = readerEvent.target.result;
      };
      reader.readAsDataURL(file, "UTF-8");
    };
    canvas {
      width: 200px;
      height: 200px;
      outline: 1px solid #000000;
    }
    <canvas id="preview"></canvas>
    <canvas id="canvas"></canvas>
    <input id="input" type="file" />

    工作原理:

    创建画布上下文

    const context = document.createElement("canvas").getContext("2d");
    

    这会将图像仅绘制到一个画布像素,使浏览器为您找到主色。

    context.drawImage(imageObject, 0, 0, 1, 1);
    

    之后,只需获取像素的图像数据:

    const i = context.getImageData(0, 0, 1, 1).data;
    

    最后,转换成rgbaHEX

    const rgba = `rgba(${i[0]},${i[1]},${i[2]},${i[3]})`;
    
    const HEX = "#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1);
    

    这种方法有一个问题,那就是getImageData 有时会抛出错误Unable to get image data from canvas because the canvas has been tainted by cross-origin data.,这就是你需要在演示中上传图片而不是输入 URL 的原因。

    此方法也可用于像素化图像,通过增加图像的宽度和高度来绘制图像。

    这适用于 chrome,但可能不适用于其他浏览器。

    【讨论】:

      【解决方案4】:

      正如在其他答案中所指出的那样,通常您真正想要的主色,而不是往往是棕色的平均颜色。我写了一个脚本,得到了最常见的颜色,并发布在这个gist

      【讨论】:

        【解决方案5】:

        一体化解决方案

        我会亲自将Color Thiefmodified version of Name that Color 结合起来,以获得足够多的图像主色结果。

        示例:

        考虑下图:

        您可以使用以下代码提取与主色相关的图像数据:

        let color_thief = new ColorThief();
        let sample_image = new Image();
        
        sample_image.onload = () => {
          let result = ntc.name('#' + color_thief.getColor(sample_image).map(x => {
            const hex = x.toString(16);
            return hex.length === 1 ? '0' + hex : hex;
            
          }).join(''));
          
          console.log(result[0]); // #f0c420     : Dominant HEX/RGB value of closest match
          console.log(result[1]); // Moon Yellow : Dominant specific color name of closest match
          console.log(result[2]); // #ffff00     : Dominant HEX/RGB value of shade of closest match
          console.log(result[3]); // Yellow      : Dominant color name of shade of closest match
          console.log(result[4]); // false       : True if exact color match
        };
        
        sample_image.crossOrigin = 'anonymous';
        sample_image.src = document.getElementById('sample-image').src;
        

        【讨论】:

          【解决方案6】:

          datauri 支持下获得图像平均颜色的不太准确但最快的方法:

          function get_average_rgb(img) {
              var context = document.createElement('canvas').getContext('2d');
              if (typeof img == 'string') {
                  var src = img;
                  img = new Image;
                  img.setAttribute('crossOrigin', ''); 
                  img.src = src;
              }
              context.imageSmoothingEnabled = true;
              context.drawImage(img, 0, 0, 1, 1);
              return context.getImageData(1, 1, 1, 1).data.slice(0,3);
          }
          

          【讨论】:

          • 一切都很好,但最后一行应该是 context.getImageData(0, 0, 1, 1).data.slice(0,3) 以读取唯一的像素
          【解决方案7】:

          AFAIK,这样做的唯一方法是使用&lt;canvas/&gt;...

          DEMO V2http://jsfiddle.net/xLF38/818/

          注意,这仅适用于同一域中的图像和支持 HTML5 画布的浏览器:

          function getAverageRGB(imgEl) {
          
              var blockSize = 5, // only visit every 5 pixels
                  defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
                  canvas = document.createElement('canvas'),
                  context = canvas.getContext && canvas.getContext('2d'),
                  data, width, height,
                  i = -4,
                  length,
                  rgb = {r:0,g:0,b:0},
                  count = 0;
          
              if (!context) {
                  return defaultRGB;
              }
          
              height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
              width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
          
              context.drawImage(imgEl, 0, 0);
          
              try {
                  data = context.getImageData(0, 0, width, height);
              } catch(e) {
                  /* security error, img on diff domain */
                  return defaultRGB;
              }
          
              length = data.data.length;
          
              while ( (i += blockSize * 4) < length ) {
                  ++count;
                  rgb.r += data.data[i];
                  rgb.g += data.data[i+1];
                  rgb.b += data.data[i+2];
              }
          
              // ~~ used to floor values
              rgb.r = ~~(rgb.r/count);
              rgb.g = ~~(rgb.g/count);
              rgb.b = ~~(rgb.b/count);
          
              return rgb;
          
          }
          

          对于 IE,请查看excanvas

          【讨论】:

          • 有什么方法可以获取位于不同域的图像的颜色直方图?
          • Dan:如果您尝试从另一个域访问图像上的图像数据,我认为您会遇到跨域限制。这是一个无赖。
          • 我认为蓝色和绿色值的位置不好,你写 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')' 应该是 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')' 。当主色是蓝色但结果是绿色时,这很奇怪:)。
          • 找到了一个很好的解决跨域限制的方法:只需在设置src 属性之前添加img.crossOrigin = '';。发现于:coderwall.com/p/pa-2uw
          • 不用数了,因为count === length / 4 / blockSize ;)
          【解决方案8】:

          有一个在线工具pickimagecolor.com 可以帮助您找到图像的平均颜色或主要颜色。您只需从计算机上传图像,然后单击图像。它给出了 HEX 、 RGB 和 HSV 的平均颜色。它还可以找到与该颜色相匹配的颜色以供选择。我已经用过很多次了。

          【讨论】:

            【解决方案9】:

            我最近遇到了一个 jQuery 插件,它实现了我最初想要的 https://github.com/briangonzalez/jquery.adaptive-backgrounds.js 从图像中获取主导颜色的功能。

            【讨论】:

              【解决方案10】:

              这是关于“颜色量化”的,它有几种方法,如MMCQ(修正中值剪切量化)或 OQ(八叉树量化)。不同的方法使用K-Means 来获取颜色簇。

              我把所有东西都放在了这里,因为我正在为 tvOS 找到一个解决方案,其中有一个 XHTML 的子集,它没有 &lt;canvas/&gt; 元素:

              Generate the Dominant Colors for an RGB image with XMLHttpRequest

              【讨论】:

                【解决方案11】:

                我想我会发布一个我最近遇到的项目以获得主色:

                Color Thief

                用于从图像中获取主色或代表性调色板的脚本。使用 javascript 和画布。

                其他提及和建议主色的解决方案从未在适当的上下文中真正回答问题(“在 javascript 中”)。希望这个项目能帮助那些想要这样做的人。

                【讨论】:

                  【解决方案12】:

                  “主色”很棘手。您要做的是比较颜色空间中每个像素与每个其他像素之间的距离(欧几里得距离),然后找到颜色最接近其他所有颜色的像素。该像素是主色。平均颜色通常是泥浆。

                  我希望我在这里有 MathML 来向您展示欧几里得距离。谷歌一下。

                  我在这里使用 PHP/GD 在 RGB 颜色空间中完成了上述执行:https://gist.github.com/cf23f8bddb307ad4abd8

                  然而,这在计算上非常昂贵。它会让你的系统在大图像上崩溃,如果你在客户端尝试它肯定会让你的浏览器崩溃。我一直在努力将我的执行重构为: - 将结果存储在查找表中,以供将来在每个像素的迭代中使用。 - 将大图像划分为 20px 20px 的网格,以获得局部优势。 - 使用 x1y1 和 x1y2 之间的欧式距离来计算 x1y1 和 x1y3 之间的距离。

                  如果您在这方面取得进展,请告诉我。我会很高兴看到它。我也会这样做。

                  Canvas 绝对是在客户端执行此操作的最佳方式。 SVG 不是,SVG 是基于矢量的。在我完成执行之后,我想做的下一件事就是让它在画布中运行(可能使用网络工作者来计算每个像素的总距离)。

                  要考虑的另一件事是 RGB 不是一个很好的颜色空间,因为 RGB 空间中颜色之间的欧几里得距离并不是很接近视觉距离。更好的色彩空间可能是 LUV,但我还没有找到一个好的库,或者任何将 RGB 转换为 LUV 的算法。

                  另一种完全不同的方法是对彩虹中的颜色进行排序,并构建一个具有容差的直方图,以说明颜色的不同深浅。我没有尝试过,因为在彩虹中排序颜色很困难,颜色直方图也是如此。接下来我可能会试试这个。再次,如果您在这里取得任何进展,请告诉我。

                  【讨论】:

                  【解决方案13】:

                  我会说是通过 HTML 画布标签。

                  你可以找到here@Georg 的帖子谈论 Opera 开发人员的一个小代码:

                  // Get the CanvasPixelArray from the given coordinates and dimensions.
                  var imgd = context.getImageData(x, y, width, height);
                  var pix = imgd.data;
                  
                  // Loop over each pixel and invert the color.
                  for (var i = 0, n = pix.length; i < n; i += 4) {
                      pix[i  ] = 255 - pix[i  ]; // red
                      pix[i+1] = 255 - pix[i+1]; // green
                      pix[i+2] = 255 - pix[i+2]; // blue
                      // i+3 is alpha (the fourth element)
                  }
                  
                  // Draw the ImageData at the given (x,y) coordinates.
                  context.putImageData(imgd, x, y);
                  

                  这通过使用每个像素的 R、G 和 B 值来反转图像。您可以轻松存储 RGB 值,然后将 Red、Green 和 Blue 数组四舍五入,最后将它们转换回 HEX 代码。

                  【讨论】:

                  • 是否有 IE 替代画布解决方案的机会?
                  • @Ben4Himv:有 IE9 平台预览版 ;-) 但说真的,恐怕没有。
                  【解决方案14】:

                  Javascript 无法访问图像的单个像素颜色数据。至少,可能要到 html5 ... p>

                  【讨论】:

                    猜你喜欢
                    • 2015-06-05
                    • 2012-07-28
                    • 2012-09-06
                    • 2011-07-06
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多