canvas 为图标叠加颜色

152 阅读2分钟

前言

项目中经常会遇到这样一种需求:地图上用不同颜色的同一种图标表达某种设备的不同状态。并且配色很可能会在项目进行过程中因各种因素被要求修改,前端就要频繁去找UI沟通、索要切图,双方都烦不胜烦。那么可不可以仅靠前端根据需求往图标上叠加颜色呢?其实openlayers引擎已经实现了该功能,ol/style/Icon里的color属性就是做这个的。但是自从我们换到maptalks引擎之后,因为maptalks没有实现这个功能,只能研究下openlayers源码自己封装下了。

image.png

实现

那么我们来解读下openlayers里是怎么实现的:
src/ol/style/IconImage.js

 /**
   * @param {number} pixelRatio Pixel ratio.
   * @private
   */
replaceColor_(pixelRatio) {
    if (
      !this.color_ ||
      this.canvas_[pixelRatio] ||
      this.imageState_ !== ImageState.LOADED
    ) {
      return;
    }

    const image = this.image_;
    const canvas = document.createElement('canvas');
    canvas.width = Math.ceil(image.width * pixelRatio);
    canvas.height = Math.ceil(image.height * pixelRatio);

    const ctx = canvas.getContext('2d');
    ctx.scale(pixelRatio, pixelRatio);
    ctx.drawImage(image, 0, 0);

    ctx.globalCompositeOperation = 'multiply';
    ctx.fillStyle = asString(this.color_);
    ctx.fillRect(0, 0, canvas.width / pixelRatio, canvas.height / pixelRatio);

    ctx.globalCompositeOperation = 'destination-in';
    ctx.drawImage(image, 0, 0);

    this.canvas_[pixelRatio] = canvas;
  }

这段代码的重点在于[globalCompositeOperation](CanvasRenderingContext2D:globalCompositeOperation 属性 - Web API | MDN)。
首先创建了和image同样尺寸的canvas,drawImage绘制图片到画布上,再设置复合模式为multiply,后面绘制的图形就会和画布原有图形正片叠底(将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片),所以当把整个画布涂成color的颜色时,image所在的位置会变成原图片正片叠底后的效果。这时把复合模式设置为destination-in(现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的),再次drawImage,整个画布就会只保留image所在的位置的现有画布内容,也就是正片叠底后的image。
考虑到性能问题,每次处理好的图片应以图片路径和颜色为key存储起来。下次再用的时候直接从缓存里取就可以。
源码:Janexr/practice: 实践、记录