Home | History | Annotate | Download | only in libjpeg-turbo
      1 /*
      2  * wrjpgcom.c
      3  *
      4  * This file was part of the Independent JPEG Group's software:
      5  * Copyright (C) 1994-1997, Thomas G. Lane.
      6  * libjpeg-turbo Modifications:
      7  * Copyright (C) 2014, D. R. Commander.
      8  * For conditions of distribution and use, see the accompanying README.ijg
      9  * file.
     10  *
     11  * This file contains a very simple stand-alone application that inserts
     12  * user-supplied text as a COM (comment) marker in a JFIF file.
     13  * This may be useful as an example of the minimum logic needed to parse
     14  * JPEG markers.
     15  */
     16 
     17 #define JPEG_CJPEG_DJPEG        /* to get the command-line config symbols */
     18 #include "jinclude.h"           /* get auto-config symbols, <stdio.h> */
     19 
     20 #ifndef HAVE_STDLIB_H           /* <stdlib.h> should declare malloc() */
     21 extern void *malloc ();
     22 #endif
     23 #include <ctype.h>              /* to declare isupper(), tolower() */
     24 #ifdef USE_SETMODE
     25 #include <fcntl.h>              /* to declare setmode()'s parameter macros */
     26 /* If you have setmode() but not <io.h>, just delete this line: */
     27 #include <io.h>                 /* to declare setmode() */
     28 #endif
     29 
     30 #ifdef USE_CCOMMAND             /* command-line reader for Macintosh */
     31 #ifdef __MWERKS__
     32 #include <SIOUX.h>              /* Metrowerks needs this */
     33 #include <console.h>            /* ... and this */
     34 #endif
     35 #ifdef THINK_C
     36 #include <console.h>            /* Think declares it here */
     37 #endif
     38 #endif
     39 
     40 #ifdef DONT_USE_B_MODE          /* define mode parameters for fopen() */
     41 #define READ_BINARY     "r"
     42 #define WRITE_BINARY    "w"
     43 #else
     44 #define READ_BINARY     "rb"
     45 #define WRITE_BINARY    "wb"
     46 #endif
     47 
     48 #ifndef EXIT_FAILURE            /* define exit() codes if not provided */
     49 #define EXIT_FAILURE  1
     50 #endif
     51 #ifndef EXIT_SUCCESS
     52 #define EXIT_SUCCESS  0
     53 #endif
     54 
     55 /* Reduce this value if your malloc() can't allocate blocks up to 64K.
     56  * On DOS, compiling in large model is usually a better solution.
     57  */
     58 
     59 #ifndef MAX_COM_LENGTH
     60 #define MAX_COM_LENGTH 65000L   /* must be <= 65533 in any case */
     61 #endif
     62 
     63 
     64 /*
     65  * These macros are used to read the input file and write the output file.
     66  * To reuse this code in another application, you might need to change these.
     67  */
     68 
     69 static FILE *infile;            /* input JPEG file */
     70 
     71 /* Return next input byte, or EOF if no more */
     72 #define NEXTBYTE()  getc(infile)
     73 
     74 static FILE *outfile;           /* output JPEG file */
     75 
     76 /* Emit an output byte */
     77 #define PUTBYTE(x)  putc((x), outfile)
     78 
     79 
     80 /* Error exit handler */
     81 #define ERREXIT(msg)  (fprintf(stderr, "%s\n", msg), exit(EXIT_FAILURE))
     82 
     83 
     84 /* Read one byte, testing for EOF */
     85 static int
     86 read_1_byte (void)
     87 {
     88   int c;
     89 
     90   c = NEXTBYTE();
     91   if (c == EOF)
     92     ERREXIT("Premature EOF in JPEG file");
     93   return c;
     94 }
     95 
     96 /* Read 2 bytes, convert to unsigned int */
     97 /* All 2-byte quantities in JPEG markers are MSB first */
     98 static unsigned int
     99 read_2_bytes (void)
    100 {
    101   int c1, c2;
    102 
    103   c1 = NEXTBYTE();
    104   if (c1 == EOF)
    105     ERREXIT("Premature EOF in JPEG file");
    106   c2 = NEXTBYTE();
    107   if (c2 == EOF)
    108     ERREXIT("Premature EOF in JPEG file");
    109   return (((unsigned int) c1) << 8) + ((unsigned int) c2);
    110 }
    111 
    112 
    113 /* Routines to write data to output file */
    114 
    115 static void
    116 write_1_byte (int c)
    117 {
    118   PUTBYTE(c);
    119 }
    120 
    121 static void
    122 write_2_bytes (unsigned int val)
    123 {
    124   PUTBYTE((val >> 8) & 0xFF);
    125   PUTBYTE(val & 0xFF);
    126 }
    127 
    128 static void
    129 write_marker (int marker)
    130 {
    131   PUTBYTE(0xFF);
    132   PUTBYTE(marker);
    133 }
    134 
    135 static void
    136 copy_rest_of_file (void)
    137 {
    138   int c;
    139 
    140   while ((c = NEXTBYTE()) != EOF)
    141     PUTBYTE(c);
    142 }
    143 
    144 
    145 /*
    146  * JPEG markers consist of one or more 0xFF bytes, followed by a marker
    147  * code byte (which is not an FF).  Here are the marker codes of interest
    148  * in this program.  (See jdmarker.c for a more complete list.)
    149  */
    150 
    151 #define M_SOF0  0xC0            /* Start Of Frame N */
    152 #define M_SOF1  0xC1            /* N indicates which compression process */
    153 #define M_SOF2  0xC2            /* Only SOF0-SOF2 are now in common use */
    154 #define M_SOF3  0xC3
    155 #define M_SOF5  0xC5            /* NB: codes C4 and CC are NOT SOF markers */
    156 #define M_SOF6  0xC6
    157 #define M_SOF7  0xC7
    158 #define M_SOF9  0xC9
    159 #define M_SOF10 0xCA
    160 #define M_SOF11 0xCB
    161 #define M_SOF13 0xCD
    162 #define M_SOF14 0xCE
    163 #define M_SOF15 0xCF
    164 #define M_SOI   0xD8            /* Start Of Image (beginning of datastream) */
    165 #define M_EOI   0xD9            /* End Of Image (end of datastream) */
    166 #define M_SOS   0xDA            /* Start Of Scan (begins compressed data) */
    167 #define M_COM   0xFE            /* COMment */
    168 
    169 
    170 /*
    171  * Find the next JPEG marker and return its marker code.
    172  * We expect at least one FF byte, possibly more if the compressor used FFs
    173  * to pad the file.  (Padding FFs will NOT be replicated in the output file.)
    174  * There could also be non-FF garbage between markers.  The treatment of such
    175  * garbage is unspecified; we choose to skip over it but emit a warning msg.
    176  * NB: this routine must not be used after seeing SOS marker, since it will
    177  * not deal correctly with FF/00 sequences in the compressed image data...
    178  */
    179 
    180 static int
    181 next_marker (void)
    182 {
    183   int c;
    184   int discarded_bytes = 0;
    185 
    186   /* Find 0xFF byte; count and skip any non-FFs. */
    187   c = read_1_byte();
    188   while (c != 0xFF) {
    189     discarded_bytes++;
    190     c = read_1_byte();
    191   }
    192   /* Get marker code byte, swallowing any duplicate FF bytes.  Extra FFs
    193    * are legal as pad bytes, so don't count them in discarded_bytes.
    194    */
    195   do {
    196     c = read_1_byte();
    197   } while (c == 0xFF);
    198 
    199   if (discarded_bytes != 0) {
    200     fprintf(stderr, "Warning: garbage data found in JPEG file\n");
    201   }
    202 
    203   return c;
    204 }
    205 
    206 
    207 /*
    208  * Read the initial marker, which should be SOI.
    209  * For a JFIF file, the first two bytes of the file should be literally
    210  * 0xFF M_SOI.  To be more general, we could use next_marker, but if the
    211  * input file weren't actually JPEG at all, next_marker might read the whole
    212  * file and then return a misleading error message...
    213  */
    214 
    215 static int
    216 first_marker (void)
    217 {
    218   int c1, c2;
    219 
    220   c1 = NEXTBYTE();
    221   c2 = NEXTBYTE();
    222   if (c1 != 0xFF || c2 != M_SOI)
    223     ERREXIT("Not a JPEG file");
    224   return c2;
    225 }
    226 
    227 
    228 /*
    229  * Most types of marker are followed by a variable-length parameter segment.
    230  * This routine skips over the parameters for any marker we don't otherwise
    231  * want to process.
    232  * Note that we MUST skip the parameter segment explicitly in order not to
    233  * be fooled by 0xFF bytes that might appear within the parameter segment;
    234  * such bytes do NOT introduce new markers.
    235  */
    236 
    237 static void
    238 copy_variable (void)
    239 /* Copy an unknown or uninteresting variable-length marker */
    240 {
    241   unsigned int length;
    242 
    243   /* Get the marker parameter length count */
    244   length = read_2_bytes();
    245   write_2_bytes(length);
    246   /* Length includes itself, so must be at least 2 */
    247   if (length < 2)
    248     ERREXIT("Erroneous JPEG marker length");
    249   length -= 2;
    250   /* Copy the remaining bytes */
    251   while (length > 0) {
    252     write_1_byte(read_1_byte());
    253     length--;
    254   }
    255 }
    256 
    257 static void
    258 skip_variable (void)
    259 /* Skip over an unknown or uninteresting variable-length marker */
    260 {
    261   unsigned int length;
    262 
    263   /* Get the marker parameter length count */
    264   length = read_2_bytes();
    265   /* Length includes itself, so must be at least 2 */
    266   if (length < 2)
    267     ERREXIT("Erroneous JPEG marker length");
    268   length -= 2;
    269   /* Skip over the remaining bytes */
    270   while (length > 0) {
    271     (void) read_1_byte();
    272     length--;
    273   }
    274 }
    275 
    276 
    277 /*
    278  * Parse the marker stream until SOFn or EOI is seen;
    279  * copy data to output, but discard COM markers unless keep_COM is true.
    280  */
    281 
    282 static int
    283 scan_JPEG_header (int keep_COM)
    284 {
    285   int marker;
    286 
    287   /* Expect SOI at start of file */
    288   if (first_marker() != M_SOI)
    289     ERREXIT("Expected SOI marker first");
    290   write_marker(M_SOI);
    291 
    292   /* Scan miscellaneous markers until we reach SOFn. */
    293   for (;;) {
    294     marker = next_marker();
    295     switch (marker) {
    296       /* Note that marker codes 0xC4, 0xC8, 0xCC are not, and must not be,
    297        * treated as SOFn.  C4 in particular is actually DHT.
    298        */
    299     case M_SOF0:                /* Baseline */
    300     case M_SOF1:                /* Extended sequential, Huffman */
    301     case M_SOF2:                /* Progressive, Huffman */
    302     case M_SOF3:                /* Lossless, Huffman */
    303     case M_SOF5:                /* Differential sequential, Huffman */
    304     case M_SOF6:                /* Differential progressive, Huffman */
    305     case M_SOF7:                /* Differential lossless, Huffman */
    306     case M_SOF9:                /* Extended sequential, arithmetic */
    307     case M_SOF10:               /* Progressive, arithmetic */
    308     case M_SOF11:               /* Lossless, arithmetic */
    309     case M_SOF13:               /* Differential sequential, arithmetic */
    310     case M_SOF14:               /* Differential progressive, arithmetic */
    311     case M_SOF15:               /* Differential lossless, arithmetic */
    312       return marker;
    313 
    314     case M_SOS:                 /* should not see compressed data before SOF */
    315       ERREXIT("SOS without prior SOFn");
    316       break;
    317 
    318     case M_EOI:                 /* in case it's a tables-only JPEG stream */
    319       return marker;
    320 
    321     case M_COM:                 /* Existing COM: conditionally discard */
    322       if (keep_COM) {
    323         write_marker(marker);
    324         copy_variable();
    325       } else {
    326         skip_variable();
    327       }
    328       break;
    329 
    330     default:                    /* Anything else just gets copied */
    331       write_marker(marker);
    332       copy_variable();          /* we assume it has a parameter count... */
    333       break;
    334     }
    335   } /* end loop */
    336 }
    337 
    338 
    339 /* Command line parsing code */
    340 
    341 static const char *progname;    /* program name for error messages */
    342 
    343 
    344 static void
    345 usage (void)
    346 /* complain about bad command line */
    347 {
    348   fprintf(stderr, "wrjpgcom inserts a textual comment in a JPEG file.\n");
    349   fprintf(stderr, "You can add to or replace any existing comment(s).\n");
    350 
    351   fprintf(stderr, "Usage: %s [switches] ", progname);
    352 #ifdef TWO_FILE_COMMANDLINE
    353   fprintf(stderr, "inputfile outputfile\n");
    354 #else
    355   fprintf(stderr, "[inputfile]\n");
    356 #endif
    357 
    358   fprintf(stderr, "Switches (names may be abbreviated):\n");
    359   fprintf(stderr, "  -replace         Delete any existing comments\n");
    360   fprintf(stderr, "  -comment \"text\"  Insert comment with given text\n");
    361   fprintf(stderr, "  -cfile name      Read comment from named file\n");
    362   fprintf(stderr, "Notice that you must put quotes around the comment text\n");
    363   fprintf(stderr, "when you use -comment.\n");
    364   fprintf(stderr, "If you do not give either -comment or -cfile on the command line,\n");
    365   fprintf(stderr, "then the comment text is read from standard input.\n");
    366   fprintf(stderr, "It can be multiple lines, up to %u characters total.\n",
    367           (unsigned int) MAX_COM_LENGTH);
    368 #ifndef TWO_FILE_COMMANDLINE
    369   fprintf(stderr, "You must specify an input JPEG file name when supplying\n");
    370   fprintf(stderr, "comment text from standard input.\n");
    371 #endif
    372 
    373   exit(EXIT_FAILURE);
    374 }
    375 
    376 
    377 static int
    378 keymatch (char *arg, const char *keyword, int minchars)
    379 /* Case-insensitive matching of (possibly abbreviated) keyword switches. */
    380 /* keyword is the constant keyword (must be lower case already), */
    381 /* minchars is length of minimum legal abbreviation. */
    382 {
    383   register int ca, ck;
    384   register int nmatched = 0;
    385 
    386   while ((ca = *arg++) != '\0') {
    387     if ((ck = *keyword++) == '\0')
    388       return 0;                 /* arg longer than keyword, no good */
    389     if (isupper(ca))            /* force arg to lcase (assume ck is already) */
    390       ca = tolower(ca);
    391     if (ca != ck)
    392       return 0;                 /* no good */
    393     nmatched++;                 /* count matched characters */
    394   }
    395   /* reached end of argument; fail if it's too short for unique abbrev */
    396   if (nmatched < minchars)
    397     return 0;
    398   return 1;                     /* A-OK */
    399 }
    400 
    401 
    402 /*
    403  * The main program.
    404  */
    405 
    406 int
    407 main (int argc, char **argv)
    408 {
    409   int argn;
    410   char *arg;
    411   int keep_COM = 1;
    412   char *comment_arg = NULL;
    413   FILE *comment_file = NULL;
    414   unsigned int comment_length = 0;
    415   int marker;
    416 
    417   /* On Mac, fetch a command line. */
    418 #ifdef USE_CCOMMAND
    419   argc = ccommand(&argv);
    420 #endif
    421 
    422   progname = argv[0];
    423   if (progname == NULL || progname[0] == 0)
    424     progname = "wrjpgcom";      /* in case C library doesn't provide it */
    425 
    426   /* Parse switches, if any */
    427   for (argn = 1; argn < argc; argn++) {
    428     arg = argv[argn];
    429     if (arg[0] != '-')
    430       break;                    /* not switch, must be file name */
    431     arg++;                      /* advance over '-' */
    432     if (keymatch(arg, "replace", 1)) {
    433       keep_COM = 0;
    434     } else if (keymatch(arg, "cfile", 2)) {
    435       if (++argn >= argc) usage();
    436       if ((comment_file = fopen(argv[argn], "r")) == NULL) {
    437         fprintf(stderr, "%s: can't open %s\n", progname, argv[argn]);
    438         exit(EXIT_FAILURE);
    439       }
    440     } else if (keymatch(arg, "comment", 1)) {
    441       if (++argn >= argc) usage();
    442       comment_arg = argv[argn];
    443       /* If the comment text starts with '"', then we are probably running
    444        * under MS-DOG and must parse out the quoted string ourselves.  Sigh.
    445        */
    446       if (comment_arg[0] == '"') {
    447         comment_arg = (char *) malloc((size_t) MAX_COM_LENGTH);
    448         if (comment_arg == NULL)
    449           ERREXIT("Insufficient memory");
    450         if (strlen(argv[argn]) + 2 >= (size_t) MAX_COM_LENGTH) {
    451           fprintf(stderr, "Comment text may not exceed %u bytes\n",
    452                   (unsigned int) MAX_COM_LENGTH);
    453           exit(EXIT_FAILURE);
    454         }
    455         strcpy(comment_arg, argv[argn]+1);
    456         for (;;) {
    457           comment_length = (unsigned int) strlen(comment_arg);
    458           if (comment_length > 0 && comment_arg[comment_length-1] == '"') {
    459             comment_arg[comment_length-1] = '\0'; /* zap terminating quote */
    460             break;
    461           }
    462           if (++argn >= argc)
    463             ERREXIT("Missing ending quote mark");
    464           if (strlen(comment_arg) + strlen(argv[argn]) + 2 >=
    465               (size_t) MAX_COM_LENGTH) {
    466             fprintf(stderr, "Comment text may not exceed %u bytes\n",
    467                     (unsigned int) MAX_COM_LENGTH);
    468             exit(EXIT_FAILURE);
    469           }
    470           strcat(comment_arg, " ");
    471           strcat(comment_arg, argv[argn]);
    472         }
    473       } else if (strlen(argv[argn]) >= (size_t) MAX_COM_LENGTH) {
    474         fprintf(stderr, "Comment text may not exceed %u bytes\n",
    475                 (unsigned int) MAX_COM_LENGTH);
    476         exit(EXIT_FAILURE);
    477       }
    478       comment_length = (unsigned int) strlen(comment_arg);
    479     } else
    480       usage();
    481   }
    482 
    483   /* Cannot use both -comment and -cfile. */
    484   if (comment_arg != NULL && comment_file != NULL)
    485     usage();
    486   /* If there is neither -comment nor -cfile, we will read the comment text
    487    * from stdin; in this case there MUST be an input JPEG file name.
    488    */
    489   if (comment_arg == NULL && comment_file == NULL && argn >= argc)
    490     usage();
    491 
    492   /* Open the input file. */
    493   if (argn < argc) {
    494     if ((infile = fopen(argv[argn], READ_BINARY)) == NULL) {
    495       fprintf(stderr, "%s: can't open %s\n", progname, argv[argn]);
    496       exit(EXIT_FAILURE);
    497     }
    498   } else {
    499     /* default input file is stdin */
    500 #ifdef USE_SETMODE              /* need to hack file mode? */
    501     setmode(fileno(stdin), O_BINARY);
    502 #endif
    503 #ifdef USE_FDOPEN               /* need to re-open in binary mode? */
    504     if ((infile = fdopen(fileno(stdin), READ_BINARY)) == NULL) {
    505       fprintf(stderr, "%s: can't open stdin\n", progname);
    506       exit(EXIT_FAILURE);
    507     }
    508 #else
    509     infile = stdin;
    510 #endif
    511   }
    512 
    513   /* Open the output file. */
    514 #ifdef TWO_FILE_COMMANDLINE
    515   /* Must have explicit output file name */
    516   if (argn != argc-2) {
    517     fprintf(stderr, "%s: must name one input and one output file\n",
    518             progname);
    519     usage();
    520   }
    521   if ((outfile = fopen(argv[argn+1], WRITE_BINARY)) == NULL) {
    522     fprintf(stderr, "%s: can't open %s\n", progname, argv[argn+1]);
    523     exit(EXIT_FAILURE);
    524   }
    525 #else
    526   /* Unix style: expect zero or one file name */
    527   if (argn < argc-1) {
    528     fprintf(stderr, "%s: only one input file\n", progname);
    529     usage();
    530   }
    531   /* default output file is stdout */
    532 #ifdef USE_SETMODE              /* need to hack file mode? */
    533   setmode(fileno(stdout), O_BINARY);
    534 #endif
    535 #ifdef USE_FDOPEN               /* need to re-open in binary mode? */
    536   if ((outfile = fdopen(fileno(stdout), WRITE_BINARY)) == NULL) {
    537     fprintf(stderr, "%s: can't open stdout\n", progname);
    538     exit(EXIT_FAILURE);
    539   }
    540 #else
    541   outfile = stdout;
    542 #endif
    543 #endif /* TWO_FILE_COMMANDLINE */
    544 
    545   /* Collect comment text from comment_file or stdin, if necessary */
    546   if (comment_arg == NULL) {
    547     FILE *src_file;
    548     int c;
    549 
    550     comment_arg = (char *) malloc((size_t) MAX_COM_LENGTH);
    551     if (comment_arg == NULL)
    552       ERREXIT("Insufficient memory");
    553     comment_length = 0;
    554     src_file = (comment_file != NULL ? comment_file : stdin);
    555     while ((c = getc(src_file)) != EOF) {
    556       if (comment_length >= (unsigned int) MAX_COM_LENGTH) {
    557         fprintf(stderr, "Comment text may not exceed %u bytes\n",
    558                 (unsigned int) MAX_COM_LENGTH);
    559         exit(EXIT_FAILURE);
    560       }
    561       comment_arg[comment_length++] = (char) c;
    562     }
    563     if (comment_file != NULL)
    564       fclose(comment_file);
    565   }
    566 
    567   /* Copy JPEG headers until SOFn marker;
    568    * we will insert the new comment marker just before SOFn.
    569    * This (a) causes the new comment to appear after, rather than before,
    570    * existing comments; and (b) ensures that comments come after any JFIF
    571    * or JFXX markers, as required by the JFIF specification.
    572    */
    573   marker = scan_JPEG_header(keep_COM);
    574   /* Insert the new COM marker, but only if nonempty text has been supplied */
    575   if (comment_length > 0) {
    576     write_marker(M_COM);
    577     write_2_bytes(comment_length + 2);
    578     while (comment_length > 0) {
    579       write_1_byte(*comment_arg++);
    580       comment_length--;
    581     }
    582   }
    583   /* Duplicate the remainder of the source file.
    584    * Note that any COM markers occuring after SOF will not be touched.
    585    */
    586   write_marker(marker);
    587   copy_rest_of_file();
    588 
    589   /* All done. */
    590   exit(EXIT_SUCCESS);
    591   return 0;                     /* suppress no-return-value warnings */
    592 }
    593