/*============================================================================= pamtopdbimg =============================================================================== Convert Netpbm image to Palm Pilot PDB Image format (for viewing by Pilot Image Viewer). Bryan Henderson derived this from Eric Howe's programs named 'pgmtoimgv' and 'pbmtoimgv' in September 2010. =============================================================================*/ /* * Copyright (C) 1997 Eric A. Howe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Authors: Eric A. Howe (mu@trends.net) * Bryan Henderson, September 2010. */ #include #include #include #include #include #include "pm_c_util.h" #include "mallocvar.h" #include "nstring.h" #include "shhopt.h" #include "runlength.h" #include "pam.h" #include "ipdb.h" enum CompMode {COMPRESSED, MAYBE, UNCOMPRESSED}; struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char * inputFileName; /* '-' if stdin */ const char * title; const char * notefile; /* NULL if not specified */ enum CompMode compMode; unsigned int depth4; unsigned int fixedtime; }; static void parseCommandLine(int argc, const char ** argv, struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- parse program command line described in Unix standard form by argc and argv. Return the information in the options as *cmdlineP. If command line is internally inconsistent (invalid options, etc.), issue error message to stderr and abort program. Note that the strings we return are stored in the storage that was passed to us as the argv array. We also trash *argv. -----------------------------------------------------------------------------*/ optEntry * option_def; /* Used by OPTENT3 */ optStruct3 opt; unsigned int option_def_index; unsigned int titleSpec, notefileSpec; unsigned int compressed, maybeCompressed, uncompressed; MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "title", OPT_STRING, &cmdlineP->title, &titleSpec, 0); OPTENT3(0, "notefile", OPT_STRING, &cmdlineP->notefile, ¬efileSpec, 0); OPTENT3(0, "compressed", OPT_FLAG, NULL, &compressed, 0); OPTENT3(0, "maybecompressed", OPT_FLAG, NULL, &maybeCompressed, 0); OPTENT3(0, "uncompressed", OPT_FLAG, NULL, &uncompressed, 0); OPTENT3(0, "4depth", OPT_FLAG, NULL, &cmdlineP->depth4, 0); OPTENT3(0, "fixedtime", OPT_FLAG, NULL, &cmdlineP->fixedtime, 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 */ pm_optParseOptions4(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (!titleSpec) cmdlineP->title = "unnamed"; if (!notefileSpec) cmdlineP->notefile = NULL; if (compressed + uncompressed + maybeCompressed > 1) pm_error("You may specify only one of -compressed, -uncompressed, " "-maybecompressed"); if (compressed) cmdlineP->compMode = COMPRESSED; else if (uncompressed) cmdlineP->compMode = UNCOMPRESSED; else if (maybeCompressed) cmdlineP->compMode = MAYBE; else cmdlineP->compMode = MAYBE; if (argc-1 < 1) cmdlineP->inputFileName = "-"; else if (argc-1 == 1) cmdlineP->inputFileName = argv[1]; else pm_error("Program takes at most one argument: input file name"); free(option_def); } /* * Pixel setting macros. */ #define setg16pixel(b,v,o) ((b) |= ((v) << (4 - 4*(o)))) #define setgpixel(b,v,o) ((b) |= ((v) << (6 - 2*(o)))) #define setmpixelblack(b,o) ((b) |= (1 << (7 - (o)))) static uint32_t const unixepoch = (66*365+17)*24*3600; /* The unix epoch in Mac time (the Mac epoch is 00:00 UTC 1904.01.01). The 17 is the number of leap years. */ static void setPdbHeader(PDBHEAD * const pdbHeadP, const char * const name, bool const wantFixedTime) { STRSCPY(pdbHeadP->name, name); { /* * All of the Image Viewer pdb files that I've come across have * 3510939142U (1997.08.16 14:38:22 UTC) here. I don't know where * this bizarre datetime comes from but the real date works fine so * I'm using it unless user asked for a fixed time (probably just so he * gets repeatable output). */ uint32_t const stdTime = 3510939142U; uint32_t const hdrDt = wantFixedTime ? stdTime : (uint32_t)time(NULL) + unixepoch; pdbHeadP->ctime = pdbHeadP->mtime = hdrDt; } MEMSCPY(&pdbHeadP->type, IPDB_vIMG); MEMSCPY(&pdbHeadP->id, IPDB_View); } static int pdbheadWrite(PDBHEAD * const pdbheadP, FILE * const fileP) { fwrite(pdbheadP->name, 1, 32, fileP); pm_writebigshort(fileP, pdbheadP->flags); pm_writebigshort(fileP, pdbheadP->version); pm_writebiglong(fileP, pdbheadP->ctime); pm_writebiglong(fileP, pdbheadP->mtime); pm_writebiglong(fileP, pdbheadP->btime); pm_writebiglong(fileP, pdbheadP->mod_num); pm_writebiglong(fileP, pdbheadP->app_info); pm_writebiglong(fileP, pdbheadP->sort_info); fwrite(pdbheadP->type, 1, 4, fileP); fwrite(pdbheadP->id, 1, 4, fileP); pm_writebiglong(fileP, pdbheadP->uniq_seed); pm_writebiglong(fileP, pdbheadP->next_rec); pm_writebigshort(fileP, pdbheadP->num_recs); return 0; } static int rechdrWrite(RECHDR * const rechdrP, FILE * const fileP) { if (rechdrP) { pm_writebiglong(fileP, rechdrP->offset); fwrite(rechdrP->unknown, 1, 3, fileP); fwrite(&rechdrP->rec_type, 1, 1, fileP); if (rechdrP->n_extra != 0) fwrite(rechdrP->extra, 1, rechdrP->n_extra, fileP); } return 0; } static void imageWriteHeader(IMAGE * const imgP, FILE * const fileP) { fwrite(imgP->name, 1, 32, fileP); fwrite(&imgP->version, 1, 1, fileP); fwrite(&imgP->type, 1, 1, fileP); fwrite(imgP->reserved1, 1, 4, fileP); fwrite(imgP->note, 1, 4, fileP); pm_writebigshort(fileP, imgP->x_last); pm_writebigshort(fileP, imgP->y_last); fwrite(imgP->reserved2, 1, 4, fileP); pm_writebigshort(fileP, imgP->x_anchor); pm_writebigshort(fileP, imgP->y_anchor); pm_writebigshort(fileP, imgP->width); pm_writebigshort(fileP, imgP->height); } static void imageWriteData(IMAGE * const imgP, const uint8_t * const data, size_t const dataSize, FILE * const fileP) { fwrite(data, 1, dataSize, fileP); } static void imageWrite(IMAGE * const imgP, uint8_t * const data, size_t const dataSize, FILE * const fileP) { imageWriteHeader(imgP, fileP); imageWriteData(imgP, data, dataSize, fileP); } static int textWrite(TEXT * const textP, FILE * const fileP) { if (textP) fwrite(textP->data, 1, strlen(textP->data), fileP); return 0; } static void compressIfRequired(IPDB * const pdbP, int const comp, uint8_t ** const compressedDataP, size_t * const compressedSizeP) { if (comp == IPDB_NOCOMPRESS) { *compressedDataP = pdbP->i->data; *compressedSizeP = ipdb_imgSize(pdbP->i); } else { int const uncompressedSz = ipdb_imgSize(pdbP->i); unsigned char * outbuf; size_t compressedSz; pm_rlenc_allocoutbuf(&outbuf, uncompressedSz, PM_RLE_PALMPDB); pm_rlenc_compressbyte(pdbP->i->data, outbuf, PM_RLE_PALMPDB, uncompressedSz, &compressedSz); if (comp == IPDB_COMPMAYBE && compressedSz >= uncompressedSz) { /* Return the uncompressed data */ free(outbuf); *compressedDataP = pdbP->i->data; *compressedSizeP = uncompressedSz; } else { pdbP->i->compressed = TRUE; if (pdbP->i->type == IMG_GRAY16) pdbP->i->version = 9; else pdbP->i->version = 1; if (pdbP->t != NULL) pdbP->t->r->offset -= uncompressedSz - compressedSz; *compressedDataP = outbuf; *compressedSizeP = compressedSz; } } } static void ipdbWrite(IPDB * const pdbP, int const comp, FILE * const fileP) { RECHDR * const trP = pdbP->t == NULL ? NULL : pdbP->t->r; RECHDR * const irP = pdbP->i->r; int rc; uint8_t * compressedData; /* This is the image raster, compressed as required. (I.e. if it doesn't have to be compressed, it isn't). */ size_t compressedSize; assert(pdbP->i); compressIfRequired(pdbP, comp, &compressedData, &compressedSize); rc = pdbheadWrite(pdbP->p, fileP); if (rc != 0) pm_error("Failed to write PDB header. %s", ipdb_err(rc)); rc = rechdrWrite(irP, fileP); if (rc != 0) pm_error("Failed to write image record header. %s", ipdb_err(rc)); rc = rechdrWrite(trP, fileP); if (rc != 0) pm_error("Failed to write text record header. %s", ipdb_err(rc)); imageWrite(pdbP->i, compressedData, compressedSize, fileP); rc = textWrite(pdbP->t, fileP); if (rc != 0) pm_error("Failed to write text. %s", ipdb_err(rc)); /* Oh, gross. compressIfRequired() might have returned a pointer to storage that was already allocated, or it might have returned a pointer to newly malloc'ed storage. In the latter case, we have to free the storage. */ if (compressedData != pdbP->i->data) free(compressedData); } static void g16pack(tuple * const tupleRow, struct pam * const pamP, uint8_t * const outData, unsigned int const paddedWidth) { /*---------------------------------------------------------------------------- Pack a row of 16-level graysacle pixels 'tupleRow', described by *pamP into 'outData', padding it to 'paddedWidth' with white. We pack 2 input pixels into one output byte. -----------------------------------------------------------------------------*/ unsigned int col; unsigned int off; uint8_t * seg; for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) { if (col < pamP->width) setg16pixel(*seg, 15 - tupleRow[col][0] * 15 / pamP->maxval, off); else /* Pad on the right with white */ setgpixel(*seg, 0, off); if (++off == 2) { ++seg; off = 0; } } } static void gpack(tuple * const tupleRow, struct pam * const pamP, uint8_t * const outData, unsigned int const paddedWidth) { /*---------------------------------------------------------------------------- Pack a row of 4-level graysacle pixels 'tupleRow', described by *pamP into 'outData', padding it to 'paddedWidth' with white. We pack 4 input pixels into one output byte. -----------------------------------------------------------------------------*/ unsigned int col; unsigned int off; uint8_t * seg; for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) { if (col < pamP->width) setgpixel(*seg, 3 - tupleRow[col][0] * 3 / pamP->maxval, off); else /* Pad on the right with white */ setgpixel(*seg, 0, off); if (++off == 4) { ++seg; off = 0; } } } static void mpack(tuple * const tupleRow, struct pam * const pamP, uint8_t * const outData, unsigned int const paddedWidth) { /*---------------------------------------------------------------------------- Pack a row of monochrome pixels 'tupleRow', described by *pamP into 'outData', padding it to 'paddedWidth' with white. We pack 8 input pixels into one output byte. -----------------------------------------------------------------------------*/ unsigned int col; unsigned int off; uint8_t * seg; assert(paddedWidth % 8 == 0); /* Initialize row to white, then set necessary pixels black */ memset(outData, 0, paddedWidth/8); for (col = 0, off = 0, seg = &outData[0]; col < paddedWidth; ++col) { if (col < pamP->width && tupleRow[col][0] == PAM_BLACK) setmpixelblack(*seg, off); if (++off == 8) { ++seg; off = 0; } } } static int adjustDimensions(unsigned int const w, unsigned int const h, unsigned int * const awP, unsigned int * const ahP) { unsigned int provW, provH; provW = w; provH = h; if (provW % 16 != 0) provW += 16 - (provW % 16); if (provW < 160) provW = 160; if (provH < 160) provH = 160; *awP = provW; *ahP = provH; return w == provW && h == provH; } /* * You can allocate only 64k chunks of memory on the pilot and that * supplies an image size limit. */ #define MAX_SIZE(t) ((1 << 16)*((t) == IMG_GRAY ? 4 : 8)) static void imageInsertInit(IPDB * const pdbP, int const uw, int const uh, int const type) { char * const name = pdbP->p->name; unsigned int adjustedWidth, adjustedHeight; if (pdbP->p->num_recs != 0) pm_error("Image record already present, logic error."); else { adjustDimensions(uw, uh, &adjustedWidth, &adjustedHeight); pm_message("Output dimensions: %uw x %uh", adjustedWidth, adjustedHeight); if (adjustedWidth * adjustedHeight > MAX_SIZE(type)) pm_error("Image too large. Maximum number of pixels allowed " "for a %s image is %u", ipdb_typeName(type), MAX_SIZE(type)); else { pdbP->i = ipdb_imageCreate(name, type, adjustedWidth, adjustedHeight); if (pdbP->i == NULL) pm_message("Could not get memory for %u x %u image", adjustedWidth, adjustedHeight); else pdbP->p->num_recs = 1; } } } static void insertG16image(IPDB * const pdbP, struct pam * const pamP, tuple ** const tuples) { /*---------------------------------------------------------------------------- Insert into the PDB an image in 16-level grayscale format. The pixels of the image to insert are 'tuples', described by *pamP. Note that the image inserted may be padded up to larger dimensions. -----------------------------------------------------------------------------*/ imageInsertInit(pdbP, pamP->width, pamP->height, IMG_GRAY16); { int const rowSize = ipdb_width(pdbP)/2; /* The size in bytes of a packed, padded row */ uint8_t * outP; unsigned int row; for (row = 0, outP = &pdbP->i->data[0]; row < pamP->height; ++row, outP += rowSize) g16pack(tuples[row], pamP, outP, ipdb_width(pdbP)); /* Pad with white on the bottom */ for (; row < ipdb_height(pdbP); ++row) memset(outP, 0, rowSize); } } static void insertGimage(IPDB * const pdbP, struct pam * const pamP, tuple ** const tuples) { /*---------------------------------------------------------------------------- Insert into the PDB an image in 4-level grayscale format. The pixels of the image to insert are 'tuples', described by *pamP. Note that the image inserted may be padded up to larger dimensions. -----------------------------------------------------------------------------*/ imageInsertInit(pdbP, pamP->width, pamP->height, IMG_GRAY); { int const rowSize = ipdb_width(pdbP)/4; /* The size in bytes of a packed, padded row */ uint8_t * outP; unsigned int row; for (row = 0, outP = &pdbP->i->data[0]; row < pamP->height; ++row, outP += rowSize) gpack(tuples[row], pamP, outP, ipdb_width(pdbP)); /* Pad with white on the bottom */ for (; row < ipdb_height(pdbP); ++row) memset(outP, 0, rowSize); } } static void insertMimage(IPDB * const pdbP, struct pam * const pamP, tuple ** const tuples) { /*---------------------------------------------------------------------------- Insert into the PDB an image in monochrome format. The pixels of the image to insert are 'tuples', described by *pamP. Note that the image inserted may be padded up to larger dimensions. -----------------------------------------------------------------------------*/ imageInsertInit(pdbP, pamP->width, pamP->height, IMG_MONO); { int const rowSize = ipdb_width(pdbP)/8; /* The size in bytes of a packed, padded row */ uint8_t * outP; unsigned int row; for (row = 0, outP = &pdbP->i->data[0]; row < pamP->height; ++row, outP += rowSize) mpack(tuples[row], pamP, outP, ipdb_width(pdbP)); /* Pad with white on the bottom */ for (; row < ipdb_height(pdbP); ++row) memset(outP, 0, rowSize); } } static int insertText(IPDB * const pdbP, const char * const content) { int retval; if (pdbP->i == NULL) retval = E_IMAGENOTTHERE; else if (pdbP->p->num_recs == 2) retval = E_TEXTTHERE; else { pdbP->t = ipdb_textAlloc(); if (pdbP->t == NULL) retval = ENOMEM; else { pdbP->t->data = strdup(content); if (pdbP->t->data == NULL) retval = ENOMEM; else { pdbP->p->num_recs = 2; pdbP->i->r->offset += 8; pdbP->t->r->offset = pdbP->i->r->offset + IMAGESIZE + ipdb_imgSize(pdbP->i); retval = 0; } } } return retval; } static void readimg(IPDB * const pdbP, FILE * const ifP, bool const depth4) { struct pam inpam; tuple ** tuples; tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); if (strneq(inpam.tuple_type, "RGB", 3)) pm_error("Input image is color. Cannot make a Palm color image."); if (inpam.maxval == 1) insertMimage(pdbP, &inpam, tuples); else if (depth4) insertG16image(pdbP, &inpam, tuples); else insertGimage(pdbP, &inpam, tuples); pnm_freepamarray(tuples, &inpam); } static void readtxt(IPDB * const pdbP, const char * const noteFileName) { struct stat st; char * fileContent; FILE * fP; int n; int rc; size_t bytesRead; rc = stat(noteFileName, &st); if (rc != 0) pm_error("stat of '%s' failed, errno = %d (%s)", noteFileName, errno, strerror(errno)); /* The maximum size of a memory block that a Palm can allocate is 64K. Abort with error if specified note file is any larger. */ if (st.st_size + 1 >= 65535) pm_error("Note file is too large: %lu bytes", (unsigned long) st.st_size); fP = pm_openr(noteFileName); MALLOCARRAY(fileContent, st.st_size + 1); if (fileContent == NULL) pm_error("Couldn't get %lu bytes of storage to read in note file", (unsigned long) st.st_size); bytesRead = fread(fileContent, 1, st.st_size, fP); if (bytesRead != st.st_size) pm_error("Failed to read note file '%s'. Errno = %d (%s)", noteFileName, errno, strerror(errno)); pm_close(fP); fileContent[st.st_size] = 0x00; /* add terminating NUL char */ /* Chop of trailing newlines */ for (n = strlen(fileContent) - 1; n >= 0 && fileContent[n] == '\n'; --n) fileContent[n] = '\0'; insertText(pdbP, fileContent); } int main(int argc, const char **argv) { struct CmdlineInfo cmdline; IPDB * pdbP; FILE * ifP; int comp; pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); ifP = pm_openr(cmdline.inputFileName); switch (cmdline.compMode) { case COMPRESSED: comp = IPDB_COMPRESS; break; case UNCOMPRESSED: comp = IPDB_NOCOMPRESS; break; case MAYBE: comp = IPDB_COMPMAYBE; break; } if (strlen(cmdline.title) > 31) pm_error("Title too long. Max length is 31 characters."); pdbP = ipdb_alloc(); if (pdbP == NULL) pm_error("Failed to allocate IPDB structure"); setPdbHeader(pdbP->p, cmdline.title, cmdline.fixedtime); readimg(pdbP, ifP, cmdline.depth4); if (cmdline.notefile) readtxt(pdbP, cmdline.notefile); ipdbWrite(pdbP, comp, stdout); if (comp == IPDB_COMPMAYBE && !ipdb_compressed(pdbP)) pm_message("Image too complex to be compressed."); ipdb_free(pdbP); pm_close(ifP); return EXIT_SUCCESS; }