Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

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