UPDATE (2/17/2015): The fingerPaint module has been updated to version 1.5, with new features and updated syntax for the fingerPaint.newCanvas() function (though it is backwards-compatible with the older v1.0 syntax). The links on this page will download the updated version.
Click here for details on version 1.5.
In 2012 I programmed a fun little iPad app called Gordon & Li Li: Learn Animals in Mandarin, for which I needed the user to “draw” on the iPad with his or her finger. At the time, there was no clear established method for accomplishing this in Corona SDK (I’m still not sure there is one), and the closest bit of code I could find on the Corona forums didn’t really get the job done to my satisfaction. So I came up with my own solution that worked really well for my needs and shared it with the community on Corona’s Code Exchange. (You can view that original post here.)
Now that I’m posting Corona tutorials here, I thought I’d revisit that code, which at the time existed only as a simple main.lua file and convert it into a Lua module so that it could easily be plugged into any existing Corona project with just a single line of code. I also took it as an opportunity to clean up my code and make sure that it is fully compatible with Corona’s newer Graphics 2.0 APIs. If you need finger painting in your own app, feel free to download the module and plug it in. Or if you want to dig deeper and modify the code to suit your own needs, please do, and share your edits in the comments! I’m making it available under the standard MIT license, so do with it whatever you like.
Let’s Finger Paint!
I’ll dig into the module’s code further down in this post, but for those of you who just want to type in that “one line of code” I promised and get finger painting, here’s how:
- Download fingerPaint.lua (right-click and download linked file) and drop it into your project folder (where your main.lua file is).
-
Somewhere in your code (this could be inside of a Composer/Storyboard scene, or simply in your main.lua), require the module as follows:
local fingerPaint = require("fingerPaint")
-
Now paste this one line of code into your app to create a finger painting “canvas”:
local canvas = fingerPaint.newCanvas()
- Get painting! That one line of code generates a full-screen white “canvas” for you to paint on with your finger. By default, paint “strokes” will be 10 pixels wide and black.
Customize Your Canvas
But let’s say you don’t want a full-screen canvas. Or you don’t want your paint to be black. Or you don’t want your canvas to be white…or even opaque! Well you’re in luck. The fingerPaint module allows you to customize your canvas by setting parameters when you call fingerPaint.newCanvas(). Here is the syntax:
NOTE: The syntax below will still work, but it is highly recommended that you adopt the newer syntax introduced in v1.5 of the fingerPaint module. Click here for details on version 1.5.
fingerPaint.newCanvas(width, height, strokeWidth, paintColor, canvasColor)
- width must be a number and sets the canvas’ width in pixels. It defaults to the value returned by display.actualContentWidth.
- height must be a number and sets the canvas’ height in pixels. It defaults to the value returned by display.actualContentHeight.
- strokeWidth must be a number and sets the width of your paint strokes. It defaults to 10 pixels.
- paintColor must be a table containing 4 numbers between 0 and 1, representing the RGB and alpha values for your paint color. For example, {0, 0, 1, .5} would generate a blue stroke with 50% opacity. It defaults to {0, 0, 0, 1}, which generates a black stroke that is fully opaque.
- canvasColor must be a table containing 4 numbers between 0 and 1, representing the RGB and alpha values for your canvas’ color. For example, {1, 0, 0, .9} would generate a red canvas at 90% opacity. It defaults to {1 ,1, 1, 1}, which generates a white canvas that is fully opaque. If you want a transparent canvas background, set the alpha value to 0, regardless of what RGB values you set.
Change It Up!
Using the syntax above, we can create a canvas of any width or height, set the canvas color, paint color and/or stroke width. But the fingerPaint module also provides methods for changing any of these parameters after our canvas has been created. There’s even undo & redo methods that allow users can step backwards and forwards through their painting stroke-by-stroke. Let’s look at each of these methods individually:
-
The canvas is actually just a Corona container object, so as with all containers, we can adjust its boundaries by making changes to its width and height parameters. We can even apply transitions to these values to adjust its boundaries gradually! As an example, here is how we would change our canvas to a rectangle 300 pixels wide and 100 pixels tall:
canvas.width, canvas.height = 300, 100
-
Since the canvas/container is a display object, we can reposition it on the screen by adjusting it’s x and y parameters, just like any display object:
canvas.x, canvas.y = 200, 200
-
Want to disable painting for a while, so that the user’s finger doesn’t add any new strokes? Just set the canvas’ isActive parameter to false. And re-enable painting by making it true again:
canvas.isActive = false -- now the user cannot paint canvas.isActive = true -- now the user can paint once again
-
Change the paint color by calling canvas:setPaintColor() and inserting 4 numbers between 0 and 1, representing the RGB and alpha values for your paint color. Please note that this will only impact future paint strokes – any existing strokes will retain their color:
canvas:setPaintColor(0, 1, 0, .5) -- now the paint color is green with 50% opacity
-
Change the canvas color by calling canvas:setCanvasColor() and inserting 4 numbers between 0 and 1, representing the RGB and alpha values for your canvas’ color:
canvas:setCanvasColor(0, 0, 1, .2) -- now the canvas color is blue with 20% opacity
-
Change the stroke width by calling canvas:setStrokeWidth() and inserting a number representing your stroke’s width in pixels. Please note that this will only impact future paint strokes – any existing strokes will retain their widths.
canvas:setStrokeWidth(20) -- now the stroke width is 20 pixels
-
Undo paint strokes using the canvas:undo() method:
canvas:undo() -- steps backwards one stroke
-
Redo paint strokes using the canvas:redo() method:
canvas:redo() -- steps forward one stroke
See It In Action
As stated, adding a fingerPaint canvas to your existing app is as simple as requiring in the module’s Lua file and adding one line of code. But I’ve whipped up a sample Corona project folder that allows you to see most of the methods and options described above in the simulator or on your device, with buttons that allow you to make changes to the canvas in real-time. Just download the fingerPaint sample and load it up in the Corona Simulator to take it for a spin. The sample is best viewed as an iPad in the simulator, but it will work on any device.
The sample app also makes use of my colorPicker module, which will be made available and documented in a forthcoming post. Stay tuned!
How It Works
At its most basic level, the fingerPaint module works by creating a display.newLine() object. We start the line when the user first places their finger on the screen, and any time the user moves their finger we append a new line segment from the previous touch coordinates to the new ones. To add some polish, I place a display.newCircle() object at either end of the line with its radius set to half the stroke width of the line, so that it integrates seamlessly with the line.
What I describe above is a radical change from the version I posted on the Code Exchange years ago, and I want to take a moment to explore why I was able to make these changes, and why it results in a much more efficient bit of code. Back in 2012 the line:append() method was not as stable as it is today and often resulted in herky-jerky and sometimes flat-out wrong line segments being displayed on-screen. (Minor visual hiccups can still happen when appending to a line within a touch listener, but not nearly so bad as it used to be.) I was able to work around this issue back then by doing two things any time a touch event was triggered: drawing a line from the current touch coordinates to the previous touch coordinates, and drawing a circle at the current touch coordinates (to hide that all those line objects didn’t meet seamlessly). This method works, and produces a very smooth stroke, but it involves creating potentially dozens of new display objects every second that a user is moving their finger across the screen. Keeping track of all those display objects requires system memory, which can quickly balloon to unreasonable levels if not kept in check. This wasn’t an issue for “Gordon & Li Li,” as the user was only placing a few dozen strokes on-screen at any given time, but if I were to use that method in an app where the user could paint as much as they wanted to, it could easily become very crash-prone. But now that the line:append() method has been made stable, each stroke consists of just three display objects – meaning our users can paint to their heart’s content without fear of maxing out system memory. Hooray for bug fixes!
Have You Used It?
This module is free to download, free to use, and you are free to modify it to your heart’s content. I hope that you find it useful! But if you do use it in one of your apps, please share a link to your app in the comments, or contact me so I can see how you integrated it into your app. I’d love nothing more than to see to see my code getting used out in the wild. Enjoy!
POST EDITED ON 4/9/14 – changed all instances of “fingerPaint library” to “fingerPaint module” – it’s not really a library, seeing how it only has one primary function. Details, details! 🙂
Hi Jason,
Thanks for sharing the module its been very helpful and highly appreciated. I did encouter an issue while testing on a Android Acer 500 running ICS. Lines were being created from the canvas centre on holding of a touch. I was finger printing letters and it always occurred on the turn of a swipe. If each part was created independently no strange lines would occur. I fixed the issue by changing the code to create new lines and not append the line and this appears to address the problem. I am happy to provide the code to you if you would like. I also added a clear function.
Again thanks your code was clean and easy to work with.
Hi, can i save the painting?
Would be nice if you could give me a quick explanation.
Greetings,
M.G.