Add portable utilities for creating tiles, add AmigaFont to symbols
- bmp2iff_host: convert nhtiles.bmp to Amiga IFF tile files. Uses the AMIV UI palette in pens 0-15, remaining pens filled with tile colors sorted by frequency. Usage: bmp2iff_host -planes N input.bmp output.iff - xpm2iff_host: convert XPM to IFF for tomb.iff (RIP screen). Adapted from xpm2iff.c, Copyright (c) 1995 Gregg Wonderly. - Auto-select tiles32.iff (5 planes) or tiles16.iff (4 planes) based on screen color depth at runtime. - Fix NO_GLYPH in amiv_lprint_glyph: return early to prevent blitting with uninitialised data (caused black spots). - Add AmigaFont symbol set to dat/symbols for AMII text mode.
This commit is contained in:
@@ -73,8 +73,8 @@ Cross-compilation from Linux using bebbo's m68k-amigaos-gcc toolchain
|
||||
# Install toolchain to /opt/amiga
|
||||
# Clone NetHack 3.7 source
|
||||
cd NetHack
|
||||
make fetch-lua
|
||||
sys/unix/setup.sh sys/unix/hints/linux.370
|
||||
make fetch-lua
|
||||
make CROSS_TO_AMIGA=1 all
|
||||
make CROSS_TO_AMIGA=1 package
|
||||
|
||||
|
||||
480
sys/amiga/bmp2iff_host.c
Normal file
480
sys/amiga/bmp2iff_host.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* bmp2iff_host.c - Convert a BMP to Amiga BMAP IFF.
|
||||
* Copyright (c) 2026 by Ingo Paschke.
|
||||
* NetHack may be freely redistributed. See license for details.
|
||||
*
|
||||
* IFF BMAP format matches sys/amiga/xpm2iff.c by Gregg Wonderly.
|
||||
*
|
||||
* Usage: bmp2iff_host -planes N input.bmp output.iff
|
||||
*
|
||||
* This is a HOST tool -- runs on the build machine.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TILE_X 16
|
||||
#define TILE_Y 16
|
||||
|
||||
#pragma pack(push,1)
|
||||
typedef struct {
|
||||
uint16_t bfType;
|
||||
uint32_t bfSize;
|
||||
uint16_t bfReserved1, bfReserved2;
|
||||
uint32_t bfOffBits;
|
||||
} BMPFILEHEADER;
|
||||
|
||||
typedef struct {
|
||||
uint32_t biSize;
|
||||
int32_t biWidth, biHeight;
|
||||
uint16_t biPlanes, biBitCount;
|
||||
uint32_t biCompression;
|
||||
uint32_t biSizeImage;
|
||||
int32_t biXPelsPerMeter, biYPelsPerMeter;
|
||||
uint32_t biClrUsed, biClrImportant;
|
||||
} BMPINFOHEADER;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t r, g, b;
|
||||
} RGB;
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* Fixed 16-color AMIV UI palette */
|
||||
/* Must match amiv_init_map[] in winami.c */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
static const RGB amiv_pal[16] = {
|
||||
{0x00,0x00,0x00}, /* 0 black */
|
||||
{0xFF,0xFF,0xFF}, /* 1 white */
|
||||
{0x00,0xBB,0xFF}, /* 2 cyan */
|
||||
{0xFF,0x66,0x00}, /* 3 orange */
|
||||
{0x00,0x00,0xFF}, /* 4 blue */
|
||||
{0x00,0x99,0x00}, /* 5 green */
|
||||
{0x66,0x99,0xBB}, /* 6 grey */
|
||||
{0xFF,0x00,0x00}, /* 7 red */
|
||||
{0x66,0xFF,0x00}, /* 8 ltgreen */
|
||||
{0xFF,0xFF,0x00}, /* 9 yellow */
|
||||
{0xFF,0x00,0xFF}, /* 10 magenta */
|
||||
{0x99,0x44,0x00}, /* 11 brown */
|
||||
{0x44,0x66,0x66}, /* 12 greyblue */
|
||||
{0xCC,0x44,0x00}, /* 13 ltbrown */
|
||||
{0xDD,0xDD,0xBB}, /* 14 ltgrey */
|
||||
{0xFF,0xBB,0x99}, /* 15 peach */
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* Colour helpers */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
static int
|
||||
coldist(const RGB *a, const RGB *b)
|
||||
{
|
||||
int dr = a->r - b->r;
|
||||
int dg = a->g - b->g;
|
||||
int db = a->b - b->b;
|
||||
return dr*dr + dg*dg + db*db;
|
||||
}
|
||||
|
||||
static int
|
||||
nearest(const RGB *c, const RGB *pal, int n)
|
||||
{
|
||||
int best = 0, bestd = 0x7fffffff, i;
|
||||
for (i = 0; i < n; i++) {
|
||||
int d = coldist(c, &pal[i]);
|
||||
if (d < bestd) { bestd = d; best = i; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* IFF output */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
static FILE *iff_fp;
|
||||
|
||||
static void
|
||||
wr32(uint32_t v)
|
||||
{
|
||||
uint8_t b[4] = { v>>24, v>>16, v>>8, v };
|
||||
fwrite(b, 1, 4, iff_fp);
|
||||
}
|
||||
|
||||
static void
|
||||
wr_chunk(const char *id, const void *d, uint32_t len)
|
||||
{
|
||||
fwrite(id, 1, 4, iff_fp);
|
||||
wr32(len);
|
||||
fwrite(d, 1, len, iff_fp);
|
||||
if (len & 1) fputc(0, iff_fp);
|
||||
}
|
||||
|
||||
static void
|
||||
iff_write(int nplanes, int ncolors, uint8_t *cmap,
|
||||
int w, int h, uint8_t **planes,
|
||||
int ntiles, int across, int down)
|
||||
{
|
||||
long spos;
|
||||
uint32_t plsz = (uint32_t)(w / 8) * h;
|
||||
int i;
|
||||
|
||||
fwrite("FORM", 1, 4, iff_fp);
|
||||
spos = ftell(iff_fp);
|
||||
wr32(0);
|
||||
fwrite("BMAP", 1, 4, iff_fp);
|
||||
|
||||
/* BMHD */
|
||||
{
|
||||
uint8_t bm[20];
|
||||
memset(bm, 0, 20);
|
||||
bm[0] = w >> 8; bm[1] = w;
|
||||
bm[2] = h >> 8; bm[3] = h;
|
||||
bm[8] = (uint8_t)nplanes;
|
||||
bm[14] = 100; bm[15] = 100;
|
||||
wr_chunk("BMHD", bm, 20);
|
||||
}
|
||||
|
||||
/* CAMG */
|
||||
{
|
||||
uint8_t c[4] = {0,0,0x80,0x04};
|
||||
wr_chunk("CAMG", c, 4);
|
||||
}
|
||||
|
||||
/* CMAP */
|
||||
wr_chunk("CMAP", cmap, (uint32_t)ncolors * 3);
|
||||
|
||||
/* PDAT */
|
||||
{
|
||||
uint8_t pd[28], *p = pd;
|
||||
uint32_t v[7];
|
||||
v[0] = nplanes;
|
||||
v[1] = plsz;
|
||||
v[2] = across;
|
||||
v[3] = down;
|
||||
v[4] = ntiles;
|
||||
v[5] = TILE_X;
|
||||
v[6] = TILE_Y;
|
||||
for (i = 0; i < 7; i++) {
|
||||
p[0] = (v[i]>>24); p[1] = (v[i]>>16);
|
||||
p[2] = (v[i]>> 8); p[3] = v[i];
|
||||
p += 4;
|
||||
}
|
||||
wr_chunk("PDAT", pd, 28);
|
||||
}
|
||||
|
||||
/* PLNE */
|
||||
fwrite("PLNE", 1, 4, iff_fp);
|
||||
wr32(plsz * nplanes);
|
||||
for (i = 0; i < nplanes; i++)
|
||||
fwrite(planes[i], 1, plsz, iff_fp);
|
||||
|
||||
/* fix FORM size */
|
||||
{
|
||||
long end = ftell(iff_fp);
|
||||
uint32_t sz = (uint32_t)(end - spos - 4);
|
||||
fseek(iff_fp, spos, SEEK_SET);
|
||||
wr32(sz);
|
||||
fseek(iff_fp, 0, SEEK_END);
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* Pixel-to-bitplane conversion */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
to_planes(uint8_t *pix, int w, int h,
|
||||
int np, uint8_t **pl)
|
||||
{
|
||||
int rb = w / 8;
|
||||
int x, y, p;
|
||||
|
||||
for (p = 0; p < np; p++)
|
||||
memset(pl[p], 0, rb * h);
|
||||
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = 0; x < w; x++) {
|
||||
uint8_t v = pix[y * w + x];
|
||||
int off = y * rb + x / 8;
|
||||
int bit = 7 - (x & 7);
|
||||
for (p = 0; p < np; p++)
|
||||
if (v & (1 << p))
|
||||
pl[p][off] |= (1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* Palette building */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Build an output palette of 'maxcol' entries:
|
||||
* - slots 0-15: fixed AMIV UI colors
|
||||
* - slots 16+: tile colors from the BMP
|
||||
*
|
||||
* Returns remap[0..nsrc-1] mapping BMP palette index
|
||||
* to output palette index.
|
||||
*/
|
||||
static void
|
||||
build_palette(const RGB *src, int nsrc,
|
||||
const uint8_t *pix, int npix,
|
||||
int maxcol,
|
||||
RGB *out, int *remap)
|
||||
{
|
||||
int freq[256] = {0};
|
||||
int order[256];
|
||||
int i, j, nfree, nuniq;
|
||||
|
||||
/* count pixel frequency per BMP palette entry */
|
||||
for (i = 0; i < npix; i++)
|
||||
freq[pix[i]]++;
|
||||
|
||||
/* sort BMP colors by frequency (descending) */
|
||||
for (i = 0; i < nsrc; i++) order[i] = i;
|
||||
for (i = 1; i < nsrc; i++) {
|
||||
int k = order[i], kf = freq[k];
|
||||
j = i - 1;
|
||||
while (j >= 0 && freq[order[j]] < kf) {
|
||||
order[j+1] = order[j];
|
||||
j--;
|
||||
}
|
||||
order[j+1] = k;
|
||||
}
|
||||
|
||||
/* count unique colors actually used */
|
||||
nuniq = 0;
|
||||
for (i = 0; i < nsrc; i++)
|
||||
if (freq[i] > 0) nuniq++;
|
||||
|
||||
/* first 16 slots are AMIV UI */
|
||||
for (i = 0; i < 16 && i < maxcol; i++)
|
||||
out[i] = amiv_pal[i];
|
||||
for (i = 16; i < maxcol; i++)
|
||||
memset(&out[i], 0, sizeof(RGB));
|
||||
|
||||
nfree = maxcol - 16;
|
||||
if (nfree < 0) nfree = 0;
|
||||
|
||||
/*
|
||||
* Case 1: enough free slots for all unique colors.
|
||||
* Assign each unique BMP color its own pen, exact
|
||||
* AMIV matches share the UI pen.
|
||||
*/
|
||||
if (nuniq <= nfree) {
|
||||
int next = 16;
|
||||
for (i = 0; i < nsrc; i++) {
|
||||
if (freq[i] == 0) {
|
||||
remap[i] = 0;
|
||||
continue;
|
||||
}
|
||||
/* exact match to an AMIV pen? */
|
||||
int best = nearest(&src[i], amiv_pal, 16);
|
||||
if (coldist(&src[i], &amiv_pal[best]) == 0) {
|
||||
remap[i] = best;
|
||||
} else {
|
||||
remap[i] = next;
|
||||
out[next] = src[i];
|
||||
next++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Case 2: more unique colors than free slots.
|
||||
* - Direct/near AMIV matches use UI pens.
|
||||
* - Remaining slots filled by most-frequent colors.
|
||||
* - Leftovers mapped to nearest in final palette.
|
||||
*/
|
||||
{
|
||||
int next = 16;
|
||||
int assigned[256];
|
||||
memset(assigned, 0, sizeof(assigned));
|
||||
|
||||
/* pass 1: exact/near AMIV matches */
|
||||
for (i = 0; i < nsrc; i++) {
|
||||
if (freq[i] == 0) {
|
||||
remap[i] = 0;
|
||||
assigned[i] = 1;
|
||||
continue;
|
||||
}
|
||||
int best = nearest(&src[i], amiv_pal, 16);
|
||||
int d = coldist(&src[i], &amiv_pal[best]);
|
||||
if (d < 200) { /* near match threshold */
|
||||
remap[i] = best;
|
||||
assigned[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* pass 2: fill free slots with most-frequent
|
||||
* unassigned colors (order[] is freq-sorted) */
|
||||
for (i = 0; i < nsrc && next < maxcol; i++) {
|
||||
int idx = order[i];
|
||||
if (assigned[idx] || freq[idx] == 0)
|
||||
continue;
|
||||
remap[idx] = next;
|
||||
out[next] = src[idx];
|
||||
assigned[idx] = 1;
|
||||
next++;
|
||||
}
|
||||
|
||||
/* pass 3: map remaining to nearest in palette */
|
||||
for (i = 0; i < nsrc; i++) {
|
||||
if (!assigned[i])
|
||||
remap[i] = nearest(&src[i], out, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* Main */
|
||||
/* --------------------------------------------------------- */
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
FILE *bmpfp;
|
||||
BMPFILEHEADER fhdr;
|
||||
BMPINFOHEADER ihdr;
|
||||
RGB palette[256];
|
||||
int ncolors, img_w, img_h, rowstride;
|
||||
uint8_t *bmpdata, *pixels;
|
||||
int ntiles, across, down;
|
||||
int nplanes, maxcol;
|
||||
int i, y;
|
||||
RGB outpal[256];
|
||||
int remap[256];
|
||||
uint8_t *remapped;
|
||||
uint8_t *plane_data[8];
|
||||
uint8_t cmap_rgb[256 * 3];
|
||||
int planesize;
|
||||
|
||||
/* parse args */
|
||||
if (argc != 5
|
||||
|| strcmp(argv[1], "-planes") != 0) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s -planes N input.bmp output.iff\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
nplanes = atoi(argv[2]);
|
||||
if (nplanes < 1 || nplanes > 8) {
|
||||
fprintf(stderr, "planes must be 1-8\n");
|
||||
return 1;
|
||||
}
|
||||
maxcol = 1 << nplanes;
|
||||
|
||||
/* read BMP */
|
||||
bmpfp = fopen(argv[3], "rb");
|
||||
if (!bmpfp) { perror(argv[3]); return 1; }
|
||||
|
||||
if (fread(&fhdr, sizeof(fhdr), 1, bmpfp) != 1
|
||||
|| fread(&ihdr, sizeof(ihdr), 1, bmpfp) != 1) {
|
||||
fprintf(stderr, "Failed to read BMP header\n");
|
||||
return 1;
|
||||
}
|
||||
if (fhdr.bfType != 0x4D42) {
|
||||
fprintf(stderr, "Not a BMP file\n");
|
||||
return 1;
|
||||
}
|
||||
if (ihdr.biBitCount != 8) {
|
||||
fprintf(stderr,
|
||||
"Expected 8-bit BMP, got %d-bit\n",
|
||||
ihdr.biBitCount);
|
||||
return 1;
|
||||
}
|
||||
|
||||
img_w = ihdr.biWidth;
|
||||
img_h = abs(ihdr.biHeight);
|
||||
ncolors = ihdr.biClrUsed ? ihdr.biClrUsed : 256;
|
||||
if (ncolors > 256) ncolors = 256;
|
||||
|
||||
/* read palette (BMP stores BGRx) */
|
||||
{
|
||||
uint8_t raw[256][4];
|
||||
if (fread(raw, 4, ncolors, bmpfp)
|
||||
!= (size_t)ncolors) {
|
||||
fprintf(stderr, "Failed to read palette\n");
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < ncolors; i++) {
|
||||
palette[i].r = raw[i][2];
|
||||
palette[i].g = raw[i][1];
|
||||
palette[i].b = raw[i][0];
|
||||
}
|
||||
}
|
||||
|
||||
/* read pixel data */
|
||||
rowstride = (img_w + 3) & ~3;
|
||||
bmpdata = malloc(rowstride * img_h);
|
||||
fseek(bmpfp, fhdr.bfOffBits, SEEK_SET);
|
||||
if (fread(bmpdata, 1, rowstride * img_h, bmpfp)
|
||||
!= (size_t)(rowstride * img_h)) {
|
||||
fprintf(stderr, "Failed to read pixel data\n");
|
||||
return 1;
|
||||
}
|
||||
fclose(bmpfp);
|
||||
|
||||
/* flip bottom-up to top-down */
|
||||
pixels = malloc(img_w * img_h);
|
||||
if (ihdr.biHeight > 0) {
|
||||
for (y = 0; y < img_h; y++)
|
||||
memcpy(pixels + y * img_w,
|
||||
bmpdata + (img_h-1-y) * rowstride,
|
||||
img_w);
|
||||
} else {
|
||||
for (y = 0; y < img_h; y++)
|
||||
memcpy(pixels + y * img_w,
|
||||
bmpdata + y * rowstride, img_w);
|
||||
}
|
||||
free(bmpdata);
|
||||
|
||||
across = img_w / TILE_X;
|
||||
down = img_h / TILE_Y;
|
||||
ntiles = across * down;
|
||||
|
||||
/* build palette and remap pixels */
|
||||
build_palette(palette, ncolors,
|
||||
pixels, img_w * img_h,
|
||||
maxcol, outpal, remap);
|
||||
|
||||
remapped = malloc(img_w * img_h);
|
||||
for (i = 0; i < img_w * img_h; i++)
|
||||
remapped[i] = (uint8_t)remap[pixels[i]];
|
||||
|
||||
/* convert to bitplanes */
|
||||
planesize = (img_w / 8) * img_h;
|
||||
for (i = 0; i < nplanes; i++)
|
||||
plane_data[i] = calloc(1, planesize);
|
||||
|
||||
to_planes(remapped, img_w, img_h,
|
||||
nplanes, plane_data);
|
||||
|
||||
/* build CMAP */
|
||||
for (i = 0; i < maxcol; i++) {
|
||||
cmap_rgb[i*3+0] = outpal[i].r;
|
||||
cmap_rgb[i*3+1] = outpal[i].g;
|
||||
cmap_rgb[i*3+2] = outpal[i].b;
|
||||
}
|
||||
|
||||
/* write IFF */
|
||||
iff_fp = fopen(argv[4], "wb");
|
||||
if (!iff_fp) { perror(argv[4]); return 1; }
|
||||
|
||||
iff_write(nplanes, maxcol, cmap_rgb,
|
||||
img_w, img_h, plane_data,
|
||||
ntiles, across, down);
|
||||
fclose(iff_fp);
|
||||
|
||||
printf("%s: %dx%d, %d colors (%d planes), "
|
||||
"%d tiles\n",
|
||||
argv[4], img_w, img_h,
|
||||
maxcol, nplanes, ntiles);
|
||||
|
||||
for (i = 0; i < nplanes; i++) free(plane_data[i]);
|
||||
free(remapped);
|
||||
free(pixels);
|
||||
return 0;
|
||||
}
|
||||
346
sys/amiga/xpm2iff_host.c
Normal file
346
sys/amiga/xpm2iff_host.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/* xpm2iff_host.c - host-side .xpm -> Amiga BMAP IFF converter.
|
||||
* Copyright (c) 2026 by Ingo Paschke.
|
||||
* NetHack may be freely redistributed. See license for details.
|
||||
*
|
||||
* Adapted from sys/amiga/xpm2iff.c, Copyright (c) 1995 by Gregg Wonderly.
|
||||
* Rewritten for host-side cross-compilation using POSIX file I/O with
|
||||
* explicit big-endian output instead of AmigaOS IFFParse library calls.
|
||||
*
|
||||
* Input: an XPM2 file, 1 char per pixel.
|
||||
* Output: a BMAP IFF file readable by sys/amiga/winchar.c (tomb.iff).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* XPM screen descriptor and color translation table
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static struct {
|
||||
int Width;
|
||||
int Height;
|
||||
int Colors;
|
||||
int BytesPerRow;
|
||||
} XpmScreen;
|
||||
|
||||
/* Translation table: indexed by the single XPM character code.
|
||||
* slot = output palette index (0-based)
|
||||
* flag = 1 if this entry is valid
|
||||
* r,g,b = RGB components (0-255) */
|
||||
static struct {
|
||||
unsigned char flag;
|
||||
unsigned char r, g, b;
|
||||
int slot;
|
||||
} ttable[256];
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* XPM parsing
|
||||
* Adapted directly from sys/amiga/xpm2iff.c (already POSIX).
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static FILE *xpmfh;
|
||||
|
||||
#define XBUFSZ 2048
|
||||
static char xbuf[XBUFSZ];
|
||||
|
||||
/* Read the next quoted line from the XPM file.
|
||||
* Returns a pointer to the content between the first pair of double-quotes,
|
||||
* or NULL on EOF. Trailing ", and whitespace are stripped. */
|
||||
static char *
|
||||
xpmgetline(void)
|
||||
{
|
||||
char *bp;
|
||||
do {
|
||||
if (fgets(xbuf, XBUFSZ, xpmfh) == NULL)
|
||||
return NULL;
|
||||
} while (xbuf[0] != '"');
|
||||
|
||||
/* strip trailing <",> and whitespace */
|
||||
for (bp = xbuf; *bp; bp++)
|
||||
;
|
||||
bp--;
|
||||
while (isspace((unsigned char)*bp))
|
||||
bp--;
|
||||
if (*bp == ',')
|
||||
bp--;
|
||||
if (*bp == '"')
|
||||
bp--;
|
||||
bp++;
|
||||
*bp = '\0';
|
||||
|
||||
return &xbuf[1];
|
||||
}
|
||||
|
||||
/* Open an XPM file and parse its header + color table.
|
||||
* Populates XpmScreen and ttable[].
|
||||
* Returns 1 on success, 0 on failure. */
|
||||
static int
|
||||
fopen_xpm_file(const char *fn)
|
||||
{
|
||||
int temp;
|
||||
char *xb;
|
||||
|
||||
xpmfh = fopen(fn, "r");
|
||||
if (!xpmfh)
|
||||
return 0;
|
||||
|
||||
/* read dimensions header: "W H Colors 1" */
|
||||
xb = xpmgetline();
|
||||
if (!xb)
|
||||
return 0;
|
||||
if (sscanf(xb, "%d %d %d %d",
|
||||
&XpmScreen.Width, &XpmScreen.Height,
|
||||
&XpmScreen.Colors, &temp) != 4)
|
||||
return 0;
|
||||
if (temp != 1) {
|
||||
fprintf(stderr, "xpm2iff_host: only 1 char/pixel XPM files supported\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read color map: "%c c #rrggbb" */
|
||||
{
|
||||
int ccount = 0;
|
||||
while (ccount < XpmScreen.Colors) {
|
||||
char idx;
|
||||
int r, g, b;
|
||||
xb = xpmgetline();
|
||||
if (!xb)
|
||||
return 0;
|
||||
if (sscanf(xb, "%c c #%2x%2x%2x", &idx, &r, &g, &b) != 4) {
|
||||
fprintf(stderr, "xpm2iff_host: bad color entry: %s\n", xb);
|
||||
return 0;
|
||||
}
|
||||
ttable[(unsigned char)idx].flag = 1;
|
||||
ttable[(unsigned char)idx].r = (unsigned char)r;
|
||||
ttable[(unsigned char)idx].g = (unsigned char)g;
|
||||
ttable[(unsigned char)idx].b = (unsigned char)b;
|
||||
ttable[(unsigned char)idx].slot = ccount;
|
||||
ccount++;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Bitplane packing
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static char **planes;
|
||||
|
||||
#define SETBIT(plane, plane_offset, col, value) \
|
||||
do { \
|
||||
if (value) \
|
||||
planes[plane][plane_offset + ((col) / 8)] \
|
||||
|= (char)(1 << (7 - ((col) & 7))); \
|
||||
} while (0)
|
||||
|
||||
static void
|
||||
conv_image(int nplanes)
|
||||
{
|
||||
int row, col, planeno;
|
||||
|
||||
for (row = 0; row < XpmScreen.Height; row++) {
|
||||
char *xb = xpmgetline();
|
||||
int plane_offset;
|
||||
if (!xb)
|
||||
return;
|
||||
plane_offset = row * XpmScreen.BytesPerRow;
|
||||
for (col = 0; col < XpmScreen.Width; col++) {
|
||||
int color = (unsigned char)xb[col];
|
||||
int slot;
|
||||
if (!ttable[color].flag) {
|
||||
fprintf(stderr, "xpm2iff_host: bad image data at row %d col %d\n",
|
||||
row, col);
|
||||
continue;
|
||||
}
|
||||
slot = ttable[color].slot;
|
||||
for (planeno = 0; planeno < nplanes; planeno++)
|
||||
SETBIT(planeno, plane_offset, col, slot & (1 << planeno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Big-endian IFF output helpers
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static FILE *iff_out;
|
||||
|
||||
static void
|
||||
wr32(uint32_t v)
|
||||
{
|
||||
fputc((v >> 24) & 0xff, iff_out);
|
||||
fputc((v >> 16) & 0xff, iff_out);
|
||||
fputc((v >> 8) & 0xff, iff_out);
|
||||
fputc( v & 0xff, iff_out);
|
||||
}
|
||||
|
||||
static void
|
||||
write_chunk(const char *id, const void *data, uint32_t size)
|
||||
{
|
||||
fwrite(id, 1, 4, iff_out);
|
||||
wr32(size);
|
||||
fwrite(data, 1, size, iff_out);
|
||||
if (size & 1)
|
||||
fputc(0, iff_out);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* main
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i, nplanes, colors;
|
||||
uint32_t pbytes, plne_size, form_size;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s source.xpm destination.iff\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!fopen_xpm_file(argv[1])) {
|
||||
fprintf(stderr, "%s: failed to open or parse XPM file\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* nplanes = ceil(log2(Colors)) */
|
||||
nplanes = 0;
|
||||
i = XpmScreen.Colors - 1;
|
||||
while (i > 0) { nplanes++; i >>= 1; }
|
||||
|
||||
colors = 1 << nplanes;
|
||||
|
||||
XpmScreen.BytesPerRow = ((XpmScreen.Width + 15) / 16) * 2;
|
||||
pbytes = (uint32_t)XpmScreen.BytesPerRow * (uint32_t)XpmScreen.Height;
|
||||
|
||||
/* Allocate zero-initialised bitplane buffers */
|
||||
planes = malloc(nplanes * sizeof(char *));
|
||||
if (!planes) { perror("malloc"); return 1; }
|
||||
for (i = 0; i < nplanes; ++i) {
|
||||
planes[i] = calloc(1, pbytes);
|
||||
if (!planes[i]) { perror("calloc"); return 1; }
|
||||
}
|
||||
|
||||
/* Pack pixel data into bitplanes */
|
||||
conv_image(nplanes);
|
||||
fclose(xpmfh);
|
||||
|
||||
/* Open output IFF file */
|
||||
iff_out = fopen(argv[2], "wb");
|
||||
if (!iff_out) { perror(argv[2]); return 1; }
|
||||
|
||||
/* Pre-compute FORM size:
|
||||
* 4 (BMAP type tag)
|
||||
* 8 + 20 (BMHD)
|
||||
* 8 + 4 (CAMG)
|
||||
* 8 + colors*3 (CMAP; colors is a power of 2, so colors*3 is even)
|
||||
* 8 + 28 (PDAT: 7 x uint32_t)
|
||||
* 8 + nplanes*pbytes (PLNE; pbytes is always even)
|
||||
*/
|
||||
plne_size = (uint32_t)nplanes * pbytes;
|
||||
form_size = 4
|
||||
+ (8 + 20)
|
||||
+ (8 + 4)
|
||||
+ (8 + (uint32_t)colors * 3)
|
||||
+ (8 + 28)
|
||||
+ (8 + plne_size);
|
||||
|
||||
/* FORM header */
|
||||
fwrite("FORM", 1, 4, iff_out);
|
||||
wr32(form_size);
|
||||
fwrite("BMAP", 1, 4, iff_out);
|
||||
|
||||
/* BMHD chunk */
|
||||
{
|
||||
uint8_t bmhd[20];
|
||||
uint8_t *p = bmhd;
|
||||
uint16_t w = (uint16_t)XpmScreen.Width;
|
||||
uint16_t h = (uint16_t)XpmScreen.Height;
|
||||
|
||||
p[0] = w >> 8; p[1] = w & 0xff; p += 2; /* w */
|
||||
p[0] = h >> 8; p[1] = h & 0xff; p += 2; /* h */
|
||||
memset(p, 0, 4); p += 4; /* x=0, y=0 */
|
||||
*p++ = (uint8_t)nplanes; /* nPlanes */
|
||||
*p++ = 0; /* masking: none */
|
||||
*p++ = 0; /* compression: none */
|
||||
*p++ = 0; /* reserved1 */
|
||||
p[0] = 0; p[1] = 0; p += 2; /* transparentColor */
|
||||
*p++ = 100; /* xAspect */
|
||||
*p++ = 100; /* yAspect */
|
||||
p[0] = 0; p[1] = 0; p += 2; /* pageWidth (not used) */
|
||||
p[0] = 0; p[1] = 0; p += 2; /* pageHeight (not used) */
|
||||
|
||||
write_chunk("BMHD", bmhd, 20);
|
||||
}
|
||||
|
||||
/* CAMG chunk: HIRES | LACE = 0x00008004 */
|
||||
{
|
||||
uint8_t camg[4] = { 0x00, 0x00, 0x80, 0x04 };
|
||||
write_chunk("CAMG", camg, 4);
|
||||
}
|
||||
|
||||
/* CMAP chunk: built from ttable (no color reordering for XPM images) */
|
||||
{
|
||||
uint8_t *cmap = calloc(colors, 3);
|
||||
if (!cmap) { perror("calloc"); return 1; }
|
||||
for (i = 0; i < 256; i++) {
|
||||
if (ttable[i].flag) {
|
||||
int s = ttable[i].slot;
|
||||
cmap[s * 3 + 0] = ttable[i].r;
|
||||
cmap[s * 3 + 1] = ttable[i].g;
|
||||
cmap[s * 3 + 2] = ttable[i].b;
|
||||
}
|
||||
}
|
||||
write_chunk("CMAP", cmap, (uint32_t)colors * 3);
|
||||
free(cmap);
|
||||
}
|
||||
|
||||
/* PDAT chunk: 7 x uint32_t big-endian
|
||||
* nplanes, pbytes, across=0, down=0, npics=1, xsize=Width, ysize=Height */
|
||||
{
|
||||
uint32_t vals[7];
|
||||
uint8_t pdat[28];
|
||||
uint8_t *p = pdat;
|
||||
|
||||
vals[0] = (uint32_t)nplanes;
|
||||
vals[1] = pbytes;
|
||||
vals[2] = 0; /* across: not a tile sheet */
|
||||
vals[3] = 0; /* down */
|
||||
vals[4] = 1; /* npics */
|
||||
vals[5] = (uint32_t)XpmScreen.Width;
|
||||
vals[6] = (uint32_t)XpmScreen.Height;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
p[0] = (vals[i] >> 24) & 0xff;
|
||||
p[1] = (vals[i] >> 16) & 0xff;
|
||||
p[2] = (vals[i] >> 8) & 0xff;
|
||||
p[3] = vals[i] & 0xff;
|
||||
p += 4;
|
||||
}
|
||||
write_chunk("PDAT", pdat, 28);
|
||||
}
|
||||
|
||||
/* PLNE chunk: concatenated bitplane data */
|
||||
fwrite("PLNE", 1, 4, iff_out);
|
||||
wr32(plne_size);
|
||||
for (i = 0; i < nplanes; ++i)
|
||||
fwrite(planes[i], 1, pbytes, iff_out);
|
||||
if (plne_size & 1)
|
||||
fputc(0, iff_out);
|
||||
|
||||
fclose(iff_out);
|
||||
|
||||
for (i = 0; i < nplanes; ++i) free(planes[i]);
|
||||
free(planes);
|
||||
|
||||
printf("tomb.iff: %dx%d, %d colors (%d planes), %u bytes/plane\n",
|
||||
XpmScreen.Width, XpmScreen.Height, colors, nplanes,
|
||||
(unsigned)pbytes);
|
||||
return 0;
|
||||
}
|
||||
@@ -470,8 +470,8 @@ ifdef CROSS_TO_AMIGA
|
||||
#
|
||||
# Cross-compiler: https://franke.ms/git/bebbo/amiga-gcc
|
||||
# Install to /opt/amiga, then:
|
||||
# make fetch-lua
|
||||
# sys/unix/setup.sh sys/unix/hints/linux.370
|
||||
# make fetch-lua
|
||||
# make CROSS_TO_AMIGA=1 all
|
||||
# make CROSS_TO_AMIGA=1 package
|
||||
#=================================================================
|
||||
|
||||
Reference in New Issue
Block a user