/* pbmtonokia.c - convert a PBM image to Nokia Smart Messaging Formats (NOL, NGG, HEX) Copyright information is at end of file. */ #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ #define _BSD_SOURCE /* Make sure strcaseeq() is in nstring.h */ #include #include #include "pm_c_util.h" #include "nstring.h" #include "mallocvar.h" #include "shhopt.h" #include "pbm.h" enum outputFormat { FMT_HEX_NOL, FMT_HEX_NGG, FMT_HEX_NPM, FMT_NOL, FMT_NGG, FMT_NPM }; struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char * inputFileName; /* Filename of input files */ int outputFormat; const char * networkCode; const char * txt; /* NULL means unspecified */ }; static const char * uppercase(const char * const subject) { char * buffer; buffer = malloc(strlen(subject) + 1); if (buffer == NULL) pm_error("Out of memory allocating buffer for uppercasing a " "%u-character string", (unsigned)strlen(subject)); else { unsigned int i; i = 0; while (subject[i]) { buffer[i] = TOUPPER(subject[i]); ++i; } buffer[i] = '\0'; } return buffer; } 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 pm_optParseOptions3 on how to parse our options. */ optStruct3 opt; unsigned int option_def_index; unsigned int fmtSpec, netSpec, txtSpec; const char * fmtOpt; const char * netOpt; MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "fmt", OPT_STRING, &fmtOpt, &fmtSpec, 0); OPTENT3(0, "net", OPT_STRING, &netOpt, &netSpec, 0); OPTENT3(0, "txt", OPT_STRING, &cmdlineP->txt, &txtSpec, 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, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (fmtSpec) { if (strcaseeq(fmtOpt, "HEX_NOL")) cmdlineP->outputFormat = FMT_HEX_NOL; else if (strcaseeq(fmtOpt, "HEX_NGG")) cmdlineP->outputFormat = FMT_HEX_NGG; else if (strcaseeq(fmtOpt, "HEX_NPM")) cmdlineP->outputFormat = FMT_HEX_NPM; else if (strcaseeq(fmtOpt, "NOL")) cmdlineP->outputFormat = FMT_NOL; else if (strcaseeq(fmtOpt, "NGG")) cmdlineP->outputFormat = FMT_NGG; else if (strcaseeq(fmtOpt, "NPM")) cmdlineP->outputFormat = FMT_NPM; else pm_error("-fmt option must be HEX_NGG, HEX_NOL, HEX_NPM, " "NGG, NOL or NPM. You specified '%s'", fmtOpt); } else cmdlineP->outputFormat = FMT_HEX_NOL; if (netSpec) { if (strlen(netOpt) != 6) pm_error("-net option must be 6 hex digits long. " "You specified %u characters", (unsigned)strlen(netOpt)); else if (!pm_strishex(netOpt)) pm_error("-net option must be hexadecimal. You specified '%s'", netOpt); else cmdlineP->networkCode = uppercase(netOpt); } else cmdlineP->networkCode = strdup("62F210"); /* German D1 net */ if (!txtSpec) cmdlineP->txt = NULL; else if (strlen(cmdlineP->txt) > 120) pm_error("Text message is longer (%u characters) than " "the 120 characters allowed by the format.", (unsigned)strlen(cmdlineP->txt)); if (argc-1 == 0) cmdlineP->inputFileName = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " "specified %u", argc-1); else cmdlineP->inputFileName = argv[1]; } static void freeCmdline(struct cmdlineInfo const cmdline) { pm_strfree(cmdline.networkCode); } static void validateSize(unsigned int const cols, unsigned int const rows){ if (cols > 255) pm_error("This program cannot handle files with more than 255 " "columns"); if (rows > 255) pm_error("This program cannot handle files with more than 255 " "rows"); } static void convertToHexNol(bit ** const image, unsigned int const cols, unsigned int const rows, const char * const networkCode, FILE * const ofP) { unsigned int row; /* header */ fprintf(ofP, "06050415820000%s00%02X%02X01", networkCode, cols, rows); /* image */ for (row = 0; row < rows; ++row) { unsigned int col; unsigned int p; unsigned int c; c = 0; for (p = 0, col = 0; col < cols; ++col) { if (image[row][col] == PBM_BLACK) c |= 0x80 >> p; if (++p == 8) { fprintf(ofP, "%02X",c); p = c = 0; } } if (p > 0) fprintf(ofP, "%02X", c); } } static void convertToHexNgg(bit ** const image, unsigned int const cols, unsigned int const rows, FILE * const ofP) { unsigned int row; /* header */ fprintf(ofP, "0605041583000000%02X%02X01", cols, rows); /* image */ for (row = 0; row < rows; ++row) { unsigned int col; unsigned int p; unsigned int c; for (p = 0, c = 0, col = 0; col < cols; ++col) { if (image[row][col] == PBM_BLACK) c |= 0x80 >> p; if (++p == 8) { fprintf(ofP, "%02X", c); p = c = 0; } } if (p > 0) fprintf(ofP, "%02X", c); } } static void convertToHexNpm(bit ** const image, unsigned int const cols, unsigned int const rows, const char * const text, FILE * const ofP) { unsigned int row; /* header */ fprintf(ofP, "060504158A0000"); /* text */ if (text) { size_t const len = strlen(text); unsigned int it; fprintf(ofP, "00%04X", (unsigned)len); for (it = 0; it < len; ++it) fprintf(ofP, "%02X", text[it]); } /* image */ fprintf(ofP, "02%04X00%02X%02X01", (cols * rows) / 8 + 4, cols, rows); for (row = 0; row < rows; ++row) { unsigned int col; unsigned int p; unsigned int c; for (p = 0, c = 0, col = 0; col < cols; ++col) { if (image[row][col] == PBM_BLACK) c |= 0x80 >> p; if (++p == 8) { fprintf(ofP, "%02X", c); p = c = 0; } } if (p > 0) fprintf(ofP, "%02X", c); } } static void convertToNol(bit ** const image, unsigned int const cols, unsigned int const rows, FILE * const ofP) { unsigned int row; char header[32]; unsigned int it; /* header - this is a hack */ header[ 0] = 'N'; header[ 1] = 'O'; header[ 2] = 'L'; header[ 3] = 0; header[ 4] = 1; header[ 5] = 0; header[ 6] = 4; header[ 7] = 1; header[ 8] = 1; header[ 9] = 0; header[10] = cols; header[11] = 0; header[12] = rows; header[13] = 0; header[14] = 1; header[15] = 0; header[16] = 1; header[17] = 0; header[18] = 0x53; header[19] = 0; fwrite(header, 20, 1, ofP); /* image */ for (row = 0; row < rows; ++row) { unsigned int col; for (col = 0; col < cols; ++col) { char const output = image[row][col] == PBM_BLACK ? '1' : '0'; putc(output, ofP); } } /* padding (to keep gnokii happy) */ for (it = 0; it < 8 - cols * rows % 8; ++it) putc('0', ofP); } static void convertToNgg(bit ** const image, unsigned int const cols, unsigned int const rows, FILE * const ofP) { unsigned int row; char header[32]; unsigned int it; /* header - this is a hack */ header[ 0] = 'N'; header[ 1] = 'G'; header[ 2] = 'G'; header[ 3] = 0; header[ 4] = 1; header[ 5] = 0; header[ 6] = cols; header[ 7] = 0; header[ 8] = rows; header[ 9] = 0; header[10] = 1; header[11] = 0; header[12] = 1; header[13] = 0; header[14] = 0x4a; header[15] = 0; fwrite(header, 16, 1, ofP); /* image */ for (row = 0; row < rows; ++row) { unsigned int col; for (col = 0; col < cols; ++col) { char const output = image[row][col] == PBM_BLACK ? '1' : '0'; putc(output, ofP); } } /* padding (to keep gnokii happy) */ for (it = 0; it < 8 - cols * rows % 8; ++it) putc('0', ofP); } static void convertToNpm(bit ** const image, unsigned int const cols, unsigned int const rows, const char * const text, FILE * const ofP) { size_t const textLen = text ? strlen(text) : 0; unsigned int row; char header[132]; /* header and optional text */ /* Our entry condition is that 'text' be a legal text field, which means no more than 120 characters: */ assert(textLen <= 120); /* We don't have any specification of this format, but in May 2020, we found what looks like a bug: the memcpy of the text field started at &header[5] (overwriting the previous setting of header[5] and leaving header[6+len-1] not set to anything). Nobody ever complained about this, so maybe somehow it worked better than the sensible code we have now, where the text field starts in the 7th byte. */ /* The code below that deliberately avoids using 'text' as an argument to memcpy when 'text' is null looks like it should have no practical effect, because with a zero length memcpy, it shouldn't matter what the from and to arguments are. But it's logically correct - a null pointer just doesn't point anywhere. And believe it or not, it has a practical effect - a truly bizarre one. This code used to be simpler and just pass that null pointer to memcpy, with length 0, and as reported in May 2020, a new optimizing compiler caused header[3] to contain random values. That's right: 3. Skipping that presumed no-op memcpy stopped the behavior. */ header[ 0] = 'N'; header[ 1] = 'P'; header[ 2] = 'M'; header[ 3] = 0; header[ 4] = textLen; header[ 5] = 0; if (text) /* see above */ memcpy(&header[6], text, textLen); header[ 6 + textLen] = cols; header[ 7 + textLen] = rows; header[ 8 + textLen] = 1; header[ 9 + textLen] = 1; header[10 + textLen] = 0; /* unknown */ assert(10 + textLen < sizeof(header)); fwrite(header, 11 + textLen, 1, ofP); /* image: stream of bits, each row padded to a byte boundary inspired by gnokii/common/gsm-filesystems.c */ for (row = 0; row < rows; row++) { unsigned int byteNumber; int bitNumber; char buffer[32]; /* picture messages are (always?) 72 x 28 */ unsigned int col; byteNumber = 0; bitNumber = 7; memset(buffer, 0, sizeof(buffer)); for (col = 0; col < cols; ++col) { if (image[row][col] == PBM_BLACK) buffer[byteNumber] |= (1 << bitNumber); --bitNumber; if (bitNumber < 0 && col < (cols - 1)) { bitNumber = 7; ++byteNumber; } } fwrite(buffer, byteNumber + 1, 1, ofP); } } int main(int argc, char * argv[]) { struct cmdlineInfo cmdline; FILE * ifP; bit ** bits; int rows, cols; pbm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); ifP = pm_openr(cmdline.inputFileName); bits = pbm_readpbm(ifP, &cols, &rows); pm_close(ifP); validateSize(cols, rows); switch (cmdline.outputFormat) { case FMT_HEX_NGG: convertToHexNgg(bits, cols, rows, stdout); break; case FMT_HEX_NOL: convertToHexNol(bits, cols, rows, cmdline.networkCode, stdout); break; case FMT_HEX_NPM: convertToHexNpm(bits, cols, rows, cmdline.txt, stdout); break; case FMT_NGG: convertToNgg(bits, cols, rows, stdout); break; case FMT_NOL: convertToNol(bits, cols, rows, stdout); break; case FMT_NPM: convertToNpm(bits, cols, rows, cmdline.txt, stdout); break; } freeCmdline(cmdline); return 0; } /* Copyright (C)2001 OMS Open Media System GmbH, Tim Rühsen ** . ** ** 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. Created 2001.06.07 Notes: - limited to rows <= 255 and columns <= 255 - limited to b/w graphics, not animated Testing: Testing was done with SwissCom SMSC (Switzerland) and IC3S SMSC (Germany). The data was send with EMI/UCP protocol over TCP/IP. - 7.6.2001: tested with Nokia 3210: 72x14 Operator Logo - 7.6.2001: tested with Nokia 6210: 72x14 Operator Logo and 72x14 Group Graphic */