Splash Screen Fail / Suggestion

Is there any purpose to InfoHeader.ImageSize when InfoHeader.Compression is set to zero?

It seems the required size of the image data (when compression is not used) can be directly calculated from the BitCount, Width, and Height fields? If true, I’d start by rejecting files where this is set to an invalid value (not zero and not expected value). If it’s always invalid, then it should be ignored…

I'd recommend starting with lots of restrictions on the image format....

For example, refuse to load unless:

  • Header.Signature is BM
  • Header.FileSize is >= sizeof header + sizeof InfoHeader
  • InfoHeader.size == 40
  • InfoHeader.Width == BP5 display width
  • InfoHeader.Heigth == BP5 display height
  • InfoHeader.Planes == 1
  • InfoHeader.BitCount == 16 or 1 (useful to limit supported formats initially… choose one or two to support?)
  • InfoHeader.Compression == 0 (can of worms … avoid it for now)
  • InfoHeader.ColorsUsed consistent with InfoHeader.BitCount

Then check that remaining data is avialable:

  • FileSize is >= sizeof header + sizeof InfoHeader + sizeof ColorTable + sizeof RasterData (have to parse InfoHeader to know this)

Finally, read in the raster data … have a separate function per InfoHeader.BitCount to deal with monochrome vs. 16-bit RGB? (also allows low-impact adding support for other formats later).

Whatever format is supported, it’d be great for documentation to indicate how to convert a bitmap of the appropriate size to a supported format. E.g., using free CLI tool for graphics manipulation (name escaping me at the moment).

@henrygab are you thinking the Linux convert command?

convert input_image.png -resize 320x240 -depth 16 -define bmp:subtype=RGB565 output_image.bmp

(Note I just snagged this from Google and haven’t had the chance to test it)

Not the image manipulation program I had in mind, but if it works, that’s all that matters. :slight_smile: I am not tied to a particular option…

Imagick is the one I remember from toiling away on LAMP stacks. I believe it is all-platform. It seems to still exist.

2 Likes

Compression method 3, but…

So I guess it is ignored? Wikipedia.

I’m going to make up several samples or working/nonworking images and try it out.

Oh, it does for sure. It’s a workhorse. In fact, when I was converting those logos a couple of weeks ago, I was convinced that using Imagemagick alone I could eventually make it work. Problem was, I’d need a PhD in digital photography storage formats, and I took the lazy approach. I clobbered up an existing tool I found on GitHub that made RGB565 output files in C/C++ and H files by [CommandedRedYT]. I forked it and bolted on the ability to make Python output files, tried to distinguish between RGB and RGBA, and tossed in 4-bit grayscale for good measure. It worked, although I could never get the grayscale images to blit. I think that was a problem with the display driver I had selected and not the conversion. I found the Python PIL library extremely powerful like imagemagick, but marginally easier to understand. YMMV.

Here’s the total kludge that I made It might help some people, but I think you guys are on the right track making your own purpose designed tool.

-Chris

1 Like

I used GNU Image Manipulation Program to make 16 and 24 bit bitmaps.

image

24 bit version is ok.

image

16 bit version has wrong header length.

Interesting. Let’s burn a barrel of crude and ask Chad:

The standard BITMAPINFOHEADER is 40 bytes in size, but there are several extended versions of the bitmap info header that include additional fields for more detailed image information.

Extended Bitmap Info Headers

  • BITMAPV2INFOHEADER: 52 bytes
  • BITMAPV3INFOHEADER: 56 bytes
  • BITMAPV4HEADER: 108 bytes
  • BITMAPV5HEADER: 124 bytes
typedef struct {
    uint32_t biSize;
    int32_t  biWidth;
    int32_t  biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
    uint32_t biRedMask;
    uint32_t biGreenMask;
    uint32_t biBlueMask;
    uint32_t biAlphaMask;
} BITMAPV3INFOHEADER;

And for good measure let’s do v2 while we’re here:

typedef struct __attribute__((packed)) {
    uint32_t biSize;
    int32_t  biWidth;
    int32_t  biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
    uint32_t biRedMask;       // Mask identifying bits of red component
    uint32_t biGreenMask;     // Mask identifying bits of green component
    uint32_t biBlueMask;      // Mask identifying bits of blue component
} BITMAPV2INFOHEADER;

Still have that compression = 3 on the 16 bit image…

I’ve never used a C union before, that was fun :slight_smile:

robot-bitmap.zip (81.7 KB)

Time for pixel data. Here’s the files I’m testing with.

1 Like

One more thing… I looked around and couldn’t find a lot of options to do these conversions. I figured there would be dozens of converters out there. If there are, I couldn’t find them. I did find several on-line tools but I really wanted something that could run locally and not be gone next year in a corporate buyout. For that reason alone, I think it is a great idea to make such a tool.

image

The goal :slight_smile:

The commands.

The result.

This is the 16 bit 565 bitmap. The color seems to be off, but the pixels are in the right place.

    //TODO:pre-adjust the images so we can just DMA it.
    for(uint32_t b=offset; b<(320*240*2+offset); b+=2){
        pixel[0]=image[b+1];
        pixel[1]=image[b];
        spi_write_blocking(BP_SPI_PORT, pixel, 2);  
    }

I believe each byte pair has to be swapped in order, at least that is what is happening in the firmware elsewhere, that seems annoying… Is there any way around this?

        //sort image data
        for (int i = 0; i < 256; i += 2) {
            image_data_sorted[i] = image_data[i + 1];
            image_data_sorted[i + 1] = image_data[i];
        }

Swap byte order in pixels…

Yeah! That did it. Let’s try to turn 24 bit into 565 format.

2 Likes
    //calculate length of image data by width, height, and bits per pixel
    uint32_t image_data_length = (infoHeaderUnion.infoHeader.biWidth * infoHeaderUnion.infoHeader.biHeight * infoHeaderUnion.infoHeader.biBitCount) / 8;
    uint8_t image_data[256]; //buffer for image data
    uint8_t image_data_sorted[256]; //buffer for sorted image data
    uint32_t remaining_data = image_data_length;
    lcd_set_bounding_box(0, 240, 0, 320);
    while (remaining_data > 0) {
        //align to pixel boundary
        uint32_t chunk_size = (remaining_data > sizeof(image_data)) ? (infoHeaderUnion.infoHeader.biBitCount==24? ((sizeof(image_data)/3)*3) : ((sizeof(image_data)/2)*2)) : remaining_data;
        result = f_read(file_handle, image_data, chunk_size, &bytes_read);
        if (result != FR_OK || bytes_read != chunk_size) {
            printf("Failed to read image data!\r\n");
            return;
        }
        //turn 24-bit image data into 16-bit
        uint32_t sort_cnt = 0;
        if(infoHeaderUnion.infoHeader.biBitCount==24){
            for (int i = 0; i < chunk_size; i += 3) {
                uint16_t pixel = (image_data[i] >> 3) | ((image_data[i + 1] >> 2) << 5) | ((image_data[i + 2] >> 3) << 11);
                image_data_sorted[sort_cnt] = pixel >> 8;
                image_data_sorted[sort_cnt + 1] = pixel & 0xFF;
                sort_cnt += 2;
            }
        }else{
            //16-bit image data
            for (int i = 0; i < chunk_size; i += 2) {
                image_data_sorted[i] = image_data[i + 1];
                image_data_sorted[i + 1] = image_data[i];
            }
            sort_cnt = chunk_size;
        }

        //send pixel data to display
        remaining_data -= chunk_size;  
        spi_busy_wait(true);
        gpio_put(DISPLAY_DP, 1);
        gpio_put(DISPLAY_CS, 0);     
        spi_write_blocking(BP_SPI_PORT, image_data_sorted, sort_cnt);   
        gpio_put(DISPLAY_CS, 1);
        spi_busy_wait(false);
    }

Okay! 16 and 24 bit bitmaps are now supported. The 24 bit data needs to be aligned to the pixel boundary before being sorted. I’m sure there’s a ton of optimizing that could be done, and the display may even have modes where we don’t need to shuffle the byte order?

I’m going to clean this up and push to main :slight_smile:

1 Like

I bet there’s a killer opportunity to render the bitmap as ASCII art in the terminal :slight_smile:

Updated and pushed.

  • image <file> - shows the header info from ANY recognized BMP format (v1/2/3)
  • image <file> -d - draw the file content on the LCD. Checks if the file is the correct height/width and a supported pixel format (16/565, or 24 bits)

I realize this doesn’t satisfy the original goal of a splash or error screen. Couple thoughts on my next steps:

  1. A utility to convert and properly pack images in .c files in the format demanded by the LCD (no more byte swapping for internally stored images = somewhat faster)
  2. A default splash screen for V5/5XL/6
  3. A command to configure (and save) a user defined splash screen BMP and a user defined background BMP from the NAND flash.

Tired: get Chad to help me knock out some python crapware, which would make it a lot easier to import multiple formats. However, that’s going to bitrot over time and I doubt anyone uses it but me.

Wired: put it straight into the Bus Pirate firmware. Probably nobody uses it but me, however I won’t forget where I put the script when we need it 10 years from now and it should still work a treat.

1 Like

image

I reshuffled the background and image stuff into the display folder. Included the robot bitmaps, as well as the default background bitmap.

Crapware won out, it was too easy not to go python. A new image.py script converts images (of any format?), flips them, mirrors them (to get right pixel order), resizes them to 320x240, shuffles the bytes, and saves them to a .h byte array.

Install:

pip install pillow

Install the pillow image processing library.

    //Update October 2024: new image headers in pre-sorted pixel format for speed
    // see image.py in the display folder to create new headers
    //TODO:pre-adjust the images so we can just DMA it.
    //for(uint32_t b=offset; b<(320*240*2+offset); b+=2){
        //uint8_t *buff = (uint8_t *) &lcd_background_image[b];
        //pixel[0]=image[b+1];
        //pixel[1]=image[b];
        spi_write_blocking(BP_SPI_PORT, image, (320*240*2));  
 
    //}

Writing the background is WAY faster now without shifting those bytes around. Startup is snappier too. This should help the splash screen seem less awkward.

If we updated this to DMA we’d really be cooking, but I think it would take a whole chain of transfers on the RP2040 (only 32K per DMA transfer?).

Place for confusion

image

When loading a BMP with the image command on the Bus Pirate the image needs to match the pixel order. That’s flipped 270 degrees clock-wise from the normal viewing angle of the Bus Pirate screen.

image

When converting an image to a C header file, it should be the correct orientation.

The script will flip the image so that width is greater than height, so any orientation should work.

Splash screen

A processed robot16.h is checked in and added to CMakelists.txt. The only thing left to do is call void lcd_write_background(const char *image) (in ui/ui_lcd.c) in pirate.c after the LCD is initialized and before lcd_configure(), potentially with a delay.

The tricky part if figuring exactly where during startup is safe to start using the LCD, definitely after the 595s setup on BP5&5XL.

If anyone wants to take a stab at that, feel free. This was a fun warm up exercise after doing web stuff for two weeks, but I can’t finish it up yet. Tomorrow I will move on to the more pressing bugs and pull requests, and circle back to this later if nobody else has done it.

3 Likes

5, 5XL and 6 now have customized splash screens at startup. It doesn’t display for long, but should be enough you can quickly determine the hardware version.

The splash screen can be disabled in pirate.h. The image adds 150K or so to the firmware. Disabling the splash during development saves a lot of compile/program time.

robot5x16
robot5xx16
robot6x16