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.