分享一个从图片中提取色卡的实现

概述

最近在做"在线地图样式配置"的功能的时候,发现百度地图有个功能时上传一张图片,从图片中提取颜色并进行配图。本文就简单实现一下如何从图片中提取色卡。

效果

实现

实现思路

通过canvasdrawImage绘制图片,并通过getImageData获取颜色,根据颜色的距离对颜色进行分组,分组后的结果进行求平均。

实现代码

html 复制代码
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>

    <script>
      const canvas = document.querySelector("#canvas");
      const { offsetWidth, offsetHeight } = document.body;
      canvas.width = offsetWidth * 0.8;
      canvas.height = offsetHeight * 0.8;
      const ctx = canvas.getContext("2d");
      
      /**
       * 计算颜色距离,判断是否为相近颜色
       * @param {Array} color1
       * @param {Array} color2
       */
      function colorDistance([r1, g1, b1], [r2, g2, b2]) {
        const rmean = (r1 + r2) / 2;
        const r = r1 - r2;
        const g = g1 - g2;
        const b = b1 - b2;
        return Math.sqrt(
          (2 + rmean / 256) * (r * r) +
            4 * (g * g) +
            (2 + (255 - rmean) / 256) * (b * b)
        );
      }

      const img = new Image();
      img.src = "./earth.jpg";
      let maxSize = 400
      img.onload = () => {
        let { width, height } = img;
        // 进行等比缩放
        if(width > height && width > maxSize) {
          const ratio = maxSize / width
          width = maxSize
          height = ratio * height
        }
        if(height > width && height > maxSize) {
          const ratio = maxSize / height
          height = maxSize
          width = ratio * width
        }
        // 绘制图片
        ctx.drawImage(img, 0, 0, width, height);
        
        const data = ctx.getImageData(0, 0, width, height).data;
        
        const dict = {};
        
        // 获取分组颜色
        const getKey = (color2) => {
          const colors = Object.keys(dict).map((c) => c.split(",").map(Number));
          if (colors.length === 0) return color2.join(",");
          const delt = 88; // 分组范围
          let res = color2.join(",");
          for (let i = 0; i < colors.length; i++) {
            const color = colors[i];
            const dis = colorDistance(color, color2);
            if (dis < delt) {
              res = color.join(",");
              break;
            }
          }
          return res;
        };
        
        // 将颜色进行分组
        for (let i = 0; i < data.length; i += 4) {
          const r = data[i];
          const g = data[i + 1];
          const b = data[i + 2];
          const ckey = getKey([r, g, b]);
          if (!dict[ckey]) dict[ckey] = [];
          dict[ckey].push([r, g, b]);
        }
        
        // 将颜色按照数量进行排序
        const result = Object.keys(dict)
          .map((c) => {
            return {
              key: c,
              colors: dict[c],
            };
          })
          .sort((a, b) => b.colors.length - a.colors.length);
        
        // 取相近色的颜色平均值   
        for (let i = 0; i < result.length; i++) {
          const { colors } = result[i];
          let r = 0,
            g = 0,
            b = 0;
          for (let j = 0; j < colors.length; j++) {
            const [r1, g1, b1] = colors[j];
            r += r1;
            g += g1;
            b += b1;
          }
          r = Math.round(r / colors.length);
          g = Math.round(g / colors.length);
          b = Math.round(b / colors.length);
          result[i]["key"] = [r, g, b].join(",");
        }
        // 根据颜色亮度进行排序
        const colorss = result.map(({ key }) => key).sort((a, b) => {
          const [ra, ga, ba] = a.split(",").map(Number)
          const [rb, gb, bb] = b.split(",").map(Number)
          const la = ra * 0.299 + ga * 0.587 + ba *0.114
          const lb = rb * 0.299 + gb * 0.587 + bb *0.114
          return lb - la
        });
        // 渲染颜色
        const h = 20;
        for (let i = 0; i < colorss.length; i++) {
          ctx.fillStyle = `rgb(${colorss[i]})`;
          ctx.strokeStyle = `#fff`;
          const y = i * h + (height + 20);
          ctx.fillRect(0, y, 30, h);
          ctx.strokeRect(0, y, 30, h);
        }
      };
    </script>
  </body>
</html>
相关推荐
敲敲敲敲暴你脑袋2 天前
用canvas绘制兰伯特投影地图
typescript·数据可视化·canvas
用户2519162427113 天前
Canvas之图形变换
前端·javascript·canvas
FogLetter3 天前
用Three.js打造炫酷3D地球:从入门到实现交互式体验
webgl·canvas
寅时码3 天前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
德育处主任4 天前
p5.js 用 beginGeometry () 和 endGeometry () 打造自定义 3D 模型
前端·前端框架·canvas
德育处主任6 天前
p5.js 3D模型(model)入门指南
前端·前端框架·canvas
VincentFHR7 天前
Three.js 利用 shader 实现 3D 热力图
前端·three.js·canvas
德育处主任7 天前
p5.js 加载 3D 模型(loadModel)
前端·数据可视化·canvas
用户2519162427118 天前
Canvas之颜色渐变
前端·javascript·canvas
德育处主任9 天前
p5.js 从零开始创建 3D 模型,createModel入门指南
前端·数据可视化·canvas