MODULE CLUTs;
IMPORT
Streams;
CONST
colors* = 256;
TYPE
CLUT* = RECORD
col*: ARRAY colors OF LONGINT;
size*: INTEGER;
bits: INTEGER;
cube: POINTER TO ARRAY OF CHAR;
END;
PROCEDURE Set* (VAR clut: CLUT; n, col: LONGINT);
BEGIN
ASSERT((0 <= n) & (n < colors), 100);
clut.col[n] := col MOD 1000000H;
clut.bits := 0
END Set;
PROCEDURE Get* (VAR clut: CLUT; n: LONGINT; VAR col: LONGINT);
BEGIN
ASSERT((0 <= n) & (n < colors), 100);
col := clut.col[n]
END Get;
PROCEDURE Init* (VAR clut: CLUT; size, bits: LONGINT);
VAR
nbits, x, colormax, cur, rcol, gcol, bcol, col: LONGINT;
xsqr, txsqr, rstride, gstride, bsize, i, rdist, gdist, bdist, cdist: LONGINT;
dbuf: POINTER TO ARRAY OF LONGINT;
rcenter, gcenter, bcenter, ghere, bhere, gmin, bmin, gmax, bmax: LONGINT;
incr, incg, incb, p, rp, gp: LONGINT;
ginc, binc: LONGINT;
PROCEDURE blueloop(): BOOLEAN;
VAR detect: BOOLEAN; blue, bp, bdist, bxx: LONGINT;
BEGIN
detect := FALSE;
blue := bhere; bp := gp; bdist := gdist; bxx := binc;
WHILE (blue < bmax) & (dbuf[bp] <= bdist) DO
INC(blue); INC(bp); INC(bdist, bxx); INC(bxx, txsqr)
END;
IF blue < bmax THEN
IF blue > bhere THEN
bhere := blue; gp := bp; gdist := bdist; binc := bxx
END;
detect := TRUE;
WHILE (blue < bmax) & (dbuf[bp] > bdist) DO
dbuf[bp] := bdist; clut.cube[bp] := CHR(cur);
INC(blue); INC(bp); INC(bdist, bxx); INC(bxx, txsqr)
END
END;
blue := bhere-1; bp := gp-1; bxx := binc - txsqr; bdist := gdist - bxx;
IF ~detect THEN
WHILE (blue >= bmin) & (dbuf[bp] <= bdist) DO
DEC(blue); DEC(bp); DEC(bxx, txsqr); DEC(bdist, bxx)
END;
IF blue >= bmin THEN
bhere := blue; gp := bp; gdist := bdist; binc := bxx; detect := TRUE
END
END;
WHILE (blue >= bmin) & (dbuf[bp] > bdist) DO
dbuf[bp] := bdist; clut.cube[bp] := CHR(cur);
DEC(blue); DEC(bp); DEC(bxx, txsqr); DEC(bdist, bxx)
END;
RETURN detect
END blueloop;
PROCEDURE greenloop(): BOOLEAN;
VAR detect: BOOLEAN; green, ggp, ggdist, gxx: LONGINT;
BEGIN
detect := FALSE;
bhere := bcenter; bmin := 0; bmax := colormax; binc := incb;
green := ghere; gp := rp; ggp := gp; gdist := rdist; ggdist := gdist; gxx := ginc;
WHILE green < gmax DO
IF blueloop() THEN
IF ~detect THEN
IF green > ghere THEN
ghere := green; rp := ggp; rdist := ggdist; ginc := gxx
END;
detect := TRUE
END;
INC(green); INC(gp, gstride); INC(ggp, gstride); INC(gdist, gxx); INC(ggdist, gxx); INC(gxx, txsqr)
ELSIF ~detect THEN
green := gmax
ELSE
INC(green); INC(gp, gstride); INC(ggp, gstride); INC(gdist, gxx); INC(ggdist, gxx); INC(gxx, txsqr)
END
END;
bhere := bcenter; bmin := 0; bmax := colormax; binc := incb;
green := ghere-1; gp := rp - gstride; ggp := gp; gxx := ginc - txsqr; gdist := rdist - gxx; ggdist := gdist;
WHILE green >= gmin DO
IF blueloop() THEN
IF ~detect THEN
ghere := green; rp := ggp; rdist := ggdist; ginc := gxx; detect := TRUE
END;
DEC(green); DEC(gp, gstride); DEC(ggp, gstride); DEC(gxx, txsqr); DEC(gdist, gxx); DEC(ggdist, gxx)
ELSIF ~detect THEN
green := gmin-1
ELSE
DEC(green); DEC(gp, gstride); DEC(ggp, gstride); DEC(gxx, txsqr); DEC(gdist, gxx); DEC(ggdist, gxx)
END
END;
RETURN detect
END greenloop;
PROCEDURE redloop;
VAR detect: BOOLEAN; red, rxx: LONGINT;
BEGIN
detect := FALSE;
ghere := gcenter; gmin := 0; gmax := colormax; ginc := incg;
red := rcenter; rp := p; rdist := cdist; rxx := incr;
WHILE red < colormax DO
IF greenloop() THEN detect := TRUE; INC(red); INC(rp, rstride); INC(rdist, rxx); INC(rxx, txsqr)
ELSIF detect THEN red := colormax
ELSE INC(red); INC(rp, rstride); INC(rdist, rxx); INC(rxx, txsqr)
END
END;
ghere := gcenter; gmin := 0; gmax := colormax; ginc := incg;
red := rcenter-1; rp := p - rstride; rxx := incr - txsqr; rdist := cdist - rxx;
WHILE red >= 0 DO
IF greenloop() THEN detect := TRUE; DEC(red); DEC(rp, rstride); DEC(rxx, txsqr); DEC(rdist, rxx)
ELSIF detect THEN red := -1
ELSE DEC(red); DEC(rp, rstride); DEC(rxx, txsqr); DEC(rdist, rxx)
END
END
END redloop;
BEGIN
ASSERT((2 <= size) & (size <= colors), 100);
clut.size := SHORT(size);
IF bits <= 0 THEN RETURN END;
IF bits > 6 THEN bits := 6 END;
nbits := 8-bits; x := INTEGER(ASH(1, nbits)); xsqr := ASH(1, 2*nbits); txsqr := 2*xsqr;
colormax := INTEGER(ASH(1, bits)); rstride := ASH(1, 2*bits); gstride := colormax;
bsize := ASH(1, 3*bits); NEW(dbuf, bsize);
i := 0; WHILE i < bsize DO dbuf[i] := MAX(LONGINT); INC(i) END;
IF (clut.cube = NIL) OR (LEN(clut.cube^) < bsize) THEN NEW(clut.cube, bsize) END;
clut.bits := SHORT(bits);
cur := 0;
WHILE cur < size DO
col := clut.col[cur];
rcol := ASH(col, -16) MOD 100H; gcol := ASH(col, -8) MOD 100H; bcol := col MOD 100H;
rcenter := INTEGER(ASH(rcol, -nbits)); rdist := rcol - (rcenter * x + x DIV 2);
gcenter := INTEGER(ASH(gcol, -nbits)); gdist := gcol - (gcenter * x + x DIV 2);
bcenter := INTEGER(ASH(bcol, -nbits)); bdist := bcol - (bcenter * x + x DIV 2);
cdist := rdist * rdist + gdist * gdist + bdist * bdist;
incr := 2*((rcenter+1) * xsqr - rcol * x); incg := 2*((gcenter+1) * xsqr - gcol * x); incb := 2*((bcenter+1) * xsqr - bcol * x);
p := rcenter * rstride + gcenter * gstride + bcenter;
redloop;
INC(cur)
END
END Init;
PROCEDURE Match* (VAR clut: CLUT; col: LONGINT): LONGINT;
VAR shift, red, green, blue, n, best, err, r, g, b, e: LONGINT;
BEGIN
IF clut.bits # 0 THEN
shift := clut.bits-8;
RETURN ORD(clut.cube[
ASH(ASH(ASH(col, -16) MOD 100H, shift), 2*clut.bits) +
ASH(ASH(ASH(col, -8) MOD 100H, shift), clut.bits) +
ASH(col MOD 100H, shift)])
END;
red := ASH(col, -16) MOD 100H; green := ASH(col, -8) MOD 100H; blue := col MOD 100H;
n := 0; best := 0; err := MAX(LONGINT);
WHILE n < clut.size DO
e := clut.col[n];
IF e = col THEN RETURN n END;
r := ASH(e, -16) MOD 100H - red;
g := ASH(e, -8) MOD 100H - green;
b := e MOD 100H - blue;
e := r * r + g * g + b * b;
IF e < err THEN
err := e; best := n
END;
INC(n)
END;
RETURN best
END Match;
PROCEDURE Copy* (from, to: CLUT);
VAR n: LONGINT;
BEGIN
to.col := from.col; to.size := from.size; to.bits := from.bits;
IF from.cube = NIL THEN
to.cube := NIL
ELSE
NEW(to.cube, LEN(from.cube^));
FOR n := 0 TO LEN(from.cube^)-1 DO
to.cube[n] := from.cube[n]
END
END
END Copy;
PROCEDURE Write* (VAR w: Streams.Writer; VAR clut: CLUT);
VAR n, col: LONGINT;
BEGIN
FOR n := 0 TO clut.size-1 DO
col := clut.col[n];
w.Char(CHR(ASH(col, -16) MOD 100H));
w.Char(CHR(ASH(col, -8) MOD 100H));
w.Char(CHR(col MOD 100H));
w.Update
END
END Write;
PROCEDURE Read* (r: Streams.Reader; VAR clut: CLUT; size: LONGINT);
VAR n: LONGINT; red, green, blue: CHAR;
BEGIN
FOR n := 0 TO size-1 DO
r.Char(red); r.Char(green); r.Char(blue);
Set(clut, n, ASH(ASH(ORD(red), 8) + ORD(green), 8) + ORD(blue));
END
END Read;
END CLUTs.
(**
Notes
1. Color Lookup Tables
A color lookup table stores up to 256 color values. CLUTs are necessary for mapping index values to color values in displays or bitmaps that have a depth of at most 8 bits.
2. Reverse Color Lookup
To speed up the conversion from a RGB triple to a matching index with Match, Init computes a reverse color lookup table. The amount of memory this reverse lookup table requires grows exponentially with the value of bits. (The exact number of bytes used is 2^(3*bits)). A reasonable value for bits is 4, which makes Init allocate 4096 bytes on the heap. Values larger than 6 are automatically truncated.
A value of zero for bits indicates that no reverse lookup structure should be created. Match still returns a matching index, but has to linearly search the whole table, taking up O(size) instead of O(1) time.
3. Input/Output
Write and Read can be used to store and load color values to and from a palette file. Only the colors themselves are written and read. After a CLUT has been read, it should therefore be set up with Init.
**)