Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define MAX_W 600
#define MAX_H 300
#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)
typedef unsigned char Byte;
typedef enum Mode { V1, V2 } Mode; // V1 for older files, V2 for newer
typedef struct Info {
Byte image[MAX_H][MAX_W];
int min_width, max_width;
int width, height;
int adder;
int output_file_index;
int n_palette;
const char *filename;
} Info;
Info info = {
.min_width = INT_MAX,
.n_palette = -1,
.filename = NULL
};
Mode ProgramMode = V1;
void reset() {
memset(info.image, 0, MAX_H * MAX_W);
info.min_width = INT_MAX;
info.max_width = 0;
info.width = 0;
info.height = 0;
info.adder = 0;
}
void default_palette(Byte *pal) {
const int shift = (ProgramMode == V1 ? 3 : 2);
for (int color = 0; color < PALETTE_ENTRIES; color++) {
*pal++ = color << shift;
*pal++ = color << shift;
*pal++ = color << shift;
*pal++ = 0;
}
}
void fill_palette(Byte *pal, const char *infilename, int n_palette) {
if (n_palette == -1) {
default_palette(pal);
return;
}
FILE *f = fopen(infilename, "rb");
int byte, cnt = 0;
const int shift = (ProgramMode == V1 ? 3 : 2);
const int size = (ProgramMode == V1 ? 32 : 64);
while ((byte = fgetc(f)) != EOF) {
if (byte == 0xFF && ++cnt == n_palette) {
for (int i = 0; i < size; ++i) {
int r = fgetc(f), g = fgetc(f), b = fgetc(f);
pal[i * 4 ] = b << shift;
pal[i * 4 + 1] = g << shift;
pal[i * 4 + 2] = r << shift;
}
break;
}
}
fclose(f);
}
void save_bmp() {
Byte header[HEADER_SIZE] = {
0x42, 0x4D, // 0: BM signature
-1, -1, -1, -1, // 2: filesize
0, 0, 0, 0, // 6: reserved
-2, -2, -2, -2, // 10: offset to pixel array
0x28, 0, 0, 0, // 14: DIB header size
-3, -3, -3, -3, // 18: width
-4, -4, -4, -4, // 22: height
0x01, 0, // 26: planes
0x08, 0 // 28: bits per pixel
};
int pad = (4 - info.max_width % 4) % 4;
int file_size = DATA_OFFSET + (info.max_width + pad) * info.height;
Byte *output = malloc(file_size);
memcpy(output, header, HEADER_SIZE);
*(int*)(output + 2) = file_size;
*(int*)(output + 10) = DATA_OFFSET;
*(int*)(output + 18) = info.max_width;
*(int*)(output + 22) = -info.height;
Byte *pal = output + HEADER_SIZE;
if (info.n_palette == -1)
default_palette(pal);
else
fill_palette(pal, info.filename, info.n_palette);
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;
}
// Output filenames are: filename-outN.bmp where N is 0,1,...
char outfilename[100];
strcpy(outfilename, info.filename);
char *p = strrchr(outfilename, '.');
if (p) *p = '\0';
sprintf(outfilename + strlen(outfilename), "-out%d.bmp",
info.output_file_index);
FILE *f = fopen(outfilename, "wb");
fwrite(output, 1, file_size, f);
fclose(f);
free(output);
}
void add(int value) {
if (info.height < MAX_H && info.width < MAX_W)
info.image[info.height][info.width++] = value;
}
void process(FILE *fp) {
for (int byte, prev = 0xFF; (byte = fgetc(fp)) != EOF; prev = byte) {
int count, value;
if (prev == 0xFF)
printf("Chunk: %d\n", info.output_file_index);
switch (byte) {
case 0xFF: // End of image
if (prev == 0xFE) {
if (info.min_width != info.max_width)
printf(" Error: min (%d) and max (%d) widths do not match\n",
info.min_width, info.max_width);
printf(" Width: %d Height: %d\n", info.max_width, info.height);
save_bmp();
}
reset();
++info.output_file_index;
break;
case 0xFE: // End of image line
if (info.width < info.min_width) info.min_width = info.width;
if (info.width > info.max_width) info.max_width = info.width;
++info.height;
info.width = 0;
break;
case 0xFD: // next two bytes are count-1 and value
count = fgetc(fp) + 1;
value = fgetc(fp);
for (int i = 0; i < count; ++i) add(value);
break;
case 0xFC: // next two bytes are count-1 and value
value = fgetc(fp); // orig. code had +1 here (but why?)
count = fgetc(fp) + 1;
for (int i = 0; i < count; ++i) // draw dithered line
add(i % 2 == 0 ? value : value + 1);
break;
case 0xFB: // Set adder used in default case
info.adder = fgetc(fp);
break;
default:
if (ProgramMode == V1) {
value = (byte >> 3) + info.adder; // High 5 bits are value
count = (byte & 7) + 1; // Low 3 bits are count-1
}
else {
value = (byte >> 2) + info.adder; // High 6 bits are value
count = (byte & 3) + 1; // Low 2 bits are count-1
}
for (int i = 0; i < count; ++i) add(value);
}
}
}
int main(int argc, char *argv[]) {
if (argc < 2 || argc > 5) {
printf("Usage: %s [-n] [-p N] INPUT_FILE\n", argv[0]);
exit(EXIT_FAILURE);
}
while (**++argv == '-') {
if (strcmp(*argv, "-n") == 0)
ProgramMode = V2;
else if (strncmp(*argv, "-p", 2) == 0) {
if (argv[0][2])
info.n_palette = atoi(&argv[0][2]);
else {
if (*++argv == NULL) {
fprintf(stderr, "Error: Missing parameters\n");
exit(EXIT_FAILURE);
}
info.n_palette = atoi(*argv);
}
}
else {
fprintf(stderr, "Error: Unknown switch: %s\n", *argv);
exit(EXIT_FAILURE);
}
}
if (!*argv) {
fprintf(stderr, "Error: No filename\n");
exit(EXIT_FAILURE);
}
info.filename = *argv;
FILE *fp = fopen(info.filename, "rb");
if (!fp) {
perror(info.filename);
exit(EXIT_FAILURE);
}
process(fp);
return 0;
}