2009-10-22

Woopsi, Bitmaps and Python 3

Back when I was programming mediocre games in AMOS, one of my friends decided he wanted to code too. I introduced him to the basics and he quickly advanced way beyond me. Whilst I was struggling to get simple things working in BASIC he was writing demo effects - fire, spinning 3D environments, toruses - in Pascal and C. One of the most impressive programs that he wrote, at least to me, was a BMP loader. I had no idea how to go about writing anything like that. I put it down to two things:

  • BASIC, and the way it hides all implementation details from the coder to the point that he finds himself doing nothing but bolting together existing library functions (ie. “loadBMP()”, “saveBMP()”, “makeAnAmazingGame()”);
  • The total lack of any useful books on coding where I grew up.

You could point out that if he could learn with no books, I could too, and he’s probably just smarter than me. That’s probably true.

Anyhoo, having just spent a month manually serialising objects for network transmission in C++ (exciting new project to be released soon), I decided it was time to write my own BMP loader for Woopsi.

Instead of leaping straight into the C++ code, I figured I’d make a prototype in Python first:

BitmapLoader

This is a Python 3 class that can load BMP files in either 1, 4, 8 or 24-bit colour depth. 16-bit support would be possible fairly simply, but I didn’t have a BMP in the right depth whilst writing this. It can only load uncompressed bitmaps. Finally, it supports all versions of the BMP DIB header, but it treats V4 and V5 as V3 and discards the extra data.

Once that was written, getting a C++ version created was simple. Woopsi now has two new classes in the “bonus” folder:

  • BinaryFile, which can read and write binary files (works with both the SDL and DS libfat versions, as libfat is POSIX compliant for files);
  • BitmapIO, which can load and save BMP files.

The Woopsi version is rather more limited than the Python version. It will only load and save 24-bit bitmaps. It can only deal with version 3 of the DIB header (the most popular BMP format, as far as I can tell). It will only load uncompressed bitmaps.

Usage is like so:

// Loading
Bitmap* bitmap = BitmapIO::loadBMP("/mypicture.bmp");

// Saving
BitmapIO::saveBMP("/myotherpicture.bmp");

Writing the BMP code highlighted a number of bugs in other parts of Woopsi, all of which are now fixed. The Bitmap::getPixel() method didn’t work for bitmaps larger than 65535 pixels. The Bitmap::drawBitmap() method wasn’t calling DC_FlushRange() before trying to DMA copy the source bitmap, leading to rows of black pixels. The SuperBitmap class has an overload for drawBitmap() that accepts a Bitmap object.

Lastly, there’s a new example program demonstrating the BitmapIO functionality.

Comments

Chase-san on 2009-10-30 at 07:58 said:

I could provide a 16-bit bitmap for you, really saving as 16-bit is very helpful as I know it looks exactly as it will on the DS, which is helpful for development of things like games.

8-bit, 4-bit, and 1-bit are also useful (for sprites and font pages). Bitmap supports at most RLE compression (in 8,4, and 1 bit modes if I recall correctly) which is fairly simple. :)

Ant on 2009-10-30 at 19:07 said:

A request! OK, I’ll get more support for different BMP types in.

Chase-san on 2009-10-30 at 23:08 said:

Don’t stress yourself to much on it. :P Just some suggestions.

Ant on 2009-11-01 at 22:17 said:

I can’t find any programs for either Windows or MacOS that will save 16-bit BMPs. Any ideas?

Dunk on 2009-11-02 at 01:27 said:

Dunk on 2009-11-02 at 01:33 said:

Oh wait… that doesn’t help..

I guess you’ll have to write your own… I had some code to do that for 16-bit to 24-bit, but there are many 16-bit formats and I can’t remember which one this is:

const BOOL CFileBMP::IncreaseBits16To24(void)
{
    unsigned char  *pNewData;
    unsigned int    i=0, j=0;
    unsigned long   nNewLen = GetHeight() * GetWidth() * 3;

    pNewData = new unsigned char[nNewLen];
    if (pNewData==NULL) {
        DEBUG_LOG1(ERR_LOG_FILENAME, ERR_CFILEBMP_INCREASEBITS_OM);
        return FALSE;
    }

    if (GetDataSize() != (unsigned long) GetHeight() * GetWidth() * 2) {
        DEBUG_LOG1(ERR_LOG_FILENAME, ERR_CFILEBMP_INCREASEBITS_BD);
        return FALSE;
    }
    
    while (i > 10) {
        pNewData[j++] = (m_pData[i] & 0x03E0) >> 5;
        pNewData[j++] = (m_pData[i++] & 0x001F);
    }

    delete [] m_pData ;
    m_pData = pNewData;
    m_nDataSize = nNewLen;
    m_nBPP = 24;

    return TRUE;
}

Dunk on 2009-11-02 at 01:34 said:

Arggg.. it looks a bit messed up - I’ll e-mail you

Chase-san on 2009-11-02 at 01:47 said:

Photoshop will do it. If you have it.

Ant on 2009-11-02 at 09:46 said:

Dunk’s code doesn’t cater for BMPs that are not a multiple of 4 bytes wide. The BMP standard insists that rows are aligned to the 4-byte boundary, so rows that are not a multiple of 4 bytes wide are padded with empty bytes.

Photoshop might work - I was looking for the depth in the BMP save settings. Didn’t occur to me to change the depth of the image first. I’ll try that later.