diff options
Diffstat (limited to 'converter/other/pdbimgtopam.c')
-rw-r--r-- | converter/other/pdbimgtopam.c | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/converter/other/pdbimgtopam.c b/converter/other/pdbimgtopam.c new file mode 100644 index 00000000..3cb4129a --- /dev/null +++ b/converter/other/pdbimgtopam.c @@ -0,0 +1,774 @@ +/*============================================================================= + pamtopdbimg +=============================================================================== + + Convert Palm Pilot PDB Image format (for viewing by + Pilot Image Viewer) to Netpbm image. + + Bryan Henderson derived this from Eric Howe's program named + 'imgvtopnm', 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 + */ +#include <stdlib.h> +#include <assert.h> + +#include "pm_c_util.h" +#include "mallocvar.h" +#include "nstring.h" +#include "shhopt.h" +#include "pam.h" + +#include "ipdb.h" + + +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 * notefile; /* NULL if not specified */ + unsigned int verbose; +}; + + + +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 notefileSpec; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "notefile", OPT_STRING, &cmdlineP->notefile, + ¬efileSpec, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, + &cmdlineP->verbose, 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 (!notefileSpec) + cmdlineP->notefile = NULL; + + 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"); +} + + + +#define getg16pixel(b,o) (((b) >> (4 - 4*(o))) & 0x0f) +#define getgpixel(b,o) (((b) >> (6 - 2*(o))) & 0x03) +#define getmpixel(b,o) (((b) >> (7 - (o))) & 0x01) + + +static void +abortShort() { + pm_error("Invalid image. Compression algorithm runs out of " + "compressed data before generating the expected " + "amount of image data"); +} + + + +static void +abortOverrun() { + pm_error("Invalid image. Compression algorithm finds the end of " + "the image in the middle of a run"); +} + + + +static void +decompress(const uint8_t * const compressed, + size_t const compressedSize, + size_t const imageSize, + uint8_t ** const uncompressedP) { +/*---------------------------------------------------------------------------- + Decompress the data 'compressed', which is 'compressedSize' bytes long. + Return the decompressed data in newly malloced storage as + *decompressedP. Decompression should yield exactly 'imageSize' bytes. +-----------------------------------------------------------------------------*/ + /* + * The compression scheme used is a simple RLE; the control codes, + * CODE, are one byte and have the following meanings: + * + * CODE > 0x80 Insert (CODE + 1 - 0x80) copies of the next byte. + * CODE <= 0x80 Insert the next (CODE + 1) literal bytes. + * + * Compressed pieces can (and do) cross row boundaries. + */ + uint8_t * uncompressed; + + MALLOCARRAY(uncompressed, imageSize); + + if (uncompressed) { + const uint8_t * inP; + uint8_t * outP; + size_t bytesLeft; + + for (bytesLeft = imageSize, + inP = &compressed[0], outP = &uncompressed[0]; + bytesLeft > 0; + ) { + + int got, put; + + if (inP > compressed + compressedSize) + abortShort(); + + if (*inP > 0x80) { + put = *inP++ + 1 - 0x80; + if (outP + put > uncompressed + imageSize) + abortOverrun(); + memset(outP, *inP, put); + got = 1; + } else { + put = *inP++ + 1; + if (inP + put > compressed + compressedSize) + abortShort(); + if (outP + put > uncompressed + imageSize) + abortOverrun(); + memcpy(outP, inP, put); + got = put; + } + inP += got; + outP += put; + assert(bytesLeft >= put); + bytesLeft -= put; + } + } + *uncompressedP = uncompressed; +} + + + +#define UNKNOWN_OFFSET (uint32_t)-1 + +static void +readCompressed(IMAGE * const imgP, + uint32_t const end_offset, + FILE * const fP, + size_t * const dataSizeP, + uint8_t ** const dataP, + int * const retvalP) { +/*---------------------------------------------------------------------------- + Read the compressed data from file *fP (actually, if the image isn't + compressed, then it's just the regular data). + + Return the data in newly malloced storage as *dataP, which is + *dataSizeP bytes long. +-----------------------------------------------------------------------------*/ + int retval; + uint8_t * buffer; + size_t dataSize; + + dataSize = 0; /* initial value */ + + if (end_offset == UNKNOWN_OFFSET) { + /* + * Read until EOF. Some of them have an extra zero byte + * dangling off the end. I originally thought this was + * an empty note record (even though there was no record + * header for it); however, the release notes for Image + * Compression Manager 1.1 on http://www.pilotgear.com + * note this extra byte as a bug in Image Compression + * Manager 1.0 which 1.1 fixes. We'll just blindly read + * this extra byte and ignore it by paying attention to + * the image dimensions. + */ + MALLOCARRAY(buffer, ipdb_img_size(imgP)); + + if (buffer == NULL) + retval = ENOMEM; + else { + dataSize = fread(buffer, 1, ipdb_img_size(imgP), fP); + if (dataSize <= 0) + retval = EIO; + else + retval = 0; + + if (retval != 0) + free(buffer); + } + } else { + /* + * Read to the indicated offset. + */ + dataSize = end_offset - ftell(fP) + 1; + + MALLOCARRAY(buffer, dataSize); + + if (buffer == NULL) + retval = ENOMEM; + else { + ssize_t rc; + rc = fread(buffer, 1, dataSize, fP); + if (rc != dataSize) + retval = EIO; + else + retval = 0; + + if (retval != 0) + free(buffer); + } + } + *dataSizeP = dataSize; + *dataP = buffer; + *retvalP = retval; +} + + + +static void +imageReadHeader(FILE * const fileP, + IMAGE * const imgP, + bool const dump) { + + fread(&imgP->name, 1, 32, fileP); + pm_readcharu(fileP, &imgP->version); + pm_readcharu(fileP, &imgP->type); + fread(&imgP->reserved1, 1, 4, fileP); + fread(&imgP->note, 1, 4, fileP); + pm_readbigshortu(fileP, &imgP->x_last); + pm_readbigshortu(fileP, &imgP->y_last); + fread(&imgP->reserved2, 1, 4, fileP); + pm_readbigshortu(fileP, &imgP->x_anchor); + pm_readbigshortu(fileP, &imgP->y_anchor); + pm_readbigshortu(fileP, &imgP->width); + pm_readbigshortu(fileP, &imgP->height); + + if (dump) { + pm_message("PDB IMAGE header:"); + pm_message(" Name: '%.*s'", (int)sizeof(imgP->name), imgP->name); + pm_message(" Version: %02x", imgP->version); + pm_message(" Type: %s", ipdb_typeName(imgP->type)); + pm_message(" Note: %02x %02x %02x %02x", + imgP->note[0], imgP->note[1], imgP->note[2], imgP->note[3]); + pm_message(" X_last: %u", imgP->x_last); + pm_message(" Y_last: %u", imgP->y_last); + pm_message(" X_anchor: %u", imgP->x_anchor); + pm_message(" Y_anchor: %u", imgP->y_anchor); + pm_message(" Width: %u", imgP->width); + pm_message(" Height: %u", imgP->height); + pm_message("Pixels per byte: %u", ipdb_img_ppb(imgP)); + pm_message("Image size: %lu bytes", + (unsigned long)ipdb_img_size(imgP)); + } +} + + +static int +imageReadData(FILE * const fileP, + IMAGE * const imgP, + uint32_t const end_offset) { + + int retval; + size_t dataSize; + uint8_t * buffer; + + readCompressed(imgP, end_offset, fileP, &dataSize, &buffer, &retval); + + if (retval == 0) { + /* + * Compressed data can cross row boundaries so we decompress + * the data here to avoid messiness in the row access functions. + */ + if (dataSize != ipdb_img_size(imgP)) { + decompress(buffer, dataSize, ipdb_img_size(imgP), &imgP->data); + if (imgP->data == NULL) + retval = ENOMEM; + else + imgP->compressed = true; + free(buffer); + } else { + imgP->compressed = false; + imgP->data = buffer; + /* Storage at 'buffer' now belongs to *imgP */ + } + } + return retval; +} + + + +static int +imageRead(IMAGE * const imgP, + uint32_t const end_offset, + FILE * const fileP, + bool const verbose) { + + if (imgP) { + imgP->r->offset = (uint32_t)ftell(fileP); + + imageReadHeader(fileP, imgP, verbose); + + imageReadData(fileP, imgP, end_offset); + } + return 0; +} + + + +static int +textRead(TEXT * const textP, + FILE * const fileP) { + + int retval; + char * s; + char buf[128]; + int used, alloced, len; + + if (textP == NULL) + return 0; + + textP->r->offset = (uint32_t)ftell(fileP); + + /* + * What a pain in the ass! Why the hell isn't there a length + * attached to the text record? I suppose the designer wasn't + * concerned about non-seekable (i.e. pipes) input streams. + * Perhaps I'm being a little harsh, the lack of a length probably + * isn't much of an issue on the Pilot. + */ + used = 0; + alloced = 0; + s = NULL; + retval = 0; /* initial value */ + while ((len = fread(buf, 1, sizeof(buf), fileP)) != 0 && retval == 0) { + if (buf[len - 1] == '\0') + --len; + if (used + len > alloced) { + alloced += 2 * sizeof(buf); + REALLOCARRAY(s, alloced); + + if (s == NULL) + retval = ENOMEM; + } + if (retval == 0) { + memcpy(s + used, buf, len); + used += len; + } + } + if (retval == 0) { + textP->data = calloc(1, used + 1); + if (textP->data == NULL) + retval = ENOMEM; + else + memcpy(textP->data, s, used); + } + if (s) + free(s); + + return retval; +} + + + +static int +pdbheadRead(PDBHEAD * const pdbHeadP, + FILE * const fileP) { + + int retval; + + fread(pdbHeadP->name, 1, 32, fileP); + pm_readbigshortu(fileP, &pdbHeadP->flags); + pm_readbigshortu(fileP, &pdbHeadP->version); + pm_readbiglongu2(fileP, &pdbHeadP->ctime); + pm_readbiglongu2(fileP, &pdbHeadP->mtime); + pm_readbiglongu2(fileP, &pdbHeadP->btime); + pm_readbiglongu2(fileP, &pdbHeadP->mod_num); + pm_readbiglongu2(fileP, &pdbHeadP->app_info); + pm_readbiglongu2(fileP, &pdbHeadP->sort_info); + fread(pdbHeadP->type, 1, 4, fileP); + fread(pdbHeadP->id, 1, 4, fileP); + pm_readbiglongu2(fileP, &pdbHeadP->uniq_seed); + pm_readbiglongu2(fileP, &pdbHeadP->next_rec); + pm_readbigshortu(fileP, &pdbHeadP->num_recs); + + if (!memeq(pdbHeadP->type, IPDB_vIMG, 4) + || !memeq(pdbHeadP->id, IPDB_View, 4)) + retval = E_NOTIMAGE; + else + retval = 0; + + return retval; +} + + + +static int +rechdrRead(RECHDR * const rechdrP, + FILE * const fileP) { + + int retval; + off_t len; + + pm_readbiglongu2(fileP, &rechdrP->offset); + + len = (off_t)rechdrP->offset - ftell(fileP); + switch(len) { + case 4: + case 12: + /* + * Version zero (eight bytes of record header) or version + * two with a note (two chunks of eight record header bytes). + */ + fread(&rechdrP->unknown[0], 1, 3, fileP); + fread(&rechdrP->rec_type, 1, 1, fileP); + rechdrP->n_extra = 0; + rechdrP->extra = NULL; + retval = 0; + break; + case 6: + /* + * Version one (ten bytes of record header). + */ + fread(&rechdrP->unknown[0], 1, 3, fileP); + fread(&rechdrP->rec_type, 1, 1, fileP); + rechdrP->n_extra = 2; + MALLOCARRAY(rechdrP->extra, rechdrP->n_extra); + if (rechdrP->extra == NULL) + retval = ENOMEM; + else { + fread(rechdrP->extra, 1, rechdrP->n_extra, fileP); + retval = 0; + } + break; + default: + /* + * hmmm.... I'll assume this is the record header + * for a text record. + */ + fread(&rechdrP->unknown[0], 1, 3, fileP); + fread(&rechdrP->rec_type, 1, 1, fileP); + rechdrP->n_extra = 0; + rechdrP->extra = NULL; + retval = 0; + break; + } + if (retval == 0) { + if ((rechdrP->rec_type != IMG_REC && rechdrP->rec_type != TEXT_REC) + || !memeq(rechdrP->unknown, IPDB_MYST, 3)) + retval = E_NOTRECHDR; + } + return retval; +} + + + +static int +ipdbRead(IPDB * const pdbP, + FILE * const fileP, + bool const verbose) { + + int retval; + + ipdb_clear(pdbP); + + pdbP->p = ipdb_pdbhead_alloc(NULL); + + if (pdbP->p == NULL) + retval = ENOMEM; + else { + int status; + + status = pdbheadRead(pdbP->p, fileP); + + if (status != 0) + retval = status; + else { + pdbP->i = ipdb_image_alloc(pdbP->p->name, IMG_GRAY, 0, 0); + if (pdbP->i == NULL) + retval = ENOMEM; + else { + int status; + status = rechdrRead(pdbP->i->r, fileP); + if (status != 0) + retval = status; + else { + if (pdbP->p->num_recs > 1) { + pdbP->t = ipdb_text_alloc(NULL); + if (pdbP->t == NULL) + retval = ENOMEM; + else { + int status; + status = rechdrRead(pdbP->t->r, fileP); + if (status != 0) + retval = status; + else + retval = 0; + } + } else + retval = 0; + + if (retval == 0) { + uint32_t const offset = + pdbP->t == NULL ? + UNKNOWN_OFFSET : pdbP->t->r->offset - 1; + + int status; + + status = imageRead(pdbP->i, offset, fileP, verbose); + if (status != 0) + retval = status; + else { + if (pdbP->t != NULL) { + int status; + + status = textRead(pdbP->t, fileP); + if (status != 0) + retval = status; + } + } + } + } + } + } + } + return retval; +} + + + +static void +g16unpack(const uint8_t * const p, + uint8_t * const g, + int const w) { + + static const uint8_t pal[] = + {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; + const uint8_t * seg; + unsigned int i; + + for (i = 0, seg = p; i < w; i += 2, ++seg) { + g[i + 0] = pal[getg16pixel(*seg, 0)]; + g[i + 1] = pal[getg16pixel(*seg, 1)]; + } +} + + + +static void +gunpack(const uint8_t * const p, + uint8_t * const g, + int const w) { + + static const uint8_t pal[] = {0xff, 0xaa, 0x55, 0x00}; + const uint8_t * seg; + unsigned int i; + + for (i = 0, seg = p; i < w; i += 4, ++seg) { + g[i + 0] = pal[getgpixel(*seg, 0)]; + g[i + 1] = pal[getgpixel(*seg, 1)]; + g[i + 2] = pal[getgpixel(*seg, 2)]; + g[i + 3] = pal[getgpixel(*seg, 3)]; + } +} + + + +static void +munpack(const uint8_t * const p, + uint8_t * const b, + int const w) { + + static const uint8_t pal[] = {PAM_BW_WHITE, PAM_BLACK}; + const uint8_t * seg; + unsigned int i; + + for (i = 0, seg = p; i < w; i += 8, ++seg) { + b[i + 0] = pal[getmpixel(*seg, 0)]; + b[i + 1] = pal[getmpixel(*seg, 1)]; + b[i + 2] = pal[getmpixel(*seg, 2)]; + b[i + 3] = pal[getmpixel(*seg, 3)]; + b[i + 4] = pal[getmpixel(*seg, 4)]; + b[i + 5] = pal[getmpixel(*seg, 5)]; + b[i + 6] = pal[getmpixel(*seg, 6)]; + b[i + 7] = pal[getmpixel(*seg, 7)]; + } +} + + + +static void +g16row(IPDB * const pdbP, + unsigned int const row, + uint8_t * const buffer) { + + g16unpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP)); +} + + + +static void +grow(IPDB * const pdbP, + unsigned int const row, + uint8_t * const buffer) { + + gunpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP)); +} + + + +static void +mrow(IPDB * const pdbP, + unsigned int const row, + uint8_t * const buffer) { + + munpack(ipdb_img_row(pdbP->i, row), buffer, ipdb_width(pdbP)); +} + + + +static void +writeImgPam(IPDB * const pdbP, + FILE * const ofP) { + + struct pam pam; + tuple * tupleRow; + unsigned int row; + uint8_t * imgRow; + + MALLOCARRAY(imgRow, ipdb_width(pdbP)); + + pam.size = sizeof(pam); + pam.len = PAM_STRUCT_SIZE(tuple_type); + pam.file = ofP; + pam.plainformat = 0; + pam.width = ipdb_width(pdbP); + pam.height = ipdb_height(pdbP); + pam.depth = 1; + pam.maxval = ipdb_type(pdbP) == IMG_MONO ? 1 : 255; + pam.bytes_per_sample = pnm_bytespersample(pam.maxval); + pam.format = PAM_FORMAT; + strcpy(pam.tuple_type, + ipdb_type(pdbP) == IMG_MONO ? + PAM_PBM_TUPLETYPE : PAM_PGM_TUPLETYPE); + + pnm_writepaminit(&pam); + + tupleRow = pnm_allocpamrow(&pam); + + for (row = 0; row < pam.height; ++row) { + unsigned int col; + + + if (ipdb_type(pdbP) == IMG_MONO) + mrow(pdbP, row, imgRow); + else if (ipdb_type(pdbP) == IMG_GRAY) + grow(pdbP, row, imgRow); + else + g16row(pdbP, row, imgRow); + + for (col = 0; col < pam.width; ++col) + tupleRow[col][0] = imgRow[col]; + + pnm_writepamrow(&pam, tupleRow); + } + pnm_freepamrow(tupleRow); + + free(imgRow); +} + + + +static void +writeText(IPDB * const pdbP, + const char * const name) { + + const char * const note = ipdb_text(pdbP); + + FILE * fP; + + if (name == NULL || note == NULL) { + } else { + fP = pm_openw(name); + if (fP == NULL) + pm_error("Could not open note file '%s' for output", name); + + fprintf(fP, "%s\n", note); + + pm_close(fP); + } +} + + + +int +main(int argc, const char ** argv) { + + struct cmdlineInfo cmdline; + FILE * ifP; + IPDB * pdbP; + int status; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.inputFileName); + + pdbP = ipdb_alloc(NULL); + if (pdbP == NULL) + pm_error("Could not allocate IPDB structure."); + + status = ipdbRead(pdbP, ifP, cmdline.verbose); + if (status != 0) + pm_error("Image header read error: %s.", ipdb_err(status)); + + writeImgPam(pdbP, stdout); + + writeText(pdbP, cmdline.notefile); + + ipdb_free(pdbP); + + pm_close(ifP); + + return EXIT_SUCCESS; +} |