图像切片

Canvas图像切片详解,掌握精灵图切片、九宫格切片、图集切片等实用技巧。图像切片是将大图分割成多个小图的技术,常用于精灵图、UI图集等场景。

切片原理

源图像 (400x400)
+----+----+----+----+
| 0  | 1  | 2  | 3  |  每个 tile: 100x100
+----+----+----+----+
| 4  | 5  | 6  | 7  |
+----+----+----+----+
| 8  | 9  | 10 | 11 |
+----+----+----+----+
| 12 | 13 | 14 | 15 |
+----+----+----+----+

基本切片函数

function sliceImage(img, tileWidth, tileHeight) {
  const tiles = []
  const cols = Math.floor(img.width / tileWidth)
  const rows = Math.floor(img.height / tileHeight)
  
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const canvas = document.createElement('canvas')
      canvas.width = tileWidth
      canvas.height = tileHeight
      const ctx = canvas.getContext('2d')
      
      ctx.drawImage(
        img,
        col * tileWidth, row * tileHeight,
        tileWidth, tileHeight,
        0, 0,
        tileWidth, tileHeight
      )
      
      tiles.push(canvas)
    }
  }
  
  return tiles
}

切片演示

图像切片示例

精灵图切片器

class SpriteSheet {
  constructor(image, frameWidth, frameHeight) {
    this.image = image
    this.frameWidth = frameWidth
    this.frameHeight = frameHeight
    this.cols = Math.floor(image.width / frameWidth)
    this.rows = Math.floor(image.height / frameHeight)
  }
  
  drawFrame(ctx, index, x, y, scale = 1) {
    const col = index % this.cols
    const row = Math.floor(index / this.cols)
    
    ctx.drawImage(
      this.image,
      col * this.frameWidth, row * this.frameHeight,
      this.frameWidth, this.frameHeight,
      x, y,
      this.frameWidth * scale, this.frameHeight * scale
    )
  }
  
  getFrameCount() {
    return this.cols * this.rows
  }
}

精灵图演示

精灵图切片器

九宫格切片

用于可拉伸的UI边框:

function draw9Slice(img, x, y, width, height, sliceSize) {
  const s = sliceSize
  const sw = img.width
  const sh = img.height
  
  // 左上角
  ctx.drawImage(img, 0, 0, s, s, x, y, s, s)
  // 右上角
  ctx.drawImage(img, sw - s, 0, s, s, x + width - s, y, s, s)
  // 左下角
  ctx.drawImage(img, 0, sh - s, s, s, x, y + height - s, s, s)
  // 右下角
  ctx.drawImage(img, sw - s, sh - s, s, s, x + width - s, y + height - s, s, s)
  
  // 上边
  ctx.drawImage(img, s, 0, sw - 2 * s, s, x + s, y, width - 2 * s, s)
  // 下边
  ctx.drawImage(img, s, sh - s, sw - 2 * s, s, x + s, y + height - s, width - 2 * s, s)
  // 左边
  ctx.drawImage(img, 0, s, s, sh - 2 * s, x, y + s, s, height - 2 * s)
  // 右边
  ctx.drawImage(img, sw - s, s, s, sh - 2 * s, x + width - s, y + s, s, height - 2 * s)
  
  // 中间
  ctx.drawImage(img, s, s, sw - 2 * s, sh - 2 * s, x + s, y + s, width - 2 * s, height - 2 * s)
}

九宫格演示

九宫格切片拉伸

图集管理器

class AtlasManager {
  constructor() {
    this.tiles = new Map()
  }
  
  addTile(name, image, x, y, width, height) {
    const canvas = document.createElement('canvas')
    canvas.width = width
    canvas.height = height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
    this.tiles.set(name, canvas)
  }
  
  draw(ctx, name, x, y, width, height) {
    const tile = this.tiles.get(name)
    if (tile) {
      ctx.drawImage(tile, x, y, width || tile.width, height || tile.height)
    }
  }
  
  getTile(name) {
    return this.tiles.get(name)
  }
}

图集演示

图集管理器

动态切片

根据需求动态切割图像:

function dynamicSlice(img, regions) {
  return regions.map(region => {
    const canvas = document.createElement('canvas')
    canvas.width = region.width
    canvas.height = region.height
    const ctx = canvas.getContext('2d')
    
    ctx.drawImage(
      img,
      region.x, region.y,
      region.width, region.height,
      0, 0,
      region.width, region.height
    )
    
    return { name: region.name, canvas }
  })
}

注意事项

内存占用

大量切片会占用较多内存,按需加载。

缓存策略

频繁使用的切片应缓存到离屏Canvas。

边界处理

注意切片边界不要超出源图像范围。