diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
commit | 1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch) | |
tree | 64c8c96cf54d8718847339a403e5e67b922e8c3f /converter/other/pnmtopalm/palmtopnm.c | |
download | netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip |
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter/other/pnmtopalm/palmtopnm.c')
-rw-r--r-- | converter/other/pnmtopalm/palmtopnm.c | 1131 |
1 files changed, 1131 insertions, 0 deletions
diff --git a/converter/other/pnmtopalm/palmtopnm.c b/converter/other/pnmtopalm/palmtopnm.c new file mode 100644 index 00000000..9cd695e3 --- /dev/null +++ b/converter/other/pnmtopalm/palmtopnm.c @@ -0,0 +1,1131 @@ +/****************************************************************************** + palmtopnm +******************************************************************************* + By Bryan Henderson, San Jose, California, June 2004. + + Inspired by and using methods from Tbmptopnm by Ian Goldberg + <iang@cs.berkeley.edu>, and Bill Janssen <bill@janssen.org>. + + Major fixes and new capability added by Paul Bolle <pebolle@tiscali.nl> + in late 2004 / early 2005. + + Bryan's work is contributed to the public domain by its author. +******************************************************************************/ + +#include <string.h> +#include <assert.h> + +#include "pnm.h" +#include "shhopt.h" +#include "mallocvar.h" + +#include "palm.h" + + + +enum palmCompressionType { + COMPRESSION_NONE, + COMPRESSION_RLE, + COMPRESSION_SCANLINE, + COMPRESSION_PACKBITS +}; + +struct palmHeader { + unsigned short cols; + unsigned short rows; + unsigned short bytesPerRow; + unsigned short flags; + bool directColor; + /* The header indicates a direct color raster, either by flag + (the old way) or by pixel format (the new way) + */ + bool hasColormap; + bool hasTransparency; + unsigned char pixelSizeCode; + unsigned int pixelSize; + unsigned char version; + unsigned int transparentIndex; + enum palmCompressionType compressionType; + /* version 3 encoding specific */ + unsigned char size; + unsigned char pixelFormat; + unsigned short density; + unsigned long transparentValue; +}; + + + +struct directPixelFormat { + unsigned int redbits; + unsigned int greenbits; + unsigned int bluebits; +}; + + + +struct directColorInfo { + struct directPixelFormat pixelFormat; + Color_s transparentColor; +}; + + + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * inputFilespec; + unsigned int verbose; + unsigned int rendition; + unsigned int showhist; + unsigned int transparent; +}; + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo *cmdlineP) { +/*---------------------------------------------------------------------------- + Note that the file spec array we return is stored in the storage that + was passed to us as the argv array. +-----------------------------------------------------------------------------*/ + optEntry *option_def = malloc( 100*sizeof( optEntry ) ); + /* Instructions to optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int renditionSpec; + + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "verbose", OPT_FLAG, NULL, + &cmdlineP->verbose, 0); + OPTENT3(0, "showhist", OPT_FLAG, NULL, + &cmdlineP->showhist, 0); + OPTENT3(0, "transparent", OPT_FLAG, NULL, + &cmdlineP->transparent, 0); + OPTENT3(0, "rendition", OPT_UINT, &cmdlineP->rendition, + &renditionSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We may have parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + + if (renditionSpec) { + if (cmdlineP->rendition < 1) + pm_error("The -rendition value must be at least 1"); + } else + cmdlineP->rendition = 1; + + if (cmdlineP->transparent && cmdlineP->showhist) + pm_error("You can't specify -showhist with -transparent"); + + if (argc-1 < 1) + cmdlineP->inputFilespec = "-"; + else { + cmdlineP->inputFilespec = argv[1]; + if (argc-1 > 1) + pm_error("Too many arguments (%d). The only non-option " + "argument is the file name", argc-1); + } +} + + + +static xelval * +createGraymap(unsigned int const ncolors, + xelval const maxval) { + int i; + xelval *map; + + MALLOCARRAY_NOFAIL(map, ncolors); + for (i = 0; i < ncolors; ++i) { + map[i] = maxval - (i * maxval) / (ncolors - 1); + } + return map; +} + + + +static void +skipbytes(FILE * const ifP, + unsigned int const nbytes) { + + unsigned char buf[256]; + unsigned int n; + size_t bytesRead; + + n = nbytes; /* initial value */ + + while (n > 0) { + if (n > sizeof(buf)) { + bytesRead = fread(buf, sizeof(char), sizeof(buf), ifP); + if (bytesRead != sizeof(buf)) + pm_error("Error reading Palm file. Short read."); + n -= sizeof(buf); + } else { + bytesRead = fread(buf, sizeof(char), n, ifP); + if (bytesRead != n) + pm_error("Error reading Palm file. Short read."); + n = 0; + } + } +} + + + +static void +interpretCompression(unsigned char const compressionValue, + enum palmCompressionType * const compressionTypeP) { + + switch (compressionValue) { + case PALM_COMPRESSION_RLE: + *compressionTypeP = COMPRESSION_RLE; + break; + case PALM_COMPRESSION_SCANLINE: + *compressionTypeP = COMPRESSION_SCANLINE; + break; + case PALM_COMPRESSION_PACKBITS: + *compressionTypeP = COMPRESSION_PACKBITS; + break; + case PALM_COMPRESSION_NONE: + /* according to the spec this is not possible */ + *compressionTypeP = COMPRESSION_NONE; + break; + default: + pm_error("The Palm image header has an unrecognized value for " + "compression type: 0x%02x", (unsigned)compressionValue); + } +} + + + +static void +readRestOfHeaderVersion3(FILE * const ifP, + unsigned int const pixelSize, + unsigned char * const sizeP, + unsigned char * const pixelFormatP, + unsigned char * const compressionTypeP, + short * const densityP, + unsigned int * const transparentIndexP, + long * const transparentValueP, + long * const nextBitmapOffsetP, + short * const nextDepthOffsetP) { + + unsigned char unused; + + pm_readcharu(ifP, sizeP); + /* should be 0x18, but I can't see why we should really care */ + if (*sizeP != 0x18) + pm_message("Strange value for Palm bitmap header size: %hu", *sizeP); + + pm_readcharu(ifP, pixelFormatP); + if (*pixelFormatP != PALM_FORMAT_INDEXED && + *pixelFormatP != PALM_FORMAT_565) + pm_error("Unrecognized pixelformat type: %u", *pixelFormatP); + + pm_readcharu(ifP, &unused); + + + pm_readcharu(ifP, compressionTypeP); + + pm_readbigshort(ifP, densityP); + /* the specs imply that 0x00 is not valid */ + if (*densityP != PALM_DENSITY_LOW && + *densityP != PALM_DENSITY_ONEANDAHALF && + *densityP != PALM_DENSITY_DOUBLE && + *densityP != PALM_DENSITY_TRIPLE && + *densityP != PALM_DENSITY_QUADRUPLE) + pm_error("Invalid value for -density: %d.", *densityP); + + pm_readbiglong(ifP, transparentValueP); + if (pixelSize < 16) + *transparentIndexP = *transparentValueP; + else + *transparentIndexP = 0; + + pm_readbiglong(ifP, nextBitmapOffsetP); + + /* version < 3 specific */ + *nextDepthOffsetP = 0; +} + + + +static void +readRestOfHeaderOld(FILE * const ifP, + unsigned char * const sizeP, + unsigned char * const pixelFormatP, + unsigned char * const compressionTypeP, + short * const densityP, + unsigned int * const transparentIndexP, + long * const transparentValueP, + long * const nextBitmapOffsetP, + short * const nextDepthOffsetP) { + + short pad; + unsigned char transparentIndex; + + pm_readbigshort(ifP, nextDepthOffsetP); + pm_readcharu(ifP, &transparentIndex); + *transparentIndexP = transparentIndex; + + pm_readcharu(ifP,compressionTypeP); + + pm_readbigshort(ifP, &pad); /* reserved by Palm as of 8/9/00 */ + + /* version 3 specific */ + *sizeP = 0; + *pixelFormatP = 0; + *densityP = 0; + *transparentValueP = 0; + *nextBitmapOffsetP = 0; +} + + + +static void +interpretHeader(struct palmHeader * const palmHeaderP, + short const cols, + short const rows, + short const bytesPerRow, + short const flags, + unsigned char const pixelSizeCode, + unsigned int const pixelSize, + unsigned char const version, + unsigned char const size, + unsigned char const pixelFormat, + short const density, + long const transparentValue, + unsigned int const transparentIndex, + unsigned char const compressionType) { + + palmHeaderP->cols = cols; + palmHeaderP->rows = rows; + palmHeaderP->bytesPerRow = bytesPerRow; + palmHeaderP->flags = flags; /* Just for diagnostics */ + palmHeaderP->hasColormap = !!(flags & PALM_HAS_COLORMAP_FLAG); + palmHeaderP->hasTransparency = !!(flags & PALM_HAS_TRANSPARENCY_FLAG); + palmHeaderP->pixelSizeCode = pixelSizeCode; + palmHeaderP->pixelSize = pixelSize; + palmHeaderP->version = version; + palmHeaderP->size = size; + palmHeaderP->pixelFormat = pixelFormat; + palmHeaderP->density = density; + palmHeaderP->transparentValue = transparentValue; + palmHeaderP->transparentIndex = transparentIndex; + + if (palmHeaderP->version == 3 && (flags & PALM_DIRECT_COLOR_FLAG)) + /* There's no directColorInfoType section in a version 3 Palm Bitmap */ + pm_error("PALM_DIRECT_COLOR_FLAG is not valid for version 3 " + "encoding type."); + + palmHeaderP->directColor = ((flags & PALM_DIRECT_COLOR_FLAG) || + palmHeaderP->pixelFormat == PALM_FORMAT_565); + + if (flags & PALM_IS_COMPRESSED_FLAG) + interpretCompression(compressionType, + &palmHeaderP->compressionType); + else + palmHeaderP->compressionType = COMPRESSION_NONE; +} + + + +static void +readHeader(FILE * const ifP, + unsigned int const requestedRendition, + struct palmHeader * const palmHeaderP) { +/*---------------------------------------------------------------------------- + Read the Palm Bitmap header from the file 'ifP'. Read past all + renditions up to 'requestedRendition' and read the header of that + rendition. Return the information contained in the header as *palmHeaderP. +-----------------------------------------------------------------------------*/ + bool gotHeader; + unsigned int currentRendition; + + gotHeader = FALSE; + currentRendition = 1; + while (!gotHeader) { + short cols, rows, bytesPerRow, flags, nextDepthOffset, density; + unsigned char pixelSizeCode, version, compressionType, + size, pixelFormat; + long transparentValue, nextBitmapOffset; + unsigned int pixelSize, transparentIndex; + + pm_readbigshort(ifP, &cols); + pm_readbigshort(ifP, &rows); + pm_readbigshort(ifP, &bytesPerRow); + pm_readbigshort(ifP, &flags); + + pm_readcharu(ifP, &pixelSizeCode); + pixelSize = pixelSizeCode == 0 ? 1 : pixelSizeCode; + if (pixelSizeCode != 0x00 && + pixelSizeCode != 0x01 && + pixelSizeCode != 0x02 && + pixelSizeCode != 0x04 && + pixelSizeCode != 0x08 && + pixelSizeCode != 0x10 && + pixelSizeCode != 0xFF) + pm_error("Invalid value for bits per pixel: %u.", pixelSizeCode); + + if ((bytesPerRow * 8) < (cols * pixelSize)) + pm_error("%u bytes per row is not valid with %u columns and %u " + "bits per pixel.", bytesPerRow, cols, pixelSize); + + pm_readcharu(ifP, &version); + if (version > 3) + pm_error("Unknown encoding version type: %d", version); + else if (version == 3) + readRestOfHeaderVersion3(ifP, pixelSize, + &size, &pixelFormat, &compressionType, + &density, &transparentIndex, + &transparentValue, &nextBitmapOffset, + &nextDepthOffset); + else + readRestOfHeaderOld(ifP, + &size, &pixelFormat, &compressionType, + &density, &transparentIndex, + &transparentValue, &nextBitmapOffset, + &nextDepthOffset); + + if (currentRendition < requestedRendition) { + if (version < 3 && nextDepthOffset == 0 && pixelSizeCode != 0xFF) + pm_error("Not enough renditions in the input Palm Bitmap " + "to extract the %dth", requestedRendition); + if (version == 3 && nextBitmapOffset == 0) + pm_error("Not enough renditions in the input Palm Bitmap " + "to extract the %dth", requestedRendition); + /* nextDepthOffset is calculated in 4 byte words + from the beginning of this bitmap (so it equals its size) + */ + if (version < 3 && pixelSizeCode != 0xFF ) + skipbytes(ifP, (nextDepthOffset*4)-16); + else if (version == 3) + /* FIXME rewrite skipbytes to accept longs? */ + skipbytes(ifP, (short) nextBitmapOffset-24); + if (pixelSizeCode != 0xFF) + ++currentRendition; + } else if (pixelSizeCode != 0xFF) { + gotHeader = TRUE; + + interpretHeader(palmHeaderP, + cols, rows, bytesPerRow, flags, pixelSizeCode, + pixelSize, version, size, pixelFormat, density, + transparentValue, transparentIndex, + compressionType); + } + } +} + + + +static const char * +yesno(bool const arg) { + + if (arg) + return "YES"; + else + return "NO"; +} + + +static void +reportPalmHeader(struct palmHeader const palmHeader, + struct directColorInfo const directColorInfo) { + + const char *ctype; + + switch (palmHeader.compressionType) { + case COMPRESSION_RLE: + ctype = "rle (Palm OS 3.5)"; + break; + case COMPRESSION_SCANLINE: + ctype = "scanline (Palm OS 2.0)"; + break; + case COMPRESSION_PACKBITS: + ctype = "packbits (Palm OS 4.0)"; + break; + case COMPRESSION_NONE: + ctype = "none"; + break; + } + pm_message("Dimensions: %hu columns x %hu rows", + palmHeader.cols, palmHeader.rows); + pm_message("Row layout: %hu bytes per row, %hu bits per pixel", + palmHeader.bytesPerRow, palmHeader.pixelSize); + pm_message("Pixel Size code: %hu", palmHeader.pixelSizeCode); + pm_message("Flags: 0x%04hx", palmHeader.flags); + pm_message(" Direct Color: %s", yesno(palmHeader.directColor)); + pm_message(" Colormap: %s", yesno(palmHeader.hasColormap)); + pm_message(" Transparency: %s", yesno(palmHeader.hasTransparency)); + pm_message("Version %d", palmHeader.version); + if (palmHeader.hasTransparency) { + if (palmHeader.directColor) { + /* Copied from doTransparent(...) */ + Color_s const color = directColorInfo.transparentColor; + pm_message("Transparent value: #%02x%02x%02x", + (unsigned int)((color >> 16) & 0xFF), + (unsigned int)((color >> 8) & 0xFF), + (unsigned int)((color >> 0) & 0xFF)); + } else + pm_message("Transparent index: %u", palmHeader.transparentIndex); + } + pm_message("Compression type: %s", ctype); + if (palmHeader.version == 3) + pm_message("Density: %d", palmHeader.density); +} + + + +static void +determineOutputFormat(struct palmHeader const palmHeader, + int * const formatP, + xelval * const maxvalP) { + + if (palmHeader.directColor) { + *formatP = PPM_TYPE; + *maxvalP = 255; + } else if (palmHeader.hasColormap) { + *formatP = PPM_TYPE; + *maxvalP = 255; + } else if (palmHeader.pixelSize == 1) { + *formatP = PBM_TYPE; + *maxvalP = 1; + } else if (palmHeader.pixelSize >= 8) { + *formatP = PPM_TYPE; + *maxvalP = pm_bitstomaxval(palmHeader.pixelSize); + } else { + *formatP = PGM_TYPE; + *maxvalP = pm_bitstomaxval(palmHeader.pixelSize); + } +} + + + +static void +readRgbFormat(FILE * const ifP, + struct directPixelFormat * const pixelFormatP) { + + unsigned char r, g, b; + + pm_readcharu(ifP, &r); + pm_readcharu(ifP, &g); + pm_readcharu(ifP, &b); + + if (r != 5 || g != 6 || b != 5) + pm_error("This image has a direct color pixel format of " + "%u red, %u green, %u blue bits. This program " + "can handle only 5, 6, 5.", r, g, b); + else { + pixelFormatP->redbits = r; + pixelFormatP->greenbits = g; + pixelFormatP->bluebits = b; + } +} + + + +static void +readDirectTransparentColor(FILE * const ifP, + Color_s * const colorP) { + + unsigned char r, g, b; + + pm_readcharu(ifP, &r); + pm_readcharu(ifP, &g); + pm_readcharu(ifP, &b); + + *colorP = (r << 16) | (g << 8) | (b << 0); +} + + + +static void +readDirectInfoType(FILE * const ifP, + struct palmHeader const palmHeader, + struct directColorInfo * const directInfoTypeP) { +/*---------------------------------------------------------------------------- + Read the Palm Bitmap Direct Info Type section, if any. + + The Direct Info Type section is a section of a pre-Version 3 direct + color Palm Bitmap that tells how to interpret the direct color + raster. + + Return an undefined value as *directInfoTypeP if there is no such + section in this Palm Bitmap. +-----------------------------------------------------------------------------*/ + if ((palmHeader.directColor) && palmHeader.pixelSize != 16) + pm_error("The image is of the direct color type, but has %u " + "bits per pixel. The only kind of direct color images " + "this program understands are 16 bit ones.", + palmHeader.pixelSize); + + if (palmHeader.version == 3) { + /* All direct color info is in the header, because it'sversion + 3 encoding. No Direct Info Type section. + */ + } else { + if (palmHeader.directColor) { + unsigned char padding; + + readRgbFormat(ifP, &directInfoTypeP->pixelFormat); + + pm_readcharu(ifP, &padding); + pm_readcharu(ifP, &padding); + + readDirectTransparentColor(ifP, + &directInfoTypeP->transparentColor); + } else { + /* Not a direct color image; no Direct Info Type section. */ + } + } +} + + + +static void +readColormap(FILE * const ifP, + struct palmHeader const palmHeader, + Colormap * const colormapP) { +/*---------------------------------------------------------------------------- + Read the colormap, if any from the Palm Bitmap. + + If the image described by 'palmHeader' doesn't have a colormap, + return an undefined value as *colormapP. +-----------------------------------------------------------------------------*/ + if (palmHeader.hasColormap) + *colormapP = palmcolor_read_colormap(ifP); +} + + + +static void +getColorInfo(struct palmHeader const palmHeader, + struct directColorInfo const directInfoType, + Colormap const colormapFromImage, + Colormap * const colormapP, + unsigned int * const ncolorsP, + struct directColorInfo * const directColorInfoP) { +/*---------------------------------------------------------------------------- + Gather color encoding information from the various sources. + + Note that 'directInfoType' and 'colormapFromImage' are meaningful only + with certain values of 'palmHeader'. + + If it's a version 3 direct color, the pixel format must be "565". +-----------------------------------------------------------------------------*/ + if (palmHeader.version == 3 && palmHeader.directColor) { + *colormapP = NULL; + + assert(palmHeader.pixelFormat == PALM_FORMAT_565); + + directColorInfoP->pixelFormat.redbits = 5; + directColorInfoP->pixelFormat.greenbits = 6; + directColorInfoP->pixelFormat.bluebits = 5; + /* FIXME Just guessing here ... */ + directColorInfoP->transparentColor = + (((palmHeader.transparentValue >> 11) & 0x1F) << 16) | + (((palmHeader.transparentValue >> 5) & 0x3F) << 8) | + (((palmHeader.transparentValue >> 0) & 0x1F) << 0); + } else if (palmHeader.directColor) { + *colormapP = NULL; + *directColorInfoP = directInfoType; + } else if (palmHeader.hasColormap) + *colormapP = colormapFromImage; + else if (palmHeader.pixelSize >= 8) { + Colormap colormap; + colormap = palmcolor_build_default_8bit_colormap(); + qsort(colormap->color_entries, colormap->ncolors, + sizeof(Color_s), palmcolor_compare_indices); + *colormapP = colormap; + } else + *colormapP = NULL; + + *ncolorsP = 1 << palmHeader.pixelSize; +} + + + +static void +doTransparent(FILE * const ofP, + bool const hasTransparency, + bool const directColor, + unsigned char const transparentIndex, + unsigned char const pixelSize, + Colormap const colormap, + struct directColorInfo const directColorInfo) { +/*---------------------------------------------------------------------------- + Generate a PNM comment on *ofP telling what color in the raster is + supposed to be transparent. + + Note that PNM itself doesn't have any way to represent transparency. + (But this program could be converted to a PAM program and use the + RGB_ALPHA and GRAYSCALE_ALPHA tuple types). +-----------------------------------------------------------------------------*/ + if (hasTransparency) { + if (colormap) { + Color_s const color = transparentIndex << 24; + Color const actualColor = (bsearch(&color, + colormap->color_entries, + colormap->ncolors, + sizeof(color), + palmcolor_compare_indices)); + fprintf(ofP, "#%02x%02x%02x\n", + (unsigned int) ((*actualColor >> 16) & 0xFF), + (unsigned int) ((*actualColor >> 8) & 0xFF), + (unsigned int) ((*actualColor >> 0) & 0xFF)); + } else if (directColor) { + Color_s const color = directColorInfo.transparentColor; + fprintf(ofP, "#%02x%02x%02x\n", + (unsigned int)((color >> 16) & 0xFF), + (unsigned int)((color >> 8) & 0xFF), + (unsigned int)((color >> 0) & 0xFF)); + } else { + unsigned int const maxval = pm_bitstomaxval(pixelSize); + unsigned int const grayval = + ((maxval - transparentIndex) * 256) / maxval; + fprintf(ofP, "#%02x%02x%02x\n", grayval, grayval, grayval); + } + } +} + + + +static void +createHistogram(unsigned int const ncolors, + unsigned int ** const seenP) { + + unsigned int * seen; + + MALLOCARRAY(seen, ncolors); + if (!seen) + pm_error("Can't allocate array for keeping track of " + "how many pixels of each of %u colors are in the image.", + ncolors); + + { + /* Initialize the counter for each color to zero */ + unsigned int i; + for (i = 0; i < ncolors; ++i) + seen[i] = 0; + } + *seenP = seen; +} + + + +static void +readScanlineRow(FILE * const ifP, + unsigned char * const palmrow, + unsigned char * const lastrow, + unsigned int const bytesPerRow, + bool const firstRow) { + + unsigned int j; + + for (j = 0; j < bytesPerRow; j += 8) { + unsigned char diffmask; + /* A mask telling whether each of the 8 raster bytes indexed + j through j+7 is the same as in the previous row ('lastrow') + or is to be read from the file. Bit 0 of the mask refers + to byte j, Bit 1 to byte j + 1, etc. + */ + unsigned int byteCount; + /* How many bytes are covered by 'diffmask'. Normally 8, but + at the end of the row, could be less. + */ + unsigned int k; + + pm_readcharu(ifP, &diffmask); + byteCount = MIN(bytesPerRow - j, 8); + + for (k = 0; k < byteCount; ++k) { + /* the first row cannot be compressed */ + if (firstRow || ((diffmask & (1 << (7 - k))) != 0)) { + unsigned char inval; + pm_readcharu(ifP, &inval); + palmrow[j + k] = inval; + } else + palmrow[j + k] = lastrow[j + k]; + } + } + memcpy(lastrow, palmrow, bytesPerRow); +} + + + +static void +readRleRow(FILE * const ifP, + unsigned char * const palmrow, + unsigned int const bytesPerRow) { + + unsigned int j; + + for (j = 0; j < bytesPerRow; ) { + unsigned char incount; + unsigned char inval; + + pm_readcharu(ifP, &incount); + if (incount == 0) + pm_error("Invalid (zero) count in RLE compression."); + if (j + incount > bytesPerRow) + pm_error("Bytes in RLE compressed row exceed bytes per row. " + "Bytes per row is %u. A run length of %u bytes " + "pushes the bytes in this row up to %u bytes (and then" + "we gave up.", bytesPerRow, incount, j + incount); + pm_readcharu(ifP, &inval); + memset(palmrow + j, inval, incount); + j += incount; + } +} + + + +static void +readPackBitsRow(FILE * const ifP, + unsigned char * const palmrow, + unsigned int const bytesPerRow) { + + unsigned int j; + + for (j = 0; j < bytesPerRow; ) { + char incount; + pm_readchar(ifP, &incount); + if (incount < 0) { + /* How do we handle incount == -128 ? */ + unsigned int const runlength = -incount + 1; + unsigned char inval; + pm_readcharu(ifP, &inval); + memset(palmrow + j, inval, runlength); + j += runlength; + } else { + unsigned int const nonrunlength = incount + 1; + unsigned int k; + for (k = 0; k < nonrunlength; ++k) { + unsigned char inval; + pm_readcharu(ifP, &inval); + palmrow[j + k] = inval; + } + j += nonrunlength; + } + if (j > bytesPerRow) + pm_error("Bytes in PackBits compressed row exceed bytes per row."); + } +} + + + +static void +readUncompressedRow(FILE * const ifP, + unsigned char * const palmrow, + unsigned int const bytesPerRow) { + + int bytesRead; + + bytesRead = fread(palmrow, 1, bytesPerRow, ifP); + if (bytesRead != bytesPerRow) + pm_error("Error reading Palm file. Short read."); +} + + + +static void +readDecompressedRow(FILE * const ifP, + unsigned char * const palmrow, + unsigned char * const lastrow, + enum palmCompressionType const compressionType, + unsigned int const bytesPerRow, + bool const firstRow) { +/*---------------------------------------------------------------------------- + Read a row from Palm file 'ifP', in uncompressed form (i.e. decompress if + necessary). Assume the row contains 'bytesPerRow' uncompressed bytes, + compressed according to 'compressionType'. Return the data at 'palmrow'. + + 'firstRow' means decompress it as if it is the first row of the image + (some compression schemes transform the first row differently from the + rest, because each row depends on the row before it). + + If 'compressionType' is COMPRESSION_SCANLINE, (which means + transformation of a row depends on the contents of the row before + it), then 'lastRow' is as input the uncompressed contents of the + previous row (undefined if 'firstRow' is true). In that case, we + modify 'lastrow' to contain a copy of 'palmrow' (so Caller can + conveniently use it to read the next row). + + If 'compressionType' is not COMPRESSION_SCANLINE, 'lastrow' is + undefined both as input and output. +-----------------------------------------------------------------------------*/ + switch (compressionType) { + case COMPRESSION_RLE: + readRleRow(ifP, palmrow, bytesPerRow); + break; + case COMPRESSION_SCANLINE: + readScanlineRow(ifP, palmrow, lastrow, bytesPerRow, firstRow); + break; + case COMPRESSION_PACKBITS: + readPackBitsRow(ifP, palmrow, bytesPerRow); + break; + case COMPRESSION_NONE: + readUncompressedRow(ifP, palmrow, bytesPerRow); + break; + } +} + + + +static void +convertRowToPnmDirect(const unsigned char * const palmrow, + xel * const xelrow, + unsigned int const cols, + xelval const maxval, + unsigned int * const seen) { + + /* There's a problem with this. Take the Palm 16-bit + direct color. That's 5 bits for the red, 6 for the + green, and 5 for the blue. So what should the MAXVAL + be? I decided to use 255 (8 bits) for everything, + since that's the theoretical max of the number of bits + in any one color, according to Palm. So the Palm color + 0xFFFF (white) would be red=0x1F, green=0x3F, and + blue=0x1F. How do we promote those colors? Simple + shift would give us R=248,G=252,B=248; which is + slightly green. Hardly seems right. + + So I've perverted the math a bit. Each color value is + multiplied by 255, then divided by either 31 (red or + blue) or 63 (green). That's the right way to do it + anyway. + */ + + const unsigned char *inbyte; + unsigned int j; + + for (inbyte = palmrow, j = 0; j < cols; ++j) { + unsigned int inval; + inval = *inbyte++ << 8; + inval |= *inbyte++; + + if (seen) + ++seen[inval]; + + PPM_ASSIGN(xelrow[j], + (((inval >> 11) & 0x1F) * maxval) / 0x1F, + (((inval >> 5) & 0x3F) * maxval) / 0x3F, + (((inval >> 0) & 0x1F) * maxval) / 0x1F + ); + } +} + + + +static void +convertRowToPnmNotDirect(const unsigned char * const palmrow, + xel * const xelrow, + unsigned int const cols, + Colormap const colormap, + xelval * const graymap, + unsigned int * const seen, + unsigned int const pixelSize) { + + unsigned int const mask = (1 << pixelSize) - 1; + + const unsigned char *inbyte; + unsigned int inbit; + unsigned int j; + + inbit = 8 - pixelSize; + inbyte = palmrow; + for (j = 0; j < cols; ++j) { + short const color = ((*inbyte) & (mask << inbit)) >> inbit; + if (seen) + ++seen[color]; + + if (colormap) { + Color_s const color2 = color << 24; + Color const actualColor = (bsearch (&color2, + colormap->color_entries, + colormap->ncolors, + sizeof(color2), + palmcolor_compare_indices)); + PPM_ASSIGN(xelrow[j], + (*actualColor >> 16) & 0xFF, + (*actualColor >> 8) & 0xFF, + (*actualColor >> 0) & 0xFF); + } else + PNM_ASSIGN1(xelrow[j], graymap[color]); + + if (!inbit) { + ++inbyte; + inbit = 8 - pixelSize; + } else + inbit -= pixelSize; + } +} + + + +static void +writePnm(FILE * const ofP, + struct palmHeader const palmHeader, + FILE * const ifP, + Colormap const colormap, + xelval * const graymap, + unsigned int const nColors, + int const format, + xelval const maxval, + unsigned int ** const seenP) { + + int const cols = palmHeader.cols; + int const rows = palmHeader.rows; + + unsigned char * palmrow; + unsigned char * lastrow; + xel * xelrow; + unsigned int * seen; + unsigned int row; + + pnm_writepnminit(ofP, cols, rows, maxval, format, 0); + xelrow = pnm_allocrow(cols); + + /* Read the picture data, one row at a time */ + MALLOCARRAY_NOFAIL(palmrow, palmHeader.bytesPerRow); + MALLOCARRAY_NOFAIL(lastrow, palmHeader.bytesPerRow); + + if (seenP) { + createHistogram(nColors, &seen); + *seenP = seen; + } else + seen = NULL; + + /* We should actually use compressedDataSizeNN for checking the sanity + of the data we're reading ... + */ + if (palmHeader.compressionType != COMPRESSION_NONE) { + if (palmHeader.version < 3) { + short compressedDataSize16; + pm_readbigshort(ifP, &compressedDataSize16); + } else { + long compressedDataSize32; + pm_readbiglong(ifP, &compressedDataSize32); + } + } + + for (row = 0; row < rows; ++row) { + readDecompressedRow(ifP, palmrow, lastrow, + palmHeader.compressionType, + palmHeader.bytesPerRow, + row == 0); + + if (palmHeader.directColor) { + assert(palmHeader.pixelSize == 16); + convertRowToPnmDirect(palmrow, xelrow, cols, maxval, seen); + } else + convertRowToPnmNotDirect(palmrow, xelrow, cols, colormap, graymap, + seen, palmHeader.pixelSize); + + pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0); + } + free(lastrow); + free(palmrow); + pnm_freerow(xelrow); +} + + + +static void +showHistogram(unsigned int * const seen, + Colormap const colormap, + const xelval * const graymap, + unsigned int const ncolors) { + + unsigned int colorIndex; + + for (colorIndex = 0; colorIndex < ncolors; ++colorIndex) { + if (!colormap) + pm_message("%.3d -> %.3d: %d", + colorIndex, graymap[colorIndex], seen[colorIndex]); + else { + Color_s const color = colorIndex << 24; + Color const actualColor = (bsearch(&color, + colormap->color_entries, + colormap->ncolors, + sizeof(color), + palmcolor_compare_indices)); + if (actualColor) + pm_message("%.3d -> %ld,%ld,%ld: %d", colorIndex, + (*actualColor >> 16) & 0xFF, + (*actualColor >> 8) & 0xFF, + (*actualColor & 0xFF), seen[colorIndex]); + } + } +} + + + +int +main(int argc, char **argv) { + + struct cmdlineInfo cmdline; + + FILE* ifP; + struct palmHeader palmHeader; + struct directColorInfo directInfoType; + Colormap colormapFromImage; + Colormap colormap; + struct directColorInfo directColorInfo; + int format; + xelval maxval; + unsigned int nColors; + + /* Parse default params */ + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.inputFilespec); + + readHeader(ifP, cmdline.rendition, &palmHeader); + + readDirectInfoType(ifP, palmHeader, &directInfoType); + + readColormap(ifP, palmHeader, &colormapFromImage); + + determineOutputFormat(palmHeader, &format, &maxval); + + getColorInfo(palmHeader, directInfoType, colormapFromImage, + &colormap, &nColors, &directColorInfo); + + if (cmdline.verbose) + reportPalmHeader(palmHeader, directColorInfo); + + if (cmdline.transparent) + doTransparent(stdout, + palmHeader.hasTransparency, palmHeader.directColor, + palmHeader.transparentIndex, + palmHeader.pixelSize, colormap, directColorInfo); + else { + unsigned int * seen; + xelval * graymap; + + graymap = createGraymap(nColors, maxval); + + writePnm(stdout, + palmHeader, ifP, colormap, graymap, nColors, format, maxval, + cmdline.showhist ? &seen : NULL); + + if (cmdline.showhist) + showHistogram(seen, colormap, graymap, nColors); + + free(graymap); + } + pm_close(ifP); + + return 0; +} |