This color checker is part of my quest to improve color matching from sprite editor apps to sprites displayed by CircuitPython. I wrote code to generate several color charts that explore how RGB332 and RGB565 colors on Fruit Jam's DVI output compare to the colors on a regular computer.
For previous projects, I've used sprite editor apps on iOS or Linux to prepare sprites for CircuitPython. In each of those cases, I've noticed color shifts, but so far I haven't fully understood what was going or how to correct it. This time, I want to do better.
Transparency note: Adafruit provided the Fruit Jam rev B prototype board I used for this guide (Thanks Adafruit!).
Parts
I used a pre-release revision B Fruit Jam prototype board. But, you could probably get very similar results from a Metro RP2350 with an HSTX to DVI adapter. For more information on setting up a Metro RP2350 as a pseudo-Fruit Jam, you can check out some of the recent guides by Tim C. and M. LeBlanc-Williams.
DVI Capture Screenshots (8-bit)
These screenshots were taken with an EVGA XR1 Lite into macOS on a display set for the sRGB color profile. The CircuitPython code was set to use color_depth=8 for the picodvi.Framebuffer(). With this setup, picodvi quantizes the palette colors into just 4 or 8 values per color channel (RGB332). For example, if I start with an indexed color palette with a smooth ramp of 256 different values of red, it gets quantized into bands with only 8 steps of red shown on the display. The low bits of the quantized colors apparently get zero filled, which causes some weird effects with tinting and brightness. As you can see, the color selection is okay for shades of red, green, blue, cyan, and magenta. But, the grays and other hues aren't that great.
Red (3 bits, 8 values)
Gray (8 values but weird)
This one looks strange because blue is not able to change at the same rate as red and green.
DVI Capture Screenshots (16-bit)
These screenshots were taken on the same setup except for color_depth=16. The result is that the palette colors get quantized into 32 or 64 values per color channel (RGB565). As you can see, this looks a lot smoother than the 8-bit RGB332 version. In particular, the grays look fine this time.
RGB332 Colors
These look about the same as the 8-bit version. It would be possible to pick many other 256 position palettes from the 65536 available colors in 16-bit color depth mode, but I was lazy and just copied the same thing that I used for 8-bit mode.
Updating CircuitPython
As I write this (May 31, 2025), CircuitPython 10.0.0-alpha.6 is the most recent build for Fruit Jam. I'm not sure what will change by the time 10.0.0 is released. To keep your board up to date, you can use the "DOWNLOAD .UF2 NOW" buttons on the appropriate download page of circuitpython.org:
- Metro RP2350 Download page
- Fruit Jam Download page
To install the UF2 File:
- Connect your board to a computer with a USB data cable (charge-only cables won't work! )
- Press and hold the board's boot button (Button 1 on Fruit Jam)
- Press and release the reset button
- Release the boot button
- When the removable drive named RP2350 appears, copy the UF2 file onto it. After the copy finishes, you should see the RP2350 drive disappear and soon after that a new CIRCUITPY drive should appear.
CircuitPython Code
You can view the code at the samblenny/fruit-jam-color-checker GitHub repository. To download a zip archive project bundle with the code and all the necessary libraries, use the "Download Project Bundle" button:
Install Project Bundle
To copy the project bundle files to your CIRCUITPY drive:
- Download the project bundle .zip file using the "Download Project Bundle" button above.
- Expand the zip file by opening it, or use unzip in a Terminal. The zip archive should expand to a folder. When you open the folder, it should contain a README.txt file and a CircuitPython 10.x folder.
- Open the CircuitPython 10.x folder and copy all of its contents to your CIRCUITPY drive.
To learn more about copying libraries to your CIRCUITPY drive, check out the CircuitPython Libraries section of the Welcome to CircuitPython! learn guide.
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2025 Sam Blenny
#
# Fruit Jam DVI color checker
#
# The display color that gets output on DVI for each index of the
# displayio.Palette object depends on how the picodvi.Framebuffer gets
# initialized. The Palette class uses the most significant bits of each color,
# but the number of bits depends on the Framebuffer color_depth argument:
# - Framebuffer(..., color_depth=8): 3 bits red, 3 green, 2 blue
# - Framebuffer(..., color_depth=16): 5 bits red, 6 green, 5 blue
#
from board import BUTTON1, CKP, CKN, D0P, D0N, D1P, D1N, D2P, D2N
from digitalio import DigitalInOut, Direction, Pull
from displayio import (Bitmap, Group, OnDiskBitmap, Palette, TileGrid,
release_displays)
from framebufferio import FramebufferDisplay
import gc
from picodvi import Framebuffer
import supervisor
from terminalio import FONT
from time import sleep
def fill_palette(palette, style):
# Fill the palette with 256 colors in the specified style
if style == 'red':
# 256 steps of red (dark to light)
for i in range(256):
palette[i] = (i, 0, 0)
elif style == 'green':
# 256 steps of green (dark to light)
for i in range(256):
palette[i] = (0, i, 0)
elif style == 'blue':
# 256 steps of blue (dark to light)
for i in range(256):
palette[i] = (0, 0, i)
elif style == 'gray':
# 256 steps of gray (dark to light)
for i in range(256):
palette[i] = (i, i, i)
elif style == 'rgb332':
# Full range of colors for 3 bits red, 3 bits green, and 2 bits blue
i = 0
for b in range(4):
for g in range(8):
for r in range(8):
palette[i] = (r << 5, g << 5, b << 6)
i += 1
# Configure Display Mode
RGB665 = True
if RGB665:
COLOR_DEPTH = 16 # Use RGB665 palette colors
else:
COLOR_DEPTH = 8 # Use RGB332 palette colors
release_displays()
gc.collect()
fb = Framebuffer(320, 240, clk_dp=CKP, clk_dn=CKN,
red_dp=D0P, red_dn=D0N, green_dp=D1P, green_dn=D1N,
blue_dp=D2P, blue_dn=D2N, color_depth=COLOR_DEPTH)
display = FramebufferDisplay(fb, auto_refresh=False)
supervisor.runtime.display = display
grp = Group(scale=1)
display.root_group = grp
# Make a bitmap + palette + tilegrid for 8-bit per pixel indexed color image
wide = 320
high = 240
bitmap = Bitmap(wide, high, 256)
palette = Palette(256)
tilegrid = TileGrid(bitmap, pixel_shader=palette)
grp.append(tilegrid)
# Fill bitmap with a grid of squares, one square for each color of the palette.
# This will end up looking like rectangles rather than squares though, because
# the palette colors get quantized by the pixel shader (or maybe picodvi?).
#
rows = 16
cols = 16
scale = 14
for y in range(rows * scale):
base = (y // scale) * cols
for x in range(cols * scale):
c = base + (x // scale)
bitmap[x, y] = c
# Cycle through the different palettes
while True:
for style in ['red', 'green', 'blue', 'gray', 'rgb332']:
fill_palette(palette, style)
display.refresh()
sleep(8)
This page (Fruit Jam Color Checker) was last updated on June 02, 2025.
Text editor powered by tinymce.