bitArt 触控坐标转换:从手指位置到像素格子的 ArkUI 实现思路
·
bitArt 的绘制体验,很大一部分取决于触控坐标转换是否稳定。像素画编辑器不是简单响应点击位置,而是要把屏幕坐标、安全区域、画布缩放、平移偏移、矩形画布比例,最后全部映射到一个明确的 row/col 像素格子。
图 1:bitArt 在 HarmonyOS 模拟器中的像素画编辑工作台。

图 2:bitArt HarmonyOS 工程服务分层关系,触控坐标、画布视口和绘制服务保持解耦。

坐标转换不能散落在工具里
bitArt 把视口换算收敛到 EditorViewportService,绘制工具只消费 row/col。这样铅笔、橡皮、取色、形状工具都不需要各自重复处理缩放和平移。
static getCanvasBaseScale(viewportWidth: number, viewportHeight: number, canvasWidth: number,
canvasHeight: number): number {
if (viewportWidth <= 0 || viewportHeight <= 0 || canvasWidth <= 0 || canvasHeight <= 0) {
return 1;
}
return Math.min(viewportWidth / canvasWidth, viewportHeight / canvasHeight);
}
static positionToCell(position: number, canvasSide: number, canvasSize: number): number {
if (canvasSide <= 0 || canvasSize <= 0) {
return 0;
}
const rawCell = Math.floor(position / canvasSide * canvasSize);
if (rawCell < 0) {
return 0;
}
if (rawCell >= canvasSize) {
return canvasSize - 1;
}
return rawCell;
}
这里最关键的是边界裁剪。触控点可能落在画布外侧,或者因为缩放产生小数坐标,如果不 clamp 到 0 到 canvasSize - 1,后面的像素数组就会出现越界风险。
Move 事件需要补点
移动端触控事件不是每个像素格都会触发一次。手指滑得快时,如果只处理当前 Move 点,画线会断。bitArt 在 PixelCanvasService.applyStroke 中做了类似 Bresenham 的插值,让 start 到 end 之间的格子都能被处理。
static applyStroke(pixels: string[], width: number, height: number, startRow: number, startCol: number,
endRow: number, endCol: number, toolId: string, activeColor: string, toolSize: number = 1,
toolOpacity: number = 100): PixelToolResult {
let nextPixels = pixels.slice();
let currentRow = startRow;
let currentCol = startCol;
const rowDelta = Math.abs(endRow - startRow);
const colDelta = Math.abs(endCol - startCol);
const rowStep = startRow < endRow ? 1 : -1;
const colStep = startCol < endCol ? 1 : -1;
let error = rowDelta - colDelta;
while (true) {
const result = PixelCanvasService.applyTool(nextPixels, width, height, currentRow, currentCol, toolId, activeColor,
toolSize, toolOpacity);
nextPixels = result.pixels;
if (currentRow === endRow && currentCol === endCol) {
break;
}
const doubleError = error * 2;
if (doubleError > -colDelta) {
error -= colDelta;
currentRow += rowStep;
}
if (doubleError < rowDelta) {
error += rowDelta;
currentCol += colStep;
}
}
return { pixels: nextPixels, pickedColor: '' };
}
页面层只做触控状态机
Index.ets 不直接改像素,而是维护 Down、Move、Up 的状态。Down 开始一段笔画,Move 判断是否进入新格子,Up/Cancel 结束笔画。
private handleCanvasCellTouch(type: TouchType, row: number, col: number, valid: boolean): void {
if (type === TouchType.Up || type === TouchType.Cancel) {
this.endStroke();
return;
}
if (!valid) {
return;
}
if (type === TouchType.Down) {
this.beginStroke();
this.applyCanvasTool(row, col);
this.lastStrokeRow = row;
this.lastStrokeCol = col;
return;
}
if (type === TouchType.Move && this.strokeActive && this.activeTool !== 'fill' && this.activeTool !== 'picker') {
if (row === this.lastStrokeRow && col === this.lastStrokeCol) {
return;
}
this.applyCanvasStroke(this.lastStrokeRow, this.lastStrokeCol, row, col);
this.lastStrokeRow = row;
this.lastStrokeCol = col;
}
}
工程上的收益
这种拆分让触控映射、绘制插值和图层合成分别可测,也更容易适配横竖屏、矩形画布和缩放拖动画布。对 HarmonyOS ArkUI Canvas 工具来说,稳定的坐标系统往往比画笔样式本身更重要。
更多推荐


所有评论(0)