iff-ilbm-on-pc-vga/iffilbm.c

350 lines
9.3 KiB
C

/*
* Read an IFF ILBM image and display it in VGA 320x200.
*
* Copyright 2024 John Bintz. Licensed under the MIT
* License. Visit https://theindustriousrabbit.com
* for more!
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>
char buffer[8];
long current_chunk_size;
char far *VGA = (char*)0xA0000;
typedef struct BMHDHeader {
unsigned short int width;
unsigned short int height;
unsigned short int _a1;
unsigned short int _a2;
unsigned char planes;
unsigned char _a3;
unsigned char compression;
unsigned char _a4;
unsigned short int _a5;
unsigned char _a6;
unsigned char _a7;
unsigned short int _a8;
unsigned short int _a9;
};
typedef struct ILBMColor {
unsigned char red;
unsigned char green;
unsigned char blue;
};
typedef struct ImageReadData {
short int width;
short int height;
char compressed;
char planes;
FILE *fh;
char *row_data;
};
/**
* Turn a big endian long (68k) into little endian (x86)
*/
long ntohl(long input) {
return input >> 24 & 0xFF |
input >> 8 & 0xFF00 |
input << 8 & 0xFF0000 |
input << 24 & 0xFF000000;
}
/**
* Turn a big endian word (68k) into little endian (x86)
*/
unsigned short int ntohw(unsigned short int input) {
return input >> 8 & 0xFF |
input << 8 & 0xFF00;
}
/**
* Read the next IFF chunk header.
*/
void read_chunk_header(FILE *fh) {
int read_count;
read_count = fread(buffer, sizeof(char), 8, fh);
current_chunk_size = ntohl(*((long*)(buffer + 4)));
}
/**
* Read the filetype chunk, which is 4 bytes long.
*/
void read_filetype_chunk(FILE *fh) {
fread(buffer, sizeof(char), 4, fh);
// reset the chunk size to 0 so no future code tries to use it.
current_chunk_size = 0;
}
/**
* Check if the current chunk type is of the requested type.
* This doesn't use strcmp because the buffer only contains the
* four characters in the IFF header.
*/
int check_chunk_type(char* type) {
int i;
for (i = 0; i < 4; ++i) {
if (type[i] != buffer[i]) {
return 0;
}
}
return 1;
}
/**
* Read the BMHD struct.
*
* This also converts the width and height to little endian.
*/
void read_bmhd_data(FILE *fh, struct BMHDHeader *bmhd) {
fread(bmhd, sizeof(struct BMHDHeader), 1, fh);
bmhd->width = ntohw(bmhd->width);
bmhd->height = ntohw(bmhd->height);
}
/**
* Read the CMAP data into the palette.
*/
int read_cmap_data(FILE *fh, struct ILBMColor palette[]) {
int number_of_colors = current_chunk_size / 3;
fread(palette, sizeof(struct ILBMColor), number_of_colors, fh);
return number_of_colors;
}
/**
* Read Amiga-specific display info.
*/
void read_camg_data(FILE *fh) {
long whatever;
// we throw this out
fread(&whatever, sizeof(long), 1, fh);
}
/**
* Turn a bitplane byte of data into indexed color data.
*/
void add_byte_to_row_data(
unsigned char data,
int plane,
int x,
unsigned char *row_data
) {
int bit;
for (bit = 7; bit >= 0; --bit) {
row_data[x + bit] |= ((data & 1) << plane);
data >>= 1;
}
}
/**
* Read BODY data and write to the VGA buffer.
*/
void read_body_data(struct ImageReadData *image_read_data) {
int y, x, literal, plane;
unsigned char data;
char far *current_vga_pos;
unsigned char *row_data = malloc(image_read_data->width);
int pos = 0;
int run_count;
for (y = 0; y < image_read_data->height; ++y) {
for (x = 0; x < image_read_data->width; ++x) {
row_data[x] = 0;
}
for (plane = 0; plane < image_read_data->planes; ++plane) {
for (x = 0; x < image_read_data->width;) {
// ByteRun1 run encoding?
if (image_read_data->compressed) {
fread(&data, 1, 1, image_read_data->fh);
if (data < 128) {
// literal: copy the next bytes from the data stream
for (run_count = data + 1; run_count > 0; --run_count) {
fread(&data, 1, 1, image_read_data->fh);
pos++;
// we can't handle more than 320 columns of data
if (x < 320) {
add_byte_to_row_data(data, plane, x, row_data);
}
x += 8;
}
} else {
// repeat: copy the next byte this many times into the data stream
run_count = 129 - (data - 128);
fread(&data, 1, 1, image_read_data->fh);
for (; run_count > 0; --run_count) {
pos++;
// we can't handle more than 320 columns of data
if (x < 320) {
add_byte_to_row_data(data, plane, x, row_data);
}
x += 8;
}
}
} else {
// uncompressed, copy the next byte into the data stream
fread(&data, 1, 1, image_read_data->fh);
pos++;
// we can't handle more than 320 columns of data
if (x < 320) {
add_byte_to_row_data(data, plane, x, row_data);
}
x += 8;
}
}
// data is written to the stream word-padded
if (pos & 1) {
fread(&data, 1, 1, image_read_data->fh);
pos++;
}
}
// write row data to VGA buffer
current_vga_pos = (char far *)(VGA + y * 320);
for (x = 0; x < image_read_data->width; ++x) {
*(current_vga_pos++) = row_data[x];
}
// we can't handle more than 200 lines of data
if (y >= 200) break;
}
free(row_data);
}
int main(int argc, char *argv[]) {
char *filename = argv[1];
int keep_reading = 1;
FILE *image;
struct BMHDHeader bmhd_header;
struct ILBMColor palette[32];
int i, number_of_colors;
struct ImageReadData image_read_data;
union REGS regs;
if (argc < 2) {
printf("Usage: %s filename.iff\n", argv[0]);
return 1;
}
// open for binary reading
// char 26 was not being read properly when I didn't do this
image = fopen(filename, "rb");
if (!image) {
printf("Can't open %s for reading.\n", filename);
return 1;
}
read_chunk_header(image);
if (!check_chunk_type("FORM")) {
printf("Not an IFF file");
return 1;
}
read_filetype_chunk(image);
if (!check_chunk_type("ILBM")) {
printf("Not an ILBM file");
return 1;
}
/* next chunks can be in any order, but once we hit BODY, we stop */
while (keep_reading) {
keep_reading = 0;
read_chunk_header(image);
if (check_chunk_type("BMHD")) {
read_bmhd_data(image, &bmhd_header);
keep_reading = 1;
}
if (check_chunk_type("CMAP")) {
number_of_colors = read_cmap_data(image, palette);
keep_reading = 1;
}
if (check_chunk_type("CAMG")) {
read_camg_data(image);
keep_reading = 1;
}
if (check_chunk_type("BODY")) {
// we made it, let's activate VGA mode and set up the
// palette now
regs.h.ah = 0x00;
regs.h.al = 0x13;
int386(0x10, &regs, &regs);
// if we send 0 to 0x3c8, we can dump all the colors in
// one after the other
outp(0x3c8, 0);
for (i = 0; i < number_of_colors; ++i) {
outp(0x3c9, (palette[i].red >> 2));
outp(0x3c9, (palette[i].green >> 2));
outp(0x3c9, (palette[i].blue >> 2));
}
image_read_data.width = bmhd_header.width;
image_read_data.height = bmhd_header.height;
image_read_data.compressed = bmhd_header.compression;
image_read_data.planes = bmhd_header.planes;
image_read_data.fh = image;
read_body_data(&image_read_data);
// stop reading here
} else {
if (!keep_reading) {
// if we're out of chunks, stop reading
if (!feof(image)) {
// otherwise, it's an unknown chunk, skip it and keep reading
fseek(image, current_chunk_size, SEEK_CUR);
keep_reading = 1;
}
}
}
}
// wait 10 seconds
delay(10000);
// back to 80x25 text mode
regs.h.ah = 0x00;
regs.h.al = 0x03;
int386(0x10, &regs, &regs);
if (fclose(image)) {
printf("Can't close %s for reading.\n", filename);
return 1;
}
return 0;
}