前端拼图分割实现

第一步

找到一张模板图,如下:

这是一张通过RGBA的B通道区分的模板图,每个碎片的B通道,对应序号分别是0,10,20,30,40,......

即序号的10倍等于B通道的值。

第二步

找到一张需要切分的图,如下:

确保两张图的尺寸一致,不一致会导致压缩变形既。

第三步

上代码,微信小程序: ts代码:

typescript 复制代码
// components/cut-img/index.ts
Component({

  /**
   * 组件的属性列表
   */
  properties: {
    temp: {
      type: String,
      value: getApp().imgPrefix('/model/test/temp5-3.png')
    },
    img: {
      type: String,
      value: getApp().imgPrefix('/model/test/test.jpg')
    },
    size: {
      type: Number,
      value: 15
    },
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  lifetimes: {
    ready() {
      // 通过 SelectorQuery 获取 Canvas 节点
      setTimeout(() => {
        const query = wx.createSelectorQuery().in(this)
        query.select('#canvas')
          .fields({
            node: true,
            size: true,
          })
          .exec(this.init.bind(this))
      }, 1000);
    },
  },

  /**
   * 组件的方法列表
   */
  methods: {
    /**
     * 初始化
     */
    init(res: any) {
      const canvas = this.canvas = res[0]?.node
      if (!canvas) {
        return false
      }
      this.ctx = canvas.getContext('2d')

      this.setTemp()

      return false
    },
    /**
     * 设置temp 图片上图
     */
    setTemp() {
      const ctx = this.ctx
      const canvas = this.canvas

      const img = canvas.createImage()
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      img.onload = () => {
        this.width = canvas.width = img.width
        this.height = canvas.height = img.height
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height)

        this.tempData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        this.setImg()
      }
      img.src = this.data.temp
    },
    /**
     * 设置img 图片上图
     */
    setImg() {
      const ctx = this.ctx
      const canvas = this.canvas
      const img = canvas.createImage()
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      img.onload = () => {
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height)

        this.imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  
        const size = this.data.size
        const list: any[] = []
        for(let i = 0; i < size; i++) {
          this.cut(i, list)
        }
        console.log('>>> list', list)
        // this.triggerEvent('success', list)
      }
      img.src = this.data.img
    },
    /**
     * 裁剪
     * @param index 序号
     * @param list 存储列表
     */
    cut(index: number, list: any[]) {
      const ctx = this.ctx
      const canvas = this.canvas
      const newData = ctx.createImageData(this.width, this.height);
      for (let i = 0; i < newData.data.length; i += 4) {
        const temp = this.tempData.data.slice(i, i + 4)
        if (Math.abs(temp[2] - index * 10) < 2 && temp[3] > 10) {
          newData.data[i + 0] = this.imgData.data[i + 0]
          newData.data[i + 1] = this.imgData.data[i + 1]
          newData.data[i + 2] = this.imgData.data[i + 2]
          newData.data[i + 3] = this.imgData.data[i + 3]
        } else {
          newData.data[i + 0] = 0
          newData.data[i + 1] = 0
          newData.data[i + 2] = 0
          newData.data[i + 3] = 0
        }
      }
      // ctx.putImageData(newData, 0, 0)

      let minX = newData.width;
      let minY = newData.height;
      let maxX = -1;
      let maxY = -1;

      // 遍历所有像素,查找非透明像素的位置
      for (let y = 0; y < newData.height; y++) {
        for (let x = 0; x < newData.width; x++) {
          const pixelIndex = (y * newData.width + x) * 4;
          if (newData.data[pixelIndex + 3] > 0) { // Alpha通道大于0表示非透明
            minX = Math.min(minX, x);
            maxX = Math.max(maxX, x);
            minY = Math.min(minY, y);
            maxY = Math.max(maxY, y);
          }
        }
      }
      const width = maxX - minX + 1
      const height = maxY - minY + 1
      canvas.width = width
      canvas.height = height
      // 创建新的图像数据对象用于裁剪后的图像
      const croppedImage = ctx.createImageData(width, height);

      // 复制非透明像素到新的图像数据中
      for (let y = minY; y <= maxY; y++) {
        for (let x = minX; x <= maxX; x++) {
          const srcIndex = ((y * newData.width) + x) * 4;
          const destIndex = ((y - minY) * (maxX - minX + 1) + (x - minX)) * 4;
          croppedImage.data.set(newData.data.subarray(srcIndex, srcIndex + 4), destIndex);
        }
      }

      // 清除画布并绘制裁剪后的图像
      ctx.clearRect(0, 0, width, height);
      ctx.putImageData(croppedImage, 0, 0);
      const dataUrl = canvas.toDataURL('image/png');
      list.push({
        x: minX,
        y: minY,
        width,
        height,
        dataUrl
      })
    },
  }
})

wxml代码:

wxml 复制代码
<!--components/cut-img/index.wxml-->
<view style="display: none;">
  <canvas type="2d" id="canvas"></canvas>
</view>

步骤总结

1、获取canvas

2、先渲染模板,拿到模板图的imgdata

3、再渲染需要切换的目标图,拿到目标图的imgdata

4、根据切分的数量,循环切分

4.1、创建一个空的imgdata,根据序号对应的B通道,把对应的切片数据存入imgdata

4.2、计算非空白区域的位置和尺寸,裁剪图片数据

4.3、裁剪后的数据放回canvas,导出dataURL

4.4、保存对应的x、y、width、height、dataURL

5、返回list

相关推荐
别惊醒渔人7 小时前
Vue3 Diff 优化:最长递增子序列 LIS
前端·javascript·vue.js
颜酱7 小时前
07 | 把字段与指标同步到 Qdrant(生成阶段)
前端·人工智能·后端
用户059540174467 小时前
把AI长期记忆测试从手动验证换成pytest,2天揪出11个隐藏Bug
前端·css
lichenyang4538 小时前
从 Vite 空项目到 AIGC 图片工作台:我如何打通生图、任务轮询、生成库与 Canvas 动态特效
前端·人工智能
泡沫冰@8 小时前
上章节中文件的讲解
前端·网络·nginx
进击的丸子9 小时前
APP人脸识别增值版Harmony Demo实操与关键代码解析
前端·程序员·harmonyos
a1117769 小时前
唯美花朵风格的黑胶唱片音乐播放器
前端·css·css3
Hilaku9 小时前
工作 5 年后,决定你薪资上限的究竟是什么?
前端·javascript·程序员
爱分享的程序猿-Clark9 小时前
【前端分享】vue3 有 keep-alive属性吗?
前端
Revolution6110 小时前
页面更新后为什么出现 Loading chunk failed:旧页面如何请求了已删除的构建产物
前端·面试·前端工程化