I recently published an app called Wall Draw that allows users to draw shapes by dragging a finger across the screen. The app features two distinct drawing methods: one that scales a shape to fit inside a rectangular region, and another that stretches a shape across a line. We'll cover both of these methods in detail, but first take a moment to test them out on the canvas below.
The table below summarizes some of the key differences between the two drawing methods.
Property | Fit in rectangle | Stretch across line |
---|---|---|
Aspect ratio | Varies with rectangle | Fixed |
Orientation | Flips in x/y axis | Fixed |
Rotation angle | Fixed | Varies from 0° to 360° |
Size | Matches size of rectangle | Depends on length of line |
Transformation | Rect to rect | Stretch rotation |
Note that each time you move the mouse, the canvas recalculates the rectangle or line to match the start and end points of your gesture, then transforms the shape accordingly. Finally, the canvas redraws itself to give the illusion of movement. These steps are conceptually simple, but require some structure to implement; in particular, you will need...
The next five sections help you put the structure in place, and then the last two sections help you implement both of the drawing methods.
Begin by choosing a platform and rendering library. For example, Wall Draw is built on the Android platform, and uses the OpenGL ES 2.0 rendering library. The canvas at the top of this page is designed for the Web, and uses HTML5 canvas for rendering. Refer to the table below for some common options and tutorials.
Platform | Rendering library |
---|---|
Android | Canvas |
Android | OpenGL ES |
iOS | OpenGL ES |
Web | HTML5 canvas |
Web | WebGL |
Xamarin | CocosSharp |
Xamarin | SkiaSharp |
The following subsections describe the basic structure for point, vector, rectangle, and matrix objects (using Java syntax). In most cases you can reuse or extend code from your platform or rendering library instead of writing the code yourself; however, if you need to write a portion of the code on your own, you can refer to the sample code I provide for each method.
Structure your Point
class as follows:
Fields |
---|
public float x This point's horizontal position with respect to the origin. |
public float y This point's vertical position with respect to the origin. |
Constructors |
---|
public Point()
Creates a point initialized to (0,0).
|
public Point(float x, float y)
Creates a point with the specified (x,y) coordinates.
|
public Point(Point src)
Creates a point that is a deep copy of src.
|
Static factory methods |
---|
public static Point between(Point p1, Point p2)
Computes the midpoint of p1 and p2.
|
Properties |
---|
public float distanceToOrigin()
Computes the distance from this point to the origin (0,0).
|
public float distanceToPoint(Point other)
Computes the distance from this point to the other point.
|
public float distanceToPoint(float x, float y)
Computes the distance from this point to the point (x,y).
|
Setters |
---|
public void set(Point src)
Sets this point's (x,y) coordinates to those of src.
|
public void set(float x, float y)
Sets this point's (x,y) coordinates.
|
Transformations |
---|
public void offset(Vec2 vector)
Offsets this point's (x,y) coordinates by the specified vector.
|
public void offset(float dx, float dy)
Offsets this point's (x,y) coordinates by the vector (dx,dy).
|
Structure your Vec2
class as follows:
Fields |
---|
public float x The x component of this vector. |
public float y The y component of this vector. |
Constructors |
---|
public Vec2()
Creates a vector initialized to (0,0).
|
public Vec2(float x, float y)
Creates a vector with the specified (x,y) components.
|
public Vec2(Vec2 src)
Creates a vector that is a deep copy of src.
|
Static factory methods |
---|
public Vec2 fromPoints(start, end)
Computes the vector from the specified start point to the specified end point.
|
Properties |
---|
public boolean isZero()
Checks if this vector is equal to the additive identity (0,0).
|
public float length()
Computes the length of this vector.
|
public float length2()
Computes the length of this vector, squared.
|
Setters |
---|
public void set(Vec2 src)
Sets this vector's (x,y) components to those of src.
|
public void set(float x, float y)
Sets this vector's (x,y) components.
|
Transformations |
---|
public void normalize()
Normalizes this vector to unit length
|
public void normalize(float length)
Normalizes this vector to the specified length
|
public void rotate90Left()
Rotates this vector 90 degrees to the left (CCW).
|
public void rotate90Right()
Rotates this vector 90 degrees to the right (CW).
|
Operations |
---|
public float cross(Vec2 other)
Finds the cross product of this vector with the other vector.
|
public float dot(Vec2 other)
Finds the dot product of this vector with the other vector.
|
public void add(Vec2 other)
Adds the other vector to this vector.
|
public void subtract(Vec2 other)
Subtracts the other vector from this vector.
|
public void negate()
Negates this vector.
|
public void scale(float scalar)
Scales this vector by the specified scalar.
|
public void divide(float scalar)
Divides this vector by the specified scalar.
|
Structure your Rect
class as follows:
Fields |
---|
public float left The left boundary of this rectangle. |
public float top The top boundary of this rectangle. |
public float right The right boundary of this rectangle. |
public float bottom The bottom boundary of this rectangle. |
Constructors |
---|
public Rect()
Creates an empty rectangle.
|
public Rect(float left, float top, float right, float bottom)
Creates a rectangle with the specified boundaries.
|
public Rect(Rect src)
Creates a rectangle that is a deep copy of src.
|
Static factory methods |
---|
public static Rect fromDimensions(float left, float bottom, float width, float height)
Creates a rect with the specified dimensions.
|
public static Rect fromIntersection(Rect r1, Rect r2)
Finds the intersection of rectangles r1 and r2.
|
public static Rect fromLBRT(float left, float bottom, float right, float top)
Creates a rect with the specified boundaries entered in LBRT order.
|
public static Rect fromUnion(Point... points)
Finds the smallest rectangle that encloses all the specified points.
|
public static Rect fromUnion(float[] points, int offset, int count)
Finds the smallest rectangle that encloses a subset of points in the specified array.
|
public static Rect fromUnion(Rect r1, Rect r2)
Finds the union of rectangles r1 and r2.
|
Properties |
---|
public boolean isEmpty()
Checks if this rectangle is empty. True if left >= right or bottom >= top.
|
public boolean isValid()
Checks if the boundaries of this rectangle represent a valid rectangle. True if right >= left and top >= bottom.
|
public float width()
Computes the width of this rectangle.
|
public float height()
Computes the height of this rectangle.
|
public float area()
Computes the area of this rectangle.
|
public Point center()
Finds the point at the center of this rectangle.
|
public float centerX()
Finds the x-coordinate of the point at the center of this rectangle.
|
public float centerY()
Finds the y-coordinate of the point at the center of this rectangle.
|
public Point bottomLeft()
Finds the point at the bottom left corner of this rectangle.
|
public Point bottomRight()
Finds the point at the bottom right corner of this rectangle.
|
public Point topLeft()
Finds the point at the top left corner of this rectangle.
|
public Point topRight()
Finds the point at the top right corner of this rectangle.
|
Setters |
---|
public void set(Rect src)
Sets this rectangle's boundaries to those of src.
|
public void set(float left, float top, float right, float bottom)
Sets this rectangle's boundaries.
|
public void setEmpty()
Sets this rectangle empty.
|
public void setUnion(Point... points)
Sets this rectangle to the smallest rectangle that encloses all the points in the specified array
|
public void setUnion(float[] points, int offset, int count)
Sets this rectangle to the smallest rectangle that encloses a subset of points in the specified array
|
Containment Checkers |
---|
public boolean containsPoint(Point pt)
Checks if this rectangle contains the specified point.
|
public boolean containsPoint(float x, float y)
Checks if this rectangle contains the point (x,y).
|
public boolean containsRect(Rect other)
Checks if this rectangle contains the other rectangle.
|
Intersect Operations |
---|
public void intersectRect(Rect other)
Sets this rectangle to the intersection of itself and the other rectangle.
|
public boolean intersectsRect(Rect other)
Checks if this rectangle intersects the other rectangle.
|
public boolean intersectsRect(Rect other, float percent)
Checks if this rectangle intersects the other rectangle by at least the specified percentage.
|
Union Operations |
---|
public void unionPoint(Point pt)
Expands this rectangle to enclose the specified point.
|
public void unionPoint(float x, float y)
Expands this rectangle to enclose the point (x,y).
|
public void unionPoints(Point... points)
Expands this rectangle to enclose the specified points.
|
public void unionPoints(float[] points, int offset, int count)
Expands this rectangle to enclose a subset of points in the specified array.
|
public void unionRect(Rect other)
Expands this rectangle to enclose the specified rectangle.
|
Transformations |
---|
public void inset(Vec2 vector)
Insets the boundaries of this rectangle by the specified vector.
|
public void inset(float dx, float dy)
Insets the boundaries of this rectangle by the vector (dx,dy).
|
public void offset(Vec2 vector)
Offsets the boundaries of this rectangle by the specified vector.
|
public void offset(float dx, float dy)
Offsets the boundaries of this rectangle by the vector (dx,dy).
|
public void sort()
Swaps top/bottom or left/right if they are flipped, meaning left > right and/or top > bottom.
|
Structure your Matrix2D
class as follows:
ScaleToFit enum |
---|
CENTER Stretches the src rect to fit inside dst, then translates src.center() to dst.center(). |
END Stretches the src rect to fit inside dst, then translates src.bottomRight() to dst.bottomRight(). |
FILL Scales the src rect to fit inside dst exactly, then translates src to dst. |
START Stretches the src rect to fit inside dst, then translates src.topLeft() to dst.topLeft(). |
Fields |
---|
public float m11 The first item in the first row of this matrix. |
public float m12 The second item in the first row of this matrix. |
public float m13 The third item in the first row of this matrix. |
public float m21 The first item in the second row of this matrix. |
public float m22 The second item in the second row of this matrix. |
public float m23 The third item in the second row of this matrix. |
Constructors |
---|
public Matrix2D()
Creates a matrix initialized to the multiplicative idenity.
|
public Matrix2D(float m11, float m12, float m13, float m21, float m22, float m23)
Creates a matrix with the specified entries (in row-major order).
|
public Matrix2D(Matrix2D src)
Creates a matrix that is a deep copy of src.
|
Static factory methods |
---|
public static Matrix2D rectToRect(Rect src, Rect dst, ScaleToFit stf)
Creates a rect to rect matrix that maps src into dst using the specified scale to fit option.
|
public static Matrix2D rotate(float radians)
Creates a matrix to rotate about the origin by the specified angle in radians.
|
public static Matrix2D rotate(Point center, float radians)
Creates a matrix to rotate about the specified center point by the specified angle in radians.
|
public static Matrix2D scale(float widthRatio, float heightRatio)
Creates a matrix to scale about the origin by the specified width and height ratios.
|
public static Matrix2D scale(Point center, float widthRatio, float heightRatio)
Creates a matrix to scale about the specified center point by the specified width and height ratios.
|
public static Matrix2D stretch(float ratio)
Creates a matrix to stretch about the origin by the specified ratio.
|
public static Matrix2D stretch(Point center, float ratio)
Creates a matrix to stretch about the specified center point by the specified ratio.
|
public static Matrix2D stretchRotate(Point center, Point start, Point end)
Creates a stretch rotation about the specified center point that maps the start point onto the end point.
|
public static Matrix2D translate(Vec2 vector)
Creates a matrix to translate by the specified vector.
|
public static Matrix2D translate(float dx, float dy)
Creates a matrix to translate by the vector (dx,dy).
|
Properties |
---|
public float determinant()
Computes the determinant of this matrix.
|
public Matrix2D inverse()
Computes the inverse of this matrix.
|
Setters |
---|
public void set(Matrix2D src)
Sets the entries of this matrix to those of src.
|
public void set(float m11, float m12, float m13, float m21, float m22, float m23)
Sets the entries of this matrix (in row-major order).
|
public void setConcat(Matrix2D left, Matrix2D right)
Sets this matrix to the product of the specified left and right matrices.
|
public void setIdentity()
Sets this matrix to the identity matrix.
|
public void setRectToRect(Rect src, Rect dst, ScaleToFit stf)
Sets this matrix to map src into dst using the specified scale to fit option.
|
public void setRotate(float radians)
Sets this matrix to rotate CCW by the specified angle in radians.
|
public void setRotate(float sin, float cos)
Sets this matrix to rotate by the specified sin and cos values.
|
public void setScale(float widthRatio, float heightRatio)
Sets this matrix to scale by the specified width and height ratios.
|
public void setStretch(float ratio)
Sets this matrix to stretch by the specified ratio.
|
public void setStretchRotate(Point center, Point start, Point end)
Sets this matrix to stretch rotate about the specified center point from the start point to the end point.
|
public void setTranslate(float dx, float dy)
Sets this matrix to translate by the vector (dx,dy).
|
Transformations |
---|
public void invert()
Inverts this matrix.
|
public void postRotate(float radians)
Post concats this matrix with a rotation by the specified angle in radians
|
public void preRotate(float radians)
Pre concats this matrix with a rotation by the specified angle in radians.
|
public void postRotate(float sin, float cos)
Post concats this matrix with a rotation by the specified sin and cos values.
|
public void preRotate(float sin, float cos)
Pre concats this matrix with a rotation by the specified sin and cos values.
|
public void postScale(float widthRatio, float heightRatio)
Post concats this matrix with a scale of the specified width and height ratios
|
public void preScale(float widthRatio, float heightRatio)
Pre concats this matrix with a scale of the specified width and height ratios
|
public void postStretch(float ratio)
Post concats this matrix with a stretch of the specified ratio
|
public void preStretch(float ratio)
Pre concats this matrix with a stretch of the specified ratio
|
public void postTranslate(Vec2 vector)
Post concats this matrix with a translation by the specified vector
|
public void postTranslate(float dx, float dy)
Post concats this matrix with a translation by vector (dx,dy)
|
public void preTranslate(Vec2 vector)
Pre concats this matrix with a translation by the specified vector
|
public void preTranslate(float dx, float dy)
Pre concats this matrix with a translation by vector (dx,dy)
|
public void postConcat(Matrix2D other)
Post concats this matrix with the other matrix: this = other * this.
|
public void preConcat(Matrix2D other)
Pre concats this matrix with the other matrix: this = this * other.
|
Operations |
---|
public void mapPoint(Point src)
Maps the src point and writes the result back into src.
|
public void mapPoint(Point src, Point dst)
Maps the src point and writes the result into dst.
|
public void mapPoints(float[] src, int srcOffset, int count)
Maps a subset of points in src and writes the result back into src at the same place.
|
public void mapPoints(float[] src, int srcOffset, float[] dst, int dstOffset, int count)
Maps a subset of points in src and writes the result into dst.
|
public void mapRect(Rect src)
Maps the src rect and writes the result back into src.
|
public void mapRect(Rect src, Rect dst)
Maps the src rect and writes the result into dst.
|
public void mapVec2(Vec2 src)
Maps the src vector and writes the result back into src.
|
public void mapVec2(Vec2 src, Vec2 dst)
Maps the src vector and writes the result into dst.
|
public void mapX(float x, float y)
Maps the x coordinate of the point (x,y).
|
public void mapY(float x, float y)
Maps the y coordinate of the point (x,y).
|
The following subsections describe the basic structure for path, mesh, and shape objects (using Java syntax). You may need to alter some of the code depending on what platform and rendering library you chose.
Structure your Path
class as follows:
Fields |
---|
private int capacity The number of points this path can hold. |
private float[] data Backing array that stores the point data as a series of (x,y) coordinates. |
private int size The number of points contained in this path. |
Constructors |
---|
public Path(int capacity)
Creates an empty path with the specified capacity.
|
public Path(float[] pointData)
Creates a path backed by the specified array of point data.
|
Properties |
---|
public int capacity()
Gets the number of points this array can hold.
|
public float[] data()
Gets the point data backing this path. The points are entered as a series of (x,y) coordinates.
|
public int size()
Gets the number of points contained in this array.
|
Indexers |
---|
public Point get(int index)
Gets the point at the specified index of this path.
|
private float getX(int dataIndex)
Gets the x-coordinate of the point at the specified data index.
|
private float getY(int dataIndex)
Gets the y-coordinate of the point at the specified data index.
|
public void set(int index, Point pt)
Sets the point at the specified index of this path to the (x,y) values of pt.
|
public void set(int index, float x, float y)
Sets the point at the specified index of this path to (x,y).
|
private void setX(int dataIndex, float x)
Sets the x value of the point at the specified data index.
|
private void setY(int dataIndex, float y)
Sets the y value of the point at the specified data index.
|
Add Methods |
---|
public void add(Point pt)
Adds the (x,y) values of pt to this path, increasing its size by one.
|
public void add(float x, float y)
Adds the point (x,y) to this path, increasing its size by one.
|
Bounds Calculators |
---|
public Rect calculateBounds()
Calculates the boundaries of this path.
|
public Rect calculateBounds(int offset, int count)
Calculates the boundaries of a subset of this path.
|
Containment Checkers |
---|
public boolean containsPoint(Point pt)
Checks if this path contains the specified point.
|
public boolean containsPoint(float x, float y)
Checks if this path contains the point (x,y).
|
public boolean containsPoint(Point pt, int offset, int count)
Checks if a subset of this path contains the specified point.
|
public boolean containsPoint(float x, float y, int offset, int count)
Checks if a subset of this path contains the point (x,y).
|
Transformations |
---|
public void offset(Vec2 vector)
Offsets this path by the specified vector.
|
public void offset(float dx, float dy)
Offsets this path by the vector (dx,dy).
|
public void offset(Vec2 vector, int offset, int count)
Offsets a subset of this path by the specified vector.
|
public void offset(float dx, float dy, int offset, int count)
Offsets a subset of this path by the vector (dx,dy).
|
public void transform(Matrix2D matrix)
Transforms this path by the specified matrix.
|
public void transform(Matrix2D matrix, int offset, int count)
Transforms a subset of this path by the specified matrix.
|
Structure your Mesh
class as follows:
Fields |
---|
public final Path vertices Base vertex position data. |
public final Rect bounds Boundaries of the base vertices. |
public final Point fixedPoint Point that remains fixed when stretch rotating the base vertices. |
public final Point controlPoint Point that determines the length and direction of the line for stretch rotating the base vertices. |
Constructors |
---|
public Mesh(Path vertices, Point fixedPoint, Point controlPoint)
Creates a mesh with the specified data.
|
Static factory methods |
---|
public static Mesh polygon(int n)
Creates the mesh for a regular polygon with n sides.
|
public static Mesh star(int n, float innerRadius, float outerRadius)
Creates the mesh for a star with n sides and the specified inner and outer radii.
|
Containment Checkers |
---|
public boolean containsPoint(Point pt)
Checks if the base vertices of this mesh contain the specified point.
|
public boolean containsPoint(float x, float y)
Checks if the base vertices of this mesh contain the point (x,y).
|
Structure your Shape
class as follows:
Fields |
---|
public Mesh mesh Contains the base vertex data for this shape. |
public Paint paint Contains the fill color and stroke color data for this shape. |
public final Matrix2D matrix Matrix transformation applied to base vertex data when drawing this shape. Defaults to identity. |
Constructors |
---|
public Shape(Mesh mesh, Paint paint)
Creates a shape with the specified mesh and paint objects
|
Containment Checkers |
---|
public boolean containsPoint(Point pt)
Checks if this shape contains the specified point.
|
public boolean containsPoint(float x, float y)
Checks if this shape contains the point (x,y).
|
Transformations |
---|
public void offset(Vec2 vector)
Offsets this shape by the specified vector.
|
public void offset(float dx, float dy)
Offsets this shape by the vector (dx,dy).
|
public void transform(Matrix2D matrix)
Transforms this shape by the specified matrix.
|
public void fitInRect(Rect dst, Matrix2D.ScaleToFit stf)
Fits this shape inside dst using the specified scale to fit option.
|
public void stretchAcrossLine(Point start, Point end)
Stretch-rotates this shape across the line segment from start to end.
|
Draw Methods |
---|
public void draw(Canvas canvas)
Draws this shape onto the specified canvas.
|
Create a drag detector to detect drag events and send callbacks to an interface with the following methods:
Drag Detector Callbacks |
---|
void onDown(float x, float y) Called at the start of a drag event. |
void onMove(float x, float y) Called each time a move occurs during a drag event. |
void onUp(float x, float y) Called at the end of a drag event. |
See below for a sample implementation of the Surface
class:
public class Surface implements DragDetector.Callback{
//The canvas we're drawing on.
Canvas canvas;
//The shape we're drawing
Shape shape;
public Surface(Canvas canvas){
this.canvas = canvas;
//Create a hexagon mesh
Mesh hexagonMesh = Mesh.polygon(6);
//Create a paint object with red fill color (pseudo code)
Paint paint = new Paint();
paint.fillColor = Color.RED;
//Create a shape with our mesh and paint objects
this.shape = new Shape(hexagonMesh, paint);
}
public void redrawCanvas(){
//Clear the canvas (pseudo code)
this.canvas.clear();
//Redraw the shape
shape.draw(this.canvas);
}
//...Drag detector callbacks
}
Implement the drag detector callback interface inside the surface class as follows:
public class Surface implements DragDetector.Callback {
//....Code from 5.0
//Keep track of the drag event's start point
Point start = new Point();
public void onDown(float x, float y){
//Read the (x,y) values into our start point
this.start.set(x, y);
}
public void onMove(float x, float y){
//Compute the rect from our start point to (x,y)
Rect rect = Rect.fromLBRT(this.start.x, this.start.y, x, y);
//Fit shape inside the rect using the ScaleToFit.FILL option
shape.fitInRect(rect, Matrix2D.ScaleToFit.Fill);
//Clear the canvas and redraw the shape
this.redrawCanvas();
}
public void onUp(float x, float y){
}
}
Implement the drag detector callback interface inside the surface class as follows:
public class Surface implements DragDetector.Callback {
//....Code from 5.0
//Keep track of the drag event's start point
Point start = new Point();
public void onDown(float x, float y){
//Read the (x,y) values into our start point
this.start.set(x, y);
}
public void onMove(float x, float y){
//Read the (x,y) values into a new point
Point end = new Point(x, y);
//Stretch shape across line from our start point to our end point
shape.stretchAcrossLine(this.start, end);
//Clear the canvas and redraw the shape
this.redrawCanvas();
}
public void onUp(float x, float y){
}
}
Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License .