Graphics
This section covers the graphic rendering capabilities of fsmapper. The graphic rendering capabilities of fsmapper are provided as the graphics library.
Rendering Context
The rendering context (RenderingContext
) in fsmapper forms the central concept and functionality within its graphic rendering capabilities.
Created for each rendering target, it enables graphic rendering to the specified destination through method calls within the rendering context. Additionally, the rendering context retains context-specific elements such as line width and fill colors that affect the rendering process.
The targets for rendering within fsmapper include views and bitmaps.
As explained in the Render on the View, the rendering context for outputting to a view is created by fsmapper and passed as the first argument to the renderer of the Canvas
view element.
Users cannot generate this within Lua scripts.
To create a rendering context targeting a bitmap, use graphics.rendering_context()
.
local bitmap = grphics.bitmap(100, 100)
local context = graphics.rendering_context(bitmap)
The device context holds the following properties that influence the behavior of each drawing method.
Property | Type | Description |
---|---|---|
brush | Brush | A brush refers to the color or pattern used for filling shapes or outlining their contours. |
stroke_width | number | This is used as the width of lines when drawing the outlines of shapes. |
opacity_mask | Bitmap | This is the mask image used when drawing shapes. The final alpha value of the rendered result is a product of the alpha value specified in the opacity_mask and the bitmap provided. |
font | Font | This is the font object used for text rendering. |
The rendering context provides the following drawing methods.
Category | Methods |
---|---|
Drawing Geometry | draw_geometry() fill_geometry() |
Drawing Bitmap | draw_bitmap() |
Drawing Text | draw_string()) draw_number() |
Others | fill_rectangle() |
The drawing operations executed through the rendering context are finalized by calling the finish_rendering()
method.
context:finish_rendering()
Bitmap
Bitmaps are the most commonly used building blocks when rendering virtual instrument panels.
The Bitmap
object is provided to be used for these bitmap-related operations.
The simplest way to create a bitmap object is by loading a bitmap image from a file.
local button1 = graphics.bitmap('asset/button1.png')
Rather than statically preparing a file, you can dynamically create a bitmap parametrically.
Invoking graphics.bitmap()
with specified dimensions generates a bitmap object initialized with transparency.
Creating a rendering context with this object as the output destination allows dynamic generation of bitmap images through rendering operations.
-- Generate empty bitmap
local circle = graphics.bitmap(100, 100)
-- Fill a circle with blue on the bitmap
local rctx = graphics.rendering_context(circle)
rctx.brush = graphics.color('blue')
local circle = graphics.ellipse{x=50, y=50, radius_x=50, radius_y=50}
rctx:draw_fill_geometry(circle)
rctx:finish_rendering()
The final way to generate a bitmap object involves creating a portion of the bitmap as a separate bitmap object using the create_partial_bitmap()
method of the Bitmap
object. Bitmaps created using this method share the buffer that holds the bitmap data with the original bitmap object, reducing concerns about memory consumption.
This method is useful for preparing multiple image components for various canvases as a single bitmap and generating partial bitmaps for each canvas, just as in this file or this file.
local entire_bitmap = graphics.bitmap('assets/parts_library.png')
local partial_bitmap = entire_bitmap:create_partial_bitmap(0, 0, 50, 50)
Supported File Format
fsmapper can load the following types of bitmap image files.
- BMP
- GIF
- ICO
- JPEG
- PNG
- TIFF
- HD Photo
Entire Opacity
The Bitmap
object contains an alpha value indicating the opacity of each pixel. However, you can specify the overall opacity of the entire bitmap using the opacity
property.
The opacity of each pixel is determined by the product of the pixel's alpha value and the opacity
property.
Drawing Bitmap
The method used to draw a Bitmap is RenderingContext:draw_bitmap()
.
This method includes parameters for translation and rotation, both of which act relative to the origin of the bitmap.
At the bitmap's creation, the origin is at the top-left corner.
To set a different point within the bitmap space as the new origin, you can use Bitmap:set_origin()
.
-- Set the origin to the center of bitmap
local dial = graphics.bitmap(asset/dial.jpg)
dial:set_origin(dial.width / 2, dial.height / 2)
The parameters that can be specified for RenderingContext:draw_bitmap()
are as follows.
Key | Type | Description |
---|---|---|
bitmap | Bitmap | REQUIRED PARAMETER The bitmap for drawing. |
x | number | Draw the bitmap's origin to coincide with this parameter's position in the target space. The default is 0 . |
y | number | Draw the bitmap's origin to coincide with this parameter's position in the target space. The default is 0 . |
width | number | Perform horizontal scaling to match the bitmap's width to the specified value in the target space.The default is the bitmap's width. |
height | number | Perform vertical scaling to match the bitmap's height to the specified value in the target space.The default is the bitmap's height. |
angle | number | Rotate counterclockwise by the specified angle in degrees around the bitmap's origin.The default is 0 . |
scale | number | The scaling factor. If width and height are specified together, the final scaling factor becomes the cumulative value. |
Here's an example of a renderer
that displays a bitmap rotated by the canvas's value
degrees and positioned at (100, 100) on the canvas at half its size.
local bitmap = graphics.bitmap('assets/knob.png')
local renderer = function (rctx, value)
rctx:draw_bitmap(bitmap=bitmap, x=100, y=100, scale=0.5, angle=value)
end
Brush
The objects that can be set as a brushe in the rendering context are Color
objects representing solid colors or Bitmap
objects.
Solid Color
A Color
object representing a solid color is created using graphics.color()
.
Color specification can be done in the form of a color name similar to CSS, hexadecimal values, or by specifying RGB values numerically.
Each of the following three examples generates a Color object representing the same color.
local color1 = graphics.color('DarkCyan')
local color2 = graphics.color('#008b8b')
local color3 = graphics.color(0,139,139)
In any of these cases, you can also create a translucent color by adding an argument that represents opacity.
local color1 = graphics.color('DarkCyan', 0.5)
local color2 = graphics.color('#008b8b', 0.5)
local color3 = graphics.color(0,139,139, 0.5)
Bitmap Brush
Bitmap objects can be used as brushes.
When using a bitmap as a brush, the following properties of Bitmap
affect the fill effect.
Property | Description |
---|---|
brush_extend_mode_x brush_extend_mode_y | Specifies how a brush paints areas outside of the bitmap with one of the following values.'clamp' : Repeat the edge pixels of the bitmap for all regions outside the bitmap area.'wrap' : Repeat the bitmap content. This is the default.'mirror' : The same as 'wrap' , except that alternate tiles of the bitmap content are flipped. |
brush_interpolation_mode | Specifies the algorithm that is used when images are scaled or rotated with ether of following values.'nearest_neighbor' : Use the exact color of the nearest bitmap pixel to the current rendering pixel.'linear' : Interpolate a color from the four bitmap pixels that are the nearest to the rendering pixel. This is the default. |
Geometry
Geometry is an object that defines the shape of figures.
There are SimpleGeometry
objects representing system-defined basic shapes and Path
objects, which allow defining complex shapes freely by combining segments such as lines and arcs.
Simple Geometry
The SimpleGeometry
object is created using one of the following functions.
Name | Description |
---|---|
graphics.rectangle() | Create a SimpleGeometry object as a rectangle |
graphics.rounded_rectangle() | Create a SimpleGeometry object as a rounded rectangle |
graphics.ellipse() | Create a SimpleGeometry object as a ellipse |
Path
The Path
object can describe complex shapes by combining segments such as lines, arcs, and Bezier curves.
The Path object is created with graphics.path()
.
local path = graphics.path()
The Path
object can be composed of multiple figures, and you can add one figure to the Path
object using Path:add_figure()
.
A figure refers to a shape that can be represented by a single stroke.
The Path:add_figure()
specifies the following parameters.
The definition of a figure extensively employs two-dimensional vectors.
A two-dimensional vector is represented by an array table with two elements, such as {10, 20}
.
This structure will be denoted as VEC2D in the following context.
Key | Type | Description |
---|---|---|
fill_mode | string | Specifies the fill mode with one of the following values.'none' : Indicating that it's a figures representing the outline without filling.'winding' : See this site that explains the meaning of this mode in an easy-to-understand manner.'alternate' : See this site that explains the meaning of this mode in an easy-to-understand manner. |
from | VEC2D | The starting point of the figure. |
segments | table | An array table defining segments. Each element of the array refers to the Segment Definition. |
Segment Definition
The definition of segments is done using one of the following tables.
-
Line Segment
Key Type Description to
VEC2D End point of the line. The starting point of the line is the endpoint of the previous segment. If it's the first segment, the from
of the figure becomes the starting point. -
Arc Segment
Key Type Description to
VEC2D End point of the arc. The starting point of the arc is the endpoint of the previous segment. If it's the first segment, the from
of the figure becomes the starting point.radius
number The radius of the arc. direction
string A value that specifies whether the arc sweep is clockwise or counterclockwise, such as 'clockwise'
or'counterclockwise'
.arc_type
string A value that specifies whether the given arc is larger than 180 degrees, such as 'large'
or'small'
-
Bezier Curve Segment
Key Type Description to
VEC2D End point of the curve. The starting point of the curve is the endpoint of the previous segment. If it's the first segment, the from
of the figure becomes the starting point.control1
VEC2D A VEC2D that represents the first control point for the curve. control2
VEC2D A VEC2D that represents the second control point for the curve.
graphics.path()
accepts arguments in the same format as Path:add_figure()
. This means that the first figure can be simultaneously registered when generating the Path
object."
Drawing Geometry
The drawing of geometry, much like a bitmap, allows specification of translation, rotation, and scaling. Similarly, the origin of the geometry itself can be modified using SimpleGeometry:set_origin()
or Path:set_origin()
.
Where it differs from a bitmap is that geometry has two types of operations, filling (RenderingContext:fill_geometry()
) and drawing the outline (RenderingContext:draw_geometry()
). Additionally, during rendering, geometry is affected by properties of the rendering context.
The rendering context properties that affect it are as follows:
The parameters that can be specified for RenderingContext:fill_geometry()
and RenderingContext:draw_geometry()
are as follows.
Key | Type | Description |
---|---|---|
geometry | Geometry | REQUIRED PARAMETER The geometry object for drawing. It specifies SimpleGeometry or Path . |
x | number | Draw the gemometry's origin to coincide with this parameter's position in the target space. The default is 0 . |
y | number | Draw the geometry's origin to coincide with this parameter's position in the target space. The default is 0 . |
angle | number | Rotate counterclockwise by the specified angle in degrees around the geometry's origin.The default is 0 . |
scale | number | The scaling factor. The default is 1 . |
The following is the code example for the canvas that draws the heading indicator needle with the geometry version under the Render on the View heading.
-- Define a path to depict a needle
local needle = graphics.path{
fill_mode = 'winding',
from = {0, 50},
segments = {
{to = {5, 0}},
{to = {-5, 0}, radius = 5, direction = 'clockwise', arc_type = 'small'},
{to = {0, 50}},
}
}
-- Define a canvas to render the needle
local needle_canvas = mapper.canvas{
local_width = 100, local_height = 100,
value = 0,
renderer = function (rctx, value)
rctx.brush = graphics.color('DarkCyan')
rctx:fill_geometry{geometry=needle, x=50, y=50, rotation=value}
rctx.brush = graphics.color('Black')
rctx.stroke_width = 1.5
rctx:draw_geometry{geometry=needle, x=50, y=50, rotation=value}
end
}
Font
The Font refers to the object targeted by the text-drawing methods of the rendering context.
There are SystemFont
for using fonts registered in Windows and BitmapFont
objects for using glyphs provided by the user as bitmaps.
System Font
The SystemFont
object is created using graphics.system_font()
.
Parameters specified during creation include font family name, font weight, font style, and font size, which closely resemble font specifications in CSS or HTML.
What differs from CSS or HTML is that the font size is specified in logical units of the rendering context.
When the SystemFont
object is set on a rendering context associated with a bitmap, it is interpreted in pixel units. Conversely, when set on a rendering context associated with a view, it is interpreted according to the coordinate system of the Canvas view element.
Bitmap Font
To use bitmmap font, generate a BitmapFont object using graphics.bitmap_font()
and register bitmaps representing glyphs for each code point using BitmapFont:add_glyph()
.
The width and height of each glyph can vary per code point based on the bitmap size.
Additionally, adjusting the origin of the registered bitmaps allows tweaking the so-called baseline or accommodating glyphs that overlap with the previous character when rendering text.
In this Lua module used in this sample script, it parametrically generates a font for a 7-segment display. Please refer to it as an example of BitmapFont usage.
Drawing Text with Font
To render text, start by setting the font within the rendering context.
Afterwards, utilize RenderingContext:draw_string()
to output strings, or RenderingContext:draw_number()
to define formatting parameters, such as precision for decimal values, when rendering numeric data.
-- Create a SystemFont object representing the 'Segoe UI' font
local generic_font = graphics.system_font{
family_name = 'Segoe UI',
weight = 200,
style = 'italic',
height = 25,
}
-- Create a font for figures from the pre-rendered bitmap
local font_width = 24
local bitmap = graphics.bitmap('assets/fixed-width-font')
local codes='0123456789.-+*/_'
local figures_font = graphics.bitmap_font()
for ix = 1, string.len(codes) do
local glyph = bitmap:create_partial_bitmap((ix - 1) * font_width, 0, font_width, bitmap.height)
font:add_glyph(string.sub(codes, ix, ix), glyph)
end
-- Define the Canvas renderer that draws the static text
local renderer = function (rctx, value)
-- Draw a string with 'Segoe UI'
rctx.font = generic_font
rctx.brush = graphics.color('Yellow')
rctx:draw_string('Hello!', 0, 0)
-- Draw a numeric value with a bitmap font, this formats as the string '003.14'
rctx.font = figures_font
rctx:draw_number{
value = 3.1415926,
x = 0, y = 50,
precision = 5,
fraction_precision = 2,
leading_zero = true
}
end