Subversion Repositories Kolibri OS

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
6417 ashmew2 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,  */
16
 
17
#ifndef HAVE_STDLIB_H		/*  should declare malloc() */
18
extern void * malloc ();
19
#endif
20
#include 		/* to declare isupper(), tolower() */
21
#ifdef USE_SETMODE
22
#include 		/* to declare setmode()'s parameter macros */
23
/* If you have setmode() but not , just delete this line: */
24
#include 			/* to declare setmode() */
25
#endif
26
 
27
#ifdef USE_CCOMMAND		/* command-line reader for Macintosh */
28
#ifdef __MWERKS__
29
#include               /* Metrowerks needs this */
30
#include 		/* ... and this */
31
#endif
32
#ifdef THINK_C
33
#include 		/* 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
}