Now then, where were we?
Last time, we worked through the many and varied abilities the ZX Spectrum system ROM offers us when we treat the screen as being a text-based terminal. However, as we noted back then, the Spectrum does not in fact have a text mode, and instead provides only a bitmap screen that its system ROM sometimes wraps in a character-oriented API. That API is quite powerful, but we also have direct access to the bitmap memory. Today, we will discuss that memory and how to work with it.
The other relevant fact about the ZX Spectrum is that this bitmap mode is all we have. The platform got many finely-animated games over the years, so we will also cover some techniques for sprite-like behavior without actual sprites.
The Spectrum Bitmap
The Spectrum line keeps the bitmap information in the memory range $4000–$5AFF, just after the system ROM and just before the system variables. The first 6K of this, from $4000–$57FF, holds the actual pixel information in the bitmap itself, while the final 768 bytes at $5800–$5AFF holds the color information.
The Color Map
We’ll look at the color information first, because this is the place where the video hardware really is closest to the text mode that we had been pretending it was. These 768 bytes are arranged in a 32×24 grid running left-to-right, top-to-bottom, just like a text mode. Each byte holds the color information for an 8×8 block of pixels:
- The three least significant bits specify the foreground color, from 0-7. These are the same colors as we used with the
INK,PAPER, andBORDERcommands. - The next three bits specify the background color, also from 0-7.
- Bit 6 means this cell uses the high-intensity colors associated with
BRIGHT 1. - Bit 7 will cause the display hardware to flip this 8×8 pixel block between normal and inverse video 4 times per second, representing
FLASH 1.
The more exotic operations like OVER or “colors” 8 and 9 don’t exist at the hardware level and instead were used to inform the character-handling routines in ROM.
This color map is actually quite similar to the “video matrix” in the Commodore 64’s own bitmap mode. The C64 has more colors and a larger screen, though, so the full map takes 1,000 bytes to fill a grid of 40×25, and with 16 distinct colors in the palette it sacrifices BRIGHT and FLASH so that it may purely represent color data.
The Bit Map
Actual graphics data is stored in $4000–$57FF and is laid out in a rather odd way. The unsurprising part is that this is a 1 bit-per-pixel bitmap, which divides the screen into 6,144 8×1 slivers of pixels. It i also the case that the first byte at $4000 is the upper-left sliver, and that within each pixel row, incrementing or decrementing the address moves us one sliver left or right. Where things get bizarre is when we start moving vertically.
Much like the C64’s VIC-II and the MSX’s TMS9918A, the Spectrum’s bitmap display is built out of 8×8-pixel cells, and the slivers are arranged to group bitmap information together. Like the VIC but unlike the TMS, those cells correspond exactly to the screen’s color data. (The TMS’s bitmap mode lets us assign color data on a per-sliver basis, not a per-character-cell one, and its text mode attaches colors to the character code rather than the screen location. When we look at color, the TMS isn’t really directly comparable to the VIC-II or the main Spectrum line.)
Where the Spectrum differs from its peers is how the addresses are grouped together. On the C64 or MSX, we move up or down within a cell by incrementing or decrementing the address, and then move right and left within a pixel row by adding or subtracting 8. If we look at the video memory we really end up seeing a series of unique tile definitions that are then systematically laid out on the screen. The Spectrum, however, moves right or left by adding or subtracting 1, and moves up or down within a color-map cell by adding or subtracting 256. The bitmap display is divided into 8-row thirds starting at $4000, $4800, and $5000, and within a third, moving up or down eight rows involves adding or subtracting 32.
Putting this all together means that the first ten pixel rows lie at these addresses:
$4000–$401F$4100–$411F$4200–$421F, and so on up to$4700–$471F$4020–$403F$4120–$413F
This leaves open the question of why anybody would ever want to do this. There are two answers for this, and they both boil down to “corners, neatly cut:”
- The hardware decoding is dramatically simplified because the address of any pixel’s sliver on the screen has the same low byte as the address of its corresponding color map entry.
- The software blitting required to print characters out to the screen is also simpler than pure-bitmap systems like the Atari 800; copying a character from
HLinto color-map-aligned memory inDEeach iteration sees us incrementingHLandDto do the copy.
Points and Colors
That does still leave the question open of where to find the byte to start copying to, to say nothing of being able to turn a set of screen coordinates into both a pixel and a color address.
Characters aren’t too tough. The color address in particular is pretty easy; given a character-level X and Y, compute $5800+(Y*32)+X, which we can do with only bit shifts. For the pixel address, it’s more annoying to write it as a formula and easier to describe the effect. We still compute (Y*32)+X, but we take only the high byte and multiply just that high byte by 8 before we add $40 to it. The end result puts us at the top byte of the character.
For pixel-level coordinates, we’re starting with 16 bits of coordinates (8 each for X and Y) and producing a bunch of results. I think it makes the most sense to compute them in this order:
- Start by computing the address of the bitmap sliver from the pixel coordinates.
- Then create an apply the bit-level operation that isolates the specific pixel we want.
- Finally, convert the bitmap sliver’s address into a color table address. This is the operation that the Spectrum’s hardware makes particularly easy.
Even while accelerating that final step, the whole conversion isn’t too bad. The charts provided at L Break Into Program’s website are a nice demonstration of how we’re mostly just scrambling the bits we’re given rather than doing any sophisticated math. In particular, given 8-bit X and Y coordinates with bits X7–X0 and Y7–Y0, the sliver address looks like this at the bit level:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 0|Y7|Y6|Y2|Y1|Y0|Y5|Y4|Y3|X7|X6|X5|X4|X3|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
The X2–X0 bits identify the bit within the sliver and do not contribute to its address.
Let’s work through a PLOT function that takes the X coordinate in L, the Y coordinate in H, and the target color in C. Our first step will be to compute the sliver address in DE, and it’s easiest to do this a byte at a time. The high byte can be described as $40|((Y>>3)&$18)|. The last part of that is really easy:(Y&7)
PLOT LD A,H
AND 7
LD D,A
The remainder is not conceptually difficult, either, but we can do a little trick to make the code shorter than what is described. It’s shorter and faster to rotate the accumulator rather than to shift it, and we can skip an explicit OR operation on the $40 term if we force that rotation to bring in a 1 bit at the appropriate point and then don’t mask it out. Setting the carry flag is one byte and three cycles shorter, so it’s worth the hit on clarity:
LD A,H
RRA
SCF
RRA
RRA
AND $58
OR D
LD D,A
The low byte ends up being very similar but with fewer tricks; the closest thing to a trick we have here is using ADD A as a faster alternative to SL A:
LD A,H
ADD A
ADD A
AND $E0
LD E,A
LD A,L
RRA
RRA
RRA
AND $1F
OR E
LD E,A
That works out to 27 bytes of instructions, which isn’t devastatingly expensive but also isn’t cheap. I am more interested in correctness for the moment so I’m more than happy with it. I will also be choosing correctness and ease of implementation over speed for the rest of our routine, here; to get the necessary bitmask I’ll just do a looped set of rotations based on the low 3 bits of the X coordinate:
LD A,7
AND 7
LD B,A
INC B
XOR A
SCF
1 RRA
DJNZ 1B
EX DE,HL
OR (HL)
LD (HL),A
If we wished to toggle the bit instead of set it, we would replace the OR (HL) instruction with an XOR. To clear the bit is more involved; we need to replace XOR A; SCF with LD A,$FF; OR A and OR (HL) with AND (HL).
That leaves the color update. To turn the sliver address into the color address, we really just have to shift away the lowest bits of the Y coordinate and fix the prefix bits. With the sliver address already in HL and the color address in C the conversion and write is very fast:
LD A,H
RRA
RRA
RRA
AND 3
OR $58
LD H,A
LD (HL),C
Our last task will be to put the original pixel coordinates back in HL and then return.
EX DE,HL
RET
Drawing More Than Points
Given a point drawing routine we can use Bresenham’s algorithm to get lines. There isn’t anything special about the Z80 that makes it harder or easier compared to, say, my CoCo implementation but it did end up being a fun exercise. With a LINE implementation we can get produce a display similar but not identical to the “loom” screen from the BASIC tour:

Freed from BASIC’s constraints, we may now use the whole screen as we did on the MSX and CoCo, and we may also put (0,0) in the upper-left corner where it belongs.
Beyond points and lines, BASIC also offered commands that drew circles and arcs. These turn out to be a bit out of scope once we’re looking at the machine code level; the system ROM relies very heavily on its floating point libraries to accomplish arc drawing.
BASIC also doesn’t offer anything like sprites, and neither do the ROM or the hardware itself. The user-defined graphics, however, do give us a starting point—we’ll ultimately need to be blitting the images into the proper place on the screen in software. There are limits to this, but the Spectrum got a whole lot of very animated games over the years and we should be able to do something on our own along the way.

By the time we are done with the graphics part of this series, we should have a functioning Spectrum port of the shooting gallery Rosetta stone.