How to do freehand tracing with React Native?

Building Great Apps with React Native
Search for a command to run...

Building Great Apps with React Native
Here's an example of how you could do diffing
export const removedPath = (paths, target) => {
return paths.filter(path => path !== target);
};
import {removedPath} from './index';
const targetOne = [
[1, 1],
[1, 2],
[1, 3],
[1, 4],
];
const targetTwo = [
[2, 1],
[2, 2],
[2, 3],
[2, 4],
];
const originPath = [targetOne, targetTwo];
describe('removedPath', () => {
test('should remove targetOne and left targetTwo', () => {
const result = removedPath(originPath, targetOne);
expect(result).toEqual([targetTwo]);
});
test('should remove nothing', () => {
const result = removedPath([targetTwo], targetOne);
expect(result).toEqual([targetTwo]);
});
});
Can you please consolidate all the codes and put together, which will help.
what you doing inside the diff function? How do you finding that the trace line is inside or outside ?
I appreciate your insightful blog post on freehand tracing with React Native. I am curious about the implementation details, but I understand that the source code is not publicly available. Thank you for sharing your knowledge and experience.
what the diff function means? please answer
Unfortunately the source code wasn't intend to open source.
Hey Amit,
I guess the issue is around react-native-fs while getting the image path ?
Where can I find the code?
Thank you very much for spending time on this topic. I would like your help. Wanted to know if you encountered any error related to "could not parse path from string".
Background If you've never heard of React Native EU, it's the largest React Native conference in Europe. Whether you're new to React Native or a guru, you can learn something or gain useful knowledge from the conference. Callstack hosts the conferenc...

Introduction Recently at work, my colleagues engaged in a discussion about whether to use React Native Modal or React Navigation Modal. In our tech stack, we already have both integrations, so there's no extra work required for setup. I never thought...

Intro Have you ever considered visualizing your React Navigation structure? If so, which tools do you utilize? Recently, I needed to construct a React Navigation tree for a presentation. So, I looked for different ways to show my React Navigation str...

Introduction If you missed Part 1, we discussed the keynote and Day 1's React Native EU agenda. In this post, we will delve into the remaining portions of the conference. Day 2 Hermes Hacking: Demystifying JavaScript Engines - by Radek Pietruszewski ...

Introduction React Native EU is an annual conference that brings together developers, industry leaders, and enthusiasts from around the world to share their knowledge, experience, and insights about the popular cross-platform framework, React Native....

Recently I came across a LinkedIn post asking about freehand tracing with React Native.

It caught my interest and I was thinking about how would I accomplish it if it was me. And this is where the journey begins.
The first thing that comes to mind was SVG which also makes it easy to collaborate with Designers. And with some tricks, you could make SVG animatable. But, SVG alone is not enough, we still have to deal with the tracing part.
So let's break down what tracing gonna need:
When speaking of drawing, using canvas might be the easy solution. You could simply plot the to-traced SVG and draw around it.
Then it comes to the tricky part, how do you validate it's a hand tracing? Let's look at an example of what tracing an alphabet letter would look like.
![]() |
| Image Credits - slimzon.com |
A valid tracing should meet the following
This reminds me of the geofencing technique, which in my early career I did on location-based service. Identifying whether certain geolocation is outside of the geofence. In the end, it is all algorithms and math.
The concept could be borrowed, with some algorithms applied to determine whether the tracing is within the border.
Nowadays, text recognition can be done by cloud services such as Firebase or locally with pre-trained ML models through an ML kit. Out there should already exist some text-recognition React Native package that could be applied.
A short summary of what we need so far
The fundamental building block for this would be the Canvas. Although there exists plenty of Canvas packages for React Native, we also need to consider performance.
After some research, I decided to go with rn-perfect-sketch-canvas. Which is easy to use and well-maintained. The highlight of this package is that it uses Skia Graphics Library, so we shouldn't have much concerns on the performance side.
To display a Canvas is fairly simple
import React, { useRef } from 'react';
import { StyleSheet, SafeAreaView, Button } from 'react-native';
import { SketchCanvas, SketchCanvasRef } from 'rn-perfect-sketch-canvas';
export default function App() {
const canvasRef = useRef<SketchCanvasRef>(null);
return (
<SafeAreaView style={styles.container}>
<SketchCanvas
ref={canvasRef}
strokeColor={'black'}
strokeWidth={8}
containerStyle={styles.container}
/>
<Button onPress={canvasRef.current?.reset} title="Reset" />
</SafeAreaView>
);
}
const styles = StyleSheet.create({ container: {flex: 1,}});
Within the canvas setup, simply long press to draw
The package provides some useful methods, which come in handy
reset()addPoints(points: [x, y][][]) => Drawing from point of groupstoPoints() => Get the array of point groups that got drawn in the canvas.toImage() => Get a snapshot from the canvas in the surfaceThe next thing to do is to display the SVG on Canvas. The package we are using provides a method addPoints(points) to plot x&y coordinates on the Canvas.
We'll be using this SVG for the letter tracing
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110.4 147.44">
<path d="M9.06,27.66c0,4.74,3.18,7.38,7,7.38A6.15,6.15,0,0,0,22.2,30.6l8,2.46C28.92,38.28,24,43.44,16,43.44c-8.88,0-16-6.54-16-15.78A15.39,15.39,0,0,1,15.72,11.82c8.22,0,13,5,14.34,10.38L21.9,24.66a5.86,5.86,0,0,0-6-4.44C12.12,20.22,9.06,22.86,9.06,27.66Z" />
</svg>
The d attribute is a string that contains a series of path commands that define the path to be drawn. Here, we'll need to do some conversion work, processing the d to coordinates.
Luckily, Spotify had built a tool for this conversion.
![]() | ![]() |
| Step 1. import the desired SVG | Step 2. Get the coordinates in JSON format |
The final step is to apply points retrieved from the tool and use addPoints().
const guide = [
[
[128.7200050354004, 351.9199981689453],
[129.13100814819336, 361.90721130371094],
...
[130.4096565246582, 371.8199005126953],
]];
canvasRef.current?.addPoints(guide);
The next topic on the list is to figure out which algorithms to apply for the border check. This problem is equivalent to determining whether a point is inside a polygon. After some digging, I decided to go with the package point -in-polygon which is based on this algorithm. Since the package is straightforward to use and the algorithm looks solid. So why not?
import pointInPolygon from 'point-in-polygon';
const polygon = [ [ 1, 1 ], [ 1, 2 ], [ 2, 2 ], [ 2, 1 ] ];
console.log(pointInPolygon([ 1.5, 1.5 ], polygon)); // true
console.log(pointInPolygon([ 4.9, 1.2 ], polygon)); // false
console.log(pointInPolygon([ 1.8, 1.1 ], polygon)); // true
Now on the Canvas should exist two groups of points after the user's input, one is our guide and one is the hand tracing. For the border check part, we are only interested in the user's hand drawing part. To retrieve it, we do a diff between the guide and the group of points from toPoints(). Once you have the user's hand drawing group of points, we validate them one by one. Only if all the points are valid, do we consider the whole hand tracing valid.
import {diff} from './utils';
import {guide} from './constants';
const handTracing = diff(guide, canvasRef.current?.toPoints());
let flag = true;
handTracing.map(point => {
flag = flag && pointInPolygon(point, guide);
});
Alert.alert(flag ? 'Within Guide' : 'Outside Guide');
Now, the final step of the POC is Text Recognition. I found an on-device Text Recognition package that uses Google's ML kit which is react-native-ml-kit
import TextRecognition from '@react-native-ml-kit/text-recognition';
const result = await TextRecognition.recognize(imageURL);
console.log('Recognized text:', result.text);
Here we'll need an extra step, before applying the recognize() method. The imageURL is a file path of the image. While the toImage() method from rn-perfect-sketch-canvas is just an image data, we need to store it as a image first. And here's when we introduce the react-native-fs to achieve writing files to the filesystem.
import RNFS from 'react-native-fs'
const path = RNFS.DocumentDirectoryPath + '/handTrace.png';
// write the file
RNFS.writeFile(path, canvasRef.current?.toBase64(), 'base64');
const imageURL = "file://" + path;
The following is the POC while we put all things together.
![]() | ![]() |
| POC 1. Within border | POC 2. Outside border |
Notes: In this POC text recognition is not included, I met an issue that seems like GoogleMLKit for iOS doesn't yet support Apple Silicon. If you're also using M1 mac, in the meantime you might want to go with other on-device text recognition libraries or the Cloud API solution.
A recap of what we did so far:
It's quite a fun journey to put together this proof of concept.
Thanks for reading this far, hope you enjoyed it too.