From cb0b11be112a2ddace1a56a65eb29f03bcc6d8bc Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Mon, 23 Mar 2026 20:50:14 +0100 Subject: [PATCH] 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. --- dat/symbols | 78 +++++ sys/amiga/README.amiga | 2 +- sys/amiga/bmp2iff_host.c | 480 ++++++++++++++++++++++++++ sys/amiga/xpm2iff_host.c | 346 +++++++++++++++++++ sys/unix/hints/include/cross-pre2.370 | 2 +- 5 files changed, 906 insertions(+), 2 deletions(-) create mode 100644 sys/amiga/bmp2iff_host.c create mode 100644 sys/amiga/xpm2iff_host.c diff --git a/dat/symbols b/dat/symbols index af6c04ade..f2bc486fa 100644 --- a/dat/symbols +++ b/dat/symbols @@ -895,4 +895,82 @@ start: Enhanced1 G_trwall_mines: U+251C/113-126-142 finish +start: AmigaFont + Description: Amiga hack.font line-drawing and effect characters + # Dungeon features + S_stone: \x20 + S_vwall: \xc0 + S_hwall: \xc1 + S_tlcorn: \xc2 + S_trcorn: \xc3 + S_blcorn: \xc4 + S_brcorn: \xc5 + S_crwall: \xc6 + S_tuwall: \xd8 + S_tdwall: \xd6 + S_tlwall: \xd7 + S_trwall: \xd5 + S_ndoor: \xd9 + S_vodoor: \x91 + S_hodoor: \x92 + S_vcdoor: \x93 + S_hcdoor: \x94 + S_bars: '#' + S_tree: '#' + S_room: '.' + S_darkroom: \x20 + S_corr: \xe5 + S_litcorr: \xe5 + S_upstair: '<' + S_dnstair: '>' + S_upladder: '<' + S_dnladder: '>' + S_altar: '_' + S_grave: \x5c + S_throne: '#' + S_sink: '{' + S_fountain: '}' + S_pool: '*' + S_ice: '}' + S_lava: '*' + S_vodbridge: '*' + S_hodbridge: '#' + S_vcdbridge: '#' + S_hcdbridge: '.' + S_air: '#' + S_cloud: '}' + # Traps: all default to '^' except web + S_web: '"' + # Effects + S_vbeam: \xf1 + S_hbeam: \xf0 + S_lslant: \xf2 + S_rslant: \xf3 + S_digbeam: '*' + S_flashbeam: '!' + S_boomleft: '{' + S_boomright: '}' + S_ss1: '@' + S_ss2: '&' + S_ss3: '*' + S_ss4: '#' + S_sw_tl: \xf4 + S_sw_tc: \xf5 + S_sw_tr: \xf6 + S_sw_ml: \xf7 + S_sw_mr: \xef + S_sw_bl: \xf8 + S_sw_bc: \xf9 + S_sw_br: \xfa + S_explode1: \xe6 + S_explode2: \xea + S_explode3: \xe7 + S_explode4: \xec + S_explode5: \xd4 + S_explode6: \xed + S_explode7: \xe8 + S_explode8: \xeb + S_explode9: \xe9 +finish + # symbols EOF diff --git a/sys/amiga/README.amiga b/sys/amiga/README.amiga index 165501014..8eda92c31 100644 --- a/sys/amiga/README.amiga +++ b/sys/amiga/README.amiga @@ -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 diff --git a/sys/amiga/bmp2iff_host.c b/sys/amiga/bmp2iff_host.c new file mode 100644 index 000000000..154335297 --- /dev/null +++ b/sys/amiga/bmp2iff_host.c @@ -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 +#include +#include +#include + +#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; +} diff --git a/sys/amiga/xpm2iff_host.c b/sys/amiga/xpm2iff_host.c new file mode 100644 index 000000000..589d4f56f --- /dev/null +++ b/sys/amiga/xpm2iff_host.c @@ -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 +#include +#include +#include +#include + +/* ------------------------------------------------------------------ + * 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; +} diff --git a/sys/unix/hints/include/cross-pre2.370 b/sys/unix/hints/include/cross-pre2.370 index eca47550f..d27301af1 100644 --- a/sys/unix/hints/include/cross-pre2.370 +++ b/sys/unix/hints/include/cross-pre2.370 @@ -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 #=================================================================