JavaScript - 获取图像特定区域的平均颜色

2023-12-12

我需要使用以下命令从图像的矩形区域获取平均颜色JavaScript.

我尝试使用tracking.js但它不允许指定区域而不是单个像素。


如果您需要获取单个像素的平均颜色,而不是矩形区域的颜色,请看一下另一个问题:

???? 鼠标悬停时从画布获取像素颜色

正如你所说,你需要获取图像中矩形区域的颜色,我假设您的意思是您需要获取给定区域的平均颜色,而不是单个像素的颜色。

无论如何,两者都是以非常相似的方式完成的:

???? 从图像或画布中获取单个像素的颜色/值

要获取单个像素的颜色,您首先需要将该图像绘制到画布上:

const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

然后获取单个像素的值,如下所示:

const data = context.getImageData(X, Y, 1, 1).data;

// RED   = data[0]
// GREEN = data[1]
// BLUE  = data[2]
// ALPHA = data[3]

✂️ 获取图像或画布上某个区域的平均颜色/值

你需要使用同样的CanvasRenderingContext2D.getImageData()获取更宽(多像素)区域的值,您可以通过更改其第三个和第四个参数来实现。该函数的签名是:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx:从中提取 ImageData 的矩形左上角的 x 坐标。
  • sy:从中提取 ImageData 的矩形左上角的 y 坐标。
  • sw:将从中提取图像数据的矩形的宽度。
  • sh:将从中提取 ImageData 的矩形的高度。

你可以看到它返回一个ImageData目的,不管那是什么。这里重要的部分是该对象有一个.data属性包含我们所有的像素值。

但请注意.data属性是一维的Uint8ClampedArray,这意味着所有像素的组件都已被展平,因此您将得到如下所示的结果:

假设您有一个如下所示的 2x2 图像:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

然后,你会像这样得到它们:

[ 255, 0, 0, 255,    0, 255, 0, 255,    0, 0, 255, 255,    0, 0, 0, 0          ]
|   RED PIXEL   |    GREEN PIXEL   |     BLUE PIXEL   |    TRANSPAERENT  PIXEL |
|   1ST PIXEL   |      2ND PIXEL   |      3RD PIXEL   |             4TH  PIXEL | 

✨ 让我们看看它的实际效果

const avgSolidColor = document.getElementById('avgSolidColor');
const avgAlphaColor = document.getElementById('avgAlphaColor');
const avgSolidWeighted = document.getElementById('avgSolidWeighted');

const avgSolidColorCode = document.getElementById('avgSolidColorCode');
const avgAlphaColorCode = document.getElementById('avgAlphaColorCode');
const avgSolidWeightedCOde = document.getElementById('avgSolidWeightedCode');

const brush = document.getElementById('brush');
const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

const BRUSH_SIZE = brush.offsetWidth;
const BRUSH_CENTER = BRUSH_SIZE / 2;
const MIN_X = image.offsetLeft + 4;
const MAX_X = MIN_X + width - BRUSH_SIZE;
const MIN_Y = image.offsetTop + 4;
const MAX_Y = MIN_Y + height - BRUSH_SIZE;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

function sampleColor(clientX, clientY) {
  const brushX = Math.max(Math.min(clientX - BRUSH_CENTER, MAX_X), MIN_X);
  const brushY = Math.max(Math.min(clientY - BRUSH_CENTER, MAX_Y), MIN_Y);

  const imageX = brushX - MIN_X;
  const imageY = brushY - MIN_Y;
 
  let R = 0;
  let G = 0;
  let B = 0;
  let A = 0;
  let wR = 0;
  let wG = 0;
  let wB = 0;
  let wTotal = 0;

  const data = context.getImageData(imageX, imageY, BRUSH_SIZE, BRUSH_SIZE).data;
  
  const components = data.length;
  
  for (let i = 0; i < components; i += 4) {
    // A single pixel (R, G, B, A) will take 4 positions in the array:
    const r = data[i];
    const g = data[i + 1];
    const b = data[i + 2];
    const a = data[i + 3];
    
    // Update components for solid color and alpha averages:
    R += r;
    G += g;
    B += b;
    A += a;
    
    // Update components for alpha-weighted average:
    const w = a / 255;
    wR += r * w;
    wG += g * w;
    wB += b * w;
    wTotal += w;
  }
  
  const pixelsPerChannel = components / 4;
  
 // The | operator is used here to perform an integer division:

  R = R / pixelsPerChannel | 0;
  G = G / pixelsPerChannel | 0;
  B = B / pixelsPerChannel | 0;
  wR = wR / wTotal | 0;
  wG = wG / wTotal | 0;
  wB = wB / wTotal | 0;

  // The alpha channel need to be in the [0, 1] range:

  A = A / pixelsPerChannel / 255;
  
  // Update UI:
  
  requestAnimationFrame(() => {
    brush.style.transform = `translate(${ brushX }px, ${ brushY }px)`;

    avgSolidColorCode.innerText = avgSolidColor.style.background
      = `rgb(${ R }, ${ G }, ${ B })`;

    avgAlphaColorCode.innerText = avgAlphaColor.style.background
      = `rgba(${ R }, ${ G }, ${ B }, ${ A.toFixed(2) })`;

    avgSolidWeightedCode.innerText = avgSolidWeighted.style.background
      = `rgb(${ wR }, ${ wG }, ${ wB })`;
  });
}

document.onmousemove = (e) => sampleColor(e.clientX, e.clientY);
  
sampleColor(MIN_X, MIN_Y);
body {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  cursor: crosshair;
  font-family: monospace;
}

#image {
  border: 4px solid white;
  border-radius: 2px;
  box-shadow: 0 0 32px 0 rgba(0, 0, 0, .25);
  width: 150px;
  box-sizing: border-box;
}

#brush {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  width: 50px;
  height: 50px;
  background: magenta;
  mix-blend-mode: exclusion;
}

#samples {
  position: relative;
  list-style: none;
  padding: 0;
  width: 250px;
}

#samples::before {
  content: '';
  position: absolute;
  top: 0;
  left: 27px;
  width: 2px;
  height: 100%;
  background: black;
  border-radius: 1px;
}

#samples > li {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-left: 56px;
}

#samples > li + li {
  margin-top: 8px;
}

.sample {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translate(0, -50%);
  display: block;
  width: 24px;
  height: 24px;
  border-radius: 100%;
  box-shadow: 0 0 16px 4px rgba(0, 0, 0, .25);  
  margin-right: 8px;
}

.sampleLabel {
  font-weight: bold;
  margin-bottom: 8px;
}

.sampleCode {
  
}
<img id="image" src="data:image/gif;base64,R0lGODlhSwBLAPEAACMfIO0cJAAAAAAAACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf4jUmVzaXplZCBvbiBodHRwczovL2V6Z2lmLmNvbS9yZXNpemUAIfkEBQAAAgAsAAAAAEsASwAAAv+Uj6mb4A+QY7TaKxvch+MPKpC0eeUUptdomOzJqnLUvnFcl7J6Pzn9I+l2IdfII8DZiCnYsYdK4qRTptAZwQKRVK71CusOgx2nFRrlhMu+33o2NEalC6S9zQvfi3Mlnm9WxeQ396F2+HcQsMjYGEBRVbhy5yOp6OgIeVIHpEnZyYCZ6cklKBJX+Kgg2riqKoayOWl2+VrLmtDqBptIOjZ6K4qAeSrL8PcmHExsgMs2dpyIxPpKvdhM/YxaTMW2PGr9GP76BN3VHTMurh7eoU14jsc+P845Vn6OTb/P/I68iYOfwGv+JOmRNHBfsV5ujA1LqM4eKDoNvXyDqItTxYX/DC9irKBlIhkKGPtFw1JDiMeS7CqWqySPZcKGHH/JHGgIpb6bCl1O0LmT57yCOqoI5UcU0YKjPXmFjMm0ZQ4NIVdGBdZRi9WrjLxJNMY1Yr4dYeuNxWApl1ALHb+KDHrTV1owlriedJgSr4Cybu/9dFiWYAagsqAGVkkzaZTAuqD9ywKWMUG9dCO3u2zWpVzIhpW122utZlrHnTN+Bq2Mqrlnqh8CQ+0Mrq3Kc++q7eo6dlB3rLuh3abPVbbbI2mxBdhWdsZhid8cr0oy9F08q0k5FXSadiyL1mF5z51a8VsQOp3/LlodkBfzmzWf2bOrtfzr48k/1hupDaLa9rUbO+zlwndfaOCURAXRNaCBqBT2BncJakWfTzSYkmCEFr60RX0V8sKaHOltCBJ1tAAFYhHaVVbig3jxp0IBADs=" >

<div id="brush"></div>

<ul id="samples">
  <li>
    <span class="sample" id="avgSolidColor"></span>
    <div class="sampleLabel">avgSolidColor</div>
    <div class="sampleCode" id="avgSolidColorCode">rgb(-, -, -)</div>
  </li>
  <li>
    <span class="sample" id="avgAlphaColor"></span>
    <div class="sampleLabel">avgAlphaColor</div>
    <div class="sampleCode" id="avgAlphaColorCode">rgba(-, -, -, -)</div>
  </li>
  <li>
    <span class="sample" id="avgSolidWeighted"></span>
    <div class="sampleLabel">avgSolidWeighted</div>
    <div class="sampleCode" id="avgSolidWeightedCode">rgba(-, -, -, -)</div>
  </li>
</ul>

⚠️ 注意我使用小数据 URI 来避免Cross-Origin如果我尝试使用更长的数据 URI,包含外部图像或大于允许的答案,则会出现问题。

????️这些普通的颜色看起来很奇怪,不是吗? (@SMT 的评论)

如果你把画笔移到左上角,你会看到avgSolidColor几乎是黑色的。这是因为该区域中的大多数像素是完全透明的,因此它们的值恰好或非常接近0, 0, 0, 255。这意味着对于我们处理的每一个,R, G and B不改变或改变很少,同时pixelsPerChannel仍然考虑到它们,所以我们最终除以一个小数字(因为我们添加0对于大多数人来说)是一个很大的值(画笔中的像素总数),这给了我们一个非常接近的值0(黑色的)。

例如,如果我们有两个像素,0, 0, 0, 255 and 255, 0, 0, 0,通过观察它们,我们可以期望平均值R渠道成为255(因为其中之一是完全透明的)。然而,这将是(0 + 255) / 2 | 1 = 127。但别担心,我们接下来会看看如何做到这一点。

另一方面,avgAlphaColor看起来是灰色的。嗯,这实际上不是真的,只是looks灰色,因为我们现在使用 Alpha 通道,这使其半透明并允许我们看到页面的背景,在本例中为白色。

???? Alpha 加权平均值(@SMT 评论的解决方案)

那么,我们能做些什么来解决这个问题呢?好吧,事实证明我们只需要使用 alpha 通道作为我们(现在加权)平均值的权重:

这意味着如果一个像素r, g, b, a, where a是在区间内[0, 255],我们将像这样更新我们的变量:

const w = a / 255; // w is in the interval [0, 1]
wR += r * w;
wG += g * w; 
wB += b * w; 
wTotal += w;

请注意像素越透明(w越接近 0),我们在计算中就越不关心它的值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript - 获取图像特定区域的平均颜色 的相关文章

随机推荐

  • 从静态方法调用 startActivityForResult

    我有一个按钮监听器 当用户单击按钮时我想启动相机意图 目前我有这个 public class ButtonListener implements View OnClickListener private ArrayList
  • 在整个页面加载之前显示加载栏

    我想在加载整个页面之前显示一个加载栏 目前 我只是使用了一个小的延迟 document ready function page fadeIn 2000 该页面已使用 jQuery 注意 我已经尝试过这个 但它对我不起作用 脚本运行时加载栏
  • Python“for i in”+变量

    我有以下代码 Euler Problem 1 print We are going to solve Project Euler s Problem 1 euler number input What number do you want
  • 如何在Linux上指定时间运行脚本? [关闭]

    Closed 这个问题不符合堆栈溢出指南 目前不接受答案 我有一个包含特定日期和时间的文本文件 我希望能够在该文件中指定的时间运行脚本 你将如何实现这一目标 创建另一个在后台运行的脚本 类似于守护程序 并每秒检查当前时间是否与文件中的时间匹
  • 将电子邮件另存为 MSG 文件,无需使用 Outlook(COM 对象等)或第 3 方软件

    现在 我正在使用 Exchange Web 服务 API 和 PowerShell 从 Exchange 中提取特定电子邮件并将其保存为 EML 文件 这很好用 但是 用户 客户要求电子邮件采用 msg 格式 我见过有两种方法可以做到这一点
  • 录制时拍摄相机屏幕截图 - 就像 Galaxy S3 一样?

    我正在开发一个使用 SurfaceView 进行显示的相机应用程序 我可以截取 SurfaceView 的屏幕截图 并将其保存为位图 使用 getDrawingCache 在包装 SurfaceView 的布局上 还有canvas draw
  • VBA:等待 Bloomberg BDP 通话完成

    我有一个脚本将一些外部数据导入到工作表中 这反过来会影响一些 BDP 公式 最好 我想在复制数据后立即对 BDP 结果进行一些检查 Bloomberg Excel 插件异步更新 如何等待结果然后恢复脚本 似乎只有在 VBA 脚本完成后才会导
  • 为 Nitrogen6x 构建 Qt 5 时出现 libm 重定位错误

    我正在尝试在 Qt 5 上构建氮气6x板由 i MX6Q 供电 我已经安装了Debian 喘息在板上 我正在使用乌班图12 10交叉编译机 配置 Qt 就像一个魅力 但我陷入了 make 步骤 这是我运行的配置脚本 configure v
  • 如何设计一封安全且“自毁”的电子邮件?

    正如大多数人所知 电子邮件非常不安全 即使客户端和发送电子邮件的服务器之间有 SSL 安全连接 消息本身在 Internet 上的节点间跳跃时也将采用明文形式 从而容易被窃听 另一个考虑因素是 发件人可能不希望邮件在一段时间后或在被阅读一次
  • 读取 PDF 文档中的所有书签,并使用书签的页码和标题创建字典

    我尝试使用 Python 和 PyPDF2 包来阅读 PDF 文档 目标是读取pdf中的所有书签 并构建一个以书签页码为键 书签标题为值的字典 互联网上没有太多关于如何实现它的支持 除了this文章 其中发布的代码不起作用 我不是 pyth
  • 无法将我自己的域添加到 google api 通知端点

    我正在尝试使用谷歌推送通知 我已经关注了此处列出的注册过程 简而言之 我的领域已在 https 中验证在 Google 网站管理员工具中 但是 当我尝试添加通知端点在 Google Cloud Console 中 我收到以下错误 You d
  • (git tfs fetch)TF400324:Team Foundation 服务不可用,底层连接已关闭

    我使用 git tfs 已经快 5 年了 然后有一天我在运行时遇到以下错误git tfs fetch TF400324 Team Foundation services are not available from server https
  • 如何在低于 KitKat 的 Android 版本的 Android WebView 中重置代理?

    我使用以下 2 种方法在 Android WebView 中为 Android 版本 ICS 和 JB 设置代理 但我无法重置 删除这两个版本的代理 如何重置 删除通过这些方法设置的代理 For ICS private static boo
  • 使用 Perl 获取 WMI 内存值

    我需要使用WMI收集Windows操作系统的内存数据 从这个意义上说 我开发了一个 Perl 脚本来生成此类数据 但是 我想知道我的方法是否正确以及有哪些替代方案 收集数据的方法旨在尽可能广泛地应用于 Windows 操作系统 如果你不是一
  • 信中信,模式识别

    我想检测这种模式 正如您所看到的 它基本上是一个字母 C 位于另一个字母内部 具有不同的方向 我的模式可以相互包含多个 C 我发布的带有 2 个 C 的模式只是一个示例 我想检测有多少个 C 以及每个 C 的方向 现在我已经成功地检测到了这
  • 如何检索用于编译给定 ELF 可执行文件的 GCC 版本?

    我想检索用于编译给定可执行文件的 GCC 版本 我试过readelf但没有得到信息 有什么想法吗 一般存放在评论区 strings a
  • Vagrant 端口转发不起作用。主机无法访问杯子

    所以我正在使用 vagrant 并尝试将其用作打印服务器 我安装了杯子 内部一切正常 我什至可以快速做一个curl到我的本地主机 631 我的流浪汉中的杯子端口 一切都有 问题是我无法以任何方式从主机尝试访问它 显然我转发了端口并且尝试了多
  • SQL从查询中的数据中选择该数据尚未在数据库中?

    我想在进行 Web 服务调用之前检查数据库中已记录的记录 这是我想象的查询的样子 我只是似乎无法弄清楚语法 SELECT FROM 1 2 3 4 as temp table WHERE temp table id LEFT JOIN ta
  • Objective-C:查找字符串中的数字

    我有一个包含单词和数字的字符串 如何从字符串中提取该数字 NSString str This is my string 1234 我希望能够将 1234 作为 int 去掉 每次我搜索该字符串时 该字符串都会有不同的数字和单词 Ideas
  • JavaScript - 获取图像特定区域的平均颜色

    我需要使用以下命令从图像的矩形区域获取平均颜色JavaScript 我尝试使用tracking js但它不允许指定区域而不是单个像素 如果您需要获取单个像素的平均颜色 而不是矩形区域的颜色 请看一下另一个问题 鼠标悬停时从画布获取像素颜色