From ef37709a2df6cd87d43fdbb5aaa7dbdff6559fba Mon Sep 17 00:00:00 2001 From: David Schmenk Date: Tue, 18 Mar 2025 12:20:11 -0700 Subject: [PATCH] Add C version of RGB -> DHGR converter --- src/dhgr.tk/utils/dhgrrgb.c | 412 ++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 src/dhgr.tk/utils/dhgrrgb.c diff --git a/src/dhgr.tk/utils/dhgrrgb.c b/src/dhgr.tk/utils/dhgrrgb.c new file mode 100644 index 0000000..557ca39 --- /dev/null +++ b/src/dhgr.tk/utils/dhgrrgb.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include + +/* CGA high resolution */ +#define X_RES 560 +#define Y_RES 192 +/* According to what I could find out about the NTSC color wheel: + * Red maxes at 103.5 degrees + * Green maxes at 240.7 degrees + * Blue maxes at 347.1 degrees + */ +#define RED_PHASE_NTSC 104 +#define GREEN_PHASE_NTSC 241 +#define BLUE_PHASE_NTSC 347 +/* Ideal phase angles for 4 phase */ +#define RED_PHASE_IDEAL 90 +#define GREEN_PHASE_IDEAL 270 +#define BLUE_PHASE_IDEAL 360 +/* Equal 120 deg phase angles */ +#define RED_PHASE_EQUAL 120 +#define GREEN_PHASE_EQUAL 240 +#define BLUE_PHASE_EQUAL 360 +/* Simplified phase angles: 90 deg between R and B, 135 between RG and BG */ +#define RED_PHASE_SIMPLE 90 +#define GREEN_PHASE_SIMPLE 225 +#define BLUE_PHASE_SIMPLE 360 +/* Flags */ +#define DUMP_STATE 2 /* Dump internal state */ +/* Handy macros */ +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) +#define RED 0 +#define GRN 1 +#define BLU 2 +#define DEG_TO_RAD 0.0174533 +#define TRUE 1 +#define FALSE 0 +unsigned char ntscChroma[4][3]; +int prevRed, prevBlu, prevGrn; +unsigned char gammaRed[256]; /* RED gamma correction */ +unsigned char gammaGrn[256]; /* GREEN gamma correction */ +unsigned char gammaBlu[256]; /* BLUE gamma correction */ +int phase[3] = {RED_PHASE_NTSC, GREEN_PHASE_NTSC, BLUE_PHASE_NTSC}; +int gammacorrect = 0; /* Gamma correction */ +int brightness = 0; +int saturation = 255; /* 1.0 */ +int tint = 22; /* imperically determined tint */ +int orgmode; +int errDiv = 4; +unsigned char rgbScanline[X_RES * 3]; /* RGB scanline */ +int rgbErr[(X_RES + 1) * 3]; /* Running color error array */ +#define DHGR_SIZE 16384 +unsigned char dhgrScreen[DHGR_SIZE]; +int scanOffset[] = {0x0000,0x0400,0x0800,0x0C00,0x1000,0x1400,0x1800,0x1C00, + 0x0080,0x0480,0x0880,0x0C80,0x1080,0x1480,0x1880,0x1C80, + 0x0100,0x0500,0x0900,0x0D00,0x1100,0x1500,0x1900,0x1D00, + 0x0180,0x0580,0x0980,0x0D80,0x1180,0x1580,0x1980,0x1D80, + 0x0200,0x0600,0x0A00,0x0E00,0x1200,0x1600,0x1A00,0x1E00, + 0x0280,0x0680,0x0A80,0x0E80,0x1280,0x1680,0x1A80,0x1E80, + 0x0300,0x0700,0x0B00,0x0F00,0x1300,0x1700,0x1B00,0x1F00, + 0x0380,0x0780,0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80, + 0x0028,0x0428,0x0828,0x0C28,0x1028,0x1428,0x1828,0x1C28, + 0x00A8,0x04A8,0x08A8,0x0CA8,0x10A8,0x14A8,0x18A8,0x1CA8, + 0x0128,0x0528,0x0928,0x0D28,0x1128,0x1528,0x1928,0x1D28, + 0x01A8,0x05A8,0x09A8,0x0DA8,0x11A8,0x15A8,0x19A8,0x1DA8, + 0x0228,0x0628,0x0A28,0x0E28,0x1228,0x1628,0x1A28,0x1E28, + 0x02A8,0x06A8,0x0AA8,0x0EA8,0x12A8,0x16A8,0x1AA8,0x1EA8, + 0x0328,0x0728,0x0B28,0x0F28,0x1328,0x1728,0x1B28,0x1F28, + 0x03A8,0x07A8,0x0BA8,0x0FA8,0x13A8,0x17A8,0x1BA8,0x1FA8, + 0x0050,0x0450,0x0850,0x0C50,0x1050,0x1450,0x1850,0x1C50, + 0x00D0,0x04D0,0x08D0,0x0CD0,0x10D0,0x14D0,0x18D0,0x1CD0, + 0x0150,0x0550,0x0950,0x0D50,0x1150,0x1550,0x1950,0x1D50, + 0x01D0,0x05D0,0x09D0,0x0DD0,0x11D0,0x15D0,0x19D0,0x1DD0, + 0x0250,0x0650,0x0A50,0x0E50,0x1250,0x1650,0x1A50,0x1E50, + 0x02D0,0x06D0,0x0AD0,0x0ED0,0x12D0,0x16D0,0x1AD0,0x1ED0, + 0x0350,0x0750,0x0B50,0x0F50,0x1350,0x1750,0x1B50,0x1F50, + 0x03D0,0x07D0,0x0BD0,0x0FD0,0x13D0,0x17D0,0x1BD0,0x1FD0}; +char flags = 0; + +void dhgrSet(int x, int y) +{ + int pixbit, pixofst; + + pixbit = 1 << (x % 7); + pixofst = x / 7; + pixofst = (DHGR_SIZE/2) * (pixofst & 1) + (pixofst >> 1); + dhgrScreen[scanOffset[y] + pixofst] |= pixbit; +} +long int dist(int dr, int dg, int db) +{ + long int rr, gg, bb; + + rr = (long int)dr * (long int)dr; + gg = (long int)dg * (long int)dg; + bb = (long int)db * (long int)db; + return rr + gg + bb; +} + +void calcChroma(int angle) +{ + int r, g, b, i; + + for (i = 0; i < 4; i++) + { + /* Calculate RGB for this NTSC subcycle pixel */ + r = saturation + (int)(cos((angle - phase[RED]) * DEG_TO_RAD) * 255); + g = saturation + (int)(cos((angle - phase[GRN]) * DEG_TO_RAD) * 255); + b = saturation + (int)(cos((angle - phase[BLU]) * DEG_TO_RAD) * 255); + /* Make chroma add up to white */ + ntscChroma[i][RED] = min(255, max(0, (r + 2) / 4)); + ntscChroma[i][GRN] = min(255, max(0, (g + 2) / 4)); + ntscChroma[i][BLU] = min(255, max(0, (b + 2) / 4)); + /* Next NTSC chroma subcycle pixel */ + angle = angle - 90; + } +} + +int rgbMatchChroma(int r, int g, int b, int *errptr, int cx) +{ + int currRed, currGrn, currBlu; + int errRed, errGrn, errBlu; + int pix; + + /* Apply error propogation */ + if (errDiv) + { + r += errptr[RED] / errDiv; + g += errptr[GRN] / errDiv; + b += errptr[BLU] / errDiv; + } + /* Previous RGB chroma subcycles */ + prevRed = (prevRed * 3) / 4; + prevGrn = (prevGrn * 3) / 4; + prevBlu = (prevBlu * 3) / 4; + /* Current potential RGB subcycle */ + currRed = prevRed + ntscChroma[cx][RED]; + currGrn = prevGrn + ntscChroma[cx][GRN]; + currBlu = prevBlu + ntscChroma[cx][BLU]; + /* Match chroma subcycle */ + pix = 0; + if (dist(r - currRed, g - currGrn, b - currBlu) < dist(r - prevRed, g - prevGrn, b - prevBlu)) + { + /* RGB better matched with current chroma subcycle color */ + prevRed = currRed; + prevGrn = currGrn; + prevBlu = currBlu; + pix = 1; + } + /* Propogate error down (overwrite error at this coordinate for next row) */ + errRed = r - prevRed; + errGrn = g - prevGrn; + errBlu = b - prevBlu; + errptr[RED] = errRed; + errptr[GRN] = errGrn; + errptr[BLU] = errBlu; + /* And forward (add to previous row error for next match) */ + errptr[RED + 3] += errRed; + errptr[GRN + 3] += errGrn; + errptr[BLU + 3] += errBlu; + return pix; +} + +int rgbInit(void) +{ + int i; + long int g32; + + switch (gammacorrect) + { + case -1: + for (i = 0; i < 256; i++) + { + g32 = (255 - i + 255 - ((long int)i * (long int)i + 127)/255) / 2; + gammaRed[255 - i] = g32; + gammaGrn[255 - i] = g32; + gammaBlu[255 - i] = g32; + } + break; + case -2: + for (i = 0; i < 256; i++) + { + g32 = 255 - ((long int)i * (long int)i + 127)/255; + gammaRed[255 - i] = g32; + gammaGrn[255 - i] = g32; + gammaBlu[255 - i] = g32; + } + break; + case 2: + for (i = 0; i < 256; i++) + { + g32 = ((long int)i * (long int)i + 127) / 255; + gammaRed[i] = g32; + gammaGrn[i] = g32; + gammaBlu[i] = g32; + } + break; + case 1: + for (i = 0; i < 256; i++) + { + g32 = (i + ((long int)i * (long int)i + 127) / 255) / 2; + gammaRed[i] = g32; + gammaGrn[i] = g32; + gammaBlu[i] = g32; + } + break; + default: + for (i = 0; i < 256; i++) + { + gammaRed[i] = i; + gammaGrn[i] = i; + gammaBlu[i] = i; + } + } + if (brightness) + for (i = 0; i < 256; i++) + { + gammaRed[i] = max(0, min(255, gammaRed[i] + brightness)); + gammaGrn[i] = max(0, min(255, gammaGrn[i] + brightness)); + gammaBlu[i] = max(0, min(255, gammaBlu[i] + brightness)); + } + calcChroma(tint); + if (flags & DUMP_STATE) + { + printf("Err Div = %d\n", errDiv); + printf("Gamma = %d\n", gammacorrect); + printf("Brightness = %d\n", brightness); + printf("Tint = %d\n", tint); + printf("Saturation = %d\n", saturation); + puts("Chroma cycle RGB ="); + for (i = 0; i < 4; i++) + printf(" [%3d, %3d %3d]\n", ntscChroma[i][RED], + ntscChroma[i][GRN], + ntscChroma[i][BLU]); + getchar(); + } + return TRUE; +} + +char *pnmReadElement(FILE *fp, char *bufptr) +{ + char *endptr; + + endptr = bufptr + 1; + while (fread(bufptr, 1, 1, fp) == 1 && *bufptr == '#') /* Comment */ + while (fread(endptr, 1, 1, fp) == 1 && *endptr >= ' '); + while (fread(endptr, 1, 1, fp) == 1 && *endptr > ' ' && (endptr - bufptr < 80)) + endptr++; + *endptr = '\0'; + if (flags & DUMP_STATE) + puts(bufptr); + return bufptr; +} + +int pnmVerifyHeader(FILE *fp) +{ + char buf[128]; + + if (flags & DUMP_STATE) + printf("PNM = "); + pnmReadElement(fp, buf); + if (strcmp(buf, "P6")) + { + printf("Invalid PNM magic #: %c%c\n", buf[0], buf[1]); + return FALSE; + } + if (atoi(pnmReadElement(fp, buf)) != X_RES) + { + printf("Width not 640: %s\n", buf); + return FALSE; + } + if (atoi(pnmReadElement(fp, buf)) != Y_RES) + { + printf("Height not 192: %s\n", buf); + return FALSE; + } + if (atoi(pnmReadElement(fp, buf)) != 255) + { + printf("Depth not 255: %s\n", buf); + return FALSE; + } + return TRUE; +} + +int rgbImportExport(char *pnmfile, char *dhgrfile) +{ + FILE *fp; + unsigned char *scanptr; + unsigned char chromaBits, *rgbptr; + int scan, pix, r, g, b, *errptr; + + if (flags & DUMP_STATE) + printf("PNM file = %s\n", pnmfile); + fp = fopen(pnmfile, "rb"); + if (fp) + { + if (pnmVerifyHeader(fp) && rgbInit()) + { + /* Clear screen memory */ + memset(dhgrScreen, 0, DHGR_SIZE); + /* Init error propogation array */ + memset(rgbScanline, 0, X_RES * 3); + memset(rgbErr, 0, X_RES * 3 * sizeof(int)); + for (scan = 0; scan < Y_RES; scan++) + { + fread(rgbScanline, X_RES, 3, fp); + /* Reset prev RGB to neutral color */ + prevRed = 96; + prevGrn = 96; + prevBlu = 96; + /* Reset pointers */ + rgbptr = rgbScanline; + errptr = rgbErr; + for (pix = 0; pix < X_RES; pix++) + { + /* Calc best match */ + r = gammaRed[rgbptr[RED]]; + g = gammaGrn[rgbptr[GRN]]; + b = gammaBlu[rgbptr[BLU]]; + if (rgbMatchChroma(r, g, b, errptr, pix & 3)) + dhgrSet(pix, scan); + rgbptr = rgbptr + 3; + errptr = errptr + 3; + } + } + } + fclose(fp); + fp = fopen(dhgrfile, "wb"); + if (fp) + { + fwrite(dhgrScreen, DHGR_SIZE, 1, fp); + fclose(fp); + return 0; + } + printf("Unable to open %s\n", dhgrfile); + } + printf("Unable to open %s\n", pnmfile); + return -1; +} + +int main(int argc, char **argv) +{ + puts("DHGR RGB converter 1.0"); + while (*++argv && (*argv)[0] == '-') + { + switch (toupper((*argv)[1])) + { + case 'B': /* Set brightness */ + brightness = atoi(*argv + 2); + break; + case 'D': /* Dump internal state */ + flags = flags | DUMP_STATE; + break; + case 'E': /* Set error strength */ + errDiv = atoi(*argv + 2); + break; + case 'G': /* Set gamma amount */ + gammacorrect = atoi(*argv + 2); + break; + case 'P': /* RGB phase angle */ + switch (toupper((*argv)[2])) + { + case 'I': /* Use ideal 4 sub-phase angles */ + phase[RED] = RED_PHASE_IDEAL; + phase[GRN] = GREEN_PHASE_IDEAL; + phase[BLU] = BLUE_PHASE_IDEAL; + break; + case 'E': /* Use equal 120 deg phase angles */ + phase[RED] = RED_PHASE_EQUAL; + phase[GRN] = GREEN_PHASE_EQUAL; + phase[BLU] = BLUE_PHASE_EQUAL; + break; + case 'S': /* Use simplified 90 degree and opposite phase angles */ + phase[RED] = RED_PHASE_SIMPLE; + phase[GRN] = GREEN_PHASE_SIMPLE; + phase[BLU] = BLUE_PHASE_SIMPLE; + break; + /* case 'N': Use theoretical NTSC phase angles */ + default: + phase[RED] = RED_PHASE_NTSC; + phase[GRN] = GREEN_PHASE_NTSC; + phase[BLU] = BLUE_PHASE_NTSC; + break; + } + break; + case 'S': /* Adjust saturation */ + saturation = saturation - atoi(*argv + 2); + break; + case 'T': /* Adjust tint */ + tint = tint + atoi(*argv + 2); + break; + default: + printf("? option: %c\n", (*argv)[1]); + } + } + if (argv[0] && argv[1]) + return rgbImportExport(argv[0], argv[1]); + puts("Usage:"); + puts(" dhgrrgb"); + puts(" [-b#] = Brightness: -255..255"); + puts(" [-d] = Dump state"); + puts(" [-e#] = Error strength: 1..255"); + puts(" 0 = no err"); + puts(" [-g#] = Gamma: 2, 1, 0, -1, -2"); + puts(" [-p] = Phase: Ideal, Equal, Simple, NTSC"); + puts(" [-s#] = Saturation: -255..255"); + puts(" [-t#] = Tint: -360..360 (in degrees)"); + puts(" "); + return 0; +}