You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PixiJS中用Graphics重绘ERASE混合模式擦除的区域?

解决方案:基于矢量步骤记录的可擦除绘图

你当前用双Graphics+ERASE混合模式的方案,核心问题是擦除操作直接永久删除了画布像素,导致无法重绘。要实现可编辑的擦除功能,必须放弃直接修改像素的思路,转而记录所有绘制操作的矢量步骤,通过重绘步骤来实现可逆编辑。

方案1:纯矢量步骤重绘(适合中小画布)

这种方案把每一笔绘制(画笔/擦除)都记录为独立的步骤数据,每次操作后重新绘制整个画布,擦除操作只是添加一条擦除步骤,撤销时移除即可恢复。

import { Graphics, Point, BLEND_MODES, Container } from 'pixi.js'

// 定义单步绘制数据结构
interface DrawStep {
  mode: 'pen' | 'eraser'
  points: { x: number; y: number }[]
  style: { width: number; color: number; cap: string }
}

class ClearableGraphics extends Container {
  private canvas: Graphics
  private steps: DrawStep[] = []
  private currentStep: DrawStep | null = null

  constructor() {
    super()
    this.canvas = new Graphics()
    this.addChild(this.canvas)
  }

  // 开始绘制(鼠标按下时调用)
  public startDraw(pt: { x: number; y: number }, mode: 'pen' | 'eraser' = 'pen') {
    const localPt = this.convertLocal(pt)
    // 画笔用黑色,擦除用白色(配合SRC_OUT混合模式实现擦除)
    const style = mode === 'pen' 
      ? { width: 8, color: 0x000000, cap: 'round' } 
      : { width: 8, color: 0xffffff, cap: 'round' }
    
    this.currentStep = { mode, points: [localPt], style }
  }

  // 继续绘制(鼠标拖拽时调用)
  public continueDraw(pt: { x: number; y: number }) {
    if (!this.currentStep) return
    const localPt = this.convertLocal(pt)
    this.currentStep.points.push(localPt)
    this.redraw()
  }

  // 结束绘制(鼠标松开时调用)
  public endDraw() {
    if (this.currentStep) {
      this.steps.push(this.currentStep)
      this.currentStep = null
    }
  }

  // 重绘整个画布
  private redraw() {
    this.canvas.clear()
    
    // 绘制所有已完成的步骤
    this.steps.forEach(step => this.drawStep(step))
    
    // 绘制当前正在进行的步骤(实时预览)
    if (this.currentStep) {
      this.drawStep(this.currentStep)
    }
  }

  // 绘制单步路径
  private drawStep(step: DrawStep) {
    const { mode, points, style } = step
    this.canvas.lineStyle(style)
    
    // 设置混合模式:画笔用正常覆盖,擦除用SRC_OUT(与底层像素混合实现擦除)
    this.canvas.blendMode = mode === 'pen' 
      ? BLEND_MODES.SRC_OVER 
      : BLEND_MODES.SRC_OUT
    
    if (points.length === 0) return
    
    this.canvas.moveTo(points[0].x, points[0].y)
    for (let i = 1; i < points.length; i++) {
      this.canvas.lineTo(points[i].x, points[i].y)
    }
    
    // 重置混合模式,避免影响后续绘制
    this.canvas.blendMode = BLEND_MODES.SRC_OVER
  }

  public convertLocal(pt: { x: number; y: number }) {
    return this.parent.toLocal(new Point(pt.x, pt.y))
  }

  // 撤销上一步操作(支持重绘擦除的区域)
  public undo() {
    if (this.steps.length > 0) {
      this.steps.pop()
      this.redraw()
    }
  }

  // 清空所有绘制内容
  public clearAll() {
    this.steps = []
    this.currentStep = null
    this.canvas.clear()
  }
}

方案优势

  • 所有操作都是可逆的,擦除后只需撤销对应步骤就能重绘区域
  • 基于矢量数据,缩放画布不会出现模糊
  • 逻辑简单,容易扩展(比如添加画笔颜色、粗细调整)

方案2:结合RenderTexture缓存(适合大画布)

如果画布尺寸较大,纯矢量重绘可能会有性能问题,这时候可以用RenderTexture缓存最终绘制结果,只在步骤变化时重新生成纹理,兼顾性能和可编辑性。

import { Graphics, Point, BLEND_MODES, Container, RenderTexture, Sprite, Application } from 'pixi.js'

interface DrawStep {
  mode: 'pen' | 'eraser'
  points: { x: number; y: number }[]
  style: { width: number; color: number; cap: string }
}

class ClearableGraphics extends Container {
  private renderTexture: RenderTexture
  private canvasSprite: Sprite
  private tempGraphics: Graphics
  private steps: DrawStep[] = []
  private currentStep: DrawStep | null = null
  private app: Application

  constructor(app: Application, width: number, height: number) {
    super()
    this.app = app
    this.renderTexture = RenderTexture.create({ width, height })
    this.canvasSprite = new Sprite(this.renderTexture)
    this.tempGraphics = new Graphics()
    this.addChild(this.canvasSprite)
  }

  public startDraw(pt: { x: number; y: number }, mode: 'pen' | 'eraser' = 'pen') {
    const localPt = this.convertLocal(pt)
    const style = mode === 'pen' 
      ? { width: 8, color: 0x000000, cap: 'round' } 
      : { width: 8, color: 0xffffff, cap: 'round' }
    
    this.currentStep = { mode, points: [localPt], style }
  }

  public continueDraw(pt: { x: number; y: number }) {
    if (!this.currentStep) return
    const localPt = this.convertLocal(pt)
    this.currentStep.points.push(localPt)
    
    // 实时预览当前绘制的步骤
    this.tempGraphics.clear()
    this.drawStep(this.tempGraphics, this.currentStep)
    this.canvasSprite.texture = this.renderTexture
  }

  public endDraw() {
    if (this.currentStep) {
      this.steps.push(this.currentStep)
      this.currentStep = null
      
      // 将临时绘制的内容合并到RenderTexture
      this.app.renderer.render(this.tempGraphics, { 
        renderTexture: this.renderTexture, 
        clear: false 
      })
      this.tempGraphics.clear()
    }
  }

  private drawStep(graphics: Graphics, step: DrawStep) {
    const { mode, points, style } = step
    graphics.lineStyle(style)
    graphics.blendMode = mode === 'pen' 
      ? BLEND_MODES.SRC_OVER 
      : BLEND_MODES.SRC_OUT
    
    if (points.length === 0) return
    
    graphics.moveTo(points[0].x, points[0].y)
    for (let i = 1; i < points.length; i++) {
      graphics.lineTo(points[i].x, points[i].y)
    }
    
    graphics.blendMode = BLEND_MODES.SRC_OVER
  }

  public convertLocal(pt: { x: number; y: number }) {
    return this.parent.toLocal(new Point(pt.x, pt.y))
  }

  public undo() {
    if (this.steps.length > 0) {
      this.steps.pop()
      
      // 重新生成整个RenderTexture
      this.renderTexture = RenderTexture.create({ 
        width: this.renderTexture.width, 
        height: this.renderTexture.height 
      })
      this.tempGraphics.clear()
      
      this.steps.forEach(step => {
        this.drawStep(this.tempGraphics, step)
      })
      
      this.app.renderer.render(this.tempGraphics, { renderTexture: this.renderTexture })
      this.canvasSprite.texture = this.renderTexture
    }
  }

  public clearAll() {
    this.steps = []
    this.currentStep = null
    this.renderTexture = RenderTexture.create({ 
      width: this.renderTexture.width, 
      height: this.renderTexture.height 
    })
    this.canvasSprite.texture = this.renderTexture
    this.tempGraphics.clear()
  }
}

方案优势

  • 用RenderTexture缓存最终结果,大幅提升大画布下的绘制性能
  • 保留步骤记录,依然支持撤销/重绘操作
  • 实时预览用临时Graphics,不影响缓存的纹理数据

关键原理说明

你之前用的BLEND_MODES.ERASE是直接修改目标像素的Alpha通道,删除后无法恢复。而上面两种方案用BLEND_MODES.SRC_OUT混合模式,擦除时绘制的白色路径会与底层黑色路径混合,实现视觉上的擦除效果,但所有操作都以矢量步骤的形式保留,随时可以通过重绘步骤来恢复被擦除的区域。

内容的提问来源于stack exchange,提问作者Philip Chain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:15:00