todbot / circuitpython-tricks

Some CircuitPython tricks, mostly reminders to myself
MIT License
577 stars 65 forks source link

image conversion issues #6

Closed electronicDuck closed 2 years ago

electronicDuck commented 2 years ago

Hi there,

Thank you for all your code, it has been super helpful! I'm trying to convert png or jpg files to BMP for Adafruit_imageload for use on a pico with a ga9c01 screen. On a raspberry pi I'm using PIL and ImageMagick and both images don't load. Using the cat photos from your examples they load fine.

PIL

from PIL import Image size = (240, 240) num_colors = 64 img = Image.open('myimage.jpg') img = img.resize(size) newimg = img.convert(mode='P', colors=num_colors) newimg.save('myimage_for_cirpy.bmp'

ImageMagick

convert myimage.jpg -resize 240x240 -colors 64 -type palette -compress None BMP3:myimage_for_cirpy.bmp

Both output a file but it won't load on the screen?

Any help would be greatly appreciated,

Thank you

prcutler commented 2 years ago

Can you share more information? Can you open the bitmap using the Image Viewer program in the Applications menu on the Raspberry Pi?

todbot commented 2 years ago

Yes, also knowing:

electronicDuck commented 2 years ago

Thank you for both your replies. I don't think it's a wiring issue as your locket example works, as does the gauge example. The bmp opens with the image editor, and if I set it was background for the gauge example it loads fine. The hello world example with the circles and text is fine too. the locket code I'm using is blow, lars shows but the hotendDial.bmp didn't. I have been playing around and I have now got it to show but the colours are wrong. Its not wrong in the gauge example

#
# gc9a01_picture_locket.py -- Simple Demo of GC9A01 Round LCD
#
# 2021 - Tod Kurt - todbot.com
#
# Tested on QTPy (SAMD21), QTPy RP2040, and Raspberry Pi Pico (RP2040)
# running CircuitPython 7.
#
# You'll need to install 'gc9a01' package.
# Easiest way to do this is from Terminal:
#  circup install gc9a01
#

import time
import board
import math
import busio
import terminalio
import displayio
import adafruit_imageload
import gc9a01

# A list of all the BMP images you want displayed, in order
#
# prepare image with ImageMagick like:
# convert input.jpg -resize 240x240 -type palette BMP3:output.bmp
img_filenames = ("/imgs/lars240.bmp", ""/imgs/240hotendDial.bmp"")

# time in seconds between images
img_time = 10

# Release any resources currently in use for the displays
displayio.release_displays()

# Raspberry Pi Pico pinout, one possibility, at "southwest" of board
tft_clk = board.GP10
# must be a SPI CLK
tft_mosi = board.GP11
# must be a SPI TX
tft_rst = board.GP12
tft_dc = board.GP13
tft_cs = board.GP14
tft_bl = board.GP15
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)

# Make the displayio SPI bus and the GC9A01 display
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rst)
display = gc9a01.GC9A01(display_bus, width=240, height=240, backlight_pin=tft_bl)

# Make the main display context
main = displayio.Group()
display.show(main)

i = 0
while True:
    print(time.monotonic(), "hello")
    img_filename = img_filenames[i]
    img_bitmap = displayio.OnDiskBitmap(open(img_filename, "rb"))
    img_palette = displayio.ColorConverter()
    # img_bitmap, img_palette = adafruit_imageload.load(img_filename)
    img_tilegrid = displayio.TileGrid(img_bitmap, pixel_shader=img_palette)
    main.append(img_tilegrid)
    time.sleep(img_time)
    main.pop()  # remove image
    i = (i+1) % len(img_filenames)  # go to next file

repl output:

Code stopped by auto-reload.
soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
402.129 hello
electronicDuck commented 2 years ago

sorry tried to use the code snippet but doesn't seem to have worked!

#
# gc9a01_picture_locket.py -- Simple Demo of GC9A01 Round LCD
#
# 2021 - Tod Kurt - todbot.com
#
# Tested on QTPy (SAMD21), QTPy RP2040, and Raspberry Pi Pico (RP2040)
# running CircuitPython 7.
#
# You'll need to install 'gc9a01' package.
# Easiest way to do this is from Terminal:
#  circup install gc9a01
#

import time
import board
import math
import busio
import terminalio
import displayio
import adafruit_imageload
import gc9a01

# A list of all the BMP images you want displayed, in order
#
# prepare image with ImageMagick like:
# convert input.jpg -resize 240x240 -type palette BMP3:output.bmp
img_filenames = ("/imgs/lars240.bmp", "/imgs/240hotendDial.bmp")

# time in seconds between images
img_time = 10

# Release any resources currently in use for the displays
displayio.release_displays()

# Raspberry Pi Pico pinout, one possibility, at "southwest" of board
tft_clk = board.GP10
# must be a SPI CLK
tft_mosi = board.GP11
# must be a SPI TX
tft_rst = board.GP12
tft_dc = board.GP13
tft_cs = board.GP14
tft_bl = board.GP15
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)

# Make the displayio SPI bus and the GC9A01 display
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rst)
display = gc9a01.GC9A01(display_bus, width=240, height=240, backlight_pin=tft_bl)

# Make the main display context
main = displayio.Group()
display.show(main)

i = 0
while True:
    print(time.monotonic(), "hello")
    img_filename = img_filenames[i]
    img_bitmap = displayio.OnDiskBitmap(open(img_filename, "rb"))
    img_palette = displayio.ColorConverter()
    # img_bitmap, img_palette = adafruit_imageload.load(img_filename)
    img_tilegrid = displayio.TileGrid(img_bitmap, pixel_shader=img_palette)
    main.append(img_tilegrid)
    time.sleep(img_time)
    main.pop()  # remove image
    i = (i+1) % len(img_filenames)  # go to next file
todbot commented 2 years ago

I edited your comment to include proper code block escaping. Looks like you're not familiar with Markdown.

Here's github's guide on it: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#quoting-code

todbot commented 2 years ago

Have you seen the display work at all? Something simple to verify basic functionality?

For example, this turns the entire screen purple (you will have to change your display to be the one you configured instead of a built-in display:

import board, displayio, vectorio

display = board.DISPLAY  # built-in display
maingroup = displayio.Group()  # a main group that holds everything
display.show(maingroup)

mypal = displayio.Palette(1)
mypal[0] = 0x990099
background = vectorio.Rectangle(pixel_shader=mypal, width=display.width, height=display.height, x=0, y=0)
maingroup.append(background)

while True:
    pass
electronicDuck commented 2 years ago

Ah! Thank you I’ll give that a read :)

and yes the above code with the two cat pictures provided in your examples works perfectly, they both come up and scroll through no problems :) They load top to bottom but I’m assuming that’s just a speed limitation on the hardware/spi side. And the gauge example you provide also works perfectly

todbot commented 2 years ago

Oh good! So it is just a file problem. Are you files named "/imgs/lars240.bmp" and "/imgs/240hotendDial.bmp" and are in the CIRCUITPY drive in the "CIRCUITPY/imgs" folder?

electronicDuck commented 2 years ago

Yes, the Lars one is from your examples and loads fine, mine is the other and either doesn’t load or the colours are wrong :)

todbot commented 2 years ago

Ah but it loads! You say the colors are wrong, how exactly?

electronicDuck commented 2 years ago

Most images I convert as per the first post don’t load, just a blank screen, the one that has loaded is shades of blue no other colours

electronicDuck commented 2 years ago

the colour one is using the gauge script

todbot commented 2 years ago

I don't know what the original image looks like, but it may be that your image has many colors in it. The ImageMagick command you reference specifically reduces the colors to 64.

So maybe try doing:

convert myimage.jpg -resize 240x240 -colors 256 -type palette -compress None BMP3:myimage_for_cirpy.bmp

(notice how 64 is now 256)

And I would recommend using the commandline convert instead of PIL/Pillow in Python because it's unclear to me how to specify everything in Python what is being done with convert

electronicDuck commented 2 years ago

the second colour image is the same as the original out of Inkscape. ok ill give it a go

electronicDuck commented 2 years ago

hmm so 256 produced the same blue result, and 4 produced a blank screen (originally it was an svg file with 4 solid colours and that's it) can you import svg images directly? maybe its something in the Inkscape export settings?

todbot commented 2 years ago

Generally ImageMagick handles arbitrary input images with no problem. When you open the output from Inkscape does it look correct? Can you upload the image here so I can try it?

electronicDuck commented 2 years ago

240hotendDial

electronicDuck commented 2 years ago

ah ok, so does that mean it should handle SVG files? above is the image, it has uploaded looking correct colour wise, and if I open it with an image viewer after export in png format then it looks correct too

thank you for your help with it

todbot commented 2 years ago

I don't know if ImageMagick handles vector files like SVG, it's mostly a tool for bitmaps.

As for your PNG image, I processed it through ImageMagick two ways: normal at 16 colors and dithered with 16 colors. I chose 16 colors because it seems like that's at most the number of colors in your image. I tried dithering because your image has small features and subtle colors, both things that can make the TFT image not look that good. These little TFT LCDs have 16-bit color, not the 24-bit color of our computer monitors. Dithering can fool our eyes into seeming to provide more detail or color than is really there. It's basically what printed comics do too.

The ImageMagick commands I used were:

convert 240hotendDial.png -colors 16 -type palette -compress None BMP3:240hotendDial.bmp
convert 240hotendDial.png -dither FloydSteinberg -colors 16 -type palette -compress None BMP3:240hotendDial_dither.bmp

The results on a gc9a01 TFT LCD look like a pretty good match to the source image:

240hotendDial_pico_demo

The dithered result looks a little better perhaps on the cyan nozzle graphic but otherwise

The code I used was:

# test out 240hotendDial.png from @electronicDuck 
import time
import board, busio
import displayio 
import gc9a01

image_fnames = (
    "/240hotendDial_dither.bmp",
    "/240hotendDial.bmp",
    )

#display = board.DISPLAY  # get a Display object (built-in on FunHouse)

# Raspberry Pi Pico pinout, one possibility, at "southwest" of board
displayio.release_displays()
tft_clk = board.GP10 # must be a SPI CLK
tft_mosi= board.GP11 # must be a SPI TX
tft_rst = board.GP12
tft_dc  = board.GP13
tft_cs  = board.GP14
tft_bl  = board.GP15
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rst)
display = gc9a01.GC9A01(display_bus, width=240, height=240, backlight_pin=tft_bl)

maingroup = displayio.Group()  # a main group that holds everything
display.show(maingroup) # put it on the display
maingroup.append( displayio.Group() ) # dummy group to be replaced shortly

while True:
    for fname in image_fnames:
        print(time.monotonic(), "loading image ",fname)
        bitmap = displayio.OnDiskBitmap(fname) # load bitmap

        palette = bitmap.pixel_shader # get bitmap's palette
        palette.make_transparent(0)   # make background color transparent

        image = displayio.TileGrid(bitmap, pixel_shader=palette) 
        maingroup[0] = image          # put bitmap holder on the main display group
        time.sleep(1)

The differences in dithering are visible but minor, here's zoomed in on a similar display. Top is not dithered, bottom is dithered.

IMG_0055 IMG_0056

electronicDuck commented 2 years ago

It works! :D :D Thank you so much for your help! I'm getting some slight pixel noise, but for me upping the colour count to 24 fixed it!

todbot commented 2 years ago

Excellent! I'll mark this as closed then. Good luck with the rest of your project.