/* * 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 #include #include #include #include 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; }