From c09902255277694f869b3ccd49df39153b9b8cd8 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 24 Feb 2025 20:30:06 +0100 Subject: [PATCH] shared-module/OnDiskBitmap.c: Fix loading small monochrome bitmaps --- shared-module/displayio/OnDiskBitmap.c | 67 ++++++++++++++++++-------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/shared-module/displayio/OnDiskBitmap.c b/shared-module/displayio/OnDiskBitmap.c index 6777bd738a9e4..69378589043b5 100644 --- a/shared-module/displayio/OnDiskBitmap.c +++ b/shared-module/displayio/OnDiskBitmap.c @@ -1,6 +1,7 @@ // This file is part of the CircuitPython project: https://circuitpython.org // // SPDX-FileCopyrightText: Copyright (c) 2018 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2025 SamantazFox // // SPDX-License-Identifier: MIT @@ -15,6 +16,11 @@ #include "py/mperrno.h" #include "py/runtime.h" + +#define DISPLAYIO_ODBMP_DEBUG(...) (void)0 +// #define DISPLAYIO_ODBMP_DEBUG(...) mp_printf(&mp_plat_print __VA_OPT__(,) __VA_ARGS__) + + static uint32_t read_word(uint16_t *bmp_header, uint16_t index) { return bmp_header[index] | bmp_header[index + 1] << 16; } @@ -25,39 +31,63 @@ void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, uint16_t bmp_header[69]; f_rewind(&self->file->fp); UINT bytes_read; - if (f_read(&self->file->fp, bmp_header, 138, &bytes_read) != FR_OK) { + + // Read the minimum amount of bytes required to parse a BITMAPCOREHEADER. + // If needed, we will read more bytes down below. + if (f_read(&self->file->fp, bmp_header, 26, &bytes_read) != FR_OK) { mp_raise_OSError(MP_EIO); } - if (bytes_read != 138 || - memcmp(bmp_header, "BM", 2) != 0) { + DISPLAYIO_ODBMP_DEBUG("bytes_read: %d\n", bytes_read); + if (bytes_read != 26 || memcmp(bmp_header, "BM", 2) != 0) { mp_arg_error_invalid(MP_QSTR_file); } - // We can't cast because we're not aligned. - self->data_offset = read_word(bmp_header, 5); - + // Read header size to determine if more header bytes needs to be read. uint32_t header_size = read_word(bmp_header, 7); - uint16_t bits_per_pixel = bmp_header[14]; + DISPLAYIO_ODBMP_DEBUG("header_size: %d\n", header_size); + + if (header_size == 40 || header_size == 108 || header_size == 124) { + // Read the remaining header bytes + if (f_read(&self->file->fp, bmp_header + 13, header_size - 12, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } + DISPLAYIO_ODBMP_DEBUG("bytes_read: %d\n", bytes_read); + if (bytes_read != (header_size - 12)) { + mp_arg_error_invalid(MP_QSTR_file); + } + } else if (header_size != 12) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Only Windows format, uncompressed BMP supported: given header size is %d"), header_size); + } + + uint32_t compression = read_word(bmp_header, 15); - uint32_t number_of_colors = read_word(bmp_header, 23); + DISPLAYIO_ODBMP_DEBUG("compression: %d\n", compression); // 0 is uncompressed; 3 is bitfield compressed. 1 and 2 are RLE compression. if (compression != 0 && compression != 3) { mp_raise_ValueError(MP_ERROR_TEXT("RLE-compressed BMP not supported")); } - bool indexed = bits_per_pixel <= 8; + // We can't cast because we're not aligned. + self->data_offset = read_word(bmp_header, 5); + self->bitfield_compressed = (compression == 3); - self->bits_per_pixel = bits_per_pixel; + self->bits_per_pixel = bmp_header[14]; self->width = read_word(bmp_header, 9); self->height = read_word(bmp_header, 11); + DISPLAYIO_ODBMP_DEBUG("data offset: %d\n", self->data_offset); + DISPLAYIO_ODBMP_DEBUG("width: %d\n", self->width); + DISPLAYIO_ODBMP_DEBUG("height: %d\n", self->height); + DISPLAYIO_ODBMP_DEBUG("bpp: %d\n", self->bits_per_pixel); + + displayio_colorconverter_t *colorconverter = mp_obj_malloc(displayio_colorconverter_t, &displayio_colorconverter_type); common_hal_displayio_colorconverter_construct(colorconverter, false, DISPLAYIO_COLORSPACE_RGB888); self->colorconverter = colorconverter; - if (bits_per_pixel == 16) { + if (self->bits_per_pixel == 16) { if (((header_size >= 56)) || (self->bitfield_compressed)) { self->r_bitmask = read_word(bmp_header, 27); self->g_bitmask = read_word(bmp_header, 29); @@ -68,9 +98,13 @@ void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, self->g_bitmask = 0x3e0; self->b_bitmask = 0x1f; } - } else if (indexed) { + } else if (self->bits_per_pixel <= 8) { // indexed + uint32_t number_of_colors = 0; + if (header_size >= 40) { + number_of_colors = read_word(bmp_header, 23); + } if (number_of_colors == 0) { - number_of_colors = 1 << bits_per_pixel; + number_of_colors = 1 << self->bits_per_pixel; } displayio_palette_t *palette = mp_obj_malloc(displayio_palette_t, &displayio_palette_type); @@ -101,13 +135,6 @@ void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, common_hal_displayio_palette_set_color(palette, 1, 0xffffff); } self->palette = palette; - - } else if (!(header_size == 12 || header_size == 40 || header_size == 108 || header_size == 124)) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("Only Windows format, uncompressed BMP supported: given header size is %d"), header_size); - } - - if (bits_per_pixel == 8 && number_of_colors == 0) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("Only monochrome, indexed 4bpp or 8bpp, and 16bpp or greater BMPs supported: %d bpp given"), bits_per_pixel); } uint8_t bytes_per_pixel = (self->bits_per_pixel / 8) ? (self->bits_per_pixel / 8) : 1;