利用canvas实现根据背景图片主色调动态展示字体颜色

原理:

主要通过canvas上下文对象中drawImage方法,去复刻绘制一份图片(注意该canvas不做展示,最好使用display:none隐藏),然后利用getImageData方法获取图片数据,对获取到的图片数据进行一系列判断操作,得出图片的主色调,然后再进行灰度值计算,根据灰度值判断图片的明暗从而设置出合理的字体颜色。

技术准备:

canvas参考手册

1. drawImage

语法:
drawImage(img, x, y)
drawImage(img, x, y, Width, Height)
drawImage(img, sx, sy, sWidth, sHeight, x, y, Width, Height)
利用canvas实现根据背景图片主色调动态展示字体颜色_第1张图片

值得注意的地方是:
参数 img,绘制到上下文的元素。允许任何的画布图像源,例如:HTMLImageElement、SVGImageElement(en-US)、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas 或 VideoFrame(en-US)。

常用的两种绘制手段:
1)HTMLImageElement , 直接使用dom流中的img元素。

const img = document.getElementById("flower-img"); //获取img元素
const canvas = document.getElementById("canvas-img"); //获取画布元素
const ctx = canvas.getContext("2d"); //获取画布元素上下文
ctx.drawImage(img, 200, 200, 10, 10); //(图片,宽,高,坐标x,坐标y)

这里的注意点:
在操作drawImage()函数时,经常会出现调取正常,但canvas绘制出现空白的情况:
利用canvas实现根据背景图片主色调动态展示字体颜色_第2张图片

这种情况,原因可以归为:
● 浏览器在加载图片时,图片尚未加载完毕,便开始绘图
● 主要原因为:drawImage()为异步函数
● drawImage()函数,要等到img标签里指定的图像加载完成后,再开始绘图,否则会出现无图的情况
解决方法
可以声明两种加载方式:
img.onload = function() {drawImage()}
window.onload = function() {drawImage()}
如下:

  const img = document.getElementById("flower-img"); //获取img元素
  console.log(img);
  const canvas = document.getElementById("canvas-img"); //获取画布元素
  const ctx = canvas.getContext("2d"); //获取画布元素上下文
  img.onload = () => {
     ctx.drawImage(img, 50, 50, 100, 100); //(图片源,坐标x,坐标y,宽,高)
  }

利用canvas实现根据背景图片主色调动态展示字体颜色_第3张图片

2) 使用外链图片

 const imgSrc =
      "https://img.alicdn.com/imgextra/i2/O1CN01PAqb911xdw93IOrhS_!!6000000006467-0-tps-1200-220.jpg";
//创建图片
const img = new Image();
//设置图片支持跨域
img.crossOrigin = "Anonymous";
//异步绘制图片
img.onload = () => {
  	ctx.drawImage(img, 0, 0, 400, 400);
};
img.src = imgSrc;

为什么要设置img.crossOrigin = “Anonymous”?

利用canvas实现根据背景图片主色调动态展示字体颜色_第4张图片

mdn上写到,只要用了跨域外链图片,画布则会被污染,无法使用一些设计图片数据的api。
解决方法总结了一下:

步骤1: 首先,必须有一个可以对图片响应正确 Access-Control-Allow-Origin 响应头的服务器。简单的来说就是图床服务器得支持跨域
步骤2:在 HTMLImageElement 上设置 crossOrigin(en-US) 的 crossorigin 属性,这将允许浏览器在下载图像数据时允许跨域访问请求。

两者条件缺一不可,一旦外链服务器不支持跨域。则即使设置了crossOrigin = “Anonymous”,还是会提示跨域的错误。

例如把外链图片换成下面这个链接:
https://interactive-examples.mdn.mozilla.net/media/examples/plumeria.jpg
则就会导致跨域问题:
利用canvas实现根据背景图片主色调动态展示字体颜色_第5张图片所以为了程序的健壮性以及避免报错,我们可以用异步Promise来优化加载图片这个功能:

 return new Promise((resolve, reject) => {
    img.onload = function () {
      const width = img.width 
      const height = img.height
      ctx.drawImage(img, 0, 0, width, height)
      const { data } = ctx.getImageData(0, 0, width, height)
      resolve(data)
    }
  	// 错误处理
    const errorHandler = () => reject(new Error('An error occurred attempting to load image'))
    img.onerror = errorHandler
    img.onabort = errorHandler //图片加载中止
    img.src = src
  })

2.getImageData

getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

R - 红色 (0-255)
G - 绿色 (0-255)
B - 蓝色 (0-255)
A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
color/alpha 以数组形式存在,并存储于 ImageData 对象的 data 属性中。

提示:在操作完成数组中的 color/alpha 信息之后,可以使用 putImageData() 方法将图像数据拷贝回画布上。
例子:
以下代码可获得被返回的 ImageData 对象中第一个像素的 color/alpha 信息:

red=imgData.data[0];
green=imgData.data[1];
blue=imgData.data[2];
alpha=imgData.data[3];

data打印的结果为unit8ClampedArray(8 位无符号整型固定数组) 类型化数组
表示一个由值固定在 0-255 区间的 8 位无符号整型组成的数组;如果你指定一个在 [0,255] 区间外的值,它将被替换为 0 或 255;如果你指定一个非整数,那么它将被设置为最接近它的整数。(数组)内容被初始化为 0。一旦(数组)被创建,你可以使用对象的方法引用数组里的元素,或使用标准的数组索引语法(即使用方括号标记)。
利用canvas实现根据背景图片主色调动态展示字体颜色_第6张图片

实现方法

1. 统计出图片中出现过的颜色以及次数,并按次数大小排序。

/*
        data 图像数据 :unit8ClampedArray
        accuracyIndex 精准指数:number,用于性能优化,越低越精准
        ignore 忽略的颜色数组: string[],填rgb值,用于性能优化
      */
    const getColorCount = (data, accuracyIndex, ignore) => {
      // 精准指数整数处理
      accuracyIndex = Math.round(accuracyIndex)
      //精准指数溢出处理
      if (accuracyIndex > Math.round(data.length / 4.0)) {
        accuracyIndex = Math.round(data.length / 4.0);
      }
      // 存储颜色以及数量的map集合
      const colorCountMap = new Map();

      for (let i = 0; i < data.length; i += 4 * accuracyIndex) {
        let alpha = data[i + 3];
        // 跳过透明度为0的像素点
        if (alpha === 0) continue;

        /* subarray 方法可以截取指定的字段返回与指定类型相同的类数组,与数组的slice方法相似
          Array.from 可将unit8ClampedArray类数组转数组*/
        let rgbArray = Array.from(data.subarray(i, i + 3));

        // 过滤数据含undefined的点
        if (rgbArray.indexOf(undefined) !== -1) continue;

        // 将像素处理成rgba格式
        let color =
          alpha && alpha !== 255
            ? `rgba(${[...rgbArray, alpha].join(",")})`
            : `rgb(${rgbArray.join(",")})`;

        // 过滤ignore数组中指定的颜色
        if (ignore.indexOf(color) !== -1) continue;

        if (colorCountMap[color]) {
          colorCountMap[color].count++;
        } else {
          colorCountMap[color] = { color, count: 1 };
        }
      }

      //将map集合处理成数组
      const countArray = Object.values(colorCountMap);
      //利用数组的sort进行排序
      return countArray.sort((a, b) => b.count - a.count);
    };

补充:
subarray() 返回一个新的、基于相同 ArrayBuffer、元素类型也相同的的 TypedArray。开始的索引将会被包括,而结束的索引将不会被包括。TypedArray 是指 typed array types 的其中之一。

放一张计算出来的结果:
利用canvas实现根据背景图片主色调动态展示字体颜色_第7张图片

2. 取主色调计算灰度

1)关于主色调的取法,可以去出现次数最多的颜色,当然也可以取出现次数前n的颜色进行中和。

 // 中和的方法
    const getMianColor = (colorArr, n) => {
      const mainColorArr = colorArr.slice(0, n);
      let r = 0,
        g = 0,
        b = 0,
        a = 0;

      mainColorArr.map((item) => {
        const rgbStr = item?.color.substring(
          item["color"].indexOf("(") + 1,
          item["color"].indexOf(")")
        );
        const rgbArr = rgbStr.split(",");
        r += Number(rgbArr[0]);
        g += Number(rgbArr[1]);
        b += Number(rgbArr[2]);
        //当透明度存在时才计算
        if (rgbArr.length > 3) {
          a += rgbArr[3];
        }
      });
      console.log(r, g, b);
      const finalColor =
        a === 0
          ? `rgb(${Math.round((r / n) * 1.0)},${Math.round(
              (g / n) * 1.0
            )},${Math.round((b / n) * 1.0)})`
          : `rgb(${Math.round((r / n) * 1.0)},${Math.round(
              (g / n) * 1.0
            )},${Math.round((b / n) * 1.0)},${Math.round((a / n) * 1.0)})`;

      //返回中和的颜色
      return finalColor;
    };
  1. 计算灰度(灰度原理)利用canvas实现根据背景图片主色调动态展示字体颜色_第8张图片
    ● 这里采用浮点法0.3R + 0.59G + 0.11*B 计算图片灰度值。
    ● 灰度值也是0~255,(0黑色,255白色)值越大图色则越亮,同时对应的像素点在视觉上颜色更浅。
    ● 根据灰度值决定文字颜色

我们可以判断,当灰度值大于某个值,比如180时,我们断定这样图片色彩比较亮(偏白色),适合深色字体,反之则使用浅色字体。

如有收获,请点个免费的赞吧,谢谢!~

你可能感兴趣的:(web前端,javascript,前端,html,canvas)