diff options
Diffstat (limited to 'converter/other/pamtopdbimg.c')
-rw-r--r-- | converter/other/pamtopdbimg.c | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/converter/other/pamtopdbimg.c b/converter/other/pamtopdbimg.c new file mode 100644 index 00000000..6454292e --- /dev/null +++ b/converter/other/pamtopdbimg.c @@ -0,0 +1,810 @@ +/*============================================================================= + 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 <stdlib.h> +#include <assert.h> +#include <string.h> +#include <sys/stat.h> + +#include "pm_c_util.h" +#include "mallocvar.h" +#include "nstring.h" +#include "shhopt.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; +}; + + + +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; + /* Instructions to pm_optParseOptions3 on how to parse our options. + */ + 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); + + 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_optParseOptions3(&argc, (char **)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"); +} + + + +/* + * 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 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; +} + + + +typedef struct { + unsigned int match; + uint8_t buf[128]; + int mode; + size_t len; + size_t used; + uint8_t * p; +} RLE; +#define MODE_MATCH 0 +#define MODE_LIT 1 +#define MODE_NONE 2 + +#define reset(r) { \ + (r)->match = 0xffff; \ + (r)->mode = MODE_NONE; \ + (r)->len = 0; \ + } + + + +static void +putMatch(RLE * const rleP, + size_t const n) { + + *rleP->p++ = 0x80 + n - 1; + *rleP->p++ = rleP->match; + rleP->used += 2; + reset(rleP); +} + + + +static void +putLit(RLE * const rleP, + size_t const n) { + + *rleP->p++ = n - 1; + rleP->p = (uint8_t *)memcpy(rleP->p, rleP->buf, n) + n; + rleP->used += n + 1; + reset(rleP); +} + + + +static size_t +compress(const uint8_t * const inData, + size_t const n_in, + uint8_t * const out) { + + static void (*put[])(RLE *, size_t) = {putMatch, putLit}; + RLE rle; + size_t i; + const uint8_t * p; + + MEMSZERO(&rle); + rle.p = out; + reset(&rle); + + for (i = 0, p = &inData[0]; i < n_in; ++i, ++p) { + if (*p == rle.match) { + if (rle.mode == MODE_LIT && rle.len > 1) { + putLit(&rle, rle.len - 1); + ++rle.len; + rle.match = *p; + } + rle.mode = MODE_MATCH; + ++rle.len; + } else { + if (rle.mode == MODE_MATCH) + putMatch(&rle, rle.len); + rle.mode = MODE_LIT; + rle.match = *p; + rle.buf[rle.len++] = *p; + } + if (rle.len == 128) + put[rle.mode](&rle, rle.len); + } + if (rle.len != 0) + put[rle.mode](&rle, rle.len); + + return rle.used; +} + + + +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_img_size(pdbP->i); + } else { + int const uncompressedSz = ipdb_img_size(pdbP->i); + + /* Allocate for the worst case. */ + size_t const allocSz = (3 * uncompressedSz + 2)/2; + + uint8_t * data; + + data = pdbP->i->data; + + MALLOCARRAY(data, allocSz); + + if (data == NULL) + pm_error("Could not get %lu bytes of memory to decompress", + (unsigned long)allocSz); + else { + size_t compressedSz; + compressedSz = compress(pdbP->i->data, uncompressedSz, data); + if (comp == IPDB_COMPMAYBE && compressedSz >= uncompressedSz) { + /* Return the uncompressed data */ + free(data); + *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 = data; + *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_image_alloc(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 s) { + + int retval; + + if (pdbP->i == NULL) + retval = E_IMAGENOTTHERE; + else if (pdbP->p->num_recs == 2) + retval = E_TEXTTHERE; + else { + pdbP->t = ipdb_text_alloc(s); + if (pdbP->t == NULL) + retval = ENOMEM; + else { + pdbP->p->num_recs = 2; + + pdbP->i->r->offset += 8; + pdbP->t->r->offset = + pdbP->i->r->offset + IMAGESIZE + ipdb_img_size(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)); + + 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); + + /* 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; + } + + pdbP = ipdb_alloc(cmdline.title); + + if (pdbP == NULL) + pm_error("Failed to allocate IPDB structure"); + + 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; +} |