Copyright 2016-2020 Moddable Tech, Inc.
Revised: November 4, 2020
This document describes the Poco renderer, starting with a set of examples that introduce many of the main concepts of working with Poco. Following the examples is the reference for Poco, which fully describes each function call.
These examples illustrate working with the Poco renderer. They all use the JavaScript API, and they also use asset loaders and other capabilities of Commodetto.
To keep the following examples concise and focused, the code makes several assumptions:
-
The examples assume a PixelsOut
object in the global variable screen
.
-
They assume that the following color variables are defined:
let white = poco.makeColor(255, 255, 255);
let black = poco.makeColor(0, 0, 0);
let gray = poco.makeColor(128, 128, 128);
let red = poco.makeColor(255, 0, 0);
let green = poco.makeColor(0, 255, 0);
let blue = poco.makeColor(0, 0, 255);
-
They assume that the drawing commands occur between calls to begin
and end
.
let poco = new Poco(screen);
poco.begin();
... // example code here
poco.end();
Each example includes the image rendered by the code. The images are scaled 150% here to make them easier to see; this scaling causes some blurring and introduces some jaggedness that is not in the actual image.
This example fills screen
with gray pixels, covers the left half with red pixels, and then uses a 50% blending level (128) to draw blue pixels over the middle half of the screen.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.fillRectangle(red, 0, 0, poco.width / 2, poco.height);
poco.blendRectangle(blue, 128, poco.width / 4, 0, poco.width / 2, poco.height);
This example shows how to move the drawing origin. Poco maintains an origin stack that is pushed when the origin changes and popped when origin
is called with no arguments. Each change to the origin offsets the previous origin. The origin stack is convenient for building container-based user interfaces.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.origin(10, 10);
poco.fillRectangle(red, 0, 0, 40, 20);
poco.origin(25, 25);
poco.fillRectangle(green, 0, 0, 40, 20);
poco.origin(25, 25);
poco.fillRectangle(blue, 0, 0, 40, 20);
poco.blendRectangle(black, 128, -4, -4, 20, 10);
poco.origin();
poco.blendRectangle(black, 128, -4, -4, 20, 10);
poco.origin();
poco.blendRectangle(black, 128, -4, -4, 20, 10);
poco.origin();
This example shows how to use the drawing clip. Poco maintains a clip stack that is pushed when the clip changes and popped when clip
is called with no arguments. Each change intersects the clip with the previous clip. The clip stack is convenient for building container-based user interfaces.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.clip(20, 20, poco.width - 40, poco.height - 40);
poco.fillRectangle(green, 0, 0, poco.width, poco.height);
poco.clip(0, 0, 40, 40);
poco.fillRectangle(blue, 0, 0, poco.width, poco.height);
poco.fillRectangle(white, 26, 0, 2, poco.height);
poco.clip();
poco.fillRectangle(red, 30, 0, 2, poco.height);
poco.clip();
poco.fillRectangle(black, 34, 0, 2, poco.height);
This example draws a monochrome bitmap (in which all pixels are either black or white) of an envelope. It shows how to control the color of the foreground and background pixels, as well as whether each is drawn. The bitmap is stored in a 1-bit BMP file with dimensions of 32 x 23.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
let envelope = parseBMP(new Resource("envelope.bmp"));
poco.drawMonochrome(envelope, black, white, 14, 10);
poco.drawMonochrome(envelope, red, white, 14, 55);
poco.drawMonochrome(envelope, green, undefined, 74, 10);
poco.drawMonochrome(envelope, undefined, blue, 74, 55);
This example draws a color bitmap image of a face in two ways using drawBitmap
: on the left side of the screen, it draws the full image; on the right side, it draws only the eyes and mouth, using the optional source rectangle parameters of drawBitmap
.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
let image = parseBMP(new Resource("lvb.bmp"));
let x = 0;
let y = Math.round((poco.height - image.height) / 2);
poco.drawBitmap(image, x, y);
x = image.width;
poco.drawBitmap(image, x + 25, y + 38, 25, 38, 11, 7); // left eye
poco.drawBitmap(image, x + 7, y + 40, 7, 40, 10, 6); // right eye
poco.drawBitmap(image, x + 15, y + 56, 15, 56, 16, 6); // mouth
This examples uses fillPattern
to draw a single 30-pixel-square pattern in two ways: first the entire pattern is used to fill the screen; then a 7-pixel-square area of the pattern is used to fill the center part of the screen.
Unlike the previous examples, this one does not first call fillRectangle
to clear the screen, because the first call to fillPattern
covers all the screen's pixels.
let pattern = parseBMP(new Resource("pattern1.bmp"));
poco.fillPattern(pattern, 0, 0, poco.width, poco.height);
poco.fillPattern(pattern, 28, 28, 63, 35, 21, 14, 7, 7);
This example uses drawGray
to draw a 16-level gray image in several colors. drawGray
treats the pixel values as alpha blending levels, blending the specified color with the background.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
let image = parseBMP(new Resource("envelope-gray.bmp"));
poco.drawGray(image, black, 10, 2);
poco.drawGray(image, white, 10, 47);
poco.drawGray(image, black, 70, 2);
poco.drawGray(image, green, 70 + 2, 2 + 2);
poco.drawGray(image, white, 70, 47);
poco.drawGray(image, red, 70 + 2, 47 + 2);
This example uses BufferOut
to create an offscreen bitmap, fills the bitmap with a series of inset squares, and then uses the offscreen bitmap as a pattern to fill the screen. It uses two instances of Poco
: the first to draw to the offscreen bitmap and the second to draw to the screen.
import BufferOut from "commodetto/BufferOut";
let offscreen = new BufferOut({width: 30, height: 30, pixelFormat: poco.pixelsOut.pixelFormat});
let pocoOff = new Poco(offscreen);
pocoOff.begin();
pocoOff.fillRectangle(gray, 0, 0, 30, 30);
pocoOff.fillRectangle(red, 2, 2, 26, 26);
pocoOff.fillRectangle(black, 4, 4, 22, 22);
pocoOff.fillRectangle(blue, 6, 6, 18, 18);
pocoOff.fillRectangle(white, 8, 8, 14, 14);
pocoOff.fillRectangle(green, 10, 10, 10, 10);
pocoOff.fillRectangle(gray, 13, 13, 4, 4);
pocoOff.end();
poco.fillPattern(offscreen.bitmap, 0, 0, poco.width, poco.height);
This example shows how to draw a bitmap through an alpha mask. The bitmap to draw and the mask are in separate bitmaps, enabling an image to be drawn using more than one alpha mask. The example draws one bitmap through both a circle and a square mask, and also draws the original image and mask.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
let girl = parseBMP(new Resource("girl.bmp"));
let circle = parseBMP(new Resource("mask_circle.bmp"));
let square = parseBMP(new Resource("mask_square.bmp"));
poco.drawBitmap(girl, 0, 2);
poco.drawGray(circle, black, 40, 2);
poco.drawMasked(girl, 80, 2, 0, 0,
circle.width, circle.height, circle, 0, 0);
poco.drawBitmap(girl, 0, 47);
poco.drawGray(square, black, 40, 47);
poco.drawMasked(girl, 80, 47, 0, 0,
square.width, square.height, square, 0, 0);
These two examples show different methods for working with JPEG images. The first example decompresses the full JPEG into a bitmap in memory and then renders the bitmap to the screen. The call to trace
shows how to access the bitmap's width and height.
import JPEG from "commodetto/readJPEG";
let piano = JPEG.decompress(new Resource("piano.jpg"));
trace(`width ${piano.width}, height ${piano.height}\n`);
poco.drawBitmap(piano, 0, 0);
The second example decodes the JPEG image one block at a time, drawing the block to the screen before decoding the next block. The advantage of this approach is that only a single JPEG block (typically 8 x 8 or 16 x 16 pixels) need be in memory at a time. The disadvantage is that the JPEG image appears to the user one block at a time rather than all at once.
let jpeg = new JPEG(new Resource("piano.jpg"));
let block;
while (block = jpeg.read()) {
poco.begin(block.x, block.y, block.width, block.height);
poco.drawBitmap(block, block.x, block.y);
poco.end();
}
When complete, each approach generates the same result.
Poco supports the BMPFont format for fonts used to rendering text. BMFont is a gray or color font used in games for anti-aliased fonts. A BMFont consists of two files: the font metrics and the font image.
The following example loads and draws a 36-point Palatino BMFont.
import parseBMF from "commodetto/parseBMF";
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.fillRectangle(white, 2, 2, poco.width - 4, poco.height - 4);
let palatino36 = parseBMF(new Resource("palatino_36.fnt"));
palatino36.bitmap = parseBMP(new Resource("palatino_36.bmp"));
poco.drawText("Hello.", palatino36, black, 4, 20);
poco.drawText("Hello.", palatino36, green, 4, 55);
To truncate text when rendering, provide the optional width
argument to drawText
indicating the horizontal space available for the text.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.fillRectangle(white, 2, 2, poco.width - 4, poco.height - 4);
let palatino36 = parseBMF(new Resource("palatino_36.fnt"));
palatino36.bitmap = parseBMP(new Resource("palatino_36.bmp"));
poco.drawText("Hello, world. This is long.", palatino36, red, 2, 10);
poco.drawText("Hello, world. This is long.", palatino36, green, 2, 45, poco.width - 2);
Text is horizontally and vertically aligned using the height
property of the font and measuring the width of strings using getTextWidth
.
poco.fillRectangle(gray, 0, 0, poco.width, poco.height);
poco.fillRectangle(white, 2, 2, poco.width - 4, poco.height - 4);
let palatino12 = parseBMF(new Resource("OpenSans-SemiboldItalic-18.fnt"));
palatino12.bitmap = parseBMP(new Resource("OpenSans-SemiboldItalic-18.bmp"));
poco.drawText("T Left", palatino12, red,
2, 2);
poco.drawText("T Right", palatino12, green,
poco.width - 2 - poco.getTextWidth("T Right", palatino12), 2);
poco.drawText("B Left", palatino12, blue,
2, poco.height - 2 - palatino12.height);
poco.drawText("B Right", palatino12, gray,
poco.width - 2 - poco.getTextWidth("B Right", palatino12),
poco.height - 2 - palatino12.height);
poco.drawText("Centered", palatino12, black,
(poco.width - poco.getTextWidth("Centered", palatino12)) / 2,
(poco.height - palatino12.height) / 2);
The drawText
function also accepts a 16-gray-level alpha bitmap in the color
argument. This bitmap is used to draw fonts that consist of multiple colors. The following example shows drawing Open Sans Bold Italic with both black and white pixels in the glyph images.
poco.fillRectangle(green, 0, 0, screen.width, screen.height);
let openSans52 = parseBMF(new Resource("OpenSans-BoldItalic-52.fnt"));
openSans52.bitmap = parseBMP(new Resource("OpenSans-BoldItalic-52-color.bmp"));
openSans52.mask = parseBMP(new Resource("OpenSans-BoldItalic-52-alpha.bmp"));
poco.drawText("Poco", openSans52, openSans52.mask, 0, 5);
The following image is a section of the OpenSans-BoldItalic-52.bmp
file, which contains the glyph images.
This is a section of the OpenSans-BoldItalic-52-alpha.bmp
file, which contains the alpha channel of the glyph images:
Destination pixel formats
Poco renders to the following pixel formats:
- 16-bit RGB565 little-endian
- 8-bit RGB332
- 8-bit gray
- 4-bit indexed color
- 4-bit gray
To keep the size of the code deployed to the target device small, Poco is configured at build time to render only one of these pixel formats.
For displays that require a different output format, the display driver is responsible for converting between a supported rendering format and the hardware required format. For example, many common LCD controllers require 16-bit RGB565 big-endian pixels. The display drivers for these controllers accept Poco rendered 16-bit RGB565 little-endian pixels and convert them to big-endian while transmitting to the LCD controller. Similarly, many displays are monochrome (1-bit). Their display driver accept pixels rendered in 4-bit or 8-bit gray and converts them to 1-bit for the monochrome display.
Source bitmap pixel formats
Poco supports 1-bit monochrome and 4-bit gray bitmaps as sources for rendering to all pixel formats. In addition, the configured destination pixel format is always supported as a source format. For example, when the rendering pixel format is 16-bit RGB565 little-endian, supported source pixel formats are 1-bit monochrome, 4-bit gray, and 16-bit RGB565 little-endian.
Poco implements support for two compressed pixel formats.
The first is a weighted run-length compression of 4-bit gray bitmaps. These are commonly used for anti-aliased fonts and image masks. They use the CommodettoBitmap
and PocoBitmap
data structures with the pixel format set to (kCommodettoBitmapGray16 | kCommodettoBitmapPacked)
.
The second is a variant of the ColorCell algorithm used to compress full color images. These are not referenced by the CommodettoBitmap
and PocoBitmap
data structures, but treated as an image file in the same way as BMP, PNG, and JPEG. ColorCell images use 16-bit RGB565 little-endian pixels and consequently may only be rendered to 16-bit RGB565 little-endian destinations.
By default, Poco is a scanline display list renderer. That means it stores all the drawing commands and then renders them all at once when all drawing commands for a given frame have been queued. When used with a display that has full frame buffers stored in memory accessible to Poco and is double buffered (e.g. has two frame buffers it flips between), scanline display list rendering is less efficient than immediate mode rendering, which executes each drawing command as it is received.
Poco optionally supports immediate mode rendering. To enable this support, define kPocoFrameBuffer
to 1 when building Poco, and use PocoDrawingBeginFrameBuffer
/PocoDrawingEndFrameBuffer
in place of PocoDrawingBegin
/PocoDrawingEnd
in the C code. No changes are required to JavaScript code to use immediate mode.
Poco provides support for rendering to a PixelsOut
at 0, 90, 180, or 270 degree rotations. This support allows use of a display in any orientation, independent of the natural scan order of the hardware.
The rotation is selected at build time, not run time, by defining kPocoRotation
to the target rotation (e.g. 90). The default rotation value is 0. The rotation is applied to the coordinates, both destination and source, of all drawing operations. Any assets (e.g. stored bitmaps and fonts) must be rotated prior to being passed to Poco. This is done either manually (e.g. in Photoshop) or automatically (e.g. by the png2bmp tool in the Moddable SDK).
This approach to rotation allows rendering of rotated output at the same performance level as unrotated images, and without requiring an intermediate bitmap buffer.
Poco is a renderer, a subclass of the Commodetto Render
class.
class Poco extends Render
constructor(pixelsOut, dictionary)
Poco extends the Render
dictionary with the displayListLength
property, which specifies the size of the display list buffer in bytes. Applications typically use the default display list length. Poco detects when a drawing operation would overflow the display list, ignores the drawing operation, and throws an exception when end
is called.
import Poco from "commodetto/Poco";
let screen = ... // SPIOut instance
let poco = new Poco(screen, {displayListLength: 4096});
Frees all memory allocated by Poco. No other functions on the instance may be called after calling close
.
clip(x, y, width, height)
Poco maintains a clip rectangle that is applied to all drawing operations.
When begin
is called, the clip rectangle is set to the update area passed to begin
. Poco maintains a clip stack, eliminating the need for applications to save and restore the current clip. Calling clip
with four arguments intersects the current clip with the area contained by the arguments; calling it with no arguments pops the clip stack, restoring the previous clip.
poco.clip(10, 10, 10, 10);
poco.clip();
The clip stack holds several clips, as follows:
poco.begin(); // Clip is entire PixelsOut area
poco.clip(10, 10, 10, 10); // Clip is {x: 10, y: 10, w: 10, h: 10}
poco.clip(0, 0, 15, 15); // Clip is {x: 10, y: 10, w: 5, h: 5}
poco.clip(); // Clip is {x: 10, y: 10, w: 10, h: 10}
poco.clip() // Clip is entire PixelsOut area
poco.end();
If the clip stack overflows or underflows, an exception is thrown from end
. The clip stack must be empty when end
is called or an exception is thrown.
When calling clip
with four arguments, the return value is true
if the resulting area contains one or more pixels and undefined
if the clip area is empty.
Note: clip
and origin
share the same stack, and so must be popped in the order they were pushed.
Poco maintains an origin that is applied to all drawing operations.
When begin
is called, the origin is set to {x: 0, y: 0}
. Poco maintains an origin stack, eliminating the need for applications to save and restore the current origin. Calling origin
with two arguments offsets the current origin by the arguments; calling it with no arguments pops the origin stack, restoring the previous origin.
poco.begin(); // Origin is {x: 0, y: 0}
poco.origin(10, 10); // Origin is {x: 10, y: 10}
poco.origin(5, 5); // Origin is {x: 15, y: 15}
poco.origin(); // Origin is {x: 10, y: 10}
poco.origin(); // Origin is {x: 0, y: 0}
poco.end();
If the origin stack overflows or underflows, an exception is thrown from end
. The origin stack must be empty when end
is called, or an exception will be thrown.
Note: Changing the origin does not change the clip rectangle. Note too that clip
and origin
share the same stack, and so must be popped in the order they were pushed.
The makeColor
function takes red, green, and blue values from 0 to 255 and returns the corresponding pixel value. The returned pixel is in the format of the PixelsOut
instance bound to the Poco
instance.
let red = poco.makeColor(255, 0, 0);
let green = poco.makeColor(0, 255, 0);
let blue = poco.makeColor(0, 0, 255);
let black = poco.makeColor(0, 0, 0);
let white = poco.makeColor(255, 255, 255);
let gray = poco.makeColor(127, 127, 127);
Many rendering functions take a color as an argument. Use makeColor
to calculate the color to avoid a dependency on the pixel format.
fillRectangle(color, x, y, width, height)
The fillRectangle
function fills the area specified by the x
, y
, width
, and height
arguments with the specified color.
poco.fillRectangle(green, 10, 20, 40, 40);
blendRectangle(color, blend, x, y, width, height)
The blendRectangle
function blends the specified color with the pixels in the area specified by the x
, y
, width
, and height
arguments. The blend
argument determines the level of blending, from a value of 0 for transparent to a value of 255 for opaque.
The following code draws 16 horizontal green lines with increasing opacity.
let green = poco.makeColor(0, 255, 0);
for (let blend = 15, y = 0; blend < 256; blend += 16, y += 1)
poco.blendRectangle(green, blend, 0, y, pixelsOut.width, 1);
The drawPixel
function draws a single pixel of the specified color at the location specified by the x
and y
arguments.
poco.drawPixel(poco.makeColor(0, 0, 127), 5, 5);
Note: Making many calls to drawPixel
in a single frame can quickly fill the display list.
drawBitmap(bits, x, y, sx, sy, sw, sh)
The drawBitmap
function draws all or part of a bitmap with pixels of type Bitmap.Default
. The bitmap is specified by the bits
argument, and the location to draw the bitmap is specified by the x
and y
arguments. The following code draws the entire image at location {x: 10, y: 5}
.
let image = parseBMP(new Resource("image.bmp"));
poco.drawBitmap(image, 10, 5);
The optional sx
, sy
, sw
, and sh
arguments specify the area of the bitmap to draw. If they are omitted, the entire bitmap is drawn.
The following code draws the bottom half of the bitmap at location {x: 0, y: 0}
.
poco.drawBitmap(image, 0, 0, 0, image.height / 2, image.width, image.height / 2);
drawMonochrome(monochrome, fore, back, x, y, sx, sy, sw, sh)
The drawMonochrome
function draws all or part of a bitmap with pixels of type Bitmap.Monochrome
. The bitmap is specified by the bits
argument, and the location to draw the bitmap is specified by the x
and y
arguments.
The fore
and back
arguments specify the foreground and background colors to apply to the black and white pixels of the bitmap. If fore
is undefined
, the foreground pixels are not drawn; if back
is undefined
, the background pixels are not drawn.
let red = poco.makeColor(255, 0, 0);
let gray = poco.makeColor(128, 128, 128);
let white = poco.makeColor(255, 255, 255);
let icon = parseBMP(new Resource("icon.bmp"));
poco.drawMonochrome(icon, red, white, 0, 5); // red foreground and white background
poco.drawMonochrome(icon, gray, undefined, 0, 5); // only foreground pixels in gray
poco.drawMonochrome(icon, undefined, red, 0, 5); // only background pixels in red
The optional sx
, sy
, sw
, and sh
arguments specify the area of the bitmap to draw. If they are omitted, the entire bitmap is drawn.
drawGray(bits, color, x, y, sx, sy, sw, sh[, blend])
The drawGray
function draws all or part of a bitmap with pixels of type Bitmap.Gray16
. The bitmap is specified by the bits
argument, and the location to draw the bitmap is specified by the x
and y
arguments. The pixels of the bitmap are treated as alpha values and are blended with the background. The color
argument specifies the color to apply when blending.
The optional sx
, sy
, sw
, and sh
arguments specify the area of the bitmap to draw. If they are omitted, the entire bitmap is drawn.
The optional blend
argument applies an additional blend level to all pixels in the bitmap prior to blending with the background. The blend
value ranges from 0 for transparent to 255 for opaque.
drawMasked(bits, x, y, sx, sy, sw, sh, mask, mask_sx, mask_sy[, blend])
The drawMasked
function uses two bitmaps--an image and the alpha channel--to alpha-blend the image through the mask onto the destination. The image, specified by the bits
argument, is in Bitmap.Default
format. The alpha channel, specified by the mask
argument, is in Bitmap.Gray16
format.
The x
and y
arguments specify where to locate the merged image in the output. The sx
, sy
, sw
, and sh
arguments specify the area of the image to use. The mask_sx
and mask_sy
arguments specify the top-left corner of the mask bitmap to use; the dimensions of the mask bitmap area are taken from the sw
and sh
arguments.
The following example draws a button image with an alpha channel. The image and alpha channel are stored in separate files, each previously extracted from a PNG file with an alpha channel.
let buttonImage = parseBMP(new Resource("button_image.bmp"));
let buttonAlpha = parseBMP(new Resource("button_alpha.bmp"));
poco.drawMasked(buttonImage, 0, 0, 0, 0,
buttonImage.width, buttonImage.height, buttonAlpha, 0, 0);
Storing the alpha channel separately from the image is unusual, and has benefits for resource constrained devices:
- The alpha channel image can be 4 bits per pixel, which gives good results at half the size.
- The image can be rendered with and without an alpha channel.
- A single mask can be applied to any image, allowing for effects and animations.
The optional blend
argument applies an additional blend level to all pixels in the alpha channel bitmap prior to blending with the background. The blend
value ranges from 0 for transparent to 255 for opaque.
fillPattern(bits, x, y, w, h [, sx, sy, sw, sh])
The fillPattern
function fills an area by repeatedly drawing all or part of a bitmap with pixels of type Bitmap.Default
. The bitmap is specified by the bits
argument. The location of the area to fill is specified by the x
and y
arguments, and the dimensions of the area are specified by the w
and h
arguments.
let pattern = parseBMP(new Resource("pattern.bmp"));
poco.fillPattern(pattern, 10, 10, 90, 90);
The optional sx
, sy
, sw
, and sh
arguments specify the area of the bitmap to use. If they are omitted, the entire bitmap is used.
poco.fillPattern(pattern, 10, 10, 90, 90, 0, 0, 8, 8);
drawText(text, font, color, x, y[, width])
The drawText
function draws the text
string using the BMFont in the font
argument. The text is drawn in the color of the color
argument at the location of the x
and y
arguments. Text is drawn using top-left alignment.
The following code draws the string "Hello, world"
twice: first in the top-left corner of the screen using the Chicago font in black, and then beneath that string using the Palatino 36 font in red.
poco.drawText("Hello, world", chicagoFont, black, 0, 0);
poco.drawText("Hello, world", palatino36, red, 0, chicagoFont.height);
If the optional width
argument is provided, the text is truncated on the right edge when it is too long to fit unclipped in the available width. When truncation occurs, three periods (...
) are drawn at the end of the string.
Characters in the text string that are not part of the font are ignored.
To draw full-color text with anti-aliased edges, use a BMFont with a bitmap in Bitmap.Default
format. In place of the color
argument, pass a mask bitmap in the Bitmap.Gray16
format. The mask must be at least as large as the BMFont's glyph atlas. When each glyph is drawn, the pixels in the mask image corresponding to the glyph in the font image are used to alpha-blend each glyph with the destination.
The getTextWidth
function calculates the width in pixels of the text
string when rendered using font
.
The following code draws the string "Hello, world"
horizontally centered at the top of display.
let text = "Hello, world";
let width = poco.getTextWidth(text, palatino36);
poco.drawText(text, palatino36, green, (pixelsOut.width - width) / 2, 0);
The height of the font is available in the font.height
property.
Characters in the text string that are not part of the font are not rendered.
drawFrame(frame, dictionary, x, y)
The drawFrame
function renders the ColorCell compressed image referenced by the frame
argument at the location specified by the x
and y
arguments. The dictionary
argument is an Object
that contains width and height properties that indicate the source dimensions of the image.
The logical height in pixels of the Poco instance after rotation is applied. When rotation is 0 or 180, this is equal to the PixelsOut
instance's height; when rotation is 90 or 270, it is equal to the PixelsOut
instance's width.
The logical width in pixels of the Poco instance after rotation is applied. When rotation is 0 or 180, this is equal to the PixelsOut
instance's width; when rotation is 90 or 270, it is equal to the PixelsOut
instance's height.
The Poco C API is a low-level rendering engine. It is based on a display list, meaning that all drawing calls are queued to a list prior to rendering. A display list enables the renderer to generate as little as a single scanline of fully composed output at a time, minimizing memory use by eliminating the need for a frame buffer in the memory of the application processor.
The Poco C API may be used independently of Commodetto and its JavaScript API. It makes no allocations and almost no external calls (only to memcpy
), relying on the caller to provide memory.
PocoRecord
maintains state for Poco. Many of the fields are private to the implementation and should not be accessed directly by users of the library. The PocoRecord
data structure should be initialized to 0, and the same PocoRecord
structure must be passed to all Poco function calls.
The following fields in PocoRecord
are public and can be accessed by users of the library. Poco expects these fields to be initialized by the users of the library before the first call to Poco is made.
Field |
Description |
width |
width of output in pixels |
height |
height of output in pixels |
displayList |
pointer to start of memory for display list |
displayListEnd |
pointer to end of memory for display list |
pixelsLength |
size in bytes of pixels array |
The following fields are available to read from the PocoRecord
structure between calls to PocoDrawingBegin
and PocoDrawingEnd
:
Field |
Description |
xOrigin |
x coordinate of drawing origin |
yOrigin |
y coordinate of drawing origin |
x |
x coordinate of drawing clip |
y |
y coordinate of drawing clip |
w |
width of drawing clip |
h |
height of drawing clip |
xMax |
right coordinate of drawing clip, x + w
|
yMax |
bottom coordinate of drawing clip, y + h
|
PocoCoordinate
is a signed integer value. When used in Commodetto, it is aliased to CommodettoCoordinate
. See the description of CommodettoCoordinate
in the Commodetto documentation for additional information.
PocoDimension
is an unsigned integer value. When used in Commodetto, it is aliased to CommodettoDimension
. See the description of CommodettoDimension
in the Commodetto documentation for additional information.
PocoPixel
is an integer value. When used in Commodetto, it is aliased to CommodettoPixel
. See the description of CommodettoPixel
in the Commodetto documentation for additional information.
PocoBitmapFormat
is an integer value. When used in Commodetto, it is aliased to CommodettoBitmapFormat
. See the description of CommodettoBitmapFormat
in the Commodetto documentation for additional information.
PocoRectangle
defines the area enclosed by a rectangle, with the top-left coordinate and the dimensions.
typedef struct {
PocoCoordinate x;
PocoCoordinate y;
PocoDimension w;
PocoDimension h;
} PocoRectangleRecord, *PocoRectangle;
The PocoBitmap
structure contains the width and height of the bitmap in pixels, the format of the pixels in the bitmap, and a pointer to the pixel data.
typedef struct PocoBitmapRecord {
PocoDimension width;
PocoDimension height;
PocoBitmapFormat format;
PocoPixel *pixels;
} PocoBitmapRecord, *PocoBitmap;
The pixels are organized left to right, top to bottom, with no padding between rows. There is no rowBytes
or stride
field in PocoBitmap
.
Note: Unlike CommodettoBitmap
, PocoBitmap
does not have an option to store an offset in place of the pixels pointer.
Before calls to Poco can be made, a PocoRecord
structure must be allocated and initialized. See PocoRecord
for details.
PocoPixel PocoMakeColor(Poco poco, uint8_t r, uint8_t g, uint8_t b);
PocoMakeColor
takes red, green, and blue values from 0 to 255 and returns a PocoPixel
value. The returned pixel value can be used as a color argument in some rendering calls.
Note: In the current implementation the poco parameter is unused because Poco is always built with support for only a single output pixel format.
void PocoDrawingBegin(Poco poco, PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h);
PocoDrawingBegin
begins the rendering process for an update area of pixels bounded by the x
, y
, w
, and h
parameters. Calls to draw can only be made between calls to PocoDrawingBegin
and PocoDrawingEnd
.
Important: The caller of PocoDrawingBegin
is responsible for ensuring that the drawing calls cover all pixels in the update area. Poco does not maintain the previous frame. Any pixels that are not drawn will contain unpredictable values.
int PocoDrawingEnd(Poco poco, PocoPixel *pixels, int byteLength,
PocoRenderedPixelsReceiver pixelReceiver, void *refCon);
typedef void (*PocoRenderedPixelsReceiver)(PocoPixel *pixels,
int byteCount, void *refCon);
PocoDrawingEnd
renders the drawing commands added to the display list since the call to PocoDrawingBegin
.
The caller of PocoDrawingEnd
provides a buffer for the rendered pixels in the pixels
and byteLength
arguments. Poco renders as many rows of pixels as possible into the buffer and then calls the pixelReceiver
function to output the pixels. The buffer need be only large enough to hold a single row of pixels. A buffer that can hold several rows of pixels reduces rendering overhead.
If an error occurs, adding commands to the display list as the result of drawing calls or in the execution of PocoDrawingEnd
, PocoDrawingEnd
returns a nonzero result:
- 1 -- display list overflow
- 2 -- clip and origin stack overflow
- 3 -- clip and origin stack underflow or out-of-sequence pop
void PocoRectangleFill(Poco poco, PocoPixel color, uint8_t blend,
PocoCoordinate x, PocoCoordinate y, PocoDimension w, PocoDimension h);
PocoRectangleFill
fills the area defined by the x
, y
, w
, and h
arguments as specified by color
. If the level specified by blend
is kPocoOpaque
(255), the color is drawn over with the background without blending; for other blending levels the color is blended proportionally with the background.
void PocoPixelDraw(Poco poco, PocoPixel color,
PocoCoordinate x, PocoCoordinate y);
PocoPixelDraw
renders a single pixel at the location specified by x
and y
in the specified color.
PocoCommand PocoBitmapDraw(Poco poco, PocoBitmap bits,
PocoCoordinate x, PocoCoordinate y,
PocoDimension sx, PocoDimension sy,
PocoDimension sw, PocoDimension sh);
PocoBitmapDraw
renders all or part of the bitmap bits
, of type kCommodettoBitmapDefault
, at the location specified by x
and y
. The sx
, sy
, sw
, and sh
arguments define the area of the bitmap to render.
void PocoMonochromeBitmapDraw(Poco poco, PocoBitmap bits,
PocoMonochromeMode mode, PocoPixel fgColor, PocoPixel bgColor,
PocoCoordinate x, PocoCoordinate y,
PocoDimension sx, PocoDimension sy,
PocoDimension sw, PocoDimension sh);
PocoMonochromeBitmapDraw
renders all or part of bitmap bits
of type kCommodettoBitmapMonochrome
at the location specified by x
and y
. The sx
, sy
, sw
, and sh
arguments define the area of the bitmap to render. The mode
argument determines which pixels are drawn:
-
kPocoMonochromeForeground
-- Only foreground pixels are drawn (1 pixels in the bitmap).
-
kPocoMonochromeBackground
-- Only background pixels are drawn (0 pixels in the bitmap).
-
kPocoMonochromeForeAndBackground
-- Both foreground and background pixels are drawn.
The fgColor
and bgColor
arguments specify the colors to use to render the foreground and background pixels.
void PocoGrayBitmapDraw(Poco poco, PocoBitmap bits,
PocoPixel color, uint8_t blend,
PocoCoordinate x, PocoCoordinate y,
PocoDimension sx, PocoDimension sy,
PocoDimension sw, PocoDimension sh);
PocoGrayBitmapDraw
renders all or part of bitmap bits
, of type kCommodettoBitmapGray16
, at the location specified by x
and y
. The sx
, sy
, sw
, and sh
arguments define the area of the bitmap to render. The pixels of the bitmap are treated as alpha blending levels and are used to blend the color
argument with the background pixels. The blend
argument is applied to the blend level of each pixel, with values ranging from 0 for transparent to 255 for opaque.
void PocoBitmapDrawMasked(Poco poco, PocoBitmap bits, uint8_t blend,
PocoCoordinate x, PocoCoordinate y,
PocoDimension sx, PocoDimension sy,
PocoDimension sw, PocoDimension sh,
PocoBitmap mask, PocoDimension mask_sx, PocoDimension mask_sy);
PocoBitmapDrawMasked
renders the pixels of bitmap bits
, of type kCommodettoBitmapDefault
, enclosed by sx
, sy
, sw
, and sh
at the location specified by x
and y
. The pixels are drawn using the corresponding pixels of the bitmap mask
, of type kCommodettoBitmapGray16
, enclosed by mask_sx
, mask_sy
, sw
, and sh
as alpha blending levels. The blend
argument is applied to the blend level of each pixel, with values ranging from 0 for transparent to 255 for opaque.
void PocoBitmapPattern(Poco poco, PocoBitmap bits,
PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h,
PocoDimension sx, PocoDimension sy,
PocoDimension sw, PocoDimension sh);
PocoBitmapPattern
fills the area enclosed by the x
, y
, w
, and h
arguments with repeating copies of the area of the bitmap bits
enclosed by the sx
, sy
, sw
, and sh
arguments. The bitmap must be of type kCommodettoBitmapDefault
.
void PocoDrawFrame(Poco poco,
uint8_t *data, uint32_t dataSize,
PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h);
PocoDrawFrame
renders a compressed image stored in the Moddable variant of the ColorCell algorithm. The image to render is pointed to by the data
argument with a byte count specified by the dataSize
argument. The image is rendered at the location specified by the x
and y
arguments. The source dimensions (unclipped size) of the compressed image are given by the w
and h
arguments.
void PocoClipPush(Poco poco, PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h);
PocoClipPush
pushes the current clip area on the stack and then replaces the current clip with the intersection of the current clip and the area enclosed by the x
, y
, w
, and h
arguments.
void PocoClipPop(Poco poco);
PocoClipPop
pops the clip from the stack and replaces the current clip with the popped value.
void PocoOriginPush(Poco poco, PocoCoordinate x, PocoCoordinate y);
PocoOriginPush
pushes the current origin on the stack and then offsets the current origin by the x
and y
arguments.
void PocoOriginPop(Poco poco);
PocoOriginPop
pops the origin from the stack and replaces the current origin with the popped value.
PocoDrawingBeginFrameBuffer
void PocoDrawingBeginFrameBuffer(Poco poco, PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h,
PocoPixel *pixels, int16_t rowBytes);
PocoDrawingBeginFrameBuffer
begins the immediate mode rendering process for an update area of pixels bounded by the x
, y
, w
, and h
parameters. The pixels
parameter points to the first scanline of output pixels. The rowBytes
parameters is the stride in bytes between each scanline.
PocoDrawingEndFrameBuffer
int PocoDrawingEndFrameBuffer(Poco poco);
PocoDrawingEndFrameBuffer
indicates that all drawing is complete for the current frame.
void PocoDrawExternal(Poco poco, PocoRenderExternal doDrawExternal,
uint8_t *data, uint8_t dataSize,
PocoCoordinate x, PocoCoordinate y,
PocoDimension w, PocoDimension h);
typedef void (*PocoRenderExternal)(Poco poco, uint8_t *data,
PocoPixel *dst, PocoDimension w, PocoDimension h, uint8_t xphase);
PocoDrawExternal
installs a custom rendering element into the current Poco display list. The data
argument points to a block of data of dataSize
bytes in length that describes the drawing operation. This data is copied into the Poco display list, and so should be as compact as possible. The bounds of the drawing operation are defined by the x
, y
, w
, and h
arguments. The doDrawExternal
callback function is called to render the custom element, one or more scanlines at a time.
Poco does not perform clipping or rotation on the rendering operation. These must be applied by the code that creates the rendering data and/or the rendering callback function.
When drawing to pixel formats with multiple pixels per byte, xphase
indicates the pixel where drawing begins. For example, for a pixel format that uses 4-bits per pixel, xphase is 0 for the first pixel in the byte and 1 for the second.
Note: Implementing custom rendering elements is an advanced technique that requires familiarity with the implementation of the Poco rendering engine.
Relationship to Commodetto
The C API of Poco may be used independently from Commodetto. Poco is the first renderer integrated into Commodetto. Poco runs on all hardware capable of supporting Commodetto--in particular the XS JavaScript engine. The Poco C API can also run on considerably less powerful hardware than Commodetto.
The word poco is a term used in music meaning "a little."