• 利用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)
    在这里插入图片描述

    值得注意的地方是:
    参数 img,绘制到上下文的元素。允许任何的画布图像源,例如:HTMLImageElementSVGImageElement(en-US)HTMLVideoElementHTMLCanvasElementImageBitmapOffscreenCanvasVideoFrame(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)
    
    • 1
    • 2
    • 3
    • 4

    这里的注意点:
    在操作drawImage()函数时,经常会出现调取正常,但canvas绘制出现空白的情况:
    在这里插入图片描述

    这种情况,原因可以归为:
    ● 浏览器在加载图片时,图片尚未加载完毕,便开始绘图
    ● 主要原因为: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,宽,高)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    在这里插入图片描述

    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
    则就会导致跨域问题:
    在这里插入图片描述所以为了程序的健壮性以及避免报错,我们可以用异步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
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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];
    
    • 1
    • 2
    • 3
    • 4

    data打印的结果为unit8ClampedArray(8 位无符号整型固定数组) 类型化数组
    表示一个由值固定在 0-255 区间的 8 位无符号整型组成的数组;如果你指定一个在 [0,255] 区间外的值,它将被替换为 0 或 255;如果你指定一个非整数,那么它将被设置为最接近它的整数。(数组)内容被初始化为 0。一旦(数组)被创建,你可以使用对象的方法引用数组里的元素,或使用标准的数组索引语法(即使用方括号标记)。
    在这里插入图片描述

    实现方法

    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);
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

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

    放一张计算出来的结果:
    在这里插入图片描述

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1. 计算灰度(灰度原理在这里插入图片描述
      ● 这里采用浮点法0.3R + 0.59G + 0.11*B 计算图片灰度值。
      ● 灰度值也是0~255,(0黑色,255白色)值越大图色则越亮,同时对应的像素点在视觉上颜色更浅。
      ● 根据灰度值决定文字颜色

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

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

  • 相关阅读:
    Linux下Doris1.1+Mysql安装启动
    java计算机毕业设计山西农谷企业产品推广展网源码+系统+数据库+lw文档+mybatis+运行部署
    【CSS】深入了解圆角属性border-radius
    丁鹿学堂前端培训:typescript之抽象类和接口简介
    76. 最小覆盖子串
    Mybatis如何批量插入数据?
    CTF-php代码审计(MD5)
    Airtest框架和Poco框架常见问题
    字节跳动八进八出,offer到手,发现项目不重要算法才最重要
    C#:实现BinaryInsertionSorter折半插入排序算法(附完整源码)
  • 原文地址:https://blog.csdn.net/qq_43682422/article/details/127324118