Gallery: BMP support

Signed-off-by: Francesco Gazzetta <fgaz@fgaz.me>
This commit is contained in:
Francesco Gazzetta 2022-01-29 21:34:15 +01:00 committed by Daniel Thompson
parent eba94cd4f6
commit 61d9dbce7c
1 changed files with 61 additions and 15 deletions

View File

@ -9,24 +9,25 @@ An application that shows images stored in the filesystem.
.. figure:: res/GalleryApp.png
:width: 179
Only 240x240 images are supported for now.
The images have to be uploaded in the gallery directory.
The images have to be encoded as raw RGB565 data in big endian byte order.
To encode them, you can use ffmpeg:
The images have to be uploaded in the "gallery" directory.
The images have to be encoded as BMP RGB565 data in big endian byte order.
To encode them, you can use GIMP (File Export, select the BMP format,
set "R5 G6 B5" in "Advanced Options"), or ImageMagick:
.. code-block:: sh
ffmpeg -vcodec png -i my_image.png -vcodec rawvideo -f rawvideo -pix_fmt rgb565be my_image.rgb565
convert -define bmp:subtype=RGB565 my_image.png my_image.bmp
And to upload:
.. code-block:: sh
./tools/wasptool --binary --upload my_image.rgb565 --as gallery/my_image
./tools/wasptool --binary --upload my_image.bmp --as gallery/my_image
"""
import wasp
import icons
from apps.pager import PagerApp
class GalleryApp():
NAME = 'Gallery'
@ -84,6 +85,12 @@ class GalleryApp():
self.index = (self.index + increment) % len(self.files)
self._draw()
def _invalid_file(self, filename):
draw = wasp.watch.drawable
draw.string('Invalid BMP file', 0, 10, width=240)
draw.blit(self.ICON, 72, 72)
draw.line(72,52, 168,148, 3, 0xf800)
def _draw(self):
draw = wasp.watch.drawable
draw.fill()
@ -97,17 +104,56 @@ class GalleryApp():
draw.string(filename[:(draw.wrap(filename, 240)[1])], 0, 200)
file = open("gallery/{}".format(filename), "rb")
display = wasp.watch.display
display.set_window(0, 0, 240, 240)
# check that we are reading a RGB565 BMP
magic = file.read(2)
if magic != b'BM': # check BMP magic number
self._invalid_file(filename)
return
file.seek(0x0A)
data_offset = int.from_bytes(file.read(4), 'little')
file.seek(0x0E)
dib_len = int.from_bytes(file.read(4), 'little')
if dib_len != 124: # check header V5
self._invalid_file(filename)
return
width = int.from_bytes(file.read(4), 'little')
height = int.from_bytes(file.read(4), 'little')
# width and height are signed, but only height can actually be negative
if height >= 2147483648:
height = 4294967296 - height
bottom_up = False
else: bottom_up = True
if width > 240 or height > 240: # check size <= 240x240
self._invalid_file(filename)
return
file.seek(0x1C)
bit_count = int.from_bytes(file.read(2), 'little')
if bit_count != 16: # check 16 bpp
self._invalid_file(filename)
return
compression = int.from_bytes(file.read(4), 'little')
if compression != 3: # check bitmask mode
self._invalid_file(filename)
return
file.seek(0x36)
bitmask = file.read(4), file.read(4), file.read(4)
if bitmask != (b'\x00\xF8\x00\x00', b'\xE0\x07\x00\x00', b'\x1F\x00\x00\x00'): # check bitmask RGB565
self._invalid_file(filename)
return
display.set_window((240 - width) // 2, 0, width, height)
file.seek(data_offset)
# We don't have enough memory to load the entire image at once, so
# we stream it from flash memory to the display
#TODO: why can't we do it in a single quick write session?
#display.quick_start()
buf = display.linebuffer[:2*240]
read = 1
while read > 0:
read = file.readinto(buf)
#display.quick_write(buf)
buf = display.linebuffer[:2*width]
for y in reversed(range(0, height)):
if bottom_up: file.seek(data_offset + y * width * 2)
file.readinto(buf)
for x in range(0, width):
buf[x*2], buf[x*2+1] = buf[x*2+1], buf[x*2]
display.write_data(buf)
#display.quick_end()
file.close()