This module is the portable graphics layer, so a caller stores pixels, inspects them, and draws shapes and text through the same types on every supported platform. It is two reader tasks that share geometry, not two separate libraries: images own or wrap pixel buffers, and painters render onto paint surfaces. A Bitmap is both a paint surface and an image source, so drawing results can be read back as pixels, and an Image can be drawn onto a surface.
Geometry is shared. Point, Size, and Rect are floating-point values used by drawing; PointI, SizeI, and RectI are integer pixel values used by images and by rounding. PointF, SizeF, and RectF are typedefs of the floating-point types. Drawing commands take logical coordinates. A Scaling on the paint surface converts those logical units to physical device pixels. Image width, height, and pixel positions are already physical pixels.
Ownership does not move when one type uses another. An image either owns its buffer or wraps a caller buffer that must outlive the image. A view refers to an image region and does not copy pixels. A Painter does not own the PaintSurface or PaintContext it paints on. The code that creates a surface, an image, or a wrapped buffer keeps it alive while painting or viewing is in progress.
Forms uses this module for widget painting. A Forms PaintSurface is another paint target, and a Forms Painter issues the same drawing commands. The Forms updating chapter documents when a widget paints; pens, brushes, paths, and text are documented here.
The rest of this chapter is the image model, then the drawing model.
This chapter covers:
An image is a rectangular pixel buffer. BasicImage either owns that buffer or wraps a caller-supplied one. Constructors that take width and height allocate. Constructors that take a data pointer wrap; the buffer must cover the given size and padding and must remain valid for the lifetime of the image. reset() switches between those two modes. clear() drops the buffer. Copying an owning image copies the pixels. A wrapped image does not take ownership on copy of the pointer; the original buffer still belongs to the caller.
Format is either a compile-time type or a runtime value. A typed image binds the format in the template argument: Argb32Image, Rgb32Image, Rgb16Image, and Yuv12Image are BasicImage specializations, and their pixels are the matching concrete types such as Argb32Pixel. Use a typed image when the calling code already knows the format. Image selects the format at runtime through ImageFormat. Decoders, file loaders, and other input that discovers a format while running use Image. Image exposes Pixel<Color>. ImageF is not a float storage format: it is a ColorF lens on the same runtime ImageFormat, and it exposes Pixel<ColorF>. The generic pixels forward storage operations to the selected format.
Color is the 8-bit working color, stored as a packed ARGB value with straight alpha. ColorF is a four-float working color in RGBA order, with channels in the unit interval and values above 1 allowed for HDR. Neither type is a pixel buffer. Pixels convert to and from the working color of their traits.
Views do not copy pixels. The free functions view(), pixelView(), and lineView() build a non-owning region, a pixel-iterator range, or a row span over an image or another view. Each view keeps the source format, typed or runtime, and the source buffer must outlive the view. ViewBase stores width, height, and stride for every image and view.
A pixel object is a cursor, not a color value. It refers to one position in an image or view. Copy construction duplicates the cursor. Copy assignment is deleted so a pixel = other cannot silently rebind the position; call reset() to bind another location. Assigning a Color or calling assign() writes through to storage. advance() and skipPadding() walk the buffer in scanline order.
JPEG and PNG codecs read and write Image values on iostreams. JpegReader and PngReader attach a stream and an image, then get() fills that image. PngWriter attaches an output stream and write() emits the image. The reader owns neither the stream nor the image.
Add a format by deriving it from ImageFormat, implementing its pixel storage operations, and providing the matching concrete pixel types and an ImageTraits specialization. Typed images then use the format as a BasicImage argument, and the runtime image API uses it through ImageFormat.
The example decodes a JPEG into a runtime Image and opens a crop that does not copy pixels.
BasicImage is the image type both families use. A typed image such as Argb32Image is this template with a concrete format. Image is this template with ImageFormat, so the format is chosen at run time. Width, height, and stride come from ViewBase.
Constructors that take width and height allocate a buffer the image owns. Constructors that take a data pointer wrap that buffer without copying it; the caller keeps the memory valid for the lifetime of the image. The optional padding is extra bytes after each row. reset() repeats either construction. clear() releases an owned buffer or drops a wrap. Copying an owning image copies the pixels. Copying a wrapped image copies the pointer, not the pixels, and does not take ownership.
data() is the first byte of the first row. format() is the format used to interpret those bytes. Use the free functions view(), pixelView(), and lineView() to look at a region without copying.
The example allocates an ARGB-32 image and wraps the same dimensions over an external buffer. The second image does not own bits.
Argb32Image is BasicImage with the Argb32 format bound at compile time. Each pixel is 32 bits with 8-bit alpha, red, green, and blue, and the matching cursor is Argb32Pixel. Use this type when the calling code already knows the layout. Use Image when the format comes from a decoder or other runtime input.
Constructors that take width and height allocate. Constructors that take a data pointer wrap the caller buffer; that buffer must remain valid for the lifetime of the image.
ImageFormat describes how bytes in a buffer become colors when the layout is not fixed at compile time. Image uses it for pixel operations. Callers do not construct a useful format directly. Use argb32(), rgb32(), rgb16(), or get(), or take the format from an existing image.
Typed images use a concrete format type such as Argb32 for their pixel operations. Those concrete types still derive from ImageFormat so a typed image can be used where a runtime format is required.
Equality compares the dynamic type, so two format objects of the same layout compare equal. Adding a format means deriving from this class, implementing the protected storage operations, and providing matching pixel types and an ImageTraits specialization.
BasicView refers to a whole image or to a sub-rectangle. It does not copy pixels and it does not own the buffer. The source image or buffer must outlive the view. Width, height, and stride come from ViewBase. The format is the source format, typed or runtime.
Construct from a source image, from a source plus a rectangle, or from a raw pointer with explicit size and padding. The free function view() is the usual way to open a region. Argb32View and ImageView are this template with a concrete or runtime format.
Writing through a pixel bound to the view changes the source image. Copying a view copies the pointer, not the pixels.
Pixel refers to one position in an Image or a runtime view. ColorT is the working color, usually Color or ColorF, not the storage layout. Storage operations go through the view's ImageFormat.
Copy construction duplicates the cursor. Copy assignment is deleted; call reset() to bind another position. Assigning a color or calling assign() or fill() writes through to the buffer. advance() and skipPadding() walk scanlines.
Typed images use a concrete pixel such as Argb32Pixel instead of this template. The cursor rules are the same.
Color is the packed ARGB value painters, pens, brushes, and 8-bit pixels use. It is not a pixel in an image and it does not refer to a buffer. Alpha is straight, not premultiplied. Channel accessors return 8-bit components. The three-argument constructor sets alpha to 255.
Convert to and from ColorF when float channels are required. Conversion clamps ColorF channels to the 8-bit range.
The example constructs an opaque red and a half-transparent blue.
JpegReader decodes JPEG data from an iostream into an Image. Attach a stream and an image, then call get() to read the whole image, or advance() to consume bytes as they become available. The reader does not own the stream or the image. detach() releases both. reset() starts a new decode on the same pair.
The image format is chosen by the decoder. Use this type when the input is JPEG; use PngReader for PNG.
PngReader decodes PNG data from an iostream into an Image. Attach a stream and an image, then call get() to read the whole image, or advance() to consume bytes as they become available. The reader does not own the stream or the image.
When advance() is called with import size 0, only bytes already in the stream buffer are consumed. A positive import size may block on the underlying stream.
PngWriter encodes an Image as PNG on an iostream. Attach an output stream, then call write() to emit the whole image, or beginWrite() and advance() to encode in steps. The writer does not own the stream or the image.
Drawing always targets a PaintSurface. The surface reports format, physical size, and Scaling, and it supplies the backend resources used to rasterize commands. Bitmap is the portable in-memory surface. After painting, Bitmap::image() returns the pixels so they can be copied, encoded, or drawn again. Other modules provide further surfaces: a Forms paint surface is the same abstraction on a window or control. Layouting and Painting documents when Forms paints; this chapter documents the drawing commands.
A PaintContext is an active session on a surface. It can install a default clip that every painter on that session intersects. Constructing a context attaches it to the surface; destroying it detaches it. The surface must outlive the context.
Painter is the type a caller constructs. It begins painting on a surface or on an existing context, through a constructor or begin(), and finish() ends the session. PainterBase is the command API: pens, brushes, fonts, transforms, clips, and the draw and fill operations. Painter does not own the surface or the context. One painter is bound to at most one target at a time.
Paint state is independent of geometry. Pen strokes outlines, Brush fills closed shapes, Font selects a typeface for text, and CompositionMode chooses source-copy or source-over blending. Paint bundles those four so the same state can be reused. setTransform() maps user coordinates before they reach the surface. setClip() restricts drawing to a rectangle. Coordinates passed to drawing commands are logical; the surface Scaling converts them to physical pixels.
Path stores moves, lines, and curves independently of a painter. A painter can stroke or fill its current path, or stroke or fill a path passed to drawPath() and fillPath(). Containment tests on the path use a FillRule and do not require a surface.
Text uses one font request for measurement and drawing. Font names a family, size, and style; it is not a loaded face. fontMetrics() returns ascent, descent, and line height for the current font. textMetrics() measures one string. drawText() draws that string at a baseline origin. Font::addFont() and Font::addFonts() register font files for later requests. Bitmap lists the families and faces the backend can resolve.
drawImage() and drawBitmap() composite image content onto the current target. The image and bitmap APIs remain the storage model; these operations only sample them.
Canvas is the backend that executes commands. Its constructor is protected. Ordinary callers do not create a canvas; the surface creates one while a painter is active.
The example paints into a bitmap, then reads the result as an image. The painter does not own the bitmap.
Painter is the type a caller constructs to draw. It begins a session on a PaintSurface or on an existing PaintContext, through a constructor or begin(). Drawing commands live on PainterBase. This type does not own the surface or the context. One painter is bound to at most one target at a time. finish() ends the session.
The example begins painting on a bitmap and fills it.
PainterBase is the command API Painter inherits. It holds the current Pen, Brush, Font, CompositionMode, user Transform, and clip, and it forwards draw and fill operations to the Canvas of the bound surface or context.
Outline commands use the pen. Fill commands use the brush. drawPath() and fillPath() use the current path or a path passed as an argument. drawText() uses the current font. drawImage() and drawBitmap() composite existing pixels. Coordinates are logical; the target Scaling converts them to physical pixels.
This type is not constructed by application code. Construct a Painter. beginPaint() is protected so only a concrete painter starts a session.
PaintSurface is the abstract render target a Painter paints on. It reports the pixel format(), the physical size(), and the scaling() from logical units to device pixels. A backend creates a Canvas while painting is active. Ordinary callers do not call getCanvas().
Bitmap is the in-memory surface in this module. Forms provides surfaces for windows and controls. The surface does not own the painter or the context attached to it. It must outlive both.
This type is not constructed by application code. Construct a Bitmap, or receive a surface from the host that owns the display.
PaintContext attaches to a PaintSurface for the duration of a paint. It can install a default clip that every painter on this session intersects. Format, size, and scaling are those of the surface. Destroying the context detaches it. The surface must outlive the context.
Forms passes a context into widget paint handlers. A caller can also construct a Painter on a context that already exists. Constructing a Painter directly on a surface creates the session without an explicit context.
Bitmap is the in-memory PaintSurface. Construct it with a physical size, paint with a Painter, then read the pixels with image(). reset() replaces the buffer with a new size or with a copy of an existing Image. setScaleFactor() sets the logical-to-physical scaling used while drawing.
The bitmap owns its image. A painter on the bitmap does not own the bitmap. After finish(), the image remains valid until the bitmap is reset or destroyed.
defaultFont(), fontFamilies(), and fontFaces() query the backend font list. Font::addFont() registers files those queries can resolve.
Paint stores the four pieces of drawing state a painter uses: CompositionMode, Pen, Brush, and Font. Set them on a Paint value and reuse that value, or set the same pieces on a Painter directly. This type does not draw and it does not own a surface.
Pen describes how a painter strokes lines, polylines, arcs, rectangle and ellipse outlines, paths, and text outlines. The default pen is null and does not stroke. A pen constructed from a Color is solid, one pixel wide, with round caps and joins.
Style selects solid, dotted, dashed, or a custom dash pattern. CapStyle is the shape of open ends. JoinStyle is the shape of corners. Width is in logical units.
Brush describes how a painter fills closed shapes, paths, and text. A solid brush is a Color. A gradient interpolates ColorStop values along a line or between two circles. A texture repeats an Image. The default brush is null and does not fill.
PositionMode chooses whether gradient and texture coordinates are absolute or relative to the shape being filled. Relative coordinates map the unit square onto that shape.
Path stores moves, lines, quadratic and cubic curves, and close commands. It does not belong to a painter. Build a path, then stroke or fill it with PainterBase::drawPath() and fillPath(), or assign it as the painter's current path.
moveTo() starts a subpath. lineTo(), quadTo(), cubicTo(), and the arc helpers append to the current subpath. contains() and intersects() test geometry against the filled area using a FillRule, without a surface.
The example builds a triangle and fills it on a bitmap.
Transform maps logical drawing coordinates. The default is identity. Translate, scale, rotate, and shear compose on the left. A painter's setTransform() applies this mapping before the surface scaling. drawText() can take an extra transform for a single run.
Invert a transform only when it is invertible. The type is a value; copying duplicates the matrix, not a painter.
Font is a request, not a loaded face. A painter uses the same request to measure and to draw, so layout and rendering stay aligned. Family, size, Weight, Slant, and Stretch select a face. Category names a generic fallback when no family is set. withSize() and the other with...() helpers return a modified copy.
addFont() and addFonts() register font files for later requests. fontFiles() lists those paths. Bitmap lists the families and faces the backend can resolve.
FontMetrics describes the selected face at the current size. ascent() is above the baseline, descent() below it. height() is their sum. lineHeight() adds leading(). Underline and strikeout positions are relative to the baseline. PainterBase::fontMetrics() returns these values for the current font.
TextMetrics is the result of PainterBase::textMetrics() for one string in the current font. advance() is the distance to move the origin for the next run. bearingX() and bearingY() are the offset from that origin to the bounding box. boundingWidth() and boundingHeight() are the box size.