Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define MAX_W 600
#define MAX_H 300
typedef unsigned char Byte;
struct Info {
Byte image[MAX_H][MAX_W];
int row_length[MAX_H];
int min_width, max_width;
int width, height;
int adder;
int output_file_index;
} info;
#define HEADER_SIZE 54
#define PALETTE_ENTRIES 256
#define PALETTE_SIZE (PALETTE_ENTRIES * 4) // 4 bytes per entry
#define DATA_OFFSET (HEADER_SIZE + PALETTE_SIZE)
void Save() {
// Header format. "Negative" values indicate bytes filled in later
// -1: filesize, -2: width, -3: height
Byte header[HEADER_SIZE] = {
0x42, 0x4D, -1, -1, -1, -1, 0, 0,
0, 0, 0x36, 0x04, 0, 0, 0x28, 0,
0, 0, -2, -2, -2, -2, -3, -3,
-3, -3, 0x01, 0, 0x08, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0x12, 0x0B,
0, 0, 0x12, 0x0B
};
int pad = (4 - info.max_width % 4) % 4; // extra % 4 so 4 becomes 0
int file_size = DATA_OFFSET + (info.max_width + pad) * info.height;
Byte *output = malloc(file_size);
memcpy(output, header, HEADER_SIZE);
*(int*)(output + 0x2) = file_size;
*(int*)(output + 0x12) = info.max_width;
*(int*)(output + 0x16) = -info.height;
// palette of 0,4,8,...,252 repeated 4 times.
// Only indices 0 to 64 are actually used (0 and 64 are both black).
for (int pos = HEADER_SIZE, color = 0; color < PALETTE_ENTRIES; color++) {
output[pos++] = color << 2;
output[pos++] = color << 2;
output[pos++] = color << 2;
output[pos++] = 0;
}
for (int h = 0, pos = DATA_OFFSET; h < info.height; h++) {
for (int w = 0; w < info.max_width; w++)
output[pos++] = info.image[h][w];
for (int p = 0; p < pad; p++)
output[pos++] = 0;
}
char filename[100];
sprintf(filename, "outfile_%d.bmp", info.output_file_index++);
FILE *f = fopen(filename, "wb");
fwrite(output, 1, file_size, f);
fclose(f);
free(output);
}
void reset() {
int ofi = info.output_file_index;
info = (struct Info){0};
info.output_file_index = ofi;
}
void Add(int b) {
if (info.height < MAX_H && info.width < MAX_W)
info.image[info.height][info.width++] = b;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s INPUT_FILE\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
perror(argv[1]);
exit(EXIT_FAILURE);
}
for (int byte, prev; (byte = fgetc(fp)) != EOF; prev = byte) {
//---------------------------------------------------------------
if (byte == 0xFF) {
// Finish chunk
fprintf(stderr, "min_width: %d\n", info.min_width);
fprintf(stderr, "max_width: %d\n", info.max_width);
if (prev == 0xFE) // got an end of row marker before it
Save();
reset();
//---------------------------------------------------------------
} else if (byte == 0xFE) {
// Next line
if (info.width < info.min_width) info.min_width = info.width;
if (info.width > info.max_width) info.max_width = info.width;
info.row_length[info.height++] = info.width;
//fprintf(stderr, "H=%d W=%d\n", info.height, info.width);
info.width = 0;
//---------------------------------------------------------------
} else if (byte == 0xFD) {
int count = fgetc(fp) + 1;
int value = fgetc(fp);
for (int n = 0; n < count; n++)
Add(value);
//---------------------------------------------------------------
} else if (byte == 0xFC) {
int value = fgetc(fp);
int count = fgetc(fp) + 1;
for (int n = 0; n < count; n++)
Add(n % 2 == 0 ? value : value + 1);
//---------------------------------------------------------------
} else if (byte == 0xFB) {
info.adder = fgetc(fp);
//---------------------------------------------------------------
} else {
int value = (byte >> 2) + info.adder;
int count = (byte & 3) + 1;
for (int n = 0; n < count; n++)
Add(value);
}
}
return 0;
}