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