This post we written by Steve Hannah , one of the newest additions to the Codename One team and a long time community contributor.
This is the first in a series of posts about drawing graphics in Codename One. In this tutorial, we will create a rudimentary drawing app to demonstrate how to use the Shape API.
Codename One has included basic 2D graphics ability since its inception, but until recently, it was missing a few primitives that were required for drawing arbitrary graphics. Most notably, it did not include a general "Shape" API to draw arbitrary paths. This meant that most graphics were composed of images, a few common geometry primitives such as rectangles and circles, and clever use of tiling and clipping. If you wanted to draw an arbitrary shape composed of lines and bezier curves, you were generally out of luck.
I ported the Pisces graphics library to Codename One last year in order to provide this type of functionality inside Codename One apps. This provided a 2D drawing API, but it was quite slow. Fine for static drawings, but too slow to be used in most animations.
The new CN1 graphics API provides the same types of functionality as the Pisces library, but with drastically improved performance. Finally we can draw complex 2D graphics (and 3D graphics in some platforms) without clogging the rendering pipeline.
- A Shape API for drawing generalized paths on a Graphics context.
- A Transformation API to be able to perform transformations on graphics contexts and shapes. (E.g. rotate, scale, translate, etc…).
- Support for 3D transformations. (Currently only available on Android and iOS).
(As of writing, this may change in the future as there is demand to backport the functionality to older platforms).
A 2D Drawing App
Let’s look at a simple example of a drawing app, that allows the user to tap the screen to draw a contour picture. The app will work by simply keeping a GeneralPath in memory, and continually add points as bezier curves. Whenever a point is added, the path is redrawn to the screen.
Step 1: Create a Project
We start by creating a standard "Hello World" project using the "Non-visual" template (i.e. built with code, not the GUI editor).
Step 2: Make the Canvas
The center of the app is the DrawingCanvas class, which extends Component .
Conceptually this is very basic component. We will be overriding the paintBackground() method to draw the path. We keep a reference to a GeneralPath object (which is the concrete implementation of the Shape interface in Codename One) to store each successive point in the drawing. We also parametrize the stroke width and color.
The implementation of the paintBackground() method (shown above) should be fairly straight forward. It creates a stroke of the appropriate width, and sets the color on the graphics context. Then it calls drawShape() to render the path of points.
The addPoint method is designed to allow us to add points to the drawing. A simple implementation that uses straight lines rather than curves might look like this:
We introduced a couple house-keeping member vars (lastX and lastY) to store the last point that was added so that we know whether this is the first tap or a subsequent tap. The first tap triggers a moveTo() call, whereas subsequent taps trigger lineTo() calls, which draw lines from the last point to the current point.
A drawing might look like this:
Using Bezier Curves
Our previous implementation of addPoint() used lines for each segment of the drawing. Let’s make an adjustment to allow for smoother edges by using quadratic curves instead of lines.
Codename One’s GeneralPath class includes two methods for drawing curves:
See the General Path javadocs for the full API.
- quadTo() : Appends a quadratic bezier curve. It takes 2 points: a control point, and an end point.
- curveTo() : Appends a cubic bezier curve, taking 3 points: 2 control points, and an end point.
We will make use of the quadTo() method to append curves to the drawing as follows:
This change should be fairly straight forward except, perhaps, the business with the odd variable. Since quadratic curves require two points (in addition to the implied starting point), we can’t simply take the last tap point and the current tap point. We need a point between them to act as a control point. This is where we get the curve from. The control point works by exerting a sort of "gravity" on the line segment, to pull the line towards it. This results in the line being curved. I use the odd marker to alternate the control point between positions above the line and below the line.
A drawing from the resulting app looks like:
Detecting Platform Support
The DrawingCanvas example is a bit naive in that it assumes that the device supports the shape API. If I were to run this code on a device that doesn’t support the Shape API, it would just draw a blank canvas where I expected my shape to be drawn. You can fall back gracefully if you make use of the Graphics.isShapeSupported() method. E.g.
The next post in this series will cover 2D animations. Stay tuned for more…
Notice: This post was automatically converted using a script from an older blogging system. Some elements might not have come out as intended.... If that is the case please let us know via the comments section below.