Pixel Board on HarmonyOS: 32x32 Canvas, Bresenham and undo

举报
yd_292978551 发表于 2026/11/14 10:15:58 2026/11/14
【摘要】 Today I opened the Pixel Board case in the Huawei Cloud Harmony Practice Center (the case page is titled 像素画板, and its code lives in the GitCode repo harmony-practice-center/HarmonyOS-AtomSer-pixel...

Today I opened the Pixel Board case in the Huawei Cloud Harmony Practice Center (the case page is titled 像素画板, and its code lives in the GitCode repo harmony-practice-center/HarmonyOS-AtomSer-pixel-paint) and spent time reading how it is built. It is a small app with a big lesson: a 32 by 32 pixel canvas is enough to teach the core ideas of ArkUI Canvas drawing, state management and local persistence. This post records what I took away from it.

1. Why 32 by 32 is the right size

A 32 by 32 grid is 1,024 cells. That is small enough to keep the whole picture in a plain two-dimensional array in memory, and large enough to draw a recognisable character, icon or emoji. It also fits a phone screen: each cell is still big enough to tap, and the whole board can be drawn in one Canvas pass without any scrolling. The case stores the picture as data (a grid of colours), not as a bitmap, so saving, loading and undo all become array operations instead of image operations.

2. Three tools, three classic algorithms

The case offers a brush, a straight-line tool and a fill tool, and each one maps to a textbook algorithm.

The brush is the simplest: convert the touch point to a grid cell and set that cell to the current colour. The conversion is the part beginners get wrong. You must use the Canvas component's own size, not the screen size: column = floor((touchX - rectLeft) / rectWidth * 32), row = floor((touchY - rectTop) / rectHeight * 32), then clamp both to 0..31 so a finger that slips off the edge cannot write outside the array.

The line tool uses Bresenham's algorithm. Instead of interpolating floating-point points along the line and rounding them, Bresenham steps cell by cell with integer error accumulation, so the line is continuous with no gaps and no floating-point drift. If you draw a line by sampling points at fixed intervals, diagonal lines come out dotted; Bresenham is the fix.

The fill tool uses scanline flood fill. Starting from the tapped cell, it expands across connected cells of the same colour, row by row. The key detail is a visited set: without it, the fill revisits cells it has already painted and can loop or do far more work than necessary. The case also guards the obvious edge case - tapping a cell whose colour already equals the fill colour should do nothing, otherwise the fill would walk the whole board for no change.

3. Eraser and eyedropper are the same idea in reverse

The eraser is not a separate drawing engine. It writes the empty state into a cell, exactly as the brush writes a colour. The eyedropper reads a cell's colour and makes it the current brush colour. Once you see the grid as data, these tools are one-line operations, and that is the point of the design: the tools differ only in what value they write or read.

4. Undo and redo with two stacks

Undo is implemented with two stacks, one for undo and one for redo. Before every stroke the case pushes a snapshot of the grid onto the undo stack. Undo pops a snapshot and pushes the current grid onto the redo stack; redo does the reverse. The rule that is easy to forget: any new stroke must clear the redo stack, otherwise redo would resurrect a stroke from a branch of history that no longer exists. A snapshot per stroke (rather than per cell) keeps the logic simple and the memory cost trivial at 1,024 cells.

5. Saving: preferences plus a gallery with a cap

Works are saved through the preferences API, and the case keeps a gallery of up to 50 saved works. Loading a saved work replaces the current canvas, so the case asks for confirmation first - overwriting an unsaved drawing by accident is the classic data-loss bug in drawing apps. Deleting a gallery entry removes it from preferences. The 50-item cap matters: preferences are not a database, and an uncapped gallery would grow without bound.

6. Export: from grid to a real image

Export renders the grid into a PixelMap and saves it as PNG or JPEG, and the result can be shared through the system share sheet. The practical lesson is the separation: the grid is the source of truth, the PixelMap is only a render target at export time. That separation is also why the app can offer both a small pixelated preview and a clean exported file.

7. Four pitfalls I noted while reading the case

First, flood fill without a visited set is the most likely performance trap in a drawing app. Second, forgetting to clear the redo stack after a new stroke creates impossible history. Third, saving without a gallery cap slowly bloats local storage. Fourth, redrawing the whole Canvas on every touch-move event wastes frames; redrawing only the changed cells, or throttling redraws, keeps the brush feeling responsive.

8. What I verified today

I browsed this case in the practice center for more than 30 seconds and starred its GitCode repo from the case page - the star count went from 99 to 100. The case was published in the practice center on 2026-11-13. If you want a first HarmonyOS Canvas project, this is a good one to read end to end before you write your own.

A good next step after understanding it is to extend it: add a second layer, animate by saving successive grids as frames, or add a symmetry mode that mirrors each stroke. Each of those is a small change on top of the same grid, and that is exactly why the grid-first design is worth copying.

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。