本人在一个项目中需要实现如下效果:将一个png格式的图片画到一个canvas上,其颜色根据玩家当前状态进行变化。

原本想法是在首次加载时进行预处理,存为多个离屏canvas,但后来添加颜色的过渡动画将此方法排除了(毕竟不可能将过渡时的这么多颜色全都存一份)

目标:

对于给定的任意颜色,能够高效的将该颜色叠加到图片上(毕竟要每帧渲染)

实现:

首先建一个简单的框架

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <title>test</title>
    <style>
        .bg {
            width: 600px;
            height: 600px;
            margin: 0;
            border: 1px solid #777;
        }
        label {
            margin: 10px;
        }
        canvas {
            background: #777;
        }
    </style>
</head>
<body style="display: flex; justify-content: center">
<div style="display: flex; flex-direction: column">
    <label>
        <span>R</span>
        <input id="red" type="range" min="0" max="255" value="128">
    </label>
    <label>
        <span>G</span>
        <input id="green" type="range" min="0" max="255" value="128">
    </label>
    <label>
        <span>B</span>
        <input id="blue" type="range" min="0" max="255" value="128">
    </label>
</div>
<div class="bg">
    <canvas id="game" width="600" height="600"></canvas>
</div>
</body>
<script>
function setColor() { //something }
</script>
</html>

将图片丢上去
圆内是不透明的(黑白色填充),圆外是透明的

JavaScript图片颜色混合效果 第5张插图

看起来只需要对draw函数进行修改就可以了,即设置要在绘制新形状时应用的合成操作的类型并填充颜色(毕竟是对当前图片进行“染色”)

对于globalCompositeOperation,请参考CanvasRenderingContext2D.globalCompositeOperation

经测试,采用multiply(正片叠底)的效果是最符合预期的

function draw() {
    ctx.save();
    ctx.clearRect(0, 0, N, N);
    ctx.drawImage(tree, 0, 0, N, N);
    ctx.globalCompositeOperation = "multiply";
    ctx.fillStyle = `rgb(${r},${g},${b})`;
    ctx.fillRect(0, 0, N, N);
    ctx.restore();
}

JavaScript图片颜色混合效果 第6张插图

要对处理后图像的透明像素进行处理,source-atop或destination-atop可以完成这个需求,给出一种采用destination-atop的解决方案

function draw() {
    ctx.save();
    ctx.clearRect(0, 0, N, N);
    ctx.drawImage(tree, 0, 0, N, N);
    ctx.globalCompositeOperation = "multiply";
    ctx.fillStyle = `rgb(${r},${g},${b})`;
    ctx.fillRect(0, 0, N, N);
    ctx.globalCompositeOperation = "destination-atop";
    ctx.drawImage(tree, 0, 0, N, N);
    ctx.restore();
}

此外,尝试采用不同的混合模式可以达到原本进行像素运算会十分麻烦的效果,比如采用lighter来还原一下东方神灵庙的特产——瞎眼光玉

JavaScript图片颜色混合效果 第7张插图

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <link href="main.css" rel="stylesheet">
    <script src="main.js"></script>
</head>
<body>
<h3>左键移动,右键添加/删除</h3>
</body>
</html>
body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    overflow: hidden;
    width: 100vw;
    height: 100vh;
}