I've been banging my head against this piece of code all night. I'm trying to crop a bitmap on an ESP32 CAM. I take a photo, convert the photo to a bmp, then call the following function:
size_t crop_image(uint8_t *fb, size_t len, uint32_t width, uint32_t height, unsigned short crop_left, unsigned short crop_top, unsigned short crop_width, unsigned short crop_height)
{
uint8_t *buf = fb + BMP_HEADER_LEN;
size_t new_size = crop_width * crop_height * 3 + BMP_HEADER_LEN;
unsigned int write_idx = 0;
for(uint32_t y = crop_top * 3; y < (crop_top + crop_height) * width * 3; y += width * 3){
for(int x = crop_left * 3; x < (crop_left + crop_width) * 3; x += 3){
int pix_idx = x + y;
buf[write_idx++] = buf[pix_idx];
buf[write_idx++] = buf[pix_idx+1];
buf[write_idx++] = buf[pix_idx+2];
}
}
// Adjust the BMP Header
*(uint32_t *)(fb + BMP_HEADER_WIDTH_OFFSET) = crop_width;
*(uint32_t *)(fb + BMP_HEADER_HEIGHT_OFFSET) = -1 * crop_height;
*(uint32_t *)(fb + 6) = new_size;
*(uint32_t *)(fb + 34) = crop_width * crop_height * 3;
return new_size;
}
This almost works. If I pass in the width & height of the image as the crop_width & crop_height parameters the output is the same as the original. If I pass a smaller height, and full width, then it also works. When I pass in a crop_width that's smaller than the original width I get an image with a diagonal "shift" line running from the upper right corner to about 1/3 of the way across the bottom. It looks like my width is off by one byte resulting in a 3/2 slope line. But I cannot figure out what is the cause.
I've attached a full size jpg and a cropped bitmap. Note that the jpg is not the source (my code doesn't save the original), but is very similar to the source of the cropped image. The cropped bmitmap was created with a call with crop_left = 0, crop_top = 0, crop_width = 799, crop_height = 600. The original image was 800x600.
I've stripped all the extra code out - so no error handling, no extracting width / height from the bitmap header, etc.
The code is so simple, this is driving me crazy. Thank you
Image very similar to the original Cropped - note not original bmp - a screenshot - but shows issue
Ok, amazing what a few hours sleep can do. The cropped image width needs to be padded to a 4-byte multiple. The number I was testing with just happened to not be divisible by 4 (except for the whole image test). Bitmap image widths have to be on 4 byte boundary - or padded out to be so,
Thanks for you consideration
Here's working code (still without error handling):
size_t crop_image(uint8_t *fb, size_t len, uint32_t width, uint32_t height, unsigned short crop_left, unsigned short crop_top, unsigned short crop_width, unsigned short crop_height)
{
uint8_t *buf = fb + BMP_HEADER_LEN;
size_t new_size = crop_width * crop_height * 3 + BMP_HEADER_LEN;
unsigned int write_idx = 0;
for(uint32_t y = crop_top * 3; y < (crop_top + crop_height) * width * 3; y += width * 3){
for(int x = crop_left * 3; x < (crop_left + crop_width) * 3; x += 3){
int pix_idx = x + y;
buf[write_idx++] = buf[pix_idx];
buf[write_idx++] = buf[pix_idx+1];
buf[write_idx++] = buf[pix_idx+2];
}
// Pad to four byte boundary
for (int i_pad=0; i_pad < (crop_width % 4); i_pad++) buf[write_idx++] = 0;
}
// Adjust the BMP Header
*(uint32_t *)(fb + BMP_HEADER_WIDTH_OFFSET) = crop_width;
*(uint32_t *)(fb + BMP_HEADER_HEIGHT_OFFSET) = -1 * crop_height;
*(uint32_t *)(fb + 6) = new_size;
*(uint32_t *)(fb + 34) = crop_width * crop_height * 3;
return new_size;
}