阅读 217

在canvas中如何在不使用格子背景图片的情况下给图像画一个格子背景

之前用canvas绘制图片的时候,如果图片有格子背景

需要找一张格子背景的图片当作canvas的background-image。

或者使用linear-gradient来画一个格子背景。

格子背景图
格子背景图

才能实现给图片添加格子背景的效果:

图片一
图片一

所以就在想,能不能不用图片来实现格子背景图,而是使用canvas的强大绘制功能,因为格子背景图也是数据,通过绘制数据来实现格子背景。

绘制格子背景

首先 添加一个 canvas

<canvas id="img-box" width="400" height="400"></canvas>
复制代码

然后使用 createImageData 创建一个新的空白 ImageData 对象。 然后往新的空白 ImageData 对象中设置代表格子背景的数据。

在往 ImageData 对象设置数据之前,我们需要了解一下 ImageData 对象的 data 属性。

data属性的值是一个元素都是数字的数组,而且每个数字的范围在 0-255

例如这样的:

console.log(imageData.data) // [255, 255, 255, 255, 78, 78, 78, 96 ....]
复制代码

它们每四个数据合起来表示一个像素点,每个数据的意义按顺序来表述为 red, green, blue, alpha

接下来就往 ImageData 对象中设置代表格子背景的数据。


let canvas = document.getElementById("img-box");
let ctx = canvas.getContext("2d");

function createTransparentData(width, height) {
  let emptyBox = ctx.createImageData(width, height);
  let emptyBoxData = emptyBox.data

  // 通过 canvas宽高 来遍历一下 canvas 上的所有像素点
  for (let i = 0; i < height; i++)
    for (let j = 0; j < width; j++) {
        let point = i * width + j << 2 // << 相当于 * 4
        let rgbData = (i >> 2) + (j >> 2) & 1 == 1 ? 204 : 255; // >> 2 相当于 / 4 取整, & 1相当于 % 2
        emptyBoxData[point] = rgbData;
        emptyBoxData[point + 1] = rgbData;
        emptyBoxData[point + 2] = rgbData;
        emptyBoxData[point + 3] = 255
    }
  return emptyBox
}

const transparentData = createTransparentData(canvas.width, canvas.height)

ctx.putImageData(transparentData, 0, 0);

复制代码

效果图如下:

格子背景效果图
格子背景效果图

融合图像和背景图

拿到背景图的数据数组(transparentData)了,接下来就要把图像的数据背景图的数据(transparentData)融合了。

首先需要获取图像的数据。所以先往canvas上画一张图片,为了更好的适应各种情况,这里特意选了一张多边形图片。

<canvas id="img-box" width="400" height="400"></canvas>
<button class="btn" onClick="addTransparent()">添加格子背景</button>
复制代码

let canvas = document.getElementById("img-box");
let ctx = canvas.getContext("2d");
let img = new Image();
img.src = 'https://raw.githubusercontent.com/Fathands/images/master/canvas-share/pic.png';
img.onload = function() {
  ctx.drawImage(img, 0, 0, 400, 400);
}

复制代码

此时已经在canvas上把图像画上去了:

原始图
原始图

并且为图片添加格子背景的操作都放在 addTransparent 函数中,并且把 addTransparent 函数绑定到 “添加格子背景” 按钮上

接下来是重点。需要获取:

1.原始图的有效图像数据,就是下图中红色边框框住的部分

2.有效图像的起点,宽和高, 起点是以图像的左上角为原点的。

如下图所示:

有效图像数据图
有效图像数据图

在这之前,我们还需要获取原图中所有表示透明度的数据


function getAllAlphaData() {

  let originImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

  let alphaDataLength = new Uint8Array(originImageData.data.length >> 2)
  let alphaDataList = new Uint8Array(alphaDataLength)

  let originData = new Uint8Array(originImageData.data)
  let len = alphaDataList.length
  for (let i = 0; i < len; i++) {
    alphaDataList[i] = originData[(i << 2) + 3]
  }
  return alphaDataList
}

复制代码

拿到所有表示透明度的数据后,我们就可以来获取有效图像数据,有效图像的起点,宽和高了


function getValidImageInfo(O, i) {

  const alphaDataList = getAllAlphaData()

  let originWidth = canvas.width // 原始图像的宽
  let originHeight = canvas.height // 原始图像的高
  let x = 0 // 有颜色的数据部分 最小横坐标
  let y = 0 // 有颜色的数据部分 最小纵坐标
  let width = 0 // 从右往左 至有颜色的部分的 横向距离
  let bottomHeight = 0 // 从底部到 有颜色的部分的 纵向距离
  let flag

  flag = false
  for (let i = 0; i < originHeight; i++) { // 从头开始 从上到下的扫描
      let line = i * originWidth;
      for (let j = 0; j < originWidth; j++)
          if (alphaDataList[line + j] !== 0) // 不等于0 就是 有透明度
              flag = true; // 设为true
      if (flag) // 如果这一行有一个透明度不为0的就跳过,否则最小纵坐标+1
          break;
      else
          y++
  }

  flag = false;
  for (let i = originHeight - 1; i >= 0; i--) { // 从底部开始 从下到上的扫描
      let line = i * originWidth;
      for (let j = 0; j < originWidth; j++)
          if (alphaDataList[line + j] !== 0)
              flag = true;
      if (flag) // 如果这一行有一个透明度不为0的就跳过,否则底部纵向距离+1
          break;
      else
          bottomHeight++
  }

  flag = false;
  for (let j = 0; j < originWidth; j++) { // 从左到右的扫描
      for (let i = 0; i < originHeight; i++)
          if (alphaDataList[i * originWidth + j] !== 0)
              flag = true;
      if (flag)
          break;
      else
          x++
  }

  flag = false;
  for (let j = originWidth - 1; j >= 0; j--) { // 从右到左的扫描
      for (let i = 0; i < originHeight; i++)
          if (alphaDataList[i * originWidth + j] !== 0)
              flag = true;
      if (flag)
          break;
      else
          width++
  }

  return {
    x: x, 
    y: y, 
    width: originWidth - x - width, 
    height: originHeight - y - bottomHeight
  }
}
复制代码

我们这张图片的出来的数据如下:


const validData = getValidImageInfo()
console.log(validData)
// {
//   height: 343
//   width: 283
//   x: 49
//   y: 24
// }
复制代码

即这张图片的有效颜色的起点为 [49, 24] ,有效图像的 宽为 283,高为 343

拿到有效颜色图像的 起点,宽高之后。就可以获取了 有效颜色的数据对象

如下:


const validImageData = new Uint8Array(ctx.getImageData(validData.x, validData.y, validData.width, validData.height).data)

复制代码

拿到 有效颜色的数据对象 之后,接下来就是把 通过createTransparentData获取的透明颜色的数据对象transparentData 有效颜色的数据对象validImageData 融合到一起。

// .......

function addTransparent() {

  const transparentData = createTransparentData(canvas.width, canvas.height)
  const transparentDataInfo = transparentData.data
  const validData = getValidImageInfo()

  const validImageData = new Uint8Array(ctx.getImageData(validData.x, validData.y, validData.width, validData.height).data)

  let validImageX = validData.x // 有效图像横坐标
  let validImageXandWidth = validData.x + validData.width // 有效图像横坐标 加上 宽度
  let validImageY = validData.y // 有效图像纵坐标
  let validImageYandHeight = validData.y + validData.height;  // 有效图像纵坐标 加上 高度

  for (let i = 0; i < canvas.height; i++) // 遍历高度
    for (let j = 0; j < canvas.width; j++) { // 遍历宽度
        let numberPoint = i * canvas.width + j // 第几个点
        let pixelPoint = numberPoint * 4
        let pointerX = j // 横坐标
        let pointerY = i // 纵坐标

        // 设置边界
        if (pointerX < validImageX || pointerX >= validImageXandWidth || pointerY < validImageY || pointerY >= validImageYandHeight) {} else {
            let red = transparentDataInfo[pixelPoint] // 透明背景的r
            let green = transparentDataInfo[pixelPoint + 1] // 透明背景的g
            let blue = transparentDataInfo[pixelPoint + 2] // 透明背景的b
            let alpha = transparentDataInfo[pixelPoint + 3] // 透明背景的a

            // 融合像素点
            let K = ((pointerY - validData.y) * validData.width + (pointerX - validData.x)) * 4;
            alpha = validImageData[K + 3] / 255;
            red = validImageData[K + 0] * alpha + red * (1 - alpha);
            green = validImageData[K + 1] * alpha + green * (1 - alpha);
            blue = validImageData[K + 2] * alpha + blue * (1 - alpha);

            // 重新赋值
            transparentDataInfo[pixelPoint] = red;
            transparentDataInfo[pixelPoint + 1] = green;
            transparentDataInfo[pixelPoint + 2] = blue;
            transparentDataInfo[pixelPoint + 3] = 255
        }
    }
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.putImageData(transparentData, 0, 0);
}

// .......

复制代码

最后的效果图:

最后效果图
最后效果图

关注下面的标签,发现更多相似文章
评论