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/ppm/ppmtoxpm.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/ppm/ppmtoxpm.c')
-rw-r--r-- | converter/ppm/ppmtoxpm.c | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/converter/ppm/ppmtoxpm.c b/converter/ppm/ppmtoxpm.c new file mode 100644 index 00000000..853ae711 --- /dev/null +++ b/converter/ppm/ppmtoxpm.c @@ -0,0 +1,659 @@ +/* ppmtoxpm.c - read a portable pixmap and produce a (version 3) X11 pixmap +** +** Copyright (C) 1990 by Mark W. Snitily +** +** Permission to use, copy, modify, and distribute this software and its +** documentation for any purpose and without fee is hereby granted, provided +** that the above copyright notice appear in all copies and that both that +** copyright notice and this permission notice appear in supporting +** documentation. This software is provided "as is" without express or +** implied warranty. +** +** This tool was developed for Schlumberger Technologies, ATE Division, and +** with their permission is being made available to the public with the above +** copyright notice and permission notice. +** +** Upgraded to XPM2 by +** Paul Breslaw, Mecasoft SA, Zurich, Switzerland (paul@mecazh.uu.ch) +** Thu Nov 8 16:01:17 1990 +** +** Upgraded to XPM version 3 by +** Arnaud Le Hors (lehors@mirsa.inria.fr) +** Tue Apr 9 1991 +** +** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91: +** - Bug fix, should should malloc space for rgbn[j].name+1 in line 441 +** caused segmentation faults +** +** - lowercase conversion of RGB names def'ed out, +** considered harmful. +** +** Michael Pall (pall@rz.uni-karlsruhe.de) - 29 Nov 93: +** - Use the algorithm from xpm-lib for pixel encoding +** (base 93 not base 28 -> saves a lot of space for colorful xpms) +*/ + +#define _BSD_SOURCE /* Make sure strdup() is in string.h */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include "ppm.h" +#include "shhopt.h" +#include "nstring.h" +#include "mallocvar.h" + +/* Max number of entries we will put in the XPM color map + Don't forget the one entry for transparency. + + We don't use this anymore. Ppmtoxpm now has no arbitrary limit on + the number of colors. + + We're assuming this isn't in fact an XPM format requirement, because + we've seen it work with 257, and 257 seems to be common, because it's + the classic 256 colors, plus transparency. The value was 256 for + ages before we added transparency capability to this program in May + 2001. At that time, we started failing with 256 color images. + Some limit was also necessary before then because ppm_computecolorhash() + required us to specify a maximum number of colors. It doesn't anymore. + + If we find out that some XPM processing programs choke on more than + 256 colors, we'll have to readdress this issue. - Bryan. 2001.05.13. +*/ +#define MAXCOLORS 256 + +#define MAXPRINTABLE 92 /* number of printable ascii chars + * minus \ and " for string compat + * and ? to avoid ANSI trigraphs. */ + +static const char * const printable = +" .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZ\ +ASDFGHJKLPIUYTREWQ!~^/()_`'][{}|"; + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char *inputFilename; + const char *name; + const char *rgb; + const char *alpha_filename; + unsigned int hexonly; + unsigned int verbose; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const 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; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + const char * nameOpt; + unsigned int nameSpec; + + MALLOCARRAY(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "alphamask", OPT_STRING, &cmdlineP->alpha_filename, + NULL, 0); + OPTENT3(0, "name", OPT_STRING, &nameOpt, + &nameSpec, 0); + OPTENT3(0, "rgb", OPT_STRING, &cmdlineP->rgb, + NULL, 0); + OPTENT3(0, "hexonly", OPT_FLAG, NULL, + &cmdlineP->hexonly, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, + &cmdlineP->verbose, 0); + + /* Set the defaults */ + cmdlineP->alpha_filename = NULL; /* no transparency */ + cmdlineP->rgb = NULL; /* no rgb file specified */ + + 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 (argc-1 == 0) + cmdlineP->inputFilename = "-"; + else if (argc-1 != 1) + pm_error("Program takes zero or one argument (filename). You " + "specified %d", argc-1); + else + cmdlineP->inputFilename = argv[1]; + + /* If output filename not specified, use input filename as default. */ + if (nameSpec) + cmdlineP->name = nameOpt; + else if (STREQ(cmdlineP->inputFilename, "-")) + cmdlineP->name = "noname"; + else { + static char name[80+1]; + char *cp; + + STRSCPY(name, cmdlineP->inputFilename); + cp = strchr(name, '.'); + if (cp) + *cp = '\0'; /* remove extension */ + cmdlineP->name = name; + } +} + + +typedef struct { /* rgb values and ascii names (from + * rgb text file) */ + int r, g, b; /* rgb values, range of 0 -> 65535 */ + char *name; /* color mnemonic of rgb value */ +} rgb_names; + +typedef struct { + /* Information for an XPM color map entry */ + char *cixel; + /* XPM color number, as might appear in the XPM raster */ + const char *rgbname; + /* color name, e.g. "blue" or "#01FF23" */ +} cixel_map; + + + +static char * +genNumstr(unsigned int const input, int const digits) { +/*--------------------------------------------------------------------------- + Given a number and a base (MAXPRINTABLE), this routine prints the + number into a malloc'ed string and returns it. The length of the + string is specified by "digits". It is not printed in decimal or + any other number system you're used to. Rather, each digit is from + the set printable[], which contains MAXPRINTABLE characters; the + character printable[n] has value n. + + The string is printable[0] filled with high order zeroes. + + Example: + Assume: + printable[0] == 'q' + printable[1] == '%' + MAXPRINTABLE == 2 + digits == 5 + input == 3 + Result is the malloc'ed string "qqq%%" +---------------------------------------------------------------------------*/ + char *str, *p; + int d; + unsigned int i; + + /* Allocate memory for printed number. Abort if error. */ + if (!(str = (char *) malloc(digits + 1))) + pm_error("out of memory"); + + i = input; + /* Generate characters starting with least significant digit. */ + p = str + digits; + *p-- = '\0'; /* nul terminate string */ + while (p >= str) { + d = i % MAXPRINTABLE; + i /= MAXPRINTABLE; + *p-- = printable[d]; + } + + if (i != 0) + pm_error("Overflow converting %d to %d digits in base %d", + input, digits, MAXPRINTABLE); + + return str; +} + + + +static unsigned int +xpmMaxvalFromMaxval(pixval const maxval) { + + unsigned int retval; + + /* + * Determine how many hex digits we'll be normalizing to if the rgb + * value doesn't match a color mnemonic. + */ + if (maxval <= 0x000F) + retval = 0x000F; + else if (maxval <= 0x00FF) + retval = 0x00FF; + else if (maxval <= 0x0FFF) + retval = 0x0FFF; + else if (maxval <= 0xFFFF) + retval = 0xFFFF; + else + pm_error("Internal error - impossible maxval %x", maxval); + + return retval; +} + + + +static unsigned int +charsPerPixelForSize(unsigned int const cmapSize) { +/*---------------------------------------------------------------------------- + Return the number of characters it will take to represent a pixel in + an XPM that has a colormap of size 'cmapSize'. Each pixel in an XPM + represents an index into the colormap with a base-92 scheme where each + character is one of 92 printable characters. Ergo, if the colormap + has at most 92 characters, each pixel will be represented by a single + character. If it has more than 92, but at most 92*92, it will take 2, + etc. + + If cmapSize is zero, there's no such thing as an XPM pixel, so we + return an undefined value. +-----------------------------------------------------------------------------*/ + unsigned int charsPerPixel; + + if (cmapSize > 0) { + unsigned int j; + + for (charsPerPixel = 0, j = cmapSize-1; j > 0; ++charsPerPixel) + j /= MAXPRINTABLE; + } + return charsPerPixel; +} + + + +static void +genCmap(colorhist_vector const chv, + int const ncolors, + pixval const maxval, + colorhash_table const colornameHash, + const char * const colornames[], + bool const includeTransparent, + cixel_map ** const cmapP, + unsigned int * const transIndexP, + unsigned int * const cmapSizeP, + unsigned int * const charsPerPixelP) { +/*---------------------------------------------------------------------------- + Generate the XPM color map in cixel_map format (which is just a step + away from the actual text that needs to be written the XPM file). The + color map is defined by 'chv', which contains 'ncolors' colors which + have maxval 'maxval'. + + Output is in newly malloc'ed storage, with its address returned as + *cmapP. We return the number of entries in it as *cmapSizeP. + + This map includes an entry for transparency, whether the raster uses + it or not. We return its index as *transIndexP. + + In the map, identify colors by the names given by 'colornameHash' and + colornames[]. 'colornameHash' maps a color in 'pixel' form to an + index into colornames[]; colornames[] contains the text to identify the + color in the XPM format. The colors in 'colornameHash' have maxval 255. + If a color is not in 'colornameHash', use hexadecimal notation in the + output colormap. + + But if 'colornameHash' is null, don't use color names at all. Just use + hexadecimal notation. + + Return as *charsPerPixel the number of characters, or digits, that + will be needed in the XPM raster to form an index into this color map. +-----------------------------------------------------------------------------*/ + unsigned int const cmapSize = ncolors + (includeTransparent ? 1 : 0); + + cixel_map * cmap; /* malloc'ed */ + unsigned int cmapIndex; + unsigned int charsPerPixel; + unsigned int xpmMaxval; + + MALLOCARRAY(cmap, cmapSize); + if (cmapP == NULL) + pm_error("Out of memory allocating %u bytes for a color map.", + sizeof(cixel_map) * (ncolors+1)); + + xpmMaxval = xpmMaxvalFromMaxval(maxval); + + charsPerPixel = charsPerPixelForSize(cmapSize); + + /* + * Generate the character-pixel string and the rgb name for each + * colormap entry. + */ + for (cmapIndex = 0; cmapIndex < ncolors; ++cmapIndex) { + pixel const color = chv[cmapIndex].color; + + pixel color255; + /* The color, scaled to maxval 255 */ + const char * colorname; /* malloc'ed */ + /* + * The character-pixel string is simply a printed number in base + * MAXPRINTABLE where the digits of the number range from + * printable[0] .. printable[MAXPRINTABLE-1] and the printed length + * of the number is 'charsPerPixel'. + */ + cmap[cmapIndex].cixel = genNumstr(cmapIndex, charsPerPixel); + + PPM_DEPTH(color255, color, maxval, 255); + + if (colornameHash == NULL) + colorname = NULL; + else { + int colornameIndex; + colornameIndex = ppm_lookupcolor(colornameHash, &color255); + if (colornameIndex >= 0) + colorname = strdup(colornames[colornameIndex]); + else + colorname = NULL; + } + if (colorname) + cmap[cmapIndex].rgbname = colorname; + else { + /* Color has no name; represent it in hexadecimal */ + + pixel scaledColor; + const char * hexString; /* malloc'ed */ + + PPM_DEPTH(scaledColor, color, maxval, xpmMaxval); + + asprintfN(&hexString, xpmMaxval == 0x000F ? "#%X%X%X" : + xpmMaxval == 0x00FF ? "#%02X%02X%02X" : + xpmMaxval == 0x0FFF ? "#%03X%03X%03X" : + "#%04X%04X%04X", + PPM_GETR(scaledColor), + PPM_GETG(scaledColor), + PPM_GETB(scaledColor) + ); + + if (hexString == NULL) + pm_error("Unable to allocate storage for hex string"); + cmap[cmapIndex].rgbname = hexString; + } + } + + if (includeTransparent) { + /* Add the special transparency entry to the colormap */ + unsigned int const transIndex = ncolors; + cmap[transIndex].rgbname = strdup("None"); + cmap[transIndex].cixel = genNumstr(transIndex, charsPerPixel); + *transIndexP = transIndex; + } + *cmapP = cmap; + *cmapSizeP = cmapSize; + *charsPerPixelP = charsPerPixel; +} + + + +static void +destroyCmap(cixel_map * const cmap, + unsigned int const cmapSize) { + + int i; + /* Free the real color entries */ + for (i = 0; i < cmapSize; i++) { + strfree(cmap[i].rgbname); + free(cmap[i].cixel); + } + free(cmap); +} + + + +static void +writeXpmFile(FILE * const outfile, + pixel ** const pixels, + gray ** const alpha, + pixval const alphamaxval, + char const name[], + int const cols, + int const rows, + unsigned int const cmapSize, + unsigned int const charsPerPixel, + cixel_map const cmap[], + colorhash_table const cht, + unsigned int const transIndex) { +/*---------------------------------------------------------------------------- + Write the whole XPM file to the open stream 'outfile'. + + 'cmap' is the colormap to be placed in the XPM. 'cmapSize' is the + number of entries in it. 'cht' is a hash table that gives you an + index into 'cmap' given a color. 'transIndex' is the index into cmap + of the transparent color, and is valid only if 'alpha' is non-null + (otherwise, cmap might not contain a transparent color). +-----------------------------------------------------------------------------*/ + /* First the header */ + printf("/* XPM */\n"); + fprintf(outfile, "static char *%s[] = {\n", name); + fprintf(outfile, "/* width height ncolors chars_per_pixel */\n"); + fprintf(outfile, "\"%d %d %d %d\",\n", cols, rows, + cmapSize, charsPerPixel); + + { + int i; + /* Write out the colormap (part of header) */ + fprintf(outfile, "/* colors */\n"); + for (i = 0; i < cmapSize; i++) { + fprintf(outfile, "\"%s c %s\",\n", cmap[i].cixel, cmap[i].rgbname); + } + } + { + int row; + + /* And now the raster */ + fprintf(outfile, "/* pixels */\n"); + for (row = 0; row < rows; row++) { + int col; + fprintf(outfile, "\""); + for (col = 0; col < cols; col++) { + if (alpha && alpha[row][col] <= alphamaxval/2) + /* It's a transparent pixel */ + fprintf(outfile, "%s", cmap[transIndex].cixel); + else + fprintf(outfile, "%s", + cmap[ppm_lookupcolor(cht, + &pixels[row][col])].cixel); + } + fprintf(outfile, "\"%s\n", (row == (rows - 1) ? "" : ",")); + } + } + /* And close up */ + fprintf(outfile, "};\n"); +} + + + +static void +readAlpha(const char filespec[], gray *** const alphaP, + int const cols, int const rows, pixval * const alphamaxvalP) { + + FILE * alpha_file; + int alphacols, alpharows; + + alpha_file = pm_openr(filespec); + *alphaP = pgm_readpgm(alpha_file, &alphacols, &alpharows, alphamaxvalP); + pm_close(alpha_file); + + if (cols != alphacols || rows != alpharows) + pm_error("Alpha mask is not the same dimensions as the " + "image. Image is %d by %d, while mask is %d x %d.", + cols, rows, alphacols, alpharows); +} + + + +static void +computecolorhash(pixel ** const pixels, + gray ** const alpha, + int const cols, + int const rows, + gray const alphaMaxval, + colorhash_table * const chtP, + unsigned int * const ncolorsP, + bool * const transparentSomewhereP) { +/*---------------------------------------------------------------------------- + Compute a colorhash_table with one entry for each color in 'pixels' that + is not mostly transparent according to alpha mask 'alpha' (which has + maxval 'alphaMaxval'). alpha == NULL means all pixels are opaque. + + The value associated with the color in the hash we build is meaningless. + + Return the colorhash_table as *chtP, and the number of colors in it + as *ncolorsP. Return *transparentSomewhereP == TRUE iff the image has + at least one pixel that is mostly transparent. +-----------------------------------------------------------------------------*/ + colorhash_table cht; + int row; + + cht = ppm_alloccolorhash( ); + *ncolorsP = 0; /* initial value */ + *transparentSomewhereP = FALSE; /* initial assumption */ + + /* Go through the entire image, building a hash table of colors. */ + for (row = 0; row < rows; ++row) { + int col; + + for (col = 0; col < cols; ++col) { + if (!alpha || alpha[row][col] > alphaMaxval/2) { + /* It's mostly opaque, so add this color to the hash + if it's not already there. + */ + pixel const color = pixels[row][col]; + int const lookupRc = ppm_lookupcolor(cht, &color); + + if (lookupRc < 0) { + /* It's not in the hash yet, so add it */ + ppm_addtocolorhash(cht, &color, 0); + ++(*ncolorsP); + } + } else + *transparentSomewhereP = TRUE; + } + } + *chtP = cht; +} + + + +static void +computeColormap(pixel ** const pixels, + gray ** const alpha, + int const cols, + int const rows, + gray const alphaMaxval, + colorhist_vector * const chvP, + colorhash_table * const chtP, + unsigned int * const ncolorsP, + bool * const transparentSomewhereP) { +/*---------------------------------------------------------------------------- + Compute the color map for the image 'pixels', which is 'cols' by 'rows', + in Netpbm data structures (a colorhist_vector for index-to-color lookups + and a colorhash_table for color-to-index lookups). + + Exclude pixels that alpha mask 'alpha' (which has maxval + 'alphaMaxval') says are mostly transparent. alpha == NULL means all + pixels are opaque. + + We return as *chvP an array of the colors present in 'pixels', + excluding those that are mostly transparent. We return as + *ncolorsP the number of such colors. We return + *transparentSomewhereP == TRUE iff the image has at least one + pixel that is mostly transparent. +-----------------------------------------------------------------------------*/ + colorhash_table histCht; + + pm_message("(Computing colormap..."); + computecolorhash(pixels, alpha, cols, rows, alphaMaxval, + &histCht, ncolorsP, transparentSomewhereP); + pm_message("...Done. %d colors found.)", *ncolorsP); + + *chvP = ppm_colorhashtocolorhist(histCht, *ncolorsP); + ppm_freecolorhash(histCht); + /* Despite the name, the following generates an index on *chvP, + with which given a color you can quickly find the entry number + in *chvP that contains that color. + */ + *chtP = ppm_colorhisttocolorhash(*chvP, *ncolorsP); +} + + + +int +main(int argc, char *argv[]) { + + FILE *ifp; + int rows, cols; + unsigned int ncolors; + bool transparentSomewhere; + pixval maxval, alphaMaxval; + colorhash_table cht; + colorhist_vector chv; + + colorhash_table colornameHash; + /* Hash table to map colors to their names */ + const char ** colornames; + /* Table of color names; 'colornameHash' yields an index into this + array. + */ + + pixel **pixels; + gray **alpha; + + /* Used for rgb value -> character-pixel string mapping */ + cixel_map *cmap; /* malloc'ed */ + /* The XPM colormap */ + unsigned int cmapSize; + /* Number of entries in 'cmap' */ + unsigned int transIndex; + /* Index into 'cmap' of the transparent color, if there is one */ + + unsigned int charsPerPixel; + + struct cmdlineInfo cmdline; + + ppm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifp = pm_openr(cmdline.inputFilename); + pixels = ppm_readppm(ifp, &cols, &rows, &maxval); + pm_close(ifp); + + if (cmdline.alpha_filename) + readAlpha(cmdline.alpha_filename, &alpha, cols, rows, &alphaMaxval); + else + alpha = NULL; + + computeColormap(pixels, alpha, cols, rows, alphaMaxval, + &chv, &cht, &ncolors, &transparentSomewhere); + + if (cmdline.hexonly) + colornameHash = NULL; + else + ppm_readcolornamefile(cmdline.rgb, FALSE, &colornameHash, &colornames); + + /* Now generate the character-pixel colormap table. */ + genCmap(chv, ncolors, maxval, + colornameHash, colornames, transparentSomewhere, + &cmap, &transIndex, &cmapSize, &charsPerPixel); + + writeXpmFile(stdout, pixels, alpha, alphaMaxval, + cmdline.name, cols, rows, cmapSize, + charsPerPixel, cmap, cht, transIndex); + + if (colornameHash) { + ppm_freecolorhash(colornameHash); + ppm_freecolornames(colornames); + } + destroyCmap(cmap, cmapSize); + ppm_freearray(pixels, rows); + if (alpha) pgm_freearray(alpha, rows); + + return 0; +} + |