commit 1ff32316f8244b60261614c127521a181a99421f Author: John Bintz Date: Sun Feb 4 17:09:38 2024 -0500 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..a52eea3 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# IFF ILBM reader on DOS PC + +There's three pieces of code in here, two for the PC and one for the Amiga. + +## PC code + +* `vgapal.c` cycles through every greyscale color that VGA can handle. +* `iffilbm.c` can read up to 32 color IFF ILBM files, either compressed + or uncompressed, cropping them to the top left 320x200 pixels. + +Compile using [Open Watcom 2.0](https://open-watcom.github.io/) +and use the the DOS/4G protected mode compiler `wlc386`. + +## Amiga code + +* `load_uncompressed.c` will render `chiicken art 32 uncompressed.iff` without + doing any translation of the BODY chunk -- it will load it raw into Chip + RAM and set up the correct modulos for the custom chips to display it. + +Compile using SAS/C: `sc load_uncompressed.c link`. + +All code and art is copyright 2024 John Bintz. Code is released under the +MIT License. Art is all rights reserved. diff --git a/chicken art 32 compressed.iff b/chicken art 32 compressed.iff new file mode 100644 index 0000000..3e1bd23 Binary files /dev/null and b/chicken art 32 compressed.iff differ diff --git a/chicken art 32 uncompressed.iff b/chicken art 32 uncompressed.iff new file mode 100644 index 0000000..1024c03 Binary files /dev/null and b/chicken art 32 uncompressed.iff differ diff --git a/iffilbm.c b/iffilbm.c new file mode 100644 index 0000000..646263e --- /dev/null +++ b/iffilbm.c @@ -0,0 +1,349 @@ +/* + * 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; +} diff --git a/load_uncompressed.c b/load_uncompressed.c new file mode 100644 index 0000000..2392d6a --- /dev/null +++ b/load_uncompressed.c @@ -0,0 +1,260 @@ +/** + * Displaying an uncompressed IFF ILBM image + * straight from RAM. + * + * Since ILBM images are interleaved bitmaps, if + * your display mode is set to be the same size as the + * image, you can treat that raw image data as the + * bitmap source for Agnus. + * + * Copyright 2024 John Bintz. Licensed under the MIT + * License. Visit https://theindustriousrabbit.com + * for more! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BPLCON0_COLOR (0x200) + +// We only need four fields from the image header +typedef struct BMHDHeader { + UWORD width; + UWORD height; + UWORD _a1; + UWORD _a2; + UBYTE planes; + UBYTE _a3; + UBYTE compression; + UBYTE _a4; + UWORD _a5; + UBYTE _a6; + UBYTE _a7; + UWORD _a8; + UWORD _a9; +}; + +// Make it very easy to load all color info at +// once. +typedef struct ILBMColor { + UBYTE red; + UBYTE green; + UBYTE blue; +}; + +extern struct GfBase *GfxBase; +extern struct Custom far custom; +extern struct CIA far ciaa,ciab; + +// The code below follows the same pattern as the +// PC DOS IFF ILBM reader. +UBYTE charBuffer[8]; +ULONG chunkLength; + +struct ILBMColor palette[32]; +struct BMHDHeader bmhdHeader; + +void readChunk(BPTR fh) { + FRead(fh, &charBuffer, 8, 1); + chunkLength = *(ULONG *)(charBuffer + 4); +} + +BYTE checkChunkType(char type[]) { + BYTE i; + for (i = 0; i < 4; ++i) { + if (type[i] != charBuffer[i]) { + return 0; + } + + } + + return 1; +} + +int main(void) { + int color, plane, imageModulo; + BYTE keepReading = 1; + void *imageData, *copperlist; + UWORD *copperlist_ptr; + + struct View *OldView = ((struct GfxBase *)GfxBase)->ActiView; + ULONG OldCopper = (ULONG)((struct GfxBase *)GfxBase)->copinit; + ULONG OldDMACON, OldINTENA, OldINTREQ, OldADKCON; + + // this is a 320x200x32 uncompressed IFF ILBM image. + BPTR fh = Open("chicken art 32 uncompressed.iff", MODE_OLDFILE); + + readChunk(fh); + + if (!checkChunkType("FORM")) { + printf("Not IFF\n"); + Close(fh); + return 1; + } + + FRead(fh, &charBuffer, 4, 1); + if (!checkChunkType("ILBM")) { + printf("Not ILBM\n"); + Close(fh); + return 1; + } + + while (keepReading) { + keepReading = 0; + + readChunk(fh); + printf("Chunk length: %d\n", chunkLength); + if (checkChunkType("BMHD")) { + FRead(fh, &bmhdHeader, sizeof(struct BMHDHeader), 1); + printf("Width: %d\n", bmhdHeader.width); + printf("Height: %d\n", bmhdHeader.height); + printf("Bitplanes: %d\n", bmhdHeader.planes); + printf("Compression: %d\n", bmhdHeader.compression); + + if (bmhdHeader.compression == 1) { + printf("Can't read compressed files\n"); + Close(fh); + return 1; + } + + keepReading = 1; + } + + if (checkChunkType("CMAP")) { + printf("Colors: %d\n", 1 << bmhdHeader.planes); + FRead(fh, &palette, sizeof(struct ILBMColor), 1 << bmhdHeader.planes); + + for (color = 0; color < (1 << bmhdHeader.planes); ++color) { + printf("Color %d: %d %d %d\n", color, palette[color].red, palette[color].green, palette[color].blue); + } + keepReading = 1; + } + + if (checkChunkType("BODY")) { + printf("Made it to BODY\n"); + + // read raw image data into Chip RAM for Agnus + imageData = AllocMem(chunkLength, MEMF_CHIP | MEMF_CLEAR); + FRead(fh, imageData, chunkLength, 1); + + // The modulo is what makes this work. While Agnus is + // reading data from the bitplane pointers, those pointer + // values are increasing (See "Amiga Rasterbars are Cool" + // to observe this in action). At the end of the display + // line, Agnus will then add the modulo vaule to the + // bitplane pointer. This allows you to interleave image + // data, row by row, in memory, rather than having each + // bitplane be a separate chunk of memory. + imageModulo = (bmhdHeader.width + 8 - 1) / 8; + printf("Modulo: %d\n", imageModulo); + + OldDMACON = custom.dmaconr | 0x8000; + OldINTENA = custom.intenar | 0x8000; + OldINTREQ = custom.intreqr | 0x8000; + OldADKCON = custom.adkconr | 0x8000; + + // disable interrupts + custom.intena = 0xc000; + custom.intena = 0x3fff; + + // set up dma + custom.dmacon = DMAF_SETCLR | DMAF_COPPER | DMAF_RASTER; + custom.dmacon = DMAF_AUDIO | DMAF_DISK | DMAF_SPRITE | DMAF_BLITTER; + + // take control + LoadView(NULL); + WaitTOF(); + WaitTOF(); + OwnBlitter(); + WaitBlit(); + Forbid(); + + // Activate color rendering for as many bitplanes in the file + custom.bplcon0 = BPLCON0_COLOR | (bmhdHeader.planes << 12); + custom.bplcon1 = 0; + + // The modulo is being added to the pointer once a display line + // has been read. We need to skip (number of planes - 1) rows down + // to get to the start of the next line for the bitplane in question. + custom.bpl1mod = imageModulo * (bmhdHeader.planes - 1); + custom.bpl2mod = imageModulo * (bmhdHeader.planes - 1); + + // 320x256 display + custom.diwstrt = 0x2c21; + custom.diwstop = 0x2cc1; + custom.ddfstrt = 0x0038; + custom.ddfstop = 0x00d0; + + // create our copperlist + copperlist = AllocMem(2000, MEMF_CHIP | MEMF_CLEAR); + copperlist_ptr = (UWORD *)copperlist; + + // bitplane pointers + for (plane = 0; plane < bmhdHeader.planes; ++plane) { + // We use the modulos here as well, to start each bitplane pointer on + // the correct row of data. + *(copperlist_ptr++) = 0x0E0 + (plane * 4); + *(copperlist_ptr++) = (((ULONG)imageData + plane * imageModulo) >> 16) & 0xffff; + + *(copperlist_ptr++) = 0x0E2 + (plane * 4); + *(copperlist_ptr++) = (((ULONG)imageData + plane * imageModulo)) & 0xffff; + } + + // palette + for (color = 0; color < (1 << bmhdHeader.planes); ++color) { + *(copperlist_ptr++) = 0x180 + color * 2; + *(copperlist_ptr++) = (palette[color].red >> 4) << 8 | + (palette[color].green >> 4) << 4 | + (palette[color].blue >> 4); + } + + *(copperlist_ptr++) = 0xffff; + *(copperlist_ptr++) = 0xfffe; + + custom.cop1lc = (ULONG)copperlist; + + // wait for a mouse click + while ((ciaa.ciapra >> 6) == 3) {} + + // put everything back + custom.dmacon = 0x7FFF; + custom.dmacon = OldDMACON; + custom.intena = 0x7FFF; + custom.intena = OldINTENA; + custom.intreq = 0x7FFF; + custom.intreq = OldINTREQ; + custom.adkcon = 0x7FFF; + custom.adkcon = OldADKCON; + + custom.cop1lc = OldCopper; + LoadView(OldView); + WaitTOF(); + WaitTOF(); + WaitBlit(); + DisownBlitter(); + Permit(); + + // free the RAM we allocated + FreeMem(copperlist, 2000); + FreeMem(imageData, chunkLength); + } else { + if (!keepReading) { + // ignore every other chunk + if (chunkLength & 1 == 1) chunkLength++; + if (Seek(fh, chunkLength, OFFSET_CURRENT) != -1) { + keepReading = 1; + } + } + } + } + + Close(fh); +} + diff --git a/vgapal.c b/vgapal.c new file mode 100644 index 0000000..bf03d4c --- /dev/null +++ b/vgapal.c @@ -0,0 +1,36 @@ +/* + * Cycle through every greyscale value that VGA can handle. + * + * Copyright 2024 John Bintz. Licensed under the MIT + * License. Visit https://theindustriousrabbit.com + * for more! + */ + +#include +#include + +char far *VGA = (char*)0xA0000; + +int main(void) { + union REGS regs; + int i; + + regs.h.ah = 0x00; + regs.h.al = 0x13; + int386(0x10, ®s, ®s); + + for (i = 0; i < 63; ++i) { + outp(0x3c8, 0); + outp(0x3c9, i); + outp(0x3c9, i); + outp(0x3c9, i); + + delay(10); + } + + regs.h.ah = 0x00; + regs.h.al = 0x03; + int386(0x10, ®s, ®s); + + return 0; +}