ref: b09e7e2594189a32716c9c202b42cd33dc8257f2
parent: 5fc2eb49dfbd74b98298494e6b153edde1f8bc62
author: qwx <qwx@sciops.net>
date: Tue Sep 22 06:51:57 EDT 2020
grp: experimental pcx palette support unless we do palette remapping like scbw does, which would add a ton of complexity to the drawing code, as well as essentially requiring a special image format or using the grp files directly and modifying them at runtime, we can only guess how to use partially transparent sprites. after countless experiments, i've settled on just generating a best-effort image with an alpha channel, for which we'll just do alpha-blending in drw, something simple and easy to implement. palette remapping is undocumented, but i'm guessing that the basic palette is on the first row, and the sprite indices are actually palette indices, changing the pixel value to a precomputed value taking into account the transparency in the sprite. these palettes are per-tileset, another source of complexity. with trial and error, we select the column with the best looking color, then compute transparency values by fitting an exponential curve with a plateau, to emulate a bias towards highly opaque values on the upper end and highly transparent values on the low end. it looks sort of ok. overall, the seems like the simplest approach to the problem, but maybe there exists a better one. i've failed to identify a better trade-off between simplicity and graphical fidelity.
--- a/utils/grp.c
+++ b/utils/grp.c
@@ -1,6 +1,7 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
+#include <memdraw.h>
#include <bio.h>
typedef struct Hdr Hdr;
@@ -12,7 +13,7 @@
u32int ofs;
};
-int split;
+int split, pcx, npal, idxonly;
Biobuf *bf;
uchar *bufp;
u32int pal[256], bgcol = 0xffff00;
@@ -31,6 +32,8 @@
void
putcol(u32int c)
{
+ if(pcx)
+ *bufp++ = c >> 24;
*bufp++ = c >> 16;
*bufp++ = c >> 8;
*bufp++ = c;
@@ -65,6 +68,60 @@
}
void
+getpcxpal(char *f)
+{
+ int i, n, a, fd;
+ uchar *buf, *bp;
+ u32int v, *p;
+ Memimage *im, *im1;
+
+ if((fd = open(f, OREAD)) < 0)
+ sysfatal("getpcxpal: %r");
+ if(memimageinit() < 0)
+ sysfatal("memimageinit: %r");
+ if((im = readmemimage(fd)) == nil)
+ sysfatal("readmemimage: %r");
+ close(fd);
+ if(im->chan != RGBA32){
+ if((im1 = allocmemimage(im->r, RGBA32)) == nil)
+ sysfatal("allocmemimage: %r");
+ memfillcolor(im1, DBlack);
+ memimagedraw(im1, im1->r, im, im->r.min, memopaque, ZP, S);
+ freememimage(im);
+ im = im1;
+ }
+ if(Dx(im->r) != 256)
+ sysfatal("invalid pcx palette: %d columns", Dx(im->r));
+ n = Dx(im->r) * Dy(im->r);
+ npal = Dy(im->r);
+ buf = emalloc(n * sizeof *pal);
+ if(unloadmemimage(im, im->r, buf, n * sizeof *pal) < 0)
+ sysfatal("unloadmemimage: %r");
+ freememimage(im);
+ /* FIXME */
+ //for(i=0, p=pal, bp=buf; i<npal; i++, p++, bp+=256*4){
+ for(i=0, p=pal, bp=buf+20*4; i<npal; i++, p++, bp+=256*4){
+ v = bp[1] << 16 | bp[2]<<8 | bp[3];
+ a = 0x7f;
+ switch(npal){
+ case 63:
+ if(i > 47)
+ a = (0xff + 1) / (1 + exp(-i + 48 - 3.4) / 0.75);
+ else
+ a = (0xff + 1) / (1 + exp(-i + 10.0) / 2.2);
+ break;
+ /* FIXME */
+ case 40: a = i < 33 ? 0xff * i / 32 : 0xff * (6 - (i - 33)) / 6; break;
+ case 32: a = 0xff * i / 30; a = a > 0xff ? 0xff : a; break;
+ case 1: break;
+ default: sysfatal("unknown palette size %d", npal);
+ }
+ *p = a<<24 | v;
+ }
+ free(buf);
+}
+
+void
getpal(char *f)
{
uchar u[3];
@@ -84,10 +141,19 @@
void
usage(void)
{
- fprint(2, "usage: %s [-s] [-b bgcol] pal pic\n", argv0);
+ fprint(2, "usage: %s [-csx] [-b bgcol] pal pic\n", argv0);
exits("usage");
}
+/* unpack a GRP file containing sprites.
+ * palette may be a plain file with 256 RGB24 entries (3*256 bytes)
+ * or a decoded PCX image serving as a palette.
+ * in that last case, palette must be provided in image(6) format,
+ * and palette indexes in the grp reference a column (palette) in
+ * the PCX image, which is used to implement transparency.
+ * we use the first column to set color and an alpha value for
+ * compositing, rather than use remapping with the PCX palette.
+ */
void
main(int argc, char **argv)
{
@@ -101,12 +167,17 @@
ARGBEGIN{
case 'b': bgcol = strtoul(EARGF(usage()), nil, 0); break;
+ case 'c': idxonly = 1; break;
case 's': split = 1; break;
+ case 'x': pcx = 1; break;
default: usage();
}ARGEND
if(argv[0] == nil || argv[1] == nil)
usage();
- getpal(argv[0]);
+ if(pcx)
+ getpcxpal(argv[0]);
+ else
+ getpal(argv[0]);
if((fd = open(argv[1], OREAD)) < 0)
sysfatal("open: %r");
if((bf = Bfdopen(fd, OREAD)) == nil)
@@ -116,7 +187,7 @@
maxy = get16();
bo = nil;
h = emalloc(ni * sizeof *h);
- buf = emalloc(maxx * maxy * 3 * (split ? 1 : ni));
+ buf = emalloc(maxx * maxy * sizeof(u32int) * (split ? 1 : ni));
ofs = emalloc(maxy * sizeof *ofs);
s = emalloc(strlen(argv[1]) + strlen(".00000.bit"));
for(hp=h; hp<h+ni; hp++){
@@ -129,12 +200,13 @@
bufp = buf;
if(!split && (bo = Bfdopen(1, OWRITE)) == nil)
sysfatal("Bfdopen: %r");
+ chantostr(c, pcx ? RGBA32 : RGB24);
for(hp=h; hp<h+ni; hp++){
if(split){
sprint(s, "%s.%05zd.bit", argv[1], hp-h);
if((bo = Bopen(s, OWRITE)) == nil)
sysfatal("Bfdopen: %r");
- Bprint(bo, "%11s %11d %11d %11d %11d ", chantostr(c, RGB24),
+ Bprint(bo, "%11s %11d %11d %11d %11d ", c,
hp->dx, hp->dy, hp->dx+hp->w, hp->dy+hp->h);
}
Bseek(bf, hp->ofs, 0);
@@ -162,12 +234,25 @@
n &= 0x3f;
x += n;
i = get8();
- while(n-- > 0)
- putcol(pal[i]);
+ if(pcx && --i >= npal)
+ sysfatal("invalid pal index %d", i);
+ while(n-- > 0){
+ if(!idxonly)
+ putcol(pal[i]);
+ else
+ putcol(0xff0000 | i);
+ }
}else{
x += i;
- while(n-- > 0)
- putcol(pal[get8()]);
+ while(n-- > 0){
+ i = get8();
+ if(pcx && --i >= npal)
+ sysfatal("invalid pal index %d", i);
+ if(!idxonly)
+ putcol(pal[i]);
+ else
+ putcol(0xff0000 | i);
+ }
}
}
if(!split)
@@ -185,7 +270,7 @@
}
}
if(!split){
- Bprint(bo, "%11s %11d %11d %11d %11d ", chantostr(c, RGB24), 0, 0, maxx, maxy * ni);
+ Bprint(bo, "%11s %11d %11d %11d %11d ", c, 0, 0, maxx, maxy * ni);
Bwrite(bo, buf, bufp - buf);
Bterm(bo);
}