Here you go! (crop_x0, crop_y0) is the topleft coordinate and (crop_x1, crop_y1) is the bottomright coordinate of the cut on the original picture. Note that the row addressed by crop_y1 and the column addressed by crop_x1 aren't included in the resulting picture.
Works with 8, 16, 24, and 32 bit bmp images without RLE compression. Handles original V4 and V5 headers as well. You might want to comment out the LITTLE_ENDIAN define if your processor is big endian.
#define LITTLE_ENDIAN
#ifdef _WIN32
# define _CRT_SECURE_NO_WARNINGS
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#else
# include <stdint.h>
#endif
#ifndef BI_RGB
# define BI_RGB 0
#endif
#ifndef BI_BITFIELDS
# define BI_BITFIELDS 3
#endif
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#pragma pack(push, 1)
typedef struct tagBITMAPFILEHEADER {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
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;
} BITMAPINFOHEADER;
#pragma pack(pop)
int Max(int a, int b)
{
return a>b ? a : b;
}
int Min(int a, int b)
{
return a<b ? a : b;
}
#ifdef LITTLE_ENDIAN
#define GET_INT16_LE(var) \
var
#define SET_INT16_LE(var, value) \
var = value
#define GET_INT32_LE(var) \
var
#define SET_INT32_LE(var, value) \
var = value
#else
#define GET_INT16_LE(var) \
((var >> 8) & 0xFF) | ((var << 8) & 0xFF00)
#define SET_INT16_LE(var, value) \
var = ((value >> 8) & 0xFF) | ((value << 8) & 0xFF00)
#define GET_INT32_LE(var) \
(((var >> 24) & 0xFF) | ((var >> 8) & 0xFF00) | ((var << 8) & 0xFF0000) | ((var << 24) & 0xFF000000))
#define SET_INT32_LE(var, value) \
var = (((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000))
#endif
int CropBmpData(const char* data, int data_size, int crop_x0, int crop_y0, int crop_x1, int crop_y1, char** out_data, int* out_size)
{
const BITMAPFILEHEADER* header = (BITMAPFILEHEADER*)data;
const BITMAPINFOHEADER* bmih = (BITMAPINFOHEADER*)(header + 1);
int width, height, top_down, bpp, stride, bytes_per_pixel, bytes_to_copy_per_row;
int width2, height2, stride2, head_size;
int y, padding_bytes;
BITMAPFILEHEADER* header2;
BITMAPINFOHEADER* bmih2;
const char* src;
char* dest;
uint16_t magic = GET_INT16_LE(header->bfType);
if (magic != 0x4D42)
return 0;
if (GET_INT32_LE(bmih->biSize)<sizeof(BITMAPINFOHEADER))
return 0;
bpp = GET_INT16_LE(bmih->biBitCount);
if (bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32)
return 0;
if (GET_INT32_LE(bmih->biCompression) != BI_RGB)
{
if (bpp != 16 || GET_INT32_LE(bmih->biCompression) != BI_BITFIELDS)
return 0;
}
if (GET_INT16_LE(bmih->biPlanes) != 1)
return 0;
width = GET_INT32_LE(bmih->biWidth);
height = GET_INT32_LE(bmih->biHeight);
if (width <= 0 || height == 0)
return 0;
top_down = height < 0;
if (top_down)
height = -height;
crop_x0 = Max(crop_x0, 0);
crop_y0 = Max(crop_y0, 0);
crop_x1 = Min(crop_x1, width);
crop_y1 = Min(crop_y1, height);
if (crop_x0 >= crop_x1)
return 0;
if (crop_y0 >= crop_y1)
return 0;
stride = ((bpp * width + 31) / 8) & ~3;
head_size = GET_INT32_LE(header->bfOffBits);
if (data_size < head_size+stride*height)
return 0;
src = data + head_size;
if (!top_down)
{
src += (height-1) * stride;
stride = -stride;
}
bytes_per_pixel = bpp / 8;
src += crop_x0 * bytes_per_pixel + crop_y0 * stride;
width2 = crop_x1 - crop_x0;
height2 = crop_y1 - crop_y0;
stride2 = ((bpp * width2 + 31) / 8) & ~3;
*out_size = head_size + stride2*height2;
*out_data = (char*)malloc(*out_size);
if (!*out_data)
return 0;
memcpy(*out_data, data, head_size);
header2 = (BITMAPFILEHEADER*)*out_data;
bmih2 = (BITMAPINFOHEADER*)(header2+1);
SET_INT32_LE(bmih2->biWidth, width2);
SET_INT32_LE(bmih2->biHeight, top_down ? -height2 : height2);
SET_INT32_LE(bmih2->biSizeImage, 0);
dest = *out_data + head_size;
if (!top_down)
{
dest += (height2-1) * stride2;
stride2 = -stride2;
}
bytes_to_copy_per_row = width2 * bytes_per_pixel;
padding_bytes = stride2 - bytes_to_copy_per_row;
for (y=0; y<height2; y++)
{
memcpy(dest, src, bytes_to_copy_per_row);
if (padding_bytes > 0)
memset(dest+bytes_to_copy_per_row, 0, padding_bytes);
src += stride;
dest += stride2;
}
return 1;
}
int CropBmpFile(const char* input_file, const char* output_file, int crop_x0, int crop_y0, int crop_x1, int crop_y1)
{
FILE* f;
long filesize;
char* data;
size_t bytes_read, bytes_written;
char* new_data;
int new_size;
int success;
f = fopen(input_file, "rb");
if (!f)
return 0;
fseek(f, 0, SEEK_END);
filesize = ftell(f);
if (filesize <= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER))
{
fclose(f);
return 0;
}
data = (char*)malloc(filesize);
if (!data)
{
fclose(f);
return 0;
}
rewind(f);
bytes_read = fread(data, 1, (size_t)filesize, f);
fclose(f);
if (bytes_read != (size_t)filesize)
{
free(data);
return 0;
}
new_data = NULL;
new_size = 0;
success = CropBmpData(data, filesize, crop_x0, crop_y0, crop_x1, crop_y1, &new_data, &new_size);
free(data);
if (success)
{
f = fopen(output_file, "wb");
if (f)
{
bytes_written = fwrite(new_data, 1, (size_t)new_size, f);
fclose(f);
success = bytes_written == (size_t)new_size;
}
else
{
success = 0;
}
}
if (new_data)
free(new_data);
return success;
}
int main(int argc, char* argv[])
{
printf("result: %d\n", CropBmpFile("e:\\w-mode-1_03.bmp", "e:\\w-mode-1_03_cropped.bmp", 5, 10, 50, 20));
printf("result: %d\n", CropBmpFile("e:\\w-mode-1_03_8.bmp", "e:\\w-mode-1_03_8_cropped.bmp", 5, 10, 50, 20));
printf("result: %d\n", CropBmpFile("e:\\w-mode-1_03_16.bmp", "e:\\w-mode-1_03_16_cropped.bmp", 5, 10, 50, 20));
return 0;
}