/* pamtotga.c - read a portable pixmap and produce a TrueVision Targa file ** ** Copyright (C) 1989, 1991 by Mark Shand and Jef Poskanzer ** ** 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. */ #define _BSD_SOURCE /* Make sure string.h contains strdup() */ #include #include "pam.h" #include "pammap.h" #include "shhopt.h" #include "mallocvar.h" #include "tga.h" /* Max number of colors allowed for colormapped output. */ #define MAXCOLORS 256 struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char *inputFilespec; /* Filespec of input file */ char *outName; enum TGAbaseImageType imgType; bool defaultFormat; unsigned int norle; }; static void parseCommandLine(int argc, char ** argv, struct cmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- Parse the program arguments (given by argc and argv) into a form the program can deal with more easily -- a cmdline_info structure. If the syntax is invalid, issue a message and exit the program via pm_error(). Note that the file spec array we return is stored in the storage that was passed to us as the argv array. -----------------------------------------------------------------------------*/ optStruct3 opt; /* set by OPTENT3 */ optEntry *option_def = malloc(100*sizeof(optEntry)); unsigned int option_def_index; unsigned int outNameSpec; unsigned int cmap, mono, rgb; option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "name", OPT_STRING, &cmdlineP->outName, &outNameSpec, 0); OPTENT3(0, "cmap", OPT_FLAG, NULL, &cmap, 0); OPTENT3(0, "mono", OPT_FLAG, NULL, &mono, 0); OPTENT3(0, "rgb", OPT_FLAG, NULL, &rgb, 0); OPTENT3(0, "norle", OPT_FLAG, NULL, &cmdlineP->norle, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ optParseOptions3(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdline_p and others. */ if (cmap + mono + rgb > 1) pm_error("You may specify only one of -cmap, -mono, and -rgb."); if (cmap + mono + rgb == 0) cmdlineP->defaultFormat = TRUE; else { cmdlineP->defaultFormat = FALSE; if (cmap) cmdlineP->imgType = TGA_MAP_TYPE; else if (mono) cmdlineP->imgType = TGA_MONO_TYPE; else if (rgb) cmdlineP->imgType = TGA_RGB_TYPE; } if (!outNameSpec) cmdlineP->outName = NULL; if (argc-1 == 0) cmdlineP->inputFilespec = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " "specified %d", argc-1); else cmdlineP->inputFilespec = argv[1]; } static void writeTgaHeader(struct ImageHeader const tgaHeader) { unsigned char flags; putchar( tgaHeader.IDLength ); putchar( tgaHeader.CoMapType ); putchar( tgaHeader.ImgType ); putchar( tgaHeader.Index_lo ); putchar( tgaHeader.Index_hi ); putchar( tgaHeader.Length_lo ); putchar( tgaHeader.Length_hi ); putchar( tgaHeader.CoSize ); putchar( tgaHeader.X_org_lo ); putchar( tgaHeader.X_org_hi ); putchar( tgaHeader.Y_org_lo ); putchar( tgaHeader.Y_org_hi ); putchar( tgaHeader.Width_lo ); putchar( tgaHeader.Width_hi ); putchar( tgaHeader.Height_lo ); putchar( tgaHeader.Height_hi ); putchar( tgaHeader.PixelSize ); flags = ( tgaHeader.AttBits & 0xf ) | ( ( tgaHeader.Rsrvd & 0x1 ) << 4 ) | ( ( tgaHeader.OrgBit & 0x1 ) << 5 ) | ( ( tgaHeader.OrgBit & 0x3 ) << 6 ); putchar( flags ); if ( tgaHeader.IDLength > 0 ) fwrite( tgaHeader.id, 1, (int) tgaHeader.IDLength, stdout ); } static void putPixel(struct pam * const pamP, tuple const tuple, enum TGAbaseImageType const imgType, tuplehash const cht) { if (imgType == TGA_MAP_TYPE) { int retval; int found; pnm_lookuptuple(pamP, cht, tuple, &found, &retval); if (!found) pm_error("Internal error: color not found in map that was " "generated from all the colors in the image"); putchar(retval); } else { if (imgType == TGA_RGB_TYPE && pamP->depth < 3) { /* Make RGB pixel out of a single input plane */ unsigned int plane; for (plane = 0; plane < 3; ++plane) putchar(pnm_scalesample(tuple[0], pamP->maxval, TGA_MAXVAL)); } else if (imgType == TGA_MONO_TYPE) putchar(pnm_scalesample(tuple[0], pamP->maxval, TGA_MAXVAL)); else { putchar(pnm_scalesample(tuple[PAM_BLU_PLANE], pamP->maxval, TGA_MAXVAL)); putchar(pnm_scalesample(tuple[PAM_GRN_PLANE], pamP->maxval, TGA_MAXVAL)); putchar(pnm_scalesample(tuple[PAM_RED_PLANE], pamP->maxval, TGA_MAXVAL)); if (pamP->depth > 3) putchar(pnm_scalesample(tuple[PAM_TRN_PLANE], pamP->maxval, TGA_MAXVAL)); } } } static void putMapEntry(struct pam * const pamP, tuple const value, int const size) { if (size == 15 || size == 16) { /* 5 bits each of red, green, and blue. Watch for byte order */ tuple const tuple31 = pnm_allocpamtuple(pamP); pnm_scaletuple(pamP, tuple31, value, 31); { int const mapentry = tuple31[PAM_BLU_PLANE] << 0 | tuple31[PAM_GRN_PLANE] << 5 | tuple31[PAM_RED_PLANE] << 10; putchar(mapentry % 256); putchar(mapentry / 256); } pnm_freepamtuple(tuple31); } else if (size == 8) putchar(pnm_scalesample(value[0], pamP->maxval, TGA_MAXVAL)); else { /* Must be 24 or 32 */ putchar(pnm_scalesample(value[PAM_BLU_PLANE], pamP->maxval, TGA_MAXVAL)); putchar(pnm_scalesample(value[PAM_GRN_PLANE], pamP->maxval, TGA_MAXVAL)); putchar(pnm_scalesample(value[PAM_RED_PLANE], pamP->maxval, TGA_MAXVAL)); if (size == 32) putchar(pnm_scalesample(value[PAM_TRN_PLANE], pamP->maxval, TGA_MAXVAL)); } } static void computeRunlengths(struct pam * const pamP, tuple * const tuplerow, int * const runlength) { int col, start; /* Initialize all run lengths to 0. (This is just an error check.) */ for (col = 0; col < pamP->width; ++col) runlength[col] = 0; /* Find runs of identical pixels. */ for ( col = 0; col < pamP->width; ) { start = col; do { ++col; } while ( col < pamP->width && col - start < 128 && pnm_tupleequal(pamP, tuplerow[col], tuplerow[start])); runlength[start] = col - start; } /* Now look for runs of length-1 runs, and turn them into negative runs. */ for (col = 0; col < pamP->width; ) { if (runlength[col] == 1) { start = col; while (col < pamP->width && col - start < 128 && runlength[col] == 1 ) { runlength[col] = 0; ++col; } runlength[start] = - ( col - start ); } else col += runlength[col]; } } static void computeOutName(struct cmdlineInfo const cmdline, const char ** const outNameP) { char * workarea; if (cmdline.outName) workarea = strdup(cmdline.outName); else if (strcmp(cmdline.inputFilespec, "-") == 0) workarea = NULL; else { char * cp; workarea = strdup(cmdline.inputFilespec); cp = strchr(workarea, '.'); if (cp != NULL) *cp = '\0'; /* remove extension */ } if (workarea == NULL) *outNameP = NULL; else { /* Truncate the name to fit TGA specs */ if (strlen(workarea) > IMAGEIDFIELDMAXSIZE) workarea[IMAGEIDFIELDMAXSIZE] = '\0'; *outNameP = workarea; } } static void validateTupleType(struct pam * const pamP) { if (strcmp(pamP->tuple_type, "RGBA") == 0) { if (pamP->depth < 4) pm_error("Invalid depth for tuple type RGBA. " "Should have at least 4 planes, but has %d.", pamP->depth); } else if (strcmp(pamP->tuple_type, "RGB") == 0) { if (pamP->depth < 3) pm_error("Invalid depth for tuple type RGB. " "Should have at least 3 planes, but has %d.", pamP->depth); } else if (strcmp(pamP->tuple_type, "GRAYSCALE") == 0) { } else if (strcmp(pamP->tuple_type, "BLACKANDWHITE") == 0) { } else pm_error("Invalid type of input. PAM tuple type is '%s'. " "This programs understands only RGBA, RGB, GRAYSCALE, " "and BLACKANDWHITE.", pamP->tuple_type); } static void computeImageType_cht(struct pam * const pamP, struct cmdlineInfo const cmdline, tuple ** const tuples, enum TGAbaseImageType * const baseImgTypeP, tupletable * const chvP, tuplehash * const chtP, int * const ncolorsP) { unsigned int ncolors; enum TGAbaseImageType baseImgType; validateTupleType(pamP); if (cmdline.defaultFormat) { /* default the image type */ if (pamP->depth > 1) { pm_message("computing colormap..."); *chvP = pnm_computetuplefreqtable(pamP, tuples, MAXCOLORS, &ncolors); if (*chvP == NULL) { pm_message("Too many colors for colormapped TGA. Doing RGB."); baseImgType = TGA_RGB_TYPE; } else baseImgType = TGA_MAP_TYPE; } else { baseImgType = TGA_MONO_TYPE; *chvP = NULL; } } else { baseImgType = cmdline.imgType; if (baseImgType == TGA_MAP_TYPE) { pm_message("computing colormap..."); *chvP = pnm_computetuplefreqtable(pamP, tuples, MAXCOLORS, &ncolors); if (*chvP == NULL) pm_error("Too many colors for colormapped TGA. " "Use 'pnmquant %d' to reduce the number of colors.", MAXCOLORS); } else *chvP = NULL; if (baseImgType == TGA_MONO_TYPE && pamP->depth > 1) pm_error("For Mono TGA output, input must be " "GRAYSCALE or BLACKANDWHITE PAM or PBM or PGM"); } if (baseImgType == TGA_MAP_TYPE) { pm_message("%d colors found.", ncolors); /* Make a hash table for fast color lookup. */ *chtP = pnm_computetupletablehash(pamP, *chvP, ncolors); } else *chtP = NULL; *baseImgTypeP = baseImgType; *ncolorsP = ncolors; } static void computeTgaHeader(struct pam * const pamP, enum TGAbaseImageType const baseImgType, bool const rle, int const ncolors, unsigned char const orgBit, const char * const id, struct ImageHeader * const tgaHeaderP) { if (rle) { switch (baseImgType ) { case TGA_MONO_TYPE: tgaHeaderP->ImgType = TGA_RLEMono; break; case TGA_MAP_TYPE: tgaHeaderP->ImgType = TGA_RLEMap; break; case TGA_RGB_TYPE: tgaHeaderP->ImgType = TGA_RLERGB; break; } } else { switch(baseImgType) { case TGA_MONO_TYPE: tgaHeaderP->ImgType = TGA_Mono; break; case TGA_MAP_TYPE: tgaHeaderP->ImgType = TGA_Map; break; case TGA_RGB_TYPE: tgaHeaderP->ImgType = TGA_RGB; break; } } if (id) tgaHeaderP->IDLength = strlen(id); else tgaHeaderP->IDLength = 0; tgaHeaderP->Index_lo = 0; tgaHeaderP->Index_hi = 0; if (baseImgType == TGA_MAP_TYPE) { tgaHeaderP->CoMapType = 1; tgaHeaderP->Length_lo = ncolors % 256; tgaHeaderP->Length_hi = ncolors / 256; tgaHeaderP->CoSize = 8 * pamP->depth; } else { tgaHeaderP->CoMapType = 0; tgaHeaderP->Length_lo = 0; tgaHeaderP->Length_hi = 0; tgaHeaderP->CoSize = 0; } switch (baseImgType) { case TGA_MAP_TYPE: tgaHeaderP->PixelSize = 8; break; case TGA_RGB_TYPE: tgaHeaderP->PixelSize = 8 * MAX(3, pamP->depth); break; case TGA_MONO_TYPE: tgaHeaderP->PixelSize = 8; } tgaHeaderP->X_org_lo = tgaHeaderP->X_org_hi = 0; tgaHeaderP->Y_org_lo = tgaHeaderP->Y_org_hi = 0; tgaHeaderP->Width_lo = pamP->width % 256; tgaHeaderP->Width_hi = pamP->width / 256; tgaHeaderP->Height_lo = pamP->height % 256; tgaHeaderP->Height_hi = pamP->height / 256; tgaHeaderP->AttBits = 0; tgaHeaderP->Rsrvd = 0; tgaHeaderP->IntrLve = 0; tgaHeaderP->OrgBit = orgBit; } static void writeTgaRaster(struct pam * const pamP, tuple ** const tuples, tuplehash const cht, enum TGAbaseImageType const imgType, bool const rle, unsigned char const orgBit) { int* runlength; /* malloc'ed */ int row; if (rle) MALLOCARRAY(runlength, pamP->width); for (row = 0; row < pamP->height; ++row) { int realrow; realrow = (orgBit != 0) ? row : pamP->height - row - 1; if (rle) { int col; computeRunlengths(pamP, tuples[realrow], runlength); for (col = 0; col < pamP->width; ) { if (runlength[col] > 0) { putchar(0x80 + runlength[col] - 1); putPixel(pamP, tuples[realrow][col], imgType, cht); col += runlength[col]; } else if (runlength[col] < 0) { int i; putchar(-runlength[col] - 1); for (i = 0; i < -runlength[col]; ++i) putPixel(pamP, tuples[realrow][col+i], imgType, cht); col += -runlength[col]; } else pm_error("Internal error: zero run length"); } } else { int col; for (col = 0; col < pamP->width; ++col) putPixel(pamP, tuples[realrow][col], imgType, cht); } } if (rle) free(runlength); } int main(int argc, char *argv[]) { struct cmdlineInfo cmdline; FILE * ifP; tuple ** tuples; struct pam pam; int ncolors; tupletable chv; tuplehash cht; struct ImageHeader tgaHeader; enum TGAbaseImageType baseImgType; const char *outName; pnm_init( &argc, argv ); parseCommandLine(argc, argv, &cmdline); ifP = pm_openr(cmdline.inputFilespec); computeOutName(cmdline, &outName); tuples = pnm_readpam(ifP, &pam, sizeof(pam)); pm_close(ifP); computeImageType_cht(&pam, cmdline, tuples, &baseImgType, &chv, &cht, &ncolors); /* Do the Targa header */ computeTgaHeader(&pam, baseImgType, !cmdline.norle, ncolors, 0, outName, &tgaHeader); writeTgaHeader(tgaHeader); if (baseImgType == TGA_MAP_TYPE) { /* Write out the Targa colormap. */ int i; for (i = 0; i < ncolors; ++i) putMapEntry(&pam, chv[i]->tuple, tgaHeader.CoSize); } writeTgaRaster(&pam, tuples, cht, baseImgType, !cmdline.norle, 0); if (cht) pnm_destroytuplehash(cht); if (chv) pnm_freetupletable(&pam, chv); pnm_freepamarray(tuples, &pam); exit(0); }