使用 JavaScript 从图像中提取调色板

2025-06-07

使用 JavaScript 从图像中提取调色板

介绍

今天我给大家带来一些非常有趣的东西,我觉得值得分享。首先,我先来展示一下最终成果。

项目图像

如果您迫不及待并想亲自测试,这里是应用程序演示和存储库的链接。

解释

我们可以加载任何图像并提取调色板,每种颜色都伴随着它的对立颜色(互补色)。

在Spotify中可以找到类似技术的示例,当您导航到歌曲/播放列表或专辑时,您会在顶部看到一个自定义颜色渐变,代表图片的主色调,这种渐变为每个页面增添了独特的感觉,这实际上也是我写这篇文章的原因。

Spotify 示例

有几个网站提供这项服务,例如coolors.cocanva.com,如果您想知道它是如何工作的,那么您来对地方了,让我们来找出答案。

📝 步骤

现在我们知道了我们要处理什么,让我们首先解释一下这个过程:

  1. 将图像加载到画布中。
  2. 提取图像信息。
  3. 构建 RGB 颜色数组。
  4. 应用颜色量化。
奖励曲目
  • 按亮度排列颜色。
  • 创建每种颜色的互补版本。
  • 构建 HTML 以显示调色板。

🖼️ 将图像加载到画布中

首先,我们创建页面的基本 HTML,我们需要一个文件类型的表单输入来上传图像和一个画布元素,因为这是我们访问图像数据的方式。

索引.html



<form action="#">
 <input type="file" id="imgfile" />
 <input type="button" id="btnLoad" value="Load" onclick="main();" />
</form>
<canvas id="canvas"></canvas>
<div id="palette"></div>
<div id="complementary"></div>


Enter fullscreen mode Exit fullscreen mode

🚜 提取图像信息

我们使用事件处理程序将图像加载到画布中,这使我们能够从画布 API.onload访问getImageData()方法。

index.js



const main = () => {
  const imgFile = document.getElementById("imgfile");
  const image = new Image();
  const file = imgFile.files[0];
  const fileReader = new FileReader();

  fileReader.onload = () => {
    image.onload = () => {
      const canvas = document.getElementById("canvas");
      canvas.width = image.width;
      canvas.height = image.height;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(image, 0, 0);

      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

返回的信息getImageData()代表了组成图像的所有像素,这意味着我们有一个以下格式的庞大的值数组:



{
  data: [133,38,51,255,133,38,255,120...],
  colorSpace: "srgb",
  height: 420,
  width: 320
}


Enter fullscreen mode Exit fullscreen mode

data里面的每个值代表一个像素的一个通道R(红色)、G(绿色)、B(蓝色)和A(Alpha),data数组的每四个元素组成RGBA颜色模型。

数据示例

🏗️ 构建 RGB 颜色数组

获取图像数据后,我们必须立即将其解析为更易读的内容,这将使我们将来的生活更加轻松。

我们每四个元素循环遍历图像数据,并返回RGB模式而不是 RGBA 模式的颜色对象数组。

index.js



const buildRgb = (imageData) => {
  const rgbValues = [];
  for (let i = 0; i < imageData.length; i += 4) {
    const rgb = {
      r: imageData[i],
      g: imageData[i + 1],
      b: imageData[i + 2],
    };
    rgbValues.push(rgb);
  }
  return rgbValues;
};


Enter fullscreen mode Exit fullscreen mode

🎨 颜色量化

构建 rgb 颜色数组后,我们需要以某种方式知道哪些颜色最能代表图像,为了获得这一点,我们使用颜色量化

维基百科将颜色量化描述为

减少图像中使用的不同颜色数量的过程,通常目的是使新图像在视觉上尽可能与原始图像相似。

中值切割算法

为了实现颜色量化,我们将使用一种称为中值切割的算法,其过程如下:

  1. 找到图像中范围最大的颜色通道(红色、绿色或蓝色)。
  2. 按该通道对像素进行排序。
  3. 将列表分成两半。
  4. 对每半部分重复该过程,直到获得所需数量的颜色。

这听起来很简单,但实际上有点复杂,所以我将尽力解释下面的代码。

让我们首先创建一个函数来查找具有最大范围的颜色通道。

将最小 rgb 值初始化为最大数,将最大 rgb 值初始化为最小数,这样我们就可以准确地确定最低值和最高值。

然后,循环遍历每个像素并使用Math.minMath.max将其与当前值进行比较

随后,我们检查每个通道最小值和最大值结果之间的差异,并返回范围最大的通道的字母。

index.js



const findBiggestColorRange = (rgbValues) => {
  let rMin = Number.MAX_VALUE;
  let gMin = Number.MAX_VALUE;
  let bMin = Number.MAX_VALUE;

  let rMax = Number.MIN_VALUE;
  let gMax = Number.MIN_VALUE;
  let bMax = Number.MIN_VALUE;

  rgbValues.forEach((pixel) => {
    rMin = Math.min(rMin, pixel.r);
    gMin = Math.min(gMin, pixel.g);
    bMin = Math.min(bMin, pixel.b);

    rMax = Math.max(rMax, pixel.r);
    gMax = Math.max(gMax, pixel.g);
    bMax = Math.max(bMax, pixel.b);
  });

  const rRange = rMax - rMin;
  const gRange = gMax - gMin;
  const bRange = bMax - bMin;

  const biggestRange = Math.max(rRange, gRange, bRange);
  if (biggestRange === rRange) {
    return "r";
  } else if (biggestRange === gRange) {
    return "g";
  } else {
    return "b";
  }
};


Enter fullscreen mode Exit fullscreen mode

递归时间

现在我们有了颜色范围最大的组件(R、G 或 B),对其进行排序,然后将其分成两半,使用这两半重复相同的过程并再次调用该函数,每次都为深度添加一个值。

index.js



const quantization = (rgbValues, depth) => {
  // base code goes here

const componentToSortBy = findBiggestColorRange(rgbValues);
  rgbValues.sort((p1, p2) => {
    return p1[componentToSortBy] - p2[componentToSortBy];
  });

  const mid = rgbValues.length / 2;
  return [
    ...quantization(rgbValues.slice(0, mid), depth + 1),
    ...quantization(rgbValues.slice(mid + 1), depth + 1),
  ];
}


Enter fullscreen mode Exit fullscreen mode

至于基本情况,当我们的深度等于 MAX_DEPTH 时我们输入它,在我们的情况中为 4,然后将所有值相加并除以二分之一得到平均值。

注意:在这种情况下,深度表示我们想要的颜色数为 2 的幂。

index.js



const quantization = (rgbValues, depth) => {

 const MAX_DEPTH = 4;
  if (depth === MAX_DEPTH || rgbValues.length === 0) {
    const color = rgbValues.reduce(
      (prev, curr) => {
        prev.r += curr.r;
        prev.g += curr.g;
        prev.b += curr.b;

        return prev;
      },
      {
        r: 0,
        g: 0,
        b: 0,
      }
    );

    color.r = Math.round(color.r / rgbValues.length);
    color.g = Math.round(color.g / rgbValues.length);
    color.b = Math.round(color.b / rgbValues.length);
    return [color];
  }
  // recursion code goes below
}


Enter fullscreen mode Exit fullscreen mode

就是这样,我们完成了中值切割和调色板提取。

📑 额外步骤

我们可以在这里做很多事情,但我不想浪费您宝贵的时间,如果您有兴趣稍微扩大项目范围,请检查存储库,它包含所有额外的代码。

  • 按亮度对颜色进行排序。有多种方法可以实现此目的,具体取决于您的需求,这里我们使用相对亮度
  • 创建每种颜色的互补版本。
  • 构建 HTML 以显示调色板。

🗃️ 资源

如果您想进一步了解该主题,我建议尝试不同的算法来创建调色板,找到主要色调,了解色彩空间的工作原理或添加不同的配色方案,这里有一些例子可以帮助您:

👋 最后的评论

感谢您抽出时间,希望您喜欢这篇文章并有所收获,祝您有美好的一天 :)

鲍勃·罗斯再见

(封面照片由 Unsplash 上的 Zhang Xinxin 拍摄)
文章来源:https://dev.to/producthackers/creating-a-color-palette-with-javascript-44ip
PREV
您可以将回调转换为承诺
NEXT
开发人员的 SQL 注入 它是什么 如何攻击 盲 SQLi 碎片化 SQLi 使用 sqlmap 自动化防御