用canvas给兔年壁纸换色

955 阅读4分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

效果

前言

年关将至,相信大伙都翘首期盼着放假过年了,在这先祝大家新年快乐!

为了增加点年味打算换个壁纸,便找了一张兔年的壁纸

WechatIMG240.jpeg

专业杠精上线,这个红不是我想要的红,我要换成中国传统的妃红,就下面这个,还得用代码实现

WechatIMG247.jpeg

如何实现

当然是利用canvas实现了,下面介绍下getImageData方法

  getImageData

作用: 用来获取canvas画布上指定矩形区域的像素数据; 参数:

参数含义
x矩形的左顶点横坐标
y矩形的左顶点纵坐标
width矩形的宽度
height矩形的高度

返回值: 返回的是一个ImageData对象,该对象包含了三个只读属性:

属性含义
ImageData.widthImageData的宽度,用像素表示
ImageData.heightImageData的高度,用像素表示
ImageData.data类型为Uint8ClampedArray的一维数组,每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255

有了这个方法那还不是简简单单就能实现,马上开始梳理需求

  1. 用户选择图片,利用canvas绘制并获取像素点信息
  2. 遍历像素点修改想要修改的rgba
  3. 利用canvas重新绘制修改后的图片
  4. 利用toDataURL生成图片

实现

先实现图片选择,并将图片绘制到canvas

// html
<div class="upload">
  <img id="imgSrc" :src="imgSrc" alt="" />
  <input type="file" @change="fileChange" />
</div>
//..
// javascript
/**
 *
 * @param {*} e
 */
    const fileChange = (e) => {
      loading.value = true;
      let file = e.target.files[0];
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function (e) {
        imgSrc.value = this.result;
        img = document.querySelector("#imgSrc");
        canvas = document.querySelector("#canvas");
        img.onload = function () {
          const naturalImgSize = [img.naturalWidth, img.naturalHeight];
          setCanvas(naturalImgSize);
        };
      };
    };
/**
 * 初始化canvas
 * @param {*} naturalImgSize
 */
    const setCanvas = (naturalImgSize) => {
      ctx = canvas.getContext("2d");
      canvas.width = naturalImgSize[0];
      canvas.height = naturalImgSize[1];
      ctx.drawImage(img, 0, 0);
      loading.value = false;
    };

接下来获取图片像素信息

//获取imageData
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
//打印该数据
console.log(imgData);

WeChat6abe3466c43ad393844b131511c5e1f3.png

再组织一下,拼成rgba,并计算每个rgba的数量

/**
     * 获取主色
     * @param {*} list
     * @param {*} num
     */
    const getMainColors = (list) => {
      let rgbaObj = {};
      let rgbaList = [];
      list.forEach((colorVal, i) => {
        if (i % 4 === 0) {
          let rgba = [list[i], list[i + 1], list[i + 2], list[i + 3]];
          if (rgbaObj[rgba.join(",")]) {
            rgbaObj[rgba.join(",")] += 1;
          } else {
            rgbaObj[rgba.join(",")] = 1;
          }
        }
      });
      Object.keys(rgbaObj).forEach((key) => {
        rgbaList.push({
          rgba: key.split(",").map((item) => (item = Number(item))),
          num: rgbaObj[key],
        });
      });
      rgbaList.sort((a, b) => {
        return b.num - a.num;
      });
      console.log(rgbaList)
    };

WeChatcb80e48e2721b71658c0af502b7ce40f.png

这时候问题就出现了,明明图片上看着就三个颜色,这里统计出来却有很多,仔细看一下发现很多色值都很接近,应该是色块边缘的过渡色了吧。

对于这种简单的图案那就先简单处理了,保留我们想要的颜色,把其他相近的颜色都修改为我们保留的主色。对于我这张图来说主色就三个,那对于别的图呢?看来还是得用户输入想要保留的颜色。

但是这时思考一下,这种方式好像只能处理比较简单的图片,图片复杂的话必然没法简单地处理就得到想要的效果,那就先实现个简单的看看效果~

既然简单处理,那么我们让用户输入想要保留的主色数量n,我们将色值按数量排序,取该数组的前n个色值,针对我这张图保留前三个,发现其中两个色值相近,而我们需要的白色不在其中,那我们还得支持用户输入色值,强制保留该色值

那就暂时设计成这样

WechatIMG249.png

获取完主色我们还需要展示出来,然后让用户可以修改颜色,这里可以使用<input type="color"/>选取颜色,但是这个选择出来的颜色是十六进制的,我们还需要转换成rgba色值后去替换原先的色值

    /**
     * 转换成16进制
     * @param {*} n
     */
    const toHex = (n) => `${n > 15 ? "" : 0}${n.toString(16)}`;
    /**
     * 转换成16进制颜色字符串
     * @param {*} colorArr
     */
    const toHexString = (colorArr) => {
      const {
        r,
        g,
        b,
        a = 1,
      } = { r: colorArr[0], g: colorArr[1], b: colorArr[2] };
      return `#${toHex(r)}${toHex(g)}${toHex(b)}${
        a === 1 ? "" : toHex(Math.floor(a * 255))
      }`;
    };
    /**
     * 16进制颜色字符串解析为颜色对象
     * @param color 颜色字符串
     * @returns IColorObj
     */
    const parseHexColor = (color) => {
      let hex = color.slice(1);
      let a = 1;
      if (hex.length === 3) {
        hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
      }
      if (hex.length === 8) {
        a = parseInt(hex.slice(6), 16) / 255;
        hex = hex.slice(0, 6);
      }
      const bigint = parseInt(hex, 16);
      return {
        r: (bigint >> 16) & 255,
        g: (bigint >> 8) & 255,
        b: bigint & 255,
        a,
      };
    };

1673589814199.jpg

这样前期基础工作就差不多了,这张图我想要保留三个主色,但是色值查下来前三个不包含白色,那么就要设置保留两个主色,再手动加上白色,一共就是三个主色。

点击色块左上角就可以选择想要的颜色,选好之后点击生成图片,就调用方法替换色值,然后利用canvas生成图片

1673591649679.jpg

这样更换图片颜色就实现啦

完整的代码可以查看 码上掘金

最后

这里只是简单地进行了图片颜色的更改,复杂的图片比较容易出问题且效果不好,建议还是拿简单的插画进行尝试哦。

祝大家新年快乐,钱兔无量!