import { ERASE_POINT_INNER_COLOR, ERASE_POINT_OUTER_COLOR, EventType, INITIAL_HARDNESS, INITIAL_RADIUS, REPAIR_POINT_INNER_COLOR, REPAIR_POINT_OUTER_COLOR } from '../constants' import { drawBrushPoint, getLoadedImage } from '../helpers/dom-helper' import { DrawingCircularConfig } from '../types/dom' import { computed, reactive, ref, Ref, UnwrapRef, watch, watchEffect } from 'vue' import iconEraser from '../assets/eraser.png' import { CursorStyle, UseCursorConfig } from '../types/cursor' export class MattingCursor { ctx: CanvasRenderingContext2D cursorImage: Ref = ref('') inputCursorStyle: Ref = ref(null) mattingCursorStyle: UnwrapRef = reactive(Object.create(null)) radius = ref(INITIAL_RADIUS) hardness = ref(INITIAL_HARDNESS) inputCanvas = computed(() => (this.inputCtx.value as CanvasRenderingContext2D).canvas as HTMLElement) pointInnerColor = computed(() => (this.isErasing.value ? ERASE_POINT_INNER_COLOR : REPAIR_POINT_INNER_COLOR)) pointOuterColor = computed(() => (this.isErasing.value ? ERASE_POINT_OUTER_COLOR : REPAIR_POINT_OUTER_COLOR)) constructor(private inputCtx: Ref, private isErasing: Ref) { this.ctx = this.creatCursorCanvas() this } creatCursorCanvas() { const ctx = document.createElement('canvas').getContext('2d') as CanvasRenderingContext2D return this.updateCtx(ctx) } updateCtx(ctx: CanvasRenderingContext2D): CanvasRenderingContext2D { ctx.canvas.width = this.radius.value * 2 ctx.canvas.height = this.radius.value * 2 return ctx } async createCursorImage() { this.ctx = this.updateCtx(this.ctx) const drawingConfig: DrawingCircularConfig = { ctx: this.ctx as CanvasRenderingContext2D, x: this.radius.value, y: this.radius.value, radius: this.radius.value, hardness: this.hardness.value, innerColor: this.pointInnerColor.value, outerColor: this.pointOuterColor.value, } drawBrushPoint(drawingConfig) await this.drawIcon() return await this.ctx.canvas.toDataURL() } async drawIcon() { if (this.isErasing.value) { const eraser = await getLoadedImage(iconEraser) this.ctx.drawImage(eraser, 0, 0, this.radius.value * 2, this.radius.value * 2) } } renderOutputCursor() { const target = this.inputCanvas.value target.addEventListener(EventType.Mouseover, this.onShowCursor.bind(this)) target.addEventListener(EventType.Mousemove, this.onRenderOutputCursor.bind(this)) target.addEventListener(EventType.Mouseout, this.onHideCursor.bind(this)) } onShowCursor() { this.mattingCursorStyle.display = 'initial' } onHideCursor() { this.mattingCursorStyle.display = 'none' } onRenderOutputCursor(e: MouseEvent) { this.mattingCursorStyle.left = e.offsetX - this.radius.value + 'px' this.mattingCursorStyle.top = e.offsetY - this.radius.value + 'px' } changeOutputCursorByDrag([isDragging]: boolean[]) { if (isDragging) { this.onHideCursor() } else { this.onShowCursor() } } updateCursorParams(currHardness: number, currRadius: number) { this.hardness.value = currHardness this.radius.value = currRadius } } export default function useMattingCursor(config: UseCursorConfig) { const { inputCtx, isDragging, isErasing, hardness, radius } = config const mattingCursor = new MattingCursor(inputCtx, isErasing) const { cursorImage, mattingCursorStyle, renderOutputCursor } = mattingCursor watchEffect(async () => { mattingCursor.updateCursorParams(hardness.value, radius.value) cursorImage.value = await mattingCursor.createCursorImage() }) watch([isDragging], mattingCursor.changeOutputCursorByDrag.bind(mattingCursor)) return { mattingCursor, mattingCursorStyle, cursorImage, renderOutputCursor: renderOutputCursor.bind(mattingCursor), } }