The blitting is a data operation used in computer graphics in which several bitmaps are combined into one using a boolean (or mathematical) function.
The operation involves at least two
bitmaps: a "source" (or "foreground") and a "destination"
(or "background"), and other fields, called "masks" or
something like that. The result may be written to a final bitmap,
though often it replaces the "destination" field.
AND
, OR
,
XOR
, and other more complex operations.
The Commodore Amiga and its coprocessor (called "blitter" because of
its primary operation) did something analogous to what we are
explaining. With only one difference: it was not possible to express
any combination but only one of the 256 possible combinations based
on the combinatorial calculus called "minterm" in jargon
(actually there were many less).
Also, the four channels (A, B, C and D) were "fixed" and had given
semantics, which could not be changed.In practice: it was difficult
to do something complicated (example: thresholds), while it was
quick to do something simple (example: masking). Another
advantage of Commodore Amiga was that it reasoned in "bitplanes",
which made the blit "sequentiable" - something that "packed"
(or "chunked") modes would not do.
Blitting operations, after the Commodore Amiga, became very
common in 90's PC video cards.
For this very same reason, after the theory, we will have
to deepen the meaning of the operations in the presence of
non-trivial organizations of the bits per pixel, like
VIC2, ZX ULA and so on. The granularity of the pixel and
any defects in the result due to the limitations of the various
video chips must be taken into account.
Blitting is similar to hardware-sprite drawing, in that both
systems reproduce a pattern, typically a square area, at different
locations on the screen. Hardware sprites have the advantage of
being stored in separate memory, and therefore don't disturb the
main display memory. This allows them to be moved about the
display, covering the "background", with no effect on it.
BLIT
for MOS 6502 is converted
in just 6 assembly instructions.BLIT IMAGE
(s), GET IMAGE
and
PUT IMAGE
commands which allow you to grab part of an
image into memory and paste it anywhere on the screen. These
instructions are mainly used for holding temporary data, since
the images cannot be saved along with your BASIC programs.
However, the PUT IMAGE
and BLIT IMAGE(S)
commands can also use images loaded from the computer, with the
IMAGE LOAD
, IMAGES LOAD
and
SEQUENCE LOAD
commands.A classic use for blitting is to render transparent sprites onto a background. In this example a background image, a sprite, and a 1-bit mask are used. As the mask is 1-bit, there is no possibility for partial transparency via alpha blending.
A loop that examines each bit in the mask and copies the pixel
from the sprite only if the mask is set will be much slower
than hardware that can apply exactly the same operation to
every pixel. Instead a masked blit can be implemented with two
regular BLIT
operations using the AND
and OR
blit operations.
BITMAP ENABLE(16)
CLS BLACK
First, let's set the graphics mode to at least 16 colors,
clear the screen, and load the various graphics resources.
mask := LOAD IMAGE("examples/blit_mask.png") EXACT TRANSPARENCY BLACK OPACITY WHITE
background := LOAD IMAGE("examples/blit_background.png")
sprite := LOAD IMAGE("examples/blit_sprite.png")
When preparing the sprite, the colors are very important. The mask
pixels are 0
(black) wherever the corresponding sprite
pixel is to be displayed, and other color wherever the background
needs to be preserved. On the other side, the sprite must be 0
(black) anywhere where it is supposed to be transparent, but note that
black can be used in the non-transparent regions.
Since ugBASIC reasons in terms of the native
representation, we must use the built-in THRESHOLD
operator. Alternatively, we can use the TRANSPARENCY
and OPACITY
keywords on LOAD IMAGE
instruction. We will use the latter.
POSITIVE CONST x = ( SCREEN WIDTH - IMAGE WIDTH(background) ) \ #2
POSITIVE CONST y = ( SCREEN HEIGHT - IMAGE HEIGHT(background) ) \ #2
PUT IMAGE background AT x, y
In the first blit, the mask is blitted onto the background using the
blit operator AND
. Because any value AND
ed
with 0 equals 0, and any value AND
ed with 1 is unchanged,
black areas are created where the actual sprites will appear,
while leaving the rest of the background alone.
BLIT bop1 AS ( ( SOURCE ) AND ( DESTINATION ) )
BLIT IMAGE mask AT x+10,y+10 WITH bop1
In the second blit, the sprite is blitted onto the newly altered
background using the blit operator of OR
. Because any
value OR
ed with 0 is unchanged, the background is
unaffected and the black areas are filled with the actual sprite image.
Note that we are using the "positional" syntax for sources.
BLIT bop2 AS ( ( ( INVERSE ( SOURCE 2 ) ) AND ( SOURCE 1 ) ) OR ( DESTINATION ) )
BLIT IMAGES sprite, mask AT x+10,y+10 WITH bop2
It is also possible to achieve the same effect using a sprite with
a white background and a white-on-black mask. In this case, the mask
would be ORed first, and the sprite AND
ed next. Finally,
the very same effect could be obtained by using a transparent PNG
and the PUT IMAGE
with the WITH TRANSPARENCY
.
We start to break down the process into a series of elementary
steps. In particular, the process has only two operations:
unary operations and binary operations.
A
)
is the input value and the second (B
) is the type of
operation to perform on that input. Output (C
) is output,
as a result value.B
value describes the type of operation to be
performed: it ranges from copying (COPY
, C = A
),
to complement copying (INVERSE
, C = ! A
),
to assigning a constant value (value
, C = value
)
or, of course, to do nothing (IGNORE
, C = undefined
).
This last case is useful for deactivating the specific step.A
and B
are the input parameters, C
the type of operation to be
performed while D
is the output parameter. It is otherwise
similar to the unary operation. We therefore have the possibility of
performing a bitwise "and" operation (AND
, C = 0
),
a bitwise "or" (OR
, C = 1
) or a bitwise exclusive
"or" (XOR
, C = 2
) . Also in this case we have the
equivalent of an IGNORE
command only that it must be specified
whether it is sufficient to copy the A
channel or the B
channel, as it arrives.As anticipated, there are two types of operations that will apply to a blit. In this post we explore operations that deal with a single input image, the so-called unary operations.
Since we try to represent the operations with the lowest number of bits possible, the first positions will be occupied by the possible values according to the depth of the graphics (bpp = bit per pixel) in order to represent a constant value. Therefore, if the first 4 bits are occupied by possible values, we will have 5 overall bits available to represent the operation: this means having 16 additional values, which will be associated with different operations. A key word has been assigned to each operation. To better represent its meaning, we will do it with examples.
The simplest operation is to assign a specific color value
(VALUE x
, for example the value 5). In practice,
we are filling the surface with the index of a specific color.
There are as many values as the color depth of the working
graphics resolution. In this case the acceptable values range
from 0 to 15.
Immediately after we have the possibility to copy the input
color (COPY
). This possibility allows you to
take exactly the input image, and therefore no specific
input operations are done.
The first operation we can do on the input color is to
take its complementary index (INVERSE
). In this
case, the image that will be obtained will be the same as
input but in "negative". Therefore, if the input has a value
of 2 and we have 16 maximum colors, the output value will be 13.
Another interesting operation is to introduce a threshold
(THRESHOLD
). With this operation, all colors other
than zero (0
) will assume the maximum available index,
in this specific case 15. Note that this is an operation that is
performed dynamically, at each execution. If you want to do this
when loading the image, then once and for all, you can use the
TRANSPARENCY
and OPACITY
instructions.
Finally we have the IGNORE
operation. This is a
special operation, not only because it equals to VALUE 0
but also because it indicates to the system that this operation can
be removed, perhaps for optimization.
AND
(bitwise
"and"). In this type of operation, each color bit
is set to 1 only if the corresponding color bit is equal to 1
in both images. Otherwise, the value will be zero (0).OR
(bitwise "or") operation.
In this type of operation, each color bit is set to 1 only if
the corresponding color bit is equal to 1 in at least one of
the two images. Otherwise, the value will be zero (0).XOR
(exclusive "or")
operation. In this type of operation, each color bit is set
to 1 only if the corresponding color bit is different in both
images. Otherwise, the value will be zero (0).XOR
operator. There is a way to customize the
blitting operator that could help in this sense.COPY A
which copies
the color from the first input parameter, ignoring the second,
while operation COPY B
copies the color from the
second input parameter, ignoring the first.
After introducing the basic operations, let's start introducing
the ugBASIC syntax that can exploit them.
We assume that operations, both unary and binary, have
parameters. The parameters are obviously the "positional"
references to the images you want to use. In turn, these
parameters are the actual images, passed as parameters.
To this purpose, the convention of indicating each image with
the term SOURCE N
where N
is precisely
the image number will be introduced. The image present on the
screen (which can also be described as DESTINATION
)
is conventionally associated with the value N = 0
.
Let's start by introducing the syntax to define a blitting operation:
BLIT name AS (bop expression)
The name must be a global and unique identifier which will define
the blitting operation. The bop expression must instead describe
the operations to be performed between the various images
(SOURCE
, SOURCE 2
, DESTINATION
,
and so on).
To draw, the BLIT IMAGE
(s) command will be used.
The syntax of this command will be identical to that of
PUT IMAGE
(therefore with management of images
and sequences), with two main differences. The first is the ability
to indicate multiple images (separated from each other by a comma)
and the second is to be able to indicate which blitting operation you
want to use.
BLIT IMAGE image AT 0,0 WITH name
BLIT IMAGE image1, image2 AT 42,42 WITH name2
The simplest expression is an emulation of the classic PUT IMAGE command:
BLIT putimage AS ( SOURCE )
Assuming that the image to draw has color 0 as background, we can
use this expression to draw it in transparency:
BLIT puttrans AS ( SOURCE OR DESTINATION )
This technique doesn't work, at least not directly, when used
in non-trivial graphics systems. Infact, bitwise operations on color
displays do not usually produce results that resemble the physical
combination of lights or inks. Some combinations are still used to
draw interactive highlight rectangles or region borders. When this
is done to color images, the unusual resulting colors are easily seen.
One of them is the VIC-II video chip. In this chipset, pixel
representation is driven partly by the bitmask and partly by the
colormap, where each 8x8 pixel cell is driven by a different set
of colors. This makes blitting operations not simple.
Staying on the MOS 6502, I'll now move on to the GTIA
(ATARI's video chipset), where instead there are fixed registers
for each combination of bits.
We have arrived at the Motorola 6847 chipset, standard for the
TRS-80 Color Computer, Dragon 32 and Dragon 64 computers. It
is a graphics processor with some specific characteristics,
the most important of which is the fact that it encodes a
different color for each combination of two bits. In multicolor
mode, the combinations correspond to the following colors:
00 = green, 01 = yellow, 10 = blue and 11 = red. In this case
the blitting operations modify the color conspicuously.
For example, 01 (yellow) AND 11 (red) equals 01 (yellow).
Or, 01 (yellow) OR 10 (blue) equals 11 (red). The final effect,
when applied to images, is not very realistic: in particular,
the masking is unrecognizable. The fenomena going on is similar
to the use of OPTIONS
on commands GET
/PUT
with boleans.
The first implementation for TMS 9918, the "standard" video chipset
with MSX machines and the various consoles, is a bit disappointing:
in practice, you don't see the masking, neither with the OR
operation nor with the THRESHOLD
one. While I was looking
in the code for the reason for this behavior, and the doubt of having
introduced bugs doesn't go away immediately, I realized a conceptual
error that I introduced.
I state that this reasoning is valid for chipsets that have a color
map, therefore it is valid for TMS 9918, MOS VIC-I, MOS VIC-II,
MOS TED and not for THOMSON EF936X, MOTOROLA 6847 and Amstrad
CPC video chipset.
The premise is simple: some time ago a very efficient algorithm
was introduced for the representation of images with many colors, when
the target resolution allows precisely to define different colors
for each "tile" or cell. This algorithm tries to optimize the use of
colors, and it is not uncommon that the combination 0 (or 00 for
chipsets with 2 bits / color) is assigned a color other than black
or in any case from the background color. Maybe, since it's a solid
white square, it's rendered with a combination of all zeros and
the dominant color.
Too bad that, when blitting is done, the value 0 is equivalent to
transparency. Which means, in other words, that although something
has been drawn, in practice it is an "empty" image, from the point
of view of the bitmap.
The solution? Prefer the value 1 for values other than the black
value, and favor the value 0 for values similar to the black value.
Obviously, we are talking about parameters that can be indicated.
Finally, the implementation for EF936x, the chipset found in the
Thomson MO5, MO6 and Olivetti Prodest PC128 computers, is complete.
On this chipset there is the possibility of programming up to 16
registers with RGB components, and this flexibility goes very well
with the management of the color indices.
If you have found a problem, if you think there is a bug or, more
simply, you would like something to be improved, write a topic on the official forum, or open an issue on GitHub.
Thank you!