-
Notifications
You must be signed in to change notification settings - Fork 4
IRenderer API
The IRenderer
interface is your means to draw content onto the Canvas. If you don't require to extend the basic rendering behaviour of the Sprite
class, this will happen transparently and you will never have to deal with the renderer.
If you however wish to override Sprite class' basic rendering behaviour (for instance to draw custom graphics), you will be dealing with the renderer as provided to the draw method of the Sprite class :
draw( renderer: IRenderer, viewport?: Viewport ): void
The renderer instance here will be used to batch draw commands on, which will be executed by the zCanvas engine whenever it is time to render. The draw commands closely mimic the API of the CanvasRenderingContext2D but provide some abstractions here and there to perform some performance improvements under the hood, abstracting away a little bit of the overhead and math that might be involved in handling certain transformations (such as center based scaled rotation).
The public methods of the IRenderer
interface are :
save(): void
restore(): void
translate( x: number, y: number ): void
scale( xScale: number, yScale?: number ): void
rotate( angleInRadians: number ): void;
transform( a: number, b: number, c: number, d: number, e: number, f: number ): void;
These are equal* to the functions of the same name in the CanvasRenderingContext2D
API. These operations basically change the state of the Canvas context in order to draw content with a transformation applied. It is recommended to only use these when you are creating an extremely custom sequence of drawing operations. All of these are automatically applied in a highly performant manner by supplying DrawProps
to any of the draw commands listed below.
*with the exception of scale()
where the yScale value is optional and will default to use xScale when not provided)
setBlendMode( type: GlobalCompositeOperation ): void
setAlpha( value: number ): void
Operations to change the blend or transparency of the content prior to drawing. Before using these the renderer should save()
the current state and afterwards the renderer should restore()
its state.
Abstractions to perform common drawing operations on the Canvas context. Note that using these commands in series are completely safe operations that require no manual save()
or restore()
operations on the renderers context. It's all taken care of under the hood.
Some methods accept a DrawProps
object. This object declares an interface to perform transformations prior to drawing the image. This object can be accessed calling getDrawProps()
inside the Sprite class. Its definition is:
type DrawProps = {
scale: number;
rotation: number;
alpha: number;
blendMode?: GlobalCompositeOperation;
pivot?: Point;
safeMode?: boolean;
};
Where scale, rotation*, alpha and blendMode should speak for themselves. pivot is an optional Point around which rotation will be applied (when undefined, all rotation will by default be around the center of the drawn graphic). safeMode can optionally be set to true to ensure that drawing operations don't overflow the Canvas range with negative indices (its however preferred to perform isVisible( viewport )
-checks prior to rendering to ensure content is visible, use this flag only when you are uncertain of the result of your draw command).
*rotation is applied in degrees.
Some methods can draw an outline, as defined by the optional StrokeProps
object:
type StrokeProps = {
color: string;
size: number;
close?: boolean;
dash?: number[];
cap?: "butt" | "round" | "square";
};
where color is a hex/RGBA string defining the stroke color and size the stroke width in pixels. You can optionally provide a value for close in order to close the path (connecting the last point to the first) before drawing. dash is an Array of numbers which can be used to create a line with dashed patterns. cap specifies the line cap to use on the line ends.
drawPath( points: Point[], color?: ColorOrTransparent, stroke?: StrokeProps ): void;
Draws a shape defined by the coordinates in provided points list. If color isn't "transparent" the provided color string (hex, RGBA) will be used to fill the shape. Optional StrokeProps can be supplied to draw an outline for the shape. The list of points does not need to be a closed shape, as such you can also draw open polygons or lines with this method.
clearRect( x: number, y: number, width: number, height: number, props?: DrawProps ): void;
Clears a rectangular area at given bounding box (effectively erasing content).
drawRect(
x: number,
y: number,
width: number,
height: number,
color?: ColorOrTransparent,
stroke?: StrokeProps,
props?: DrawProps
): void
Draws a rectangular shape defined by the provided bounding box. If color isn't "transparent", provided color string (hex, RGBA) will be used to fill the shape. Optional StrokeProps can be supplied to draw an outline for the shape.
drawRoundRect(
x: number,
y: number,
width: number,
height: number,
radius: number,
color?: ColorOrTransparent,
stroke?: StrokeProps,
props?: DrawProps
): void
Draws a rounded rectangular shape defined by the provided bounding box and radius. If color isn't "transparent", provided color string (hex, RGBA) will be used to fill the shape. Optional StrokeProps can be supplied to draw an outline for the shape.
*this is not supported in Safari versions below version 16
drawCircle( x: number, y: number, radius: number, color?: string, stroke?: StrokeProps, props?: DrawProps ): void
Draws a circle defined by the provided coordinate and radius. If color isn't "transparent", provided color string (hex, RGBA) will be used to fill the shape. Optional StrokeProps can be supplied to draw an outline for the shape.
drawEllipse( x: number, y: number, xRadius: number, yRadius: number, color?: string, stroke?: StrokeProps, props?: DrawProps ): void
Same as drawCircle()
except this supports elliptical shapes.
drawImage(
resourceId: string,
x: number,
y: number,
width?: number,
height?: number,
props?: DrawProps,
): void
Draws an image (registered in the Canvas under provided resourceId) at given coordinates. This method acts as the 3 and 5-arity version of the CanvasRenderingContext2D drawImage methods. When width and height are supplied, the image is scaled. Otherwise it is rendered at its original size.
drawImageCropped(
resourceId: string,
sourceX: number,
sourceY: number,
sourceWidth: number,
sourceHeight: number,
destinationX: number,
destinationY: number,
destinationWidth: number,
destinationHeight: number,
props?: DrawProps,
): void
Draws an image (registered in the Canvas under provided resourceId) at given coordinates. This method acts as the 9-arity version of the CanvasRenderingContext2D drawImage methods. Using this method it is possible to render a smaller subset of the image (defined by source coordinates and size) onto a specific area of the Canvas (defined by destination coordinates and size).
drawText( text: TextProps, x: number, y: number, props?: DrawProps ): void
Renders text defined by given TextProps
object at the provided coordinates. Text can be multiline (separated by newlines). The definition of the TextProps interface is:
type TextProps = {
text: string;
color: string;
font?: string;
size?: number;
unit?: string;
lineHeight?: number;
spacing?: number;
center?: boolean;
};
Where color can be a hex or RGBA string, font is the name of the font (you can load CSS fonts as usual, which will then automatically become available to the Canvas context), size is the numerical size of the font, unit is the size unit (e.g. px, pt, ex, em, mm, % anything also supported in CSS, basically). lineHeight specifies the height of each line (will otherwise be derived from the font metrics), spacing specifies the space between each letter (defaults to using the font metrics)_ and center describes whether the rendered text should be centered at the coordinates provided to the drawText()
method.
NOTE: Rendering text is an expensive operation and is not recommended when the Canvas is repeatedly rendering / animated.
createPattern( resourceId: string, repetition: "repeat" | "repeat-x" | "repeat-y" | "no-repeat" ): void
drawPattern( patternResourceId: string, x: number, y: number, width: number, height: number ): void
You can also create repeated patterns from asset resources. Prior to calling drawPattern()
you must first create the pattern using createPattern()
(only once in the Canvas lifetime).
When drawing, patternResourceId is equal to the resourceId for the pattern source provided to the createPattern
function. The pattern will be drawn at provided coordinates and cover the provided dimensions.
drawImageData( imageData: ImageData, x: number, y: number, sourceX?: number, sourceY?: number, destWidth?: number, destHeight?: number ): void
Draws the contents of supplied imageData at the specified coordinates. Optionally draws a subset (by specifying sourceX|Y and destWidth|Height) of the ImageData object.
NOTE This method is not recommended to be used on each frame of an animated zCanvas (as getting ImageData and passing it between contexts is reasonably CPU intensive). Cache a resource Bitmap instead and use drawImage|Cropped()
in that case.
In order to maintain a performant render cycle you should always minimize the amount of work the Canvas should do. This includes making state changes to the Canvas context. Instead of doing a sequence of translate()
, rotate()
, scale()
and translate()
to set a transformation, use a single transform()
instead.
When possible, use the built-in zCanvas drawing methods allowing the DrawProps
object to render transformed content by setting up the preparation for you.
Don't draw things that aren't visible (always check Sprite.isVisible( viewport )
for an early exit when overriding the Sprite's draw()
-method).
When the destination size of your images is fixed, ensure that your source image is already of the same size (e.g. don't use a 1000 x 1000 image if you will only render at 75 x 75), this minimizes work for the Canvas renderer (no interpolation and aliasing considerations need to be made making it a much cheaper blitting operation).
Apart from being aware of the impact of drawing operations, also ensure that you minimize the impact of the garbage collector. If you for instance repeatedly draw an Array of Points as a line, consider keeping the Array constant and its Points pooled. By keeping strong references to objects that will be reused, these are not eligible for garbage collection and keep both memory pressure and performance equal.
E.g., don't do :
draw( renderer: IRenderer, viewport?: Viewport ): void {
const points = []; // allocate new Array on each render iteration
for ( let i = 0; i < SIZE; ++i ) {
// allocate new Point on each loop iteration
points.push({ x: i, y: i * Math.random() });
}
renderer.drawPath( points, "red" );
}
But do :
constructor( opts: SpriteProps ) {
super( opts );
this.points = new Array( SIZE ); // declare fixed size Array
for ( let i = 0; i < SIZE; ++i ) {
this.points[ i ] = { x: 0, y: 0 }; // fill with Point Objects
}
}
draw( renderer: IRenderer, viewport?: Viewport ): void {
for ( let i = 0; i < SIZE; ++i ) {
const point = this.points[ i ]; // reuse pooled Point
point.x = i;
point.y = i * Math.random();
}
renderer.drawPath( points, "red" );
}
The same applies for instances of StrokeProps
or TextProps
. Reuse objects instead of redeclaring them on each render.