Read the original article:How to draw text&signature on top of an image?
Context
How to draw text on top of an image without using a component
In some business scenarios, for example, after a user signs their name on a device, the path to the saved signature image is stored. The requirement is to draw additional text directly onto that image—for instance, to annotate, watermark, or timestamp the signature—without placing separate UI components on top.
Description
Instead of overlaying text components on an image, developers often need to render the text directly into the pixel data of the image. In HarmonyOS, this can be accomplished using the Canvas API, which allows custom graphics drawing such as lines, shapes, and text.
This ensures that the text becomes part of the image itself (e.g. saved into the bitmap), suitable for exporting or sending to backend systems.
Two main approaches are possible for drawing on a canvas in HarmonyOS:
- Using gesture-based drawing and saving the result via snapshots.
- Capturing touch events to draw dynamic paths.
Both solutions rely on the same principle: use the CanvasRenderingContext2D to render both images and text directly into the canvas drawing context.
Solution / Approach
Below are two practical solutions that show how drawing works on a Canvas. Once you have the signature image, you could load it into the canvas context, and then draw text at your desired coordinates.
Solution 1: Use PanGesture to implement it
This solution demonstrates how to set up a signature area on a Canvas, capture freehand drawing, and save the result as a pixel map. The key steps are:
Create a Canvas component for drawing.
Bind PanGesture to detect finger movements.
Draw lines dynamically during touch events.
Capture the canvas as an image using componentSnapshot.get().
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { componentSnapshot } from '@kit.ArkUI';
@Entry
@Component
struct SignatureBoard {
// Pixel image used for storing signatures
@State componentSnapshotPixelMap: image.PixelMap | null = null;
private lastX: number = 0;
private lastY: number = 0;
private isDown: Boolean = false;
// Set gesture options
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All, distance: 1 });
// Rendering context settings
private settings: RenderingContextSettings = new RenderingContextSettings(true);
// Obtain the Canvas rendering context
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
// Method for Drawing Line Segments on a Canvas
draw(startX: number, startY: number, endX: number, endY: number) {
this.context.moveTo(startX, startY);
this.context.lineTo(endX, endY);
// By calling stroke, you can see the drawn line.
this.context.stroke();
}
build() {
Column() {
Text("Signature Return Image: ").width("90%").fontSize(16).textAlign(TextAlign.Start)
// Display the pixel image returned by the signature.
Image(this.componentSnapshotPixelMap).size({ width: "90%", height: 200 }).margin({ bottom: 20 })
Text("Signature Board").width("90%").fontSize(16).textAlign(TextAlign.Start)
Canvas(this.context)
.width('90%')
.height(200)
.borderWidth(1)
.margin({ bottom: 20 })
.id("signature")
.backgroundColor('#fff')
.onReady(() => {
this.context.lineWidth = 3;
this.context.strokeStyle = "#000";
})
.gesture(
// Add Pan Gesture Recognizer
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
this.isDown = true;
this.lastX = event.fingerList[0].localX;
this.lastY = event.fingerList[0].localY;
this.context.beginPath();
})
.onActionUpdate((event: GestureEvent) => {
if (!this.isDown) {
return;
}
const offsetX = event.fingerList[0].localX;
const offsetY = event.fingerList[0].localY;
// Invoke the drawing method
this.draw(this.lastX, this.lastY, offsetX, offsetY);
this.lastX = offsetX;
this.lastY = offsetY;
})
.onActionEnd(() => {
// When the finger is lifted, close the path.
this.isDown = false;
this.context.closePath();
})
)
// Add a dividing line
Divider()
Row({ space: 20 }) {
Button("Confirm").onClick(() => {
try {
// Attempt to capture a screenshot of the signature canvas.
componentSnapshot.get("signature", (error: Error, pixmap: image.PixelMap) => {
if (error) {
console.log("error: " + JSON.stringify(error));
return;
}
// Assign the signed pixel image to the state variable.
this.componentSnapshotPixelMap = pixmap;
}, { scale: 1, waitUntilRenderFinished: true });
} catch (err) {
console.error("errCode:" + (err as BusinessError).code + ",errMessage:" + (err as BusinessError).message);
}
})
Button("Clear").onClick(() => {
// Clear the content of the Canvas.
this.context.clearRect(0, 0, this.context.width, this.context.height);
})
}
}
.width('100%')
.height('100%');
}
}
Solution 2: Refer to the drawing board sample
This approach listens to lower-level onTouch events instead of PanGesture. It allows precise control over the drawing path, suitable for more complex custom drawing logic.
The touch-down event begins a new path.
The touch-move event adds lines to the path.
The path is drawn onto the canvas in real time.
-
.onTouch((event?: TouchEvent) => {
if (event?.touches.length === 1) {
if (event.type === TouchType.Down) {
this.context.beginPath()
this.tempPath = new Path2D()
this.pathArray.push(this.tempPath)
this.tempPath.moveTo(event.touches[0].x, event.touches[0].y)
}
if (event.type === TouchType.Move) {
this.tempPath.lineTo(event.touches[0].x, event.touches[0].y)
this.context.stroke(this.tempPath)
}
}
})
For more sample codes, please refer to: Handwriting drawing and saving pictures , Document approval - drawing board signature, file preview and download , Signature board Demo based on Canvas .
Key Takeaways
- HarmonyOS allows drawing text directly onto images via the Canvas context for scenarios like signatures or watermarks.
- You can capture gestures or raw touch events to implement custom drawing logic on Canvas.
- After drawing, save the canvas as a pixel map for exporting or backend storage.
Top comments (0)