The fp's display text colour is not a colour value.
It is a palette index, one byte wide, and the other three bytes of the word are
ignored. All 256 entries are below, read out of the firmware.
mem set 0xC0BB1208 0x00000001The OSD sub-layer is 8 bits per pixel through a colour lookup table. The glyph
blitter (FUN_c052b620) writes one byte per pixel — the low byte
of the colour word for a stroke pixel, the low byte of the background word for the rest.
Whatever you put in the upper three bytes never reaches the screen.
0xC0B520BC, and it is 256 entries
long — four bytes each, A Y U V, with U and V signed offsets from
128. The layer's descriptor advertises only the first thirty, which is why
display palette 2 0 prints thirty rows. Everything on this page is read
from that table, so it is exact rather than sampled.
fpSup's AutoRun patches three words before it draws its banner
(fp_usb_shell/patches.py, the SCREEN table). Two of them move
the text out from under the battery indicator; the third picks the colour.
| stock firmware | fpSup | |
|---|---|---|
| 0xC0BB1208 | 0xFFFFFFFF — index 255, a dark green |
0xFFFFF8B2 — index 178, #FF6668 |
| 0xC03E4698 | MOV r8,#0 — y = 0 |
MOV r8,#16 |
| 0xC03E46A0 | MOV r5,r8 — x follows r8, so 0 |
MOV r5,#120 |
So the pink the loading bar is drawn in is a deliberate choice, but index 178 sits in the block below that nothing else documents. Index 1, 3, 4, 5 or 7 would be the conservative pick.
This is what display palette 2 0 prints. The usable solid colours are
1, 3, 4, 5 and 7; 8–15 are empty, and 17–29 are a green ramp the camera
uses to antialias its own green text. RGB is a full-range BT.601 conversion of the AYUV
beside it.
| idx | colour | RGB | A | Y | U | V | |
|---|---|---|---|---|---|---|---|
| 0 | transparent | — | 0 | 0 | 0 | 0 | |
| 1 | white | #FFFFFF | 255 | 255 | 0 | 0 | |
| 2 | black | #000000 | 255 | 0 | 0 | 0 | |
| 3 | red | #FF0103 | 255 | 77 | -42 | 127 | |
| 4 | green | #00FE00 | 255 | 149 | -84 | -106 | |
| 5 | blue | #0100FE | 255 | 29 | 127 | -20 | |
| 6 | black, 73% | #000000 | 187 | 0 | 0 | 0 | |
| 7 | dark grey | #444444 | 255 | 68 | 0 | 0 | |
| 8 | unset | — | 0 | 0 | 0 | 0 | |
| 9 | unset | — | 0 | 0 | 0 | 0 | |
| 10 | unset | — | 0 | 0 | 0 | 0 | |
| 11 | unset | — | 0 | 0 | 0 | 0 | |
| 12 | unset | — | 0 | 0 | 0 | 0 | |
| 13 | unset | — | 0 | 0 | 0 | 0 | |
| 14 | unset | — | 0 | 0 | 0 | 0 | |
| 15 | unset | — | 0 | 0 | 0 | 0 | |
| 16 | black, 73% | #000000 | 186 | 0 | 0 | 0 | |
| 17 | green ramp | #275225 | 207 | 64 | -15 | -18 | |
| 18 | green ramp | #306A30 | 214 | 82 | -19 | -24 | |
| 19 | green ramp | #3B813B | 221 | 100 | -23 | -29 | |
| 20 | green ramp | #469544 | 227 | 116 | -27 | -33 | |
| 21 | green ramp | #4A9F4A | 232 | 124 | -28 | -36 | |
| 22 | green ramp | #4FAC51 | 236 | 134 | -30 | -39 | |
| 23 | green ramp | #55B754 | 240 | 142 | -33 | -41 | |
| 24 | green ramp | #59BF59 | 243 | 149 | -34 | -43 | |
| 25 | green ramp | #5ECB5E | 247 | 158 | -36 | -46 | |
| 26 | green ramp | #62D262 | 251 | 164 | -37 | -47 | |
| 27 | green ramp | #66DD67 | 255 | 172 | -39 | -50 | |
| 28 | green ramp | #6EEF6E | 255 | 186 | -43 | -54 | |
| 29 | green ramp | #75FF74 | 255 | 198 | -46 | -58 |
Every entry in this range is pure black (Y, U and V all zero) at a different
alpha: 120 populated entries spanning 25 distinct opacities, from 1/255 up to a fully
opaque black at index 158. This is what the camera darkens the picture with behind its
own readouts. Drawn as ink they are invisible on anything
dark, which is what made them look empty the first time round — but as a
background index (0xC0BB120C) they are the tidy way to put a
readable shadow behind text without covering the picture.
| idx | alpha | ||
|---|---|---|---|
| 30 | 0 | 0% | |
| 31 | 0 | 0% | |
| 32 | 1 | 0% | |
| 64 | 2 | 1% | |
| 96 | 4 | 2% | |
| 128 | 1 | 0% | |
| 152 | 12 | 5% | |
| 156 | 16 | 6% | |
| 158 | 255 | 100% | |
| 159 | 136 | 53% | |
| 160 | 68 | 27% |
Eleven entries in the range are alpha 0 and draw nothing at all: 30, 31, 54, 55, 84, 85, 117, 124, 127, 143 and 144. The three at the top of the range are the heavy ones — 158 is opaque black, 159 is 53%, 160 is 27%.
The firmware never exposes this range through display palette, but it is
in the same ROM table and the hardware CLUT is 256 entries wide, so it works. Alpha is
shown where it is not 255 — roughly half of these are semi-transparent, and over a
dark picture they land much darker than the swatch suggests.
#FFFFFF#FFFFFF · 53%#FFFFFF · 27%#FF0103#FF0103 · 53%#FFFF01#FFFF01 · 53%#00FE00#00FE00 · 53%#00FEFE#00FEFE · 53%#0100FE#0100FE · 53%#FF00FF#FF00FF · 53%#67DC67#FF9945#FF6668alpha 0#B42EFF · 45%#FF00FF · 42%#B427FF · 38%#FF00FF · 27%#A316FF · 42%#B42EFF · 45%#C2008E · 47%#8D00CB · 19%#E500FE · 37%#FF00FF · 44%#EF00FF · 31%#D221FF · 45%#FF00FF · 44%#BC41D8 · 37%#BC0083 · 39%#707070 · 44%alpha 0#006A00 · 96%#259DD2 · 38%#1C94C9 · 75%#1D95CA · 82%alpha 0#006B00 · 19%#269ED3 · 9%#1C94C9 · 75%#1D95CA · 82%#1575FF · 72%#006B00 · 2%#005731 · 2%#006B00 · 14%#FE0DFA · 31%#E300FF · 47%#9A00D8 · 40%#E700FC · 37%#FF04FF · 40%#E102FF · 30%#7600E0 · 22%#000000 · 24%#006B00 · 41%alpha 0#000000 · 9%#1575FF · 72%#006B00 · 25%#005731 · 3%#006B00 · 36%#FE0DFA · 31%#9A00D8 · 47%#E700FC · 37%#FF04FF · 40%#E102FF · 30%#7600E0 · 22%#000000 · 24%#006A00 · 96%alpha 0#000000 · 9%#1575FF · 72%#006B00 · 47%#005731 · 3%#006B00 · 55%#B300EB · 35%#FF00FF · 38%#F501ED · 45%#9C21B8 · 45%#313131 · 39%#36C35A · 89%alpha 0#000000 · 9%alpha 0#006B00 · 41%#269ED3 · 35%#1C94C9 · 75%#1D95CA · 82%alpha 0alpha 0alpha 0#006B00 · 83%Straight out of out/MAIN_c0000000.bin at 0xC0B520BC. As a
check, all 256 indices were then filled across the whole OSD layer on a camera
(display osd 1 0x000000<idx> fills the layer with that index) over an
opaque black backdrop, and captured. 245 of 256 agree within four counts. The
eleven that do not are the most saturated blues and magentas, where a full-range BT.601
conversion and the camera's own converter part company at the edge of the gamut —
the AYUV in the table is the value that is actually true, and the RGB is a preview of it.
#895087 instead of
#FF6668. The second pass concluded 30–160 were empty, when they are a
black ramp that simply cannot be seen against black. Filling the whole layer removes the
first problem, and reading the ROM table removes both.
There is one font and one size: 16×32 cells, 96 glyphs, 1 bpp, with
0 meaning ink and 1 meaning background. The second font object shares the same
descriptor, so it is not a second size. The descriptor is in RAM at
0xC37830E8 — glyph width at +2, height at +4, bytes per row at +6,
bitmap pointer at +0xC — so a larger font is a matter of uploading a scaled bitmap
and repointing it. A 2× version is 24 KB and lands in under a second. The draw
rectangle has to grow with it: DrawTextSimpleFont::v1 opens with
if (rect.h < glyphHeight) return 0, so a taller font with the stock
32-pixel rectangle draws nothing at all.