C macros for working with Playdate bitmap data

Going to share this here as I've seen a few people ask in various threads, and honestly I've had to implement this a few times.

The bitmap data of an image or the display's frame buffer is encoded as 1 pixel per bit, in 32 bit values. It can be a bit tricky to work with and basically puts every bitwise operator to work.

Here are the ways you can get the data from an image:

LCDBitmap* bitmap = ...;
uint8_t* bitmap_data = NULL;
int bitmap_rowbytes = 0;
playdate->graphics->getBitmapData(bitmap, NULL, NULL, &bitmap_rowbytes, NULL, &bitmap_data);

Or use the display's frame buffer:

uint8_t* bitmap_data = playdate->graphics->getFrame();
int display_rowbytes = LCD_ROWSIZE

Now here are the macros:

// Determine pixel at x, y is black or white.
#define samplepixel(data, x, y, rowbytes) (((data[(y)*rowbytes+(x)/8] & (1 << (uint8_t)(7 - ((x) % 8)))) != 0) ? kColorWhite : kColorBlack)

// Set the pixel at x, y to black.
#define setpixel(data, x, y, rowbytes) (data[(y)*rowbytes+(x)/8] &= ~(1 << (uint8_t)(7 - ((x) % 8))))

// Set the pixel at x, y to white.
#define clearpixel(data, x, y, rowbytes) (data[(y)*rowbytes+(x)/8] |= (1 << (uint8_t)(7 - ((x) % 8))))

// Set the pixel at x, y to the specified color.
#define drawpixel(data, x, y, rowbytes, color) (((color) == kColorBlack) ? setpixel((data), (x), (y), (rowbytes)) : clearpixel((data), (x), (y), (rowbytes)))

That's it, just pass the appropriate bitmap_data and bitmap_rowbytes into these macros with the x, y you want to modify.

Keep in mind that these macros do not do bounds checking, so make sure the x, y is within bounds of the bitmap before using. :slight_smile:

6 Likes

I get itchy when I see integer multiplications and divisions by powers of 2. I don't know if LLVM converts those to bitwise operations automatically, but I've heard plenty of stories of people getting bitten by assuming the compiler would and then the compiler didn't.

The following:

(data[(y)*rowbytes+(x)/8] & (1 << (uint8_t)(7 - ((x) % 8))))

can be replaced with:

(data[(y)*rowbytes+((x)>>3)] & (1 << (uint8_t)(7 - ((x) & 7))))

If it compiles with the actual division operations, it will be a lot slower.

I’ve experimented with this a bit in Gamekid and haven’t found a speed up using bitwise ops vs division on power of 2. I think the compiler knows what to do, especially with a relatively basic optimization. But! I haven’t inspected the output assembly so I don’t know for sure.

I tend to lean towards readable code vs optimized unless we find it actually improves speed. Not that this code is super readable anyway. :sweat_smile:

2 Likes

The bitmap data of an image or the display's frame buffer is encoded as 1 pixel per bit, in 32 bit values.

How does this map to the LCD pixel width of 400 ?

Obviously 400 pixels isn't exactly divisible by 32. Where do the 16 "unused" pixels go?
Does each row have 2 extra bytes ?

Yes, the extra 2 bytes are ignored. You can see this in the documentation for getFrame().

2 Likes

Thank you for these macros.

Is there any chance these could be official api methods in the future? Many modern consoles like the playdate do have this functionality.

You can wrap them in functions yourself (I've done it, just to try) - but IME this adds significant per-pixel overhead (stack frame stuff, etc.) and I found myself reverting back to the macros.

I imagine the same would happen for any API level implementation, and folks would just revert to using the macros again for the most part.

1 Like

nothing stopping them from adding the macros themselves (perhaps renamed or slightly adapted). I assumed he was asking to add the macro's

1 Like

You're right, I'd been using the macros so long I didn't realize they weren't part of the SDK :slight_smile:

1 Like

Thank you for this tidbit. TBH, I'm so used to having space constraints in the forefront of my mind that I forget that these games can be hundreds of megs. Though I still do think it's a good idea to incorporate even a macro at the very least for new devs to the system.