initial commit

This commit is contained in:
John Bintz 2024-02-04 17:09:38 -05:00
commit 1ff32316f8
6 changed files with 668 additions and 0 deletions

23
README.md Normal file
View File

@ -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.

Binary file not shown.

Binary file not shown.

349
iffilbm.c Normal file
View File

@ -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 <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;
}

260
load_uncompressed.c Normal file
View File

@ -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 <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <exec/memory.h>
#include <graphics/GfxBase.h>
#include <stdio.h>
#include <hardware/cia.h>
#include <hardware/dmabits.h>
#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);
}

36
vgapal.c Normal file
View File

@ -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 <dos.h>
#include <conio.h>
char far *VGA = (char*)0xA0000;
int main(void) {
union REGS regs;
int i;
regs.h.ah = 0x00;
regs.h.al = 0x13;
int386(0x10, &regs, &regs);
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, &regs, &regs);
return 0;
}