This chapter shows you how to work with layers, surfaces, and contexts using the QNX Graphics Framework.
Let's look at some of the capabilities of layers, surfaces, and contexts.
Once you've attached to a display, you can get the handle to any of the supported layers using gf_layer_attach(), passing the index of the layer you want. When you attach to a display, gf_display_attach() fills in a gf_display_info_t structure. Two members contain information about supported layers; main_layer_index tells you which layer is the main layer (the main layer index is hardware-dependent), and nlayers tells you the number of layers.
After you've attached to a layer, you can query the layer about the formats it supports using gf_layer_query(). You need this information to attach to a surface with the correct format. Here's an example where you know your hardware supports a subset of pixel formats, and you want to find the layer format that matches one of them:
static int find_rgb_format(gf_layer_t layer) { gf_layer_info_t info; int i; for (i = 0; gf_layer_query(layer, i, &info) == GF_ERR_OK; ++i) { switch (info.format) { case GF_FORMAT_PKLE_ARGB1555: case GF_FORMAT_PKLE_RGB565: case GF_FORMAT_BGR888: case GF_FORMAT_BGRA8888: return info.format; default: continue; } } return -1; }
All possible formats recognized by GF are enumerated by gf_format_t. The packed formats in the gf_layer_info_t filled in by gf_layer_query() are non-endian-specific, and you should use the non-specific formats where possible (for example, when creating a surface using gf_surface_create()). The only exception is gf_surface_attach(), because it wraps an already-allocated piece of memory as a GF surface, so you need to use an explicit endian format.
This applies only to packed formats. There are no endian-specific variants of other format types. |
The gf_layer_info_t structure filled in by gf_layer_query() also contains information about other layer capabilities which your application may need to know about. These capabilities include whether:
Once you've attached to a surface with a matching format, you set the target surface (or surfaces if there's more than one, such as with planar YUV data) for the layer using gf_layer_set_surfaces().
By default, the main layer is visible (enabled), and all other layers are invisible (disabled) when they're attached. You can make any layer non-visible by calling gf_layer_disable(), and visible with gf_layer_enable().
You can find out if the hardware allows a layer to be disabled by checking the GF_LAYER_CAP_DISABLE flag bit in the caps member of info filled by gf_layer_query().
You can set the brightness, contrast, saturation or hue attributes for a layer in YUV format by calling gf_layer_set_brightness(), gf_layer_set_contrast(), gf_layer_set_saturation(), and gf_layer_set_hue(). These functions take an integer in the range of -128 to 127, where 0 is the normal (default) value. These functions cause gf_layer_update() to return something other than GF_ERR_OK if the hardware doesn't support the attribute.
After calling any of these functions, call gf_layer_update() to make any attribute changes take effect. |
You can check to see if the hardware allows a layer to set these attributes by checking the GF_LAYER_CAP_SET_BRIGHTNESS, GF_LAYER_CAP_SET_CONTRAST, GF_LAYER_CAP_SET_SATURATION, and GF_LAYER_CAP_SET_HUE flag bits in the caps member of info filed in by gf_layer_query().
You can create a viewport for a layer, which is an area on a source surface that's displayed on the destination layer. The gf_layer_set_src_viewport() function sets the area of the source surface that's displayed, and gf_layer_set_dst_viewport() sets the “window” on the main display.
A viewport is a convenient way to scroll or pan an image, or provide a picture-in-a-picture type of GUI. To scale an image, increase or decrease the size of the destination viewport (if the hardware supports scaling). To scroll or pan the image, move the position of the source viewport.
|
There may be hardware limitations on the viewports. Check these flag bits in gf_layer_info_t.caps filled in by gf_layer_query():
You can specify how the hardware blends a layer with the layers behind it on the display by setting the layer's alpha and chroma parameters with:
Your hardware must support layers in order to use these APIs. |
In some cases, layer blending is more efficient than blending via the draw engine (using gf_context_set_alpha()). For example, in a map viewer application, the map can be rendered on one layer while HMI controls can be rendered on another, and the application doesn't have to manage clipping or re-calculating the entire screen when the map is scrolling. As well, two independent processes can render onto different layers without having to coordinate rendering. In other cases, it may be more efficient to blend using the draw engine; for example, when rendering a few static objects. In this case, layer blending would be less efficient because the layers are re-blended during every screen refresh, rather than once.
See Context Alpha Blending below for some details about how you set up alpha operations and perform alpha operations using the draw engine.
Surfaces are areas of memory that you can render to using the GF or OpenGL ES APIs. A surface can be associated with a layer (in which case, whatever is rendered to the surface appears on the display associated with the layer), or can exist on its own to facilitate offscreen rendering.
The region of memory in which a surface is located largely depends on the hardware, and the flags passed to the function that creates the surface. If you set the GF_SURFACE_CREATE_CPU_FAST_ACCESS flag bit for gf_surface_create(), the library attempts to optimize the surface for CPU access speed, which generally means allocating system RAM. By default, the library attempts to optimize the surface for hardware accelerated rendering, which generally means allocating video RAM.
The functions you can use to create surfaces are:
To create a surface that's an alpha map (see the gf_alpha_t structure), use gf_surface_create(), and set the GF_SURFACE_CREATE_ALPHA_MAP flag. The surface format must be GF_FORMAT_BYTE. |
All surfaces are freed by io-display when an application exits, though if your application creates a surface and then no longer requires it, it should free the memory by calling gf_surface_free(). Note that the GF library doesn't free memory “wrapped” by gf_surface_attach(), so the application needs to deallocate it.
If you need to query a surface about its parameters, use gf_surface_get_info(). This function fills in a gf_surface_info_t structure with information about the surface's:
If you need to get the handle of the graphics device that a surface is currently targeting, call gf_surface_get_dev().
A context is a structure that maintains parameters for rendering. You set the parameters on the context rather than on the surface itself.
To create a context, use gf_context_create(), then use gf_context_set_surface() to set the context to a specific surface. You should free the context with gf_context_free() when you're done with it, or use gf_context_init() to reinitialize the context with the default values to “reuse” it.
Let's look at the parameters you can set for a context.
Many of the “set” functions discussed in this section have a corresponding “disable” function to turn a feature off for the context. For example, gf_context_set_alpha() also has a gf_context_disable_alpha(). |
Set alpha blending for a context using gf_context_set_alpha(). This function takes a structure that defines the alpha parameters.
You can achieve blending (and text anti-aliasing) effects by setting up various alpha operations in gf_alpha_t, and passing it to gf_context_set_alpha() before rendering to the context. Alpha refers to extra information used to alter a rendered pixel, often to achieve various opacity effects. The alpha data can come from one of three places:
There are also alpha blending source factors and destination factors defined to specify how the source and destination pixels are modified with the alpha data.
Let's look at the most common form of alpha blending to understand how the operation is performed: blending a source pixel onto a destination pixel using the source pixel's alpha channel. In this example, the 8-bit alpha and color values are normalized to the range of 0 to 1, and are presented as ARGB format.
Assume we have a source pixel: (.5,0,1,0) and destination pixel: (1, 1, 0, 0). The source pixel is green with a 50% alpha, in other words, 50% opaque. The destination pixel is red, and completely opaque.
In this case, we want to use the alpha channel from the source pixel for our alpha data, so we set gf_alpha_t.mode to GF_ALPHA_M1_SRC_PIXEL_ALPHA.
Since we want to multiply the source pixel by the alpha value, we set the source factor to GF_BLEND_SRC_M1. In the case of a simple blend, the amount of blending applied to each pixel should total 1 (100%). So we want the destination blending factor to be one minus the source factor, or GF_BLEND_DST_1mM1.
When rendered, each channel of the source pixel (including the alpha channel) is multiplied by the source alpha channel, or 0.5, to yield (.25, 0, .5, 0). Each color channel of the destination pixel is multiplied by 1 - 0.5., to yield (.5, .5, 0, 0). The addition of these two results is the final rendered pixel value, or (.75, .5, .5, 0).
In code, this operation would look like this:
gf_alpha_t alpha = { 0 }; alpha.mode = GF_ALPHA_M1_SRC_PIXEL_ALPHA | GF_BLEND_SRC_M1 | GF_BLEND_DST_1mM1; gf_context_set_alpha( context, &alpha ); /* do rendering here */ gf_context_disable_alpha( context );
Let's look at another example where you are importing an image with an alpha channel and transparency. This code snippet from the sample image-loading application shipped with the QNX Advanced Graphics source package:
if (img.flags & IMG_TRANSPARENCY) { gf_chroma_t chroma; memset(&chroma, 0, sizeof chroma); chroma.mode = GF_CHROMA_OP_SRC_MATCH | GF_CHROMA_OP_NO_DRAW; if (img.format & IMG_FMT_PALETTE) { chroma.color0 = img.palette[img.transparency.index]; } else if (IMG_FMT_BPP(img.format) < 24) { chroma.color0 = img.transparency.rgb16; } else { chroma.color0 = img.transparency.rgb32; } gf_context_set_chroma(setup.context, &chroma); } if (img.format & IMG_FMT_ALPHA) { gf_alpha_t alpha; memset(&alpha, 0, sizeof alpha); alpha.mode = GF_ALPHA_M1_SRC_PIXEL_ALPHA | GF_BLEND_SRC_M1 | GF_BLEND_DST_1mM1; gf_context_set_alpha(setup.context, &alpha); } /* render the loaded image */ gf_draw_blit2(setup.context, img_surf, NULL, 0, 0, img.w - 1, img.h - 1, setup.x1, setup.y1);
For an example of using alpha blending to anti-alias text, see the font-cache and font-test example applications included in QNX Advanced Graphics.
You can set chroma operations using gf_context_set_chroma(), and passing it a gf_chroma_t structure, which defines what chroma operations to apply to subsequent draw operations.
Chroma keying is an operation where the render engine tests a pixel's color to determine how to render a source image onto a layer. The mode member of gf_chroma_t is a combination of two flags:
and one of:
For example, let's make white (0x00FFFFFF) a transparent color in an image. You would set gf_chroma_t.color0=0x00FFFFFF, and gf_chroma_t.mode=GF_CHROMA_OP_SRC_MATCH|GF_CHROMA_OP_NO_DRAW:
gf_chroma_t chroma = { 0 }; ... chroma.mode = GF_CHROMA_OP_SRC_MATCH | GF_CHROMA_OP_NO_DRAW; chroma.color0 = 0x00FFFFFF; gf_context_set_chroma( context, &chroma ); gf_draw_blit2( context, img_surface, surface, 0, 0, w - 1, h - 1, 5+w+5, 30 );
If you wanted to do a bluescreen-type operation, where a source image is only drawn on a specific color on the destination layer, you would set gf_chroma_t.mode = GF_CHROMA_OP_DRAW | GF_CHROMA_OP_DST_MATCH.
The foreground color is the color applied to most draw primitives, such as rectangles, polygons, bitmaps, and polylines. It's set with gf_context_set_fgcolor(), which takes a 32-bit ARGB color (for more information, see gf_palette_t). The background color, set with gf_context_set_bgcolor(), is applied where a second color is required, for example as the background for a dashed polyline.
Aside from the foreground and background colors, polylines also take attributes that define their width, dashing, and join style between line segments.
Use gf_context_set_penwidth() to set the width of a line. Note that line thickness is supported only for hardware that has accelerated thick line support. The maximum line width is determined by the hardware limit for thick lines (see the Hardware Capabilities appendix for more information on supported hardware). If you set the linewidth to 0, a width of 1 pixel is used.
To set the join style between segments of a polyline use gf_context_set_linejoin(). This function supports these line join styles: butt joints, bevel joints, round joints, and bevel joints.
Line dashing applies a stipple effect to polylines, giving them a “dashed” effect. Use gf_context_set_linedash() to set line dashing. For example:
gf_point_t p[] = { { 50,250},{w-100,250}}; gf_context_set_fgcolor(context,0x00ffff); gf_context_set_bgcolor(context,0xffffff); gf_context_set_linedash(context,0x000ffff, 0, 32, GF_CONTEXT_LINEDASH_BACKFILL); gf_context_set_penwidth( context,10); gf_draw_polyline(context,p,2,0);
A clipping rectangle limits what is rendered on the final display to the contents of the rectangle, even if rendering commands for a surface extend beyond the borders of the rectangle. You set a clipping rectangle with gf_context_set_clipping(). In this example, several polygons are drawn with some points outside of a clipping rectangle, which are clipped on the final display:
gf_context_set_fgcolor(context,0xffff00); gf_draw_rect(context,0,0,w,h); gf_context_set_clipping(context, 50,50,400,400); { gf_point_t p[][4] = { {80,8, 150,150, 80,80, 40,160}, {240,8, 290,80, 360,10, 290,160}, {40, 190, 150,270, 50,320, 90,270}, {210,260, 450, 190, 370,260, 450,340} }; gf_context_set_fgcolor(context,0x0000ff); gf_context_set_penwidth(context,1); gf_draw_poly_fill(context, &p[0][0],4); gf_context_set_fgcolor(context,0x00ff00); gf_context_set_penwidth(context,5); gf_draw_polyline(context, &p[1][0],4, GF_DRAW_POLYLINE_CLOSED); gf_context_set_fgcolor(context,0xf00f00); gf_context_set_penwidth(context,10); gf_draw_poly_fill(context, &p[2][0],4); gf_context_set_fgcolor(context,0x00ffff); gf_context_set_penwidth(context,20); gf_draw_polyline(context, &p[3][0],4,0); }
You can specify raster operations (ROPs) with two functions: gf_context_set_rop() sets the actual operation, while gf_context_set_pattern() sets the pattern that ternary ROPs use.
uint8_t pattern[]={ 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f }; unsigned short rop = GF_ROP_PDSnaon; gf_context_set_fgcolor(context, 0xff0000); gf_context_set_bgcolor(context, 0x0000ff); gf_context_set_pattern(context,pattern,0,0,GF_CONTEXT_PATTERN_BACKFILL); gf_context_set_rop(context, rop); gf_draw_rect (context,50,50,200,200 ); gf_draw_flush(context);
Anti-aliasing lets you visually approximate a display with an ideal resolution by varying the intensities of discrete pixels, making line edges appear smoother. To turn on this parameter, use gf_context_set_antialias().
This setting does not apply to images and fonts. To anti-alias fonts, use alpha blending. See Context Alpha Blending above. |
A translation sets x and y values that are added to all polygon, line, and polyline coordinates prior to rendering. A transformation matrix is a 2D matrix that is multiplied by all polygon, line, and polyline coordinates prior to rendering. You can use a combination of the transform matrix and translations to perform various rotation, scaling and reflection operations. To set a 2D transform matrix, use gf_context_set_transform(); to set a translation, use gf_context_set_translation().
This example shows both functions in action. The actual GF setup is performed by the gf_setup() function, which isn't shown.
int main(int argc, char *argv[]) { gf_setup_t setup; bool rotate = TRUE, translate = TRUE; unsigned dispno = 0, fc = 0; const char *dev_name = NULL; int layer_idx = GF_SETUP_LAYER_MAIN, rc; float c, s; int theta = 0, dtheta = 1, tx = 0, dx = 1, ty = 0, dy = 1; gf_fixed_t xform[] = { 1<<16, 0, 0, 1<<16 }; const gf_point_t points[] = { { 0, -100 }, { 59, 81 }, { -95, -31 }, { 95, -31 }, { -59, 81 } }; while ((rc = getopt(argc, argv, "d:D:l:rtx:y:")) != -1) { switch (rc) { case 'd': if (isdigit(*optarg)) { dev_name = GF_DEVICE_INDEX(atoi(optarg)); } else { dev_name = optarg; } break; case 'D': dispno = atoi(optarg); break; case 'l': layer_idx = atoi(optarg); break; case 'r': rotate = FALSE; break; case 't': translate = FALSE; break; case 'x': tx = atoi(optarg); break; case 'y': ty = atoi(optarg); break; } } if ((rc = gf_setup(&setup, dev_name, dispno, layer_idx, GF_SETUP_FLAG_DBLBUFFER)) != GF_ERR_OK) { fprintf(stderr, "gf_setup() failed: %d\n", rc); return -1; } gf_context_set_penwidth(setup.context, 3); gf_context_set_antialias(setup.context, GF_CONTEXT_ANTIALIAS_LINES); for (;;) { gf_surface_t surface = fc++ & 1 ? setup.surface2 : setup.surface1; gf_context_set_surface(setup.context, surface); if (gf_draw_begin(setup.context) == GF_ERR_OK) { gf_context_set_fgcolor(setup.context, 0xffffff); gf_draw_rect(setup.context, 0, 0, INT_MAX, INT_MAX); gf_context_set_fgcolor(setup.context, 0x000000); gf_draw_polyline(setup.context, points, 5, GF_DRAW_POLYLINE_CLOSED); gf_draw_end(setup.context); } gf_layer_set_surfaces(setup.layer, &surface, 1); gf_layer_update(setup.layer, 0); tx += dx; ty += dy; if (tx >= setup.display_info.xres) { dx = -dx; tx = (setup.display_info.xres << 1) - tx; } else if (tx < 0) { dx = -dx; tx = -tx; } if (ty >= setup.display_info.yres) { dy = -dy; ty = (setup.display_info.yres << 1) - ty; } else if (ty < 0) { dy = -dy; ty = -ty; } theta += dtheta; if (theta >= 360) { theta -= 360; } s = sinf(theta * 2 * M_PI / 360); c = cosf(theta * 2 * M_PI / 360); xform[0] = c * 0x10000; xform[1] = s * 0x10000; xform[2] = -s * 0x10000; xform[3] = xform[0]; if (rotate) { gf_context_set_transform(setup.context, xform); } if (translate) { gf_context_set_translation(setup.context, tx, ty); } } return 0; }