x86-16 machine code, IBM PC DOS, 43 bytes
00000000: be22 01b9 0100 ac8a d83c 10b8 db09 cd10 .".......<......
00000010: b403 cd10 7204 7809 b2ff 42b4 02cd 10eb ....r.x...B.....
00000020: e2c3 0c0e 1d0e 0a1b 0d0b 91 ...........
BE 0122 MOV SI, OFFSET CT ; load color bar table into [SI]
B9 0001 MOV CX, 1 ; display 1 char and clear CH (changed by INT 10H:3)
AC LODSB ; load next color byte
8A D8 MOV BL, AL ; move new color to BL
3C 10 CMP AL, 010H ; if this is third color in row: SF=0, CF=0
; else if last color: SF=1, CF=0
; else continue on same row: CF=1
B8 09DB MOV AX, 09DBH ; AH=9 (write char with attr), AL=0DBH (block char)
CD 10 INT 10H ; call PC BIOS, display color block
B4 03 MOV AH, 3 ; get cursor position function
CD 10 INT 10H ; call PC BIOS, get cursor
72 04 JC NEXT_COL ; if not last in row, move to next column
78 09 JS EXIT ; if last color, exit
B2 FF MOV DL, -1 ; otherwise move to first column and next row
42 INC DX ; move to next column (and/or row)
B4 02 MOV AH, 2 ; set cursor position function
CD 10 INT 10H ; call PC BIOS, set cursor position
EB E2 JMP LOOP_COLOR ; loop to next color byte
C3 RET ; return to DOS
CT DB 0CH, 0EH, 1DH ; color table
DB 0EH, 0AH, 1BH
DB 0DH, 0BH, 91H
This uses the IBM PC BIOS
INT 10H video services to write the color grid to the screen. Unfortunately the only way to write a specific color attribute requires also writing code to place the cursor in the next correct location, so there's a lot of extra code for that.
Here's the output running on an IBM PC CGA (in 40x25 text mode to make it bigger).
Here's a more verbose explanation, with more detail of what some of these machine code instructions do.
The colors are stored as bytes in the color table (
CTBL) with the 4-bit color value in the low nibble and a flag value in the high nibble. A
1 in the high nibble signals the end of the row and a
9 signals the end of the list.
This is checked using a single
CMP instruction which subtracts
0x10 from the color values and sets the result flags. These operands are treated as 8-bit signed values, so the sign flag (positive or negative result) and carry flag (a subtractive borrow was required, meaning the color byte is less than
0x10) are the interesting ones here. These flags are then checked further down for the conditional logic of whether to advance the row, the column or end the program.
0 means not the last in the row and not the last in the list:
0x0C - 0x10 = 0xFC -- Result is negative and a carry (borrow) was required
1 means it is the last color in the row and not the last in the list:
0x1D - 0x10 = 0x0D -- Result is positive and no borrow
9 means it is the last color of the list:
0x91 - 0x10 = 0x81 -- Result is negative and no borrow
Using the PC BIOS or DOS API for output, the function that allows setting a character color does not automatically advance the cursor, so that has to be done by the program. The cursor position must be fetched each time since the registers are overwritten later by the other functions. The "get" function puts these into the 8-bit registers
DL for the column, and
DH for the row. These same registers are what is used by the "set" function to move the cursor to a new location.
We can take advantage of the fact that, on x86 architecture
DL can be manipulated as a single 16-bit register (
DX). To advance to the next column, we increment
DX which effectively only increments
DL (column position). To advance to the next column we would need to instead increment the row number and set the column to
0. We can do this in only one additional instruction by setting the low nibble (
0xFF and then continuing the normal program flow incrementing
DX. Since we're now looking at
DL as a 16-bit value,
0 and the carry from
0xFF + 1 is added to
Advance to next column:
DH DL DX
1 1 0101 ; at row 1, column 1
1 2 0102 ; add one to DX
Advance to next row, first column:
DH DL DX
1 2 0102 ; at row 1, column 2
1 FF 01FF ; +set DL to 0xFF
2 0 0200 ; add one to DX