/***************************************************************** * fbham.c: FBM Release 1.0 25-Feb-90 Michael Mauldin * * Copyright (C) 1989,1990 by C. Harald Koch & Michael Mauldin. * Permission is granted to use this file in whole or in part for * any purpose, educational, recreational or commercial, provided * that this copyright notice is retained unchanged. This software * is available to all free of charge by anonymous FTP and in the * UUNET archives. * * fbham.c: Write a 24bit RGB file as an Amiga HAM IFF file * * USAGE * % fbham < image1 > image2 * * EDITLOG * LastEditDate = Mon Jun 25 00:56:54 1990 - Michael Mauldin * LastFileName = /usr2/mlm/src/misc/fbm/fbham.c * * HISTORY * 13-Jul-89 Michael Mauldin (mlm) at Carnegie Mellon University * Beta release (version 0.95) mlm@cs.cmu.edu * * 20-Apr-89 C. Harald Koch (chk) at DCIEM Toronto. * Created. chk@ben.dciem.dnd.ca * *================================================================ * based on ray2.c from DBW_Render, Copyright 1987 David B. Wecker * * From: chk@dretor.dciem.dnd.ca (C. Harald Koch) * Subject: fbham.c - convert a 24bit FBM file to an IFF file using HAM mode * To: Michael.Mauldin@nl.cs.cmu.edu (Michael Maudlin) * Date: Mon, 1 May 89 17:21:16 EDT * X-Mailer: ELM [version 2.2 PL0] * * This is the source to my program to convert from a 24bit FBM file to Amiga * HAM mode. It is based on Dave Wecker's RAY2 program, which converts the * output of his raytracer to HAM mode. His code uses the Amiga graphics * library to plot the pixels in a framebuffer for encoding; this version will * run standalone. * * There may be bugs, although it works well for me here. It is probably not in * the format you want for FBM source; Please go ahead and modify it. There is * probably room for some command line options for various things (such as the * verbose output, run length encoding, etc) which I haven't needed and so * haven't added. * * I will send you the IRIS programs when I get them. Enjoy! * -chk *****************************************************************/ #include #include #include "fbm.h" # define USAGE "Usage: fbham < image > image" #ifndef lint static char *fbmid = "$FBM fbham.c <1.0> 25-Jun-90 (C) 1989 by C. Harald Koch, source \ code available free from MLM@CS.CMU.EDU and from UUNET archives$"; #endif #define DEPTH 6 /* max depth of Amiga HAM image */ #define BMHDsize 20L /* chunk sizes */ #define CMAPsize 96L #define CAMGsize 4L #define BODYsize ((long)(16000L)) #define FORMsize (BODYsize+CAMGsize+CMAPsize+BMHDsize+36L) unsigned char *planes[DEPTH]; /* bitplane pointers */ static int comp = 1; /* compress image? */ static int depth = DEPTH; /* depth of image being created */ static int colors = 16; /* number of colors in color lookup table */ static int colorstats[4096][2]; /* color usage statistics */ static int row_size; /* number of bytes in IFF row of data */ /* temp variables for writing IFF file */ char str[40]; long lng, pos1, pos2; short wrd; unsigned char byt; unsigned char *dest, destbuf[BUFSIZ]; main(argc, argv) char *argv[]; { FBM input; /* Clear the memory pointers so alloc_fbm won't be confused */ input.cm = input.bm = (unsigned char *) NULL; /* Read the image and rotate it */ if (!read_bitmap(&input, argc > 0 ? argv[1] : (char *) NULL)) { exit(1); } /* slight sanity checks. could be better. */ if (input.hdr.physbits != 8 || input.hdr.planes != 3) { fprintf(stderr, "input file must be 24 bit RGB data\n"); exit(1); } /* convert to HAM */ if (!fbm2ham(&input, stdout)) { exit(1); } exit(0); } /************************ run length encoding from Amiga RKM *****************/ #define DUMP 0 /* list of different bytes */ #define RUN 1 /* single run of bytes */ #define MinRun 3 /* shortest allowed run */ #define MaxRun 128 /* longest run (length is signed char) */ #define MaxDat 128 /* longest block of unencoded data */ #define GetByte() (*source++) #define PutByte(c) { *dest++ = (c); ++PutSize; } #define OutDump(nn) dest = PutDump(dest,nn); #define OutRun(nn,cc) dest = PutRun(dest,nn,cc); int PutSize; char buf[256]; unsigned char * PutDump(dest, nn) unsigned char *dest; int nn; { int i; PutByte(nn - 1); for (i = 0; i < nn; i++) PutByte(buf[i]); return (dest); } unsigned char * PutRun(dest, nn, cc) unsigned char *dest; int nn, cc; { PutByte(-(nn - 1)); PutByte(cc); return (dest); } /* PackRow - pack a row of data using Amiga IFF RLE */ int PackRow(pSource, pDest, RowSize) unsigned char **pSource, **pDest; int RowSize; { unsigned char *source, *dest; char c, lastc = '\000'; int mode = DUMP, nbuf = 0, /* number of chars in buf */ rstart = 0; /* buf index current run starts */ source = *pSource; dest = *pDest; PutSize = 0; buf[0] = lastc = c = GetByte(); nbuf = 1; RowSize--; for (; RowSize; --RowSize) { buf[nbuf++] = c = GetByte(); switch (mode) { case DUMP: if (nbuf > MaxDat) { OutDump(nbuf - 1); buf[0] = c; nbuf = 1; rstart = 0; break; } if (c == lastc) { if (nbuf - rstart >= MinRun) { if (rstart > 0) OutDump(rstart); mode = RUN; } else if (rstart == 0) mode = RUN; } else rstart = nbuf - 1; break; case RUN: if ((c != lastc) || (nbuf - rstart > MaxRun)) { OutRun((nbuf - 1) - rstart, lastc); buf[0] = c; nbuf = 1; rstart = 0; mode = DUMP; } break; } lastc = c; } switch (mode) { case DUMP: OutDump(nbuf); break; case RUN: OutRun(nbuf - rstart, lastc); break; } *pSource = source; *pDest = dest; return (PutSize); } /******************* end of RKM RL encoding routines **********************/ /* build_histogram - count frequency of 12bit colors in an FBM image */ build_histogram(image) FBM *image; { int i, j, t0, t1, gap, val, used; unsigned char *rp, *gp, *bp; int diff; /* initialize color statistics. */ for (i = 0; i < 4096; i++) { colorstats[i][0] = i; colorstats[i][1] = 0; } /* obtain pointers to the beginning of each plane (Red, Green, Blue) */ rp = image->bm; gp = rp + image->hdr.plnlen; bp = gp + image->hdr.plnlen; /* count the number of occurences of each color in the image */ for (i = 0 ; i < image->hdr.plnlen ; i++) { val = ((*rp++ & 0xf0) << 4) + (*gp++ & 0xf0) + ((*bp++ & 0xf0) >> 4); val %= 4096; if (colorstats[val][1] < 32767) { colorstats[val][1]++; } } /* sort the color stats in order of decreasing usage */ for (gap = 2048; gap > 0; gap /= 2) { for (i = gap; i < 4096; i++) { for (j = i - gap; j >= 0 && colorstats[j][1] < colorstats[j + gap][1]; j -= gap) { t0 = colorstats[j][0]; t1 = colorstats[j][1]; colorstats[j][0] = colorstats[j + gap][0]; colorstats[j][1] = colorstats[j + gap][1]; colorstats[j + gap][0] = t0; colorstats[j + gap][1] = t1; } } } /* count the number of colors actually used in the image */ for (used = 0; used < 4096 && colorstats[used][1] > 0; used++); fprintf(stderr, "Used %d colors out of a possible 4096\n", used); } /*- * plot_pixel - plot a color in an Amiga style bitmap (one plane per bit) * * Description: * A somewhat optimized routine to set/reset one bit in each plane * at the specified (x,y) coordinates. plane i gets the bit in * position i of color. a replacement for the Amiga WritePixel call. -*/ plot_pixel(planes, x, y, color) unsigned char *planes[]; int x, y; register int color; { register int bit; register unsigned char shifted_bit = 1 << (7-(x%8)); register int array_offset = y * row_size + x/8; register int i; for (i = 0; color && i < depth; i++) { bit = color & 1; color >>= 1; if (bit) *(planes[i] + array_offset) |= shifted_bit; } } /*- * fbm2ham - write an FBM image in HAM IFF format on the given file pointer. * -*/ fbm2ham(image, ofil) FBM *image; FILE *ofil; { int i, j, k, gap, t0, t1, prgb, crgb, cpix, ppix, maxdis, used; int c1, c2, diff; unsigned char *rp, *gp, *bp; fprintf(stderr, "Building a color histogram:\n"); build_histogram(image); /* convert the image to an Amiga HAM bitplane based image */ cpix = 0; crgb = colorstats[0][1]; rp = image->bm; gp = rp + image->hdr.plnlen; bp = gp + image->hdr.plnlen; row_size = ((image->hdr.cols + 15) / 16) * 2; diff = image->hdr.rowlen - image->hdr.cols; for (i = 0; i < depth ; i++) { planes[i] = (unsigned char *)malloc(row_size * image->hdr.rows); } for (i = 0; i < image->hdr.rows; i ++) { /* verbose output because this program is slow even on a Sun */ if (i%50 == 0) { fprintf(stderr, "...%d", i); fflush(stderr); } /* step through current row, converting FBM pixel to nearest equivalent * HAM pixel */ for (j = 0; j < image->hdr.cols; j++) { prgb = crgb; crgb = ((*rp++ & 0xf0) << 4) + (*gp++ & 0xf0) + ((*bp++ & 0xf0) >> 4); crgb %= 4096; ppix = cpix; /* start of scan line is ALWAYS an absolute color */ if (j == 0) cpix = getcolor(ppix, &crgb, -1); else cpix = getcolor(ppix, &crgb, prgb); /* plot the computed pixel */ plot_pixel(planes, j, i, cpix); } rp += diff; gp += diff; bp += diff; } fprintf(stderr, "\n"); /* Now we write the planes[] array we just created in ILBM format */ sprintf(str, "FORM"); fwrite(str, 1, 4, ofil); pos1 = ftell(ofil); lng = FORMsize; fwrite(&lng, 4, 1, ofil); sprintf(str, "ILBM"); fwrite(str, 1, 4, ofil); sprintf(str, "BMHD"); fwrite(str, 1, 4, ofil); lng = BMHDsize; fwrite(&lng, 4, 1, ofil); wrd = image->hdr.cols; fwrite(&wrd, 2, 1, ofil); /* width */ wrd = image->hdr.rows; fwrite(&wrd, 2, 1, ofil); /* height */ wrd = 0; fwrite(&wrd, 2, 1, ofil); /* top */ wrd = 0; fwrite(&wrd, 2, 1, ofil); /* left */ byt = depth; fwrite(&byt, 1, 1, ofil); /* Depth */ byt = 0; fwrite(&byt, 1, 1, ofil); /* mask */ byt = comp; fwrite(&byt, 1, 1, ofil); /* compress */ byt = 0; fwrite(&byt, 1, 1, ofil); /* pad */ wrd = 0; fwrite(&wrd, 2, 1, ofil); /* transparency */ byt = 10; fwrite(&byt, 1, 1, ofil); /* aspect x */ byt = 11; fwrite(&byt, 1, 1, ofil); /* aspect y */ wrd = image->hdr.cols; fwrite(&wrd, 2, 1, ofil); /* page width */ wrd = image->hdr.rows; fwrite(&wrd, 2, 1, ofil); /* page height */ /* CAMG chunk for displaying files on the Amiga. should include at least * the HAM flag; I also add lace if the picture is large enough. Perhaps * this should be an option. -CHK */ sprintf(str, "CAMG"); fwrite(str, 1, 4, ofil); lng = CAMGsize; fwrite(&lng, 4, 1, ofil); if (image->hdr.rows > 200) lng = 0x804L; /* HAM | LACE */ else lng = 0x800L; /* HAM */ fwrite(&lng, 4, 1, ofil); /* write out the color lookup table */ sprintf(str, "CMAP"); fwrite(str, 1, 4, ofil); lng = CMAPsize; fwrite(&lng, 4, 1, ofil); for (i = 0; i < 32; i++) { str[0] = (colorstats[i][0] >> 4) & 0xF0; str[1] = (colorstats[i][0]) & 0xF0; str[2] = (colorstats[i][0] << 4) & 0xF0; fwrite(str, 1, 3, ofil); } sprintf(str, "BODY"); fwrite(str, 1, 4, ofil); pos2 = ftell(ofil); lng = BODYsize; fwrite(&lng, 4, 1, ofil); lng = 0L; for (i = 0; i < image->hdr.rows; i ++) { for (j = 0; j < depth; j++) { if (comp) { dest = destbuf; wrd = PackRow(&planes[j], &dest, row_size); lng += (long) wrd; fwrite(destbuf, 1, wrd, ofil); } else { fwrite(planes[j], 1, row_size, ofil); planes[j] += row_size; } } } /* make BODY a multiple of 4 bytes in length */ byt = 0; if (comp) while (lng % 4) { fwrite(&byt, 1, 1, ofil); /* pad */ lng++; } if (comp) { fseek(ofil, (long) pos2, 0); fwrite(&lng, 4, 1, ofil); lng -= BODYsize; lng += FORMsize; fseek(ofil, (long) pos1, 0); fwrite(&lng, 4, 1, ofil); } return 1; } /* get the next encoding for a pixel, based on the previous pixel and the * current 12 bit RGB value */ #define BPP 4 /* Bits per pixel */ #define MAXGRAY (1 << BPP) #define ABS(x) ((x) < 0 ? -(x) : (x)) /*- * first, check to see if pixel is the same color. if so, return * check if only one of R, G, B have changed. if so, return new pixel. * find closest entry in colormap. if exact match, return it. * modify one of R, G, B (whichever difference is largest) * compare previous calculation to best match in color table. return * whichever is a better match. -*/ getcolor(ppix, crgb, prgb) int ppix, *crgb, prgb; { int i, j, val, cr, cg, cb, pr, pg, pb, nr, ng, nb, best, dist, nrgb; /* if same color, then do a NOOP (same as previous pixel) */ if (*crgb == prgb) return (ppix); /* set up for comparisons */ cb = *crgb & (MAXGRAY - 1); cg = (*crgb >> BPP) & (MAXGRAY - 1); cr = (*crgb >> (BPP * 2)) & (MAXGRAY - 1); pb = prgb & (MAXGRAY - 1); pg = (prgb >> BPP) & (MAXGRAY - 1); pr = (prgb >> (BPP * 2)) & (MAXGRAY - 1); /* see if only one plane changed, if so, use HAM encoding */ if (prgb != -1) { if (pr == cr && pg == cg) return (cb + 0x10); if (pr == cr && pb == cb) return (cg + 0x30); if (pg == cg && pb == cb) return (cr + 0x20); } /* else look for an exact match in the color table (or minimal distance) */ for (i = 0; i < colors; i++) { if (colorstats[i][0] == *crgb) return (i); if (i == 0) { best = 0; dist = distance(colorstats[i][0], *crgb); } else if ((j = distance(colorstats[i][0], *crgb)) < dist) { best = i; dist = j; } } /* do a forced absolute */ if (prgb == -1) { *crgb = colorstats[best][0]; return (best); } /* find which color is off the most from previous */ i = 0; val = ABS(cr - pr); if (ABS(cg - pg) > val) { i = 1; val = ABS(cg - pg); } if (ABS(cb - pb) > val) { i = 2; val = ABS(cb - pb); } nr = pr; ng = pg; nb = pb; switch (i) { case 0: nr = cr; val = nr + 0x20; break; case 1: ng = cg; val = ng + 0x30; break; case 2: nb = cb; val = nb + 0x10; break; } nrgb = (nr << (2 * BPP)) + (ng << BPP) + nb; /* now pick the best */ if (distance(nrgb, *crgb) < dist) { /* do a best relative */ *crgb = nrgb; return (val); } /* do a best absolute */ *crgb = colorstats[best][0]; return (best); } /* calculate distance between two colors in 3D space */ distance(argb, brgb) { int b, g, r; /* set up for comparisons */ b = argb & (MAXGRAY - 1); g = (argb >> BPP) & (MAXGRAY - 1); r = (argb >> (BPP * 2)) & (MAXGRAY - 1); b -= brgb & (MAXGRAY - 1); g -= (brgb >> BPP) & (MAXGRAY - 1); r -= (brgb >> (BPP * 2)) & (MAXGRAY - 1); return (r * r + g * g + b * b); }