7.7. Compositing images¶
The previous page’s drawing primitives paint
geometric marks onto an image – a line, a
rectangle, a piece of text. That covers most
annotations an algorithm needs to make visible,
but not all of them. Sometimes the annotation is
itself an image: a captured reference frame to
display side by side with the current one, a
thumbnail of a previous capture shown in a corner
of the preview, a previously stored template
visualised on top of a live frame for calibration. The
mechanism for drawing one image onto another is
a single method – draw_image()
– with enough parameters to handle the position,
the scaling, the colour palette, and the
transparency that real composition needs.
7.7.1. The basic call¶
In its simplest form, draw_image takes
another Image and a position to draw
it at:
reference = image.Image("/sdcard/reference.bmp")
img.draw_image(reference, x=10, y=10)
The destination is img; the source is
reference; the source’s top-left pixel lands at
(10, 10) of img, and the rest of the
source’s pixels follow to the right and downward
from there. Pixels of the destination that the
source covers are overwritten with the source’s
corresponding pixels; pixels outside the
source’s footprint are left alone.
If the source extends past the edge of the
destination, the parts that fall off are
silently clipped – the same forgiving behaviour
set_pixel shows for out-of-range positions.
Application code does not have to clamp the
position to the image dimensions in advance; it
can pass the position it wants and let the
method handle the clipping.
7.7.2. Loading a file inline¶
draw_image accepts a file path in place of
the Image argument and loads the file before
composing it:
img.draw_image("/sdcard/reference.bmp", x=10, y=10)
That looks like a convenience – one line
instead of two – and it is, but the difference
is more than syntax. Constructing an
Image from a file allocates a buffer to
hold the decoded pixels, and that buffer
survives until garbage collection releases it.
Passing the path directly to draw_image lets
the module decode the file into a scratch
buffer, composite from it, and release the
buffer when the call returns, without the
application code having to hold a reference to a
separate Image between frames.
7.7.3. Scaling¶
When the source and the destination are different sizes – a low-resolution capture being composed onto a higher-resolution canvas, or a thumbnail that needs to be sized to a particular fraction of the frame – two scale parameters take care of resizing the source as it is drawn:
img.draw_image(reference, x=10, y=10, x_scale=2.0, y_scale=2.0)
x_scale and y_scale are independent
floats; passing both at the same value scales
uniformly, and passing different values
stretches or shrinks the source along one axis.
The scaling happens at draw time; the source
reference is not modified.
A bitmask of hint flags decides how the
scaling actually interpolates between pixels.
image.BILINEAR produces smoother results at
the cost of more computation; image.BICUBIC
produces still smoother results and costs more
again; the default uses nearest-neighbour, which
is the cheapest and the right choice when the
source is already at the destination’s pixel
resolution. Aspect-handling flags –
SCALE_ASPECT_KEEP, SCALE_ASPECT_EXPAND,
SCALE_ASPECT_IGNORE – decide what to do
when the source’s aspect ratio does not match
the rectangle it is being drawn into.
7.7.4. Alpha blending¶
By default, draw_image replaces
destination pixels with source pixels. When the
goal is a translucent overlay – so the
destination shows through the source – the
alpha parameter controls how the two are
mixed. alpha=0 shows only the destination
(no source); alpha=255 is the default and
shows only the source (full replacement);
intermediate values mix the two proportionally:
img.draw_image(overlay, x=0, y=0, alpha=128)
A separate alpha_palette argument is the
module’s only per-pixel alpha mechanism. It takes
a GRAYSCALE image whose values are
used as alpha at the matching position in the
source – a heatmap whose alpha varies with its
intensity, for example. The alpha has to be
supplied as that separate grayscale argument; a
source image that carries its own alpha channel
(a PNG with transparency, say) does not bring it
through automatically.
7.7.5. Source ROI and palette¶
Two further parameters round out the composition mechanism:
roi=(x, y, w, h)restricts the source to a sub-rectangle of itself, so only that rectangle gets composed onto the destination. Useful for cropping inside the same call, without preparing a cropped intermediate.color_palettesubstitutes each source pixel’s value through a lookup table before drawing – the same mechanismto_rainbow()andto_ironbow()use, exposed here so an overlay can be palettised on its way onto the destination without a separate conversion pass.
Both compose with everything else on the call:
the scaling, the alpha, the destination-side
mask argument, and the destination-side
roi parameter that scopes the write to a
rectangle of the destination.