No matching colour or space in palette!

Not sure why I am getting this error.

>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
Traceback (most recent call last):
  File "<stdin>", line 61, in <module>
ValueError: create_pen failed. No matching colour or space in palette!
>>> 

This is my code:

import time
import gc
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2, PEN_P8
from pimoroni import RGBLED
import machine

# Turn of RGB LED
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0)

# Setup display 2.0
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, pen_type=PEN_P8)
display.set_backlight(1.0)
WIDTH, HEIGHT = display.get_bounds()
# Change the font
display.set_font("bitmap8")

BLACK = display.create_pen(0,0,0)

# Set up the ADCs with a list
adcs = [] # An empty list of ADC pins

for i in range(3):
    adc = machine.ADC(26 + i)         # Pins GP26, GP27 & GP28
    adcs.append(adc)          # Add the new ADC pin to the list

def pot_adj(adc, minn, maxx):     # Function to rescale a potentiometer reading
    pot_min = 800                 # Take lowest readings as 0
    pot_range = 65535 - pot_min
    req_range = maxx - minn

    # Read the raw potentiometer value from specified potentiometer
    pot = adcs[adc].read_u16()

    # Re-scale
    result = ((pot - pot_min) * req_range / pot_range) + minn
    result = int(result)          # Ensure it is an Integer – whole number

    # Adjust end points as necessary
    if result < minn:  # Bottom end – set to minn if too low
        result = minn
    if result > maxx:  # Top end – set to maxx if too big
        result = maxx
    return result      # Output the result to main program

# A few procedures to be used later
def wait(z): # delay a while
    time.sleep(z)
    
def clean(): # Clear the screen to Black
    display.set_pen(BLACK)
    display.clear()

clean()

while True:
    r = pot_adj(2, 0, 255)
    g = pot_adj(1, 0, 255)
    b = pot_adj(0, 0, 255)
    gc.collect()
    colour = display.create_pen(r,g,b)
    time.sleep(0.05)
    display.set_pen(colour)    
    display.circle(120,160,50)
    display.update()
    

I’m using a Pico 2 W and Pimoroni Display 2 on a Pico Decker with 10 K pots on the ADCs.

I’ve been mixing colours from values from potentiometer for years and not met this problem before. I use the procedure to overcome the ‘jitter’ at the low end of ADC input not providing a proper zero.

I normally calculate the colour value as an appropriate bit pattern and poke that into the framebuffer but here a colour definition appears to be appended to the palette each time we redefine a colour with the same name, rather than replacing it with the new definition of the colour. After a finite number of colour changes we run out of space.

Perhaps an extra instruction to ‘pop’ the last colour from the palette so that it could then be redefined and auto appened as usual?

Can anyone think of a way round this problem?

One option would be that you cache your colors in a dict with (r,g,b) as the key. You could then do something like

color_dict = {}
while True:
  ...
  if (r,g,b) in color_dict:
     color = color_dict[(r,g,b)]
  else:
    color = display.create_pen(r,g,b)
    color_dict[(r,g,b)] = color
    ...

But I agree that this should be hidden within display.create_pen().

EDIT:

I had a look at the code and it is not as simple as this. The PEN_P8-type is a palette-based pen with 256 colors. The palette itself tracks usage of each color. Every create_pen will update one slot. So the correct strategy would be:

color_index = 0
while True:
  ...
  color_index = (color_index +1) % 256
  color = display.update_pen(color_index,r,g,b)
    ...

Note the use of update_pen() instead of create_pen(). The only difference is that the former takes an explicit palette-index and overwrites the old color, where as the latter searches for the first unused palette-index.

Btw: since your program only uses a single color at a time, you could probably also get away with having a fixed color_index.

Thanks for that idea. Just found the documentation.
( pimoroni-pico/micropython/modules/picographics/README.md at main · pimoroni/pimoroni-pico · GitHub)

I’ve tried changing the essential code as follows but it does not like the update-pen line.

COLOUR = display.create_pen(10,10,10) # This should be index zero
BLACK = display.create_pen(0,0,0)

def clean(): # Clear the screen to Black
    display.set_pen(BLACK)
    display.clear()

clean()
while True:
    r = pot_adj(2, 0, 255)
    g = pot_adj(1, 0, 255)
    b = pot_adj(0, 0, 255)

    COLOUR = display.update_pen(r,g,b) # NOT LIKED!
    time.sleep(0.05)
    display.set_pen(COLOUR)    
    display.circle(120,160,50)
    display.update()
>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
Traceback (most recent call last):
  File "<stdin>", line 60, in <module>
TypeError: function takes 5 positional arguments but 4 were given
>>> 

I've only given it 3 arguments but it sees 4 ???


I’ve tried a zero in front of the (r,g,b) == (0,r,g,b) but still not happy.

I think you might need to create a palette before you can update entries in it?

I’ve not had coffee yet so possible I’ve missed something, but is there a specific reason you’re trying to use a custom palette here? Using PEN_RGB332 instead of PEN_P8 would let you create_pen RGB colours on the fly (and is also 256 colours so should behave similarly performance wise).

This appears to work. - Thanks

import time
from random import randint
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2, PEN_P4
from pimoroni import RGBLED
import machine

# Turn of RGB LED
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0)

# Setup display 2.0
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, pen_type=PEN_P4)
display.set_backlight(1.0)
WIDTH, HEIGHT = display.get_bounds()
# Change the font
display.set_font("bitmap8")

x = []
x = display.set_palette([
      (0,0,0),             # BLACK  
      (255,100,100),       # Variable colour
      (255,255,255)        # WHITE
    ])

display.set_pen(1)
display.clear()
display.update()

time.sleep(1)
display.update_pen(1, 0,255,0)
display.clear()
display.update()

i = 0
while True:
    i = i + 1
    display.update_pen(1, randint(0,255),randint(0,255),randint(0,255))
    display.set_pen(1)
    display.clear()
    display.set_pen(0)
    display.text(str(i),20,20,300,8)
    display.update()
    time.sleep(0.3)
    
1 Like

Problem solved - Thank you.

import time
import gc
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2, PEN_P4
from pimoroni import RGBLED
import machine

# Turn of RGB LED
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0)

# Setup display 2.0

display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, pen_type=PEN_P4)
display.set_backlight(1.0)
WIDTH, HEIGHT = display.get_bounds()
# Change the font
display.set_font("bitmap8")

# Set up the ADCs with a list
adcs = [] # An empty list of ADC pins

for i in range(3):
    adc = machine.ADC(26 + i)         # Pins GP26, GP27 & GP28
    adcs.append(adc)          # Add the new ADC pin to the list

def pot_adj(adc, minn, maxx):     # Function to rescale a potentiometer reading
    pot_min = 800                 # Take lowest readings as 0
    pot_range = 65535 - pot_min
    req_range = maxx - minn

    # Read the raw potentiometer value from specified potentiometer
    pot = adcs[adc].read_u16()

    # Re-scale
    result = ((pot - pot_min) * req_range / pot_range) + minn
    result = int(result)          # Ensure it is an Integer – whole number

    # Adjust end points as necessary
    if result < minn:  # Bottom end – set to minn if too low
        result = minn
    if result > maxx:  # Top end – set to maxx if too big
        result = maxx
    return result      # Output the result to main program

x = []
x = display.set_palette([
      (0,0,0),             # BLACK  
      (100,100,100),       # Variable colour
      (255,255,255)        # WHITE
    ])

def clean(): # Clear the screen to Black
    display.set_pen(0)
    display.clear()
    
clean()
while True:
    r = pot_adj(2, 0, 255)
    g = pot_adj(1, 0, 255)
    b = pot_adj(0, 0, 255)

    display.update_pen(1, r,g,b)
    time.sleep(0.05)
    display.set_pen(1)    
    display.circle(120,160,50)
    display.update()