350 lines
9.3 KiB
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, ®s, ®s);
|
|
|
|
// 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, ®s, ®s);
|
|
|
|
if (fclose(image)) {
|
|
printf("Can't close %s for reading.\n", filename);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|