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

261 lines
7.0 KiB
C

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