about summary refs log tree commit diff
path: root/converter/other/jpegtopnm.c
blob: 98552c0058ceb888c0805648abf53d737560eac5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
/*****************************************************************************
                                jpegtopnm
******************************************************************************
  This program is part of the Netpbm package.

  This program converts from the JFIF format, which is based on JPEG, to
  the fundamental ppm or pgm format (depending on whether the JFIF 
  image is gray scale or color).

  This program is by Bryan Henderson on 2000.03.20, but is derived
  with permission from the program djpeg, which is in the Independent
  Jpeg Group's JPEG library package.  Under the terms of that permission,
  redistribution of this software is restricted as described in the 
  file README.JPEG.

  Copyright (C) 1991-1998, Thomas G. Lane.

  TODO:

    For CMYK and YCCK JPEG input, optionally produce a 4-deep PAM
    output containing CMYK values.  Define a tupletype for this.
    Extend pamtoppm to convert this to ppm using the standard
    transformation.

    See if additional decompressor options effects significant speedup.
    grayscale output of color image, downscaling, color quantization, and
    dithering are possibilities.  Djpeg's man page says they make it faster.

  IMPLEMENTATION NOTE - PRECISION

    There are two versions of the JPEG library.  One handles only JPEG
    files with 8 bit samples; the other handles only 12 bit files.
    This program may be compiled and linked against either, and run
    dynamically linked to either at runtime independently.  It uses
    the precision information from the file header.  Note that when
    the input has 12 bit precision, this program generates PPM files
    with two-byte samples, but when the input has 8 bit precision, it
    generates PPM files with one-byte samples.  One should use
    Pnmdepth to reduce precision to 8 bits if one-byte-sample output
    is essential.

  IMPLEMENTATION NOTE - EXIF

    See http://exif.org.  See the programs Exifdump
    (http://topo.math.u-psud.fr/~bousch/exifdump.py) and Jhead
    (http://www.sentex.net/~mwandel/jhead).

    
*****************************************************************************/

#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

#include <ctype.h>		/* to declare isprint() */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
/* Note: jpeglib.h prerequires stdlib.h and ctype.h.  It should include them
   itself, but doesn't.
*/
#include <jpeglib.h>

#include "pm_c_util.h"
#include "pnm.h"
#include "shhopt.h"
#include "mallocvar.h"
#include "nstring.h"
#include "exif.h"
#include "jpegdatasource.h"

#define EXIT_WARNING 2  /* Goes with EXIT_FAILURE, EXIT_SUCCESS in stdlib.h */

enum inklevel {NORMAL, ADOBE, GUESS};
   /* This describes image samples that represent ink levels.  NORMAL
      means 0 is no ink; ADOBE means 0 is maximum ink.  GUESS means we
      don't know what 0 means, so we have to guess from information in 
      the image.
      */

enum colorspace {
    /* These are the color spaces in which we can get pixels from the
       JPEG decompressor.  We include only those that are possible
       given our particular inputs to the decompressor.  The
       decompressor is theoretically capable of other, e.g. YCCK.
       Unlike the JPEG library, this type distinguishes between the
       Adobe and non-Adobe style of CMYK samples.  
    */
    GRAYSCALE_COLORSPACE,
    RGB_COLORSPACE, 
    CMYK_NORMAL_COLORSPACE, 
    CMYK_ADOBE_COLORSPACE
    };

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    char *input_filespec;
    char *exif_filespec;
        /* Filespec in which to save EXIF information.  NULL means don't
           save.  "-" means standard output
        */
    unsigned int verbose;
    unsigned int nosmooth;
    J_DCT_METHOD dct_method;
    long int max_memory_to_use;
    unsigned int trace_level;
    enum inklevel inklevel;
    unsigned int comments;
    unsigned int dumpexif;
    unsigned int multiple;
    unsigned int repair;
};


static bool displayComments;
    /* User wants comments from the JPEG to be displayed */

static void 
interpret_maxmemory(bool         const maxmemorySpec,
                    const char * const maxmemory, 
                    long int *   const max_memory_to_use_p) { 
/*----------------------------------------------------------------------------
   Interpret the "maxmemory" command line option.
-----------------------------------------------------------------------------*/
    long int lval;
    char ch;
    
    if (!maxmemorySpec) {
        *max_memory_to_use_p = -1;  /* unspecified */
    } else if (sscanf(maxmemory, "%ld%c", &lval, &ch) < 1) {
        pm_error("Invalid value for --maxmemory option: '%s'.", maxmemory);
    } else {
        if (ch == 'm' || ch == 'M') lval *= 1000L;
        *max_memory_to_use_p = lval * 1000L;
    }
}


static void
interpret_adobe(const int adobe, const int notadobe, 
                enum inklevel * const inklevel_p) {
/*----------------------------------------------------------------------------
   Interpret the adobe/notadobe command line options
-----------------------------------------------------------------------------*/
    if (adobe && notadobe)
        pm_error("You cannot specify both -adobe and -notadobe options.");
    else {
        if (adobe)
            *inklevel_p = ADOBE;
        else if (notadobe)
            *inklevel_p = NORMAL;
        else 
            *inklevel_p = GUESS;
    }
}



static void
parseCommandLine(int                  const argc,
                 char **              const argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that many of the strings that this function returns in the
   *cmdlineP structure are actually in the supplied argv array.  And
   sometimes, one of these strings is actually just a suffix of an entry
   in argv!

   On the other hand, unlike other option processing functions, we do
   not change argv at all.
-----------------------------------------------------------------------------*/
    optEntry *option_def;
        /* Instructions to pm_optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int i;  /* local loop variable */

    const char * maxmemory;
    const char * dctval;
    unsigned int adobe, notadobe;

    unsigned int tracelevelSpec, exifSpec, dctvalSpec, maxmemorySpec;
    unsigned int option_def_index;

    int argc_parse;       /* argc, except we modify it as we parse */
    char ** argv_parse;

    MALLOCARRAY_NOFAIL(option_def, 100);
    MALLOCARRAY_NOFAIL(argv_parse, argc);
    
    /* argv, except we modify it as we parse */

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
    OPTENT3(0, "dct",         OPT_STRING, &dctval,
            &dctvalSpec, 0);
    OPTENT3(0, "maxmemory",   OPT_STRING, &maxmemory,
            &maxmemorySpec, 0); 
    OPTENT3(0, "nosmooth",    OPT_FLAG,   NULL, &cmdlineP->nosmooth,      0);
    OPTENT3(0, "tracelevel",  OPT_UINT,   &cmdlineP->trace_level,   
            &tracelevelSpec, 0);
    OPTENT3(0, "adobe",       OPT_FLAG,   NULL, &adobe,                   0);
    OPTENT3(0, "notadobe",    OPT_FLAG,   NULL, &notadobe,                0);
    OPTENT3(0, "comments",    OPT_FLAG,   NULL, &cmdlineP->comments,      0);
    OPTENT3(0, "exif",        OPT_STRING, &cmdlineP->exif_filespec, 
            &exifSpec, 0);
    OPTENT3(0, "dumpexif",    OPT_FLAG,   NULL, &cmdlineP->dumpexif,      0);
    OPTENT3(0, "multiple",    OPT_FLAG,   NULL, &cmdlineP->multiple,      0);
    OPTENT3(0, "repair",      OPT_FLAG,   NULL, &cmdlineP->repair,        0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */

    /* Make private copy of arguments for pm_optParseOptions to corrupt */
    argc_parse = argc;
    for (i=0; i < argc; ++i)
        argv_parse[i] = argv[i];

    pm_optParseOptions3( &argc_parse, argv_parse, opt, sizeof(opt), 0);
        /* Uses and sets argc_parse, argv_parse, 
           and some of *cmdlineP and others. */

    if (!tracelevelSpec)
        cmdlineP->trace_level = 0;

    if (!exifSpec)
        cmdlineP->exif_filespec = NULL;

    if (argc_parse - 1 == 0)
        cmdlineP->input_filespec = strdup("-");  /* he wants stdin */
    else if (argc_parse - 1 == 1)
        cmdlineP->input_filespec = strdup(argv_parse[1]);
    else 
        pm_error("Too many arguments.  The only argument accepted "
                 "is the input file specification");

    if (!dctvalSpec)
        cmdlineP->dct_method = JDCT_DEFAULT;
    else {
        if (streq(dctval, "int"))
            cmdlineP->dct_method = JDCT_ISLOW;
        else if (streq(dctval, "fast"))
            cmdlineP->dct_method = JDCT_IFAST;
        else if (streq(dctval, "float"))
            cmdlineP->dct_method = JDCT_FLOAT;
        else pm_error("Invalid value for the --dct option: '%s'.", dctval);
    }

    interpret_maxmemory(maxmemorySpec, maxmemory, 
                        &cmdlineP->max_memory_to_use);

    interpret_adobe(adobe, notadobe, &cmdlineP->inklevel);

    free(argv_parse);
}


/*
 * Marker processor for COM and interesting APPn markers.
 * This replaces the library's built-in processor, which just skips the marker.
 * We want to print out the marker as text, to the extent possible.
 * Note this code relies on a non-suspending data source.
 */

#if 0
static unsigned int
jpeg_getc (j_decompress_ptr cinfo)
/* Read next byte */
{
  struct jpeg_source_mgr * datasrc = cinfo->src;

  if (datasrc->bytes_in_buffer == 0) {
      if (! (*datasrc->fill_input_buffer) (cinfo)) 
          pm_error("Can't suspend here.");
  }
  datasrc->bytes_in_buffer--;
  return GETJOCTET(*datasrc->next_input_byte++);
}


static boolean
print_text_marker (j_decompress_ptr cinfo) {
/*----------------------------------------------------------------------------
   This is a routine that you can register with the Jpeg decompressor
   with e.g.

     jpeg_set_marker_processor(cinfoP, JPEG_APP0 + app_type, 
                               print_text_marker);

  The decompressor then calls it when it encounters a miscellaneous marker
  of the specified type (e.g. APP1).  At that time, the jpeg input stream
  is positioned to the marker contents -- first 2 bytes of length information,
  MSB first, where the length includes those two bytes, then the data.
  
  We just get and print the contents of the marker.

  This routine is no longer used; it is kept as an example in case we want
  to use it in the future.  Instead, we use jpeg_save_markers() and have
  the Jpeg library store all the markers in memory for our later access.
-----------------------------------------------------------------------------*/
    const boolean traceit = (cinfo->err->trace_level >= 1);
    const boolean display_value = 
        traceit || (cinfo->unread_marker == JPEG_COM && displayComments);
    
    INT32 length;
    unsigned int ch;
    unsigned int lastch = 0;
    
    length = jpeg_getc(cinfo) << 8;
    length += jpeg_getc(cinfo);
    length -= 2;			/* discount the length word itself */

    if (traceit) {
        if (cinfo->unread_marker == JPEG_COM)
            fprintf(stderr, "Comment, length %ld:\n", (long) length);
        else			/* assume it is an APPn otherwise */
            fprintf(stderr, "APP%d, length %ld:\n",
                    cinfo->unread_marker - JPEG_APP0, (long) length);
    }
    
    if (cinfo->unread_marker == JPEG_COM && displayComments)
        fprintf(stderr, "COMMENT: ");
    
    while (--length >= 0) {
        ch = jpeg_getc(cinfo);
        if (display_value) {
            /* Emit the character in a readable form.
             * Nonprintables are converted to \nnn form,
             * while \ is converted to \\.
             * Newlines in CR, CR/LF, or LF form will be printed as one 
             * newline.
             */
            if (ch == '\r') {
              fprintf(stderr, "\n");
            } else if (ch == '\n') {
                if (lastch != '\r')
                    fprintf(stderr, "\n");
            } else if (ch == '\\') {
                fprintf(stderr, "\\\\");
            } else if (isprint(ch)) {
                putc(ch, stderr);
            } else {
                fprintf(stderr, "\\%03o", ch);
            }
          lastch = ch;
        }
    }
    
    if (display_value)
        fprintf(stderr, "\n");
    
    return TRUE;
}
#endif



static void
print_marker(struct jpeg_marker_struct const marker) {

    if (marker.original_length != marker.data_length) {
        /* This should be impossible, because we asked for up to 65535
           bytes, and the jpeg spec doesn't allow anything bigger than that.
        */
        pm_message("INTERNAL ERROR: %d of %d bytes of marker were "
                   "saved.", marker.data_length, marker.original_length);
    }

    {
        int i;
        JOCTET lastch;

        lastch = 0;
        for (i = 0; i < marker.data_length; i++) {
            /* Emit the character in a readable form.
             * Nonprintables are converted to \nnn form,
             * while \ is converted to \\.
             * Newlines in CR, CR/LF, or LF form will be printed as one 
             * newline.
             */
            if (marker.data[i] == '\r') {
                fprintf(stderr, "\n");
            } else if (marker.data[i] == '\n') {
                if (lastch != '\r')
                    fprintf(stderr, "\n");
            } else if (marker.data[i] == '\\') {
                fprintf(stderr, "\\\\");
            } else if (isprint(marker.data[i])) {
                putc(marker.data[i], stderr);
            } else {
                fprintf(stderr, "\\%03o", marker.data[i]);
            }
            lastch = marker.data[i];
        }
        fprintf(stderr, "\n");
    }
}


typedef struct rgb {unsigned int r; unsigned int g; unsigned int b;} rgb_type;


static rgb_type *
read_rgb(JSAMPLE *ptr, const enum colorspace color_space, 
         const unsigned int maxval) {
/*----------------------------------------------------------------------------
  Return the RGB triple corresponding to the color of the JPEG pixel at
  'ptr', which is in color space 'color_space'.  

  Assume 'maxval' is the maximum sample value in the input pixel, and also
  use it for the maximum sample value in the return value.
-----------------------------------------------------------------------------*/
    static rgb_type rgb;  /* Our return value */

    switch (color_space) {
    case RGB_COLORSPACE: {
        rgb.r = GETJSAMPLE(*(ptr+0));
        rgb.g = GETJSAMPLE(*(ptr+1)); 
        rgb.b = GETJSAMPLE(*(ptr+2)); 
    }
        break;
    case CMYK_NORMAL_COLORSPACE: {
        const int c = GETJSAMPLE(*(ptr+0));
        const int m = GETJSAMPLE(*(ptr+1));
        const int y = GETJSAMPLE(*(ptr+2));
        const int k = GETJSAMPLE(*(ptr+3));

        /* I swapped m and y below, because they looked wrong.
           -Bryan 2000.08.20
           */
        rgb.r = ((maxval-k)*(maxval-c))/maxval;
        rgb.g = ((maxval-k)*(maxval-m))/maxval;
        rgb.b = ((maxval-k)*(maxval-y))/maxval;
    }
        break;
    case CMYK_ADOBE_COLORSPACE: {
        const int c = GETJSAMPLE(*(ptr+0));
        const int m = GETJSAMPLE(*(ptr+1));
        const int y = GETJSAMPLE(*(ptr+2));
        const int k = GETJSAMPLE(*(ptr+3));

        rgb.r = (k*c)/maxval;
        rgb.g = (k*m)/maxval;
        rgb.b = (k*y)/maxval;
    }
        break;
    default:
        pm_error("Internal error: unknown color space %d passed to "
                 "read_rgb().", (int) color_space);
    }
    return(&rgb);
}



/* pnmbuffer is declared global because it would be improper to pass a
   pointer to it as input to copy_pixel_row(), since it isn't
   logically a parameter of the operation, but rather is private to
   copy_pixel_row().  But it would be impractical to allocate and free
   the storage with every call to copy_pixel_row().
*/
static xel * pnmbuffer;      /* Output buffer.  Input to pnm_writepnmrow() */

static void
copyPixelRow(JSAMPROW        const jpegbuffer,
             unsigned int    const width, 
             unsigned int    const samplesPerPixel, 
             enum colorspace const colorSpace,
             FILE *          const ofP,
             int             const format,
             xelval          const maxval) {

    JSAMPLE * ptr;
    unsigned int outputCursor;     /* Cursor into output buffer 'pnmbuffer' */

    ptr = &jpegbuffer[0];  /* Start at beginning of input row */
    
    for (outputCursor = 0; outputCursor < width; ++outputCursor) {
        xel currentPixel;
        if (samplesPerPixel >= 3) {
            const rgb_type * const rgb_p = read_rgb(ptr, colorSpace, maxval);
            PPM_ASSIGN(currentPixel, rgb_p->r, rgb_p->g, rgb_p->b);
        } else {
            PNM_ASSIGN1(currentPixel, GETJSAMPLE(*ptr));
        }
        ptr += samplesPerPixel;  /* move to next pixel of input */
        pnmbuffer[outputCursor] = currentPixel;
    }
    pnm_writepnmrow(ofP, pnmbuffer, width, maxval, format, FALSE);
}



static void
set_color_spaces(const J_COLOR_SPACE jpeg_color_space,
                 int * const output_type_p,
                 J_COLOR_SPACE * const out_color_space_p) {
/*----------------------------------------------------------------------------
   Decide what type of output (PPM or PGM) we shall generate and what 
   color space we must request from the JPEG decompressor, based on the
   color space of the input JPEG image, 'jpeg_color_space'.

   Write to stderr a message telling which type we picked.

   Exit the process with EXIT_FAILURE completion code and a message to
   stderr if the input color space is beyond our capability.
-----------------------------------------------------------------------------*/
    /* Note that the JPEG decompressor is not capable of translating
       CMYK or YCCK to RGB, but can translate YCCK to CMYK.
    */

    switch (jpeg_color_space) {
    case JCS_UNKNOWN:
        pm_error("Input JPEG image has 'unknown' color space "
                 "(JCS_UNKNOWN).\n"
                 "We cannot interpret this image.");
        break;
    case JCS_GRAYSCALE:
        *output_type_p = PGM_TYPE;
        *out_color_space_p = JCS_GRAYSCALE;
        break;
    case JCS_RGB:
        *output_type_p = PPM_TYPE;
        *out_color_space_p = JCS_RGB;
        break;
    case JCS_YCbCr:
        *output_type_p = PPM_TYPE;
        *out_color_space_p = JCS_RGB;
        /* Design note:  We found this YCbCr->RGB conversion increases
           user mode CPU time by 2.5%.  2002.10.12
        */
        break;
    case JCS_CMYK:
        *output_type_p = PPM_TYPE;
        *out_color_space_p = JCS_CMYK;
        break;
    case JCS_YCCK:
        *output_type_p = PPM_TYPE;
        *out_color_space_p = JCS_CMYK;
        break;
    default:
        pm_error("Internal error: unknown color space code %d passed "
                 "to set_color_spaces().", jpeg_color_space);
    }
    pm_message("WRITING %s FILE", 
               *output_type_p == PPM_TYPE ? "PPM" : "PGM");
}



static const char *
colorspace_name(const J_COLOR_SPACE jpeg_color_space) {

    const char *retval;

    switch(jpeg_color_space) {
    case JCS_UNKNOWN: retval = "JCS_UNKNOWN"; break;
    case JCS_GRAYSCALE: retval= "JCS_GRAYSCALE"; break;
    case JCS_RGB: retval = "JCS_RGB"; break;
    case JCS_YCbCr: retval = "JCS_YCbCr"; break;
    case JCS_CMYK: retval = "JCS_CMYK"; break;
    case JCS_YCCK: retval = "JCS_YCCK"; break;
    default: retval = "invalid"; break;
    };
    return(retval);
}



static void
print_verbose_info_about_header(struct jpeg_decompress_struct const cinfo){

    struct jpeg_marker_struct * markerP;

    pm_message("input color space is %d (%s)\n", 
               cinfo.jpeg_color_space, 
               colorspace_name(cinfo.jpeg_color_space));

    /* Note that raw information about marker, including marker type code,
       was already printed by the jpeg library, because of the jpeg library
       trace level >= 1.  Our job is to interpret it a little bit.
    */
    if (cinfo.marker_list)
        pm_message("Miscellaneous markers (excluding APP0, APP12) "
                   "in header:");
    else
        pm_message("No miscellaneous markers (excluding APP0, APP12) "
                   "in header");
    for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) {
        if (markerP->marker == JPEG_COM)
            pm_message("Comment marker (COM):");
        else if (markerP->marker >= JPEG_APP0 && 
                 markerP->marker <= JPEG_APP0+15)
            pm_message("Miscellaneous marker type APP%d:", 
                       markerP->marker - JPEG_APP0);
        else
            pm_message("Miscellaneous marker of unknown type (0x%X):",
                       markerP->marker);
        
        print_marker(*markerP);
    }
}



static void
beginJpegInput(struct jpeg_decompress_struct * const cinfoP,
               const boolean verbose, 
               const J_DCT_METHOD dct_method, 
               const int max_memory_to_use, 
               const boolean nosmooth) {
/*----------------------------------------------------------------------------
   Read the JPEG header, create decompressor object (and
   allocate memory for it), set up decompressor.
-----------------------------------------------------------------------------*/
    /* Read file header, set default decompression parameters */
    jpeg_read_header(cinfoP, TRUE);

    cinfoP->dct_method = dct_method;
    if (max_memory_to_use != -1)
        cinfoP->mem->max_memory_to_use = max_memory_to_use;
    if (nosmooth)
        cinfoP->do_fancy_upsampling = FALSE;
    
}



static void
print_comments(struct jpeg_decompress_struct const cinfo) {
    
    struct jpeg_marker_struct * markerP;

    for (markerP = cinfo.marker_list;
         markerP; markerP = markerP->next) 
        if (markerP->marker == JPEG_COM) {
            pm_message("COMMENT:");
            print_marker(*markerP);
        }
}



static void
print_exif_info(struct jpeg_marker_struct const marker) {
/*----------------------------------------------------------------------------
   Dump as informational messages the contents of the Jpeg miscellaneous
   marker 'marker', assuming it is an Exif header.
-----------------------------------------------------------------------------*/
    bool const wantTagTrace = false;
    exif_ImageInfo imageInfo;
    const char * error;

    assert(marker.data_length >= 6);

    exif_parse(marker.data+6, marker.data_length-6, 
               &imageInfo, wantTagTrace, &error);

    if (error) {
        pm_message("EXIF header is invalid.  %s", error);
        pm_strfree(error);
    } else
        exif_showImageInfo(&imageInfo, stderr);
}



static boolean
is_exif(struct jpeg_marker_struct const marker) {
/*----------------------------------------------------------------------------
   Return true iff the JPEG miscellaneous marker 'marker' is an Exif 
   header.
-----------------------------------------------------------------------------*/
    boolean retval;
    
    if (marker.marker == JPEG_APP0+1) {
        if (marker.data_length >=6 && memcmp(marker.data, "Exif", 4) == 0)
            retval = TRUE;
        else retval = FALSE;
    }
    else retval = FALSE;

    return retval;
}



static void
dump_exif(struct jpeg_decompress_struct const cinfo) {
/*----------------------------------------------------------------------------
   Dump as informational messages the contents of all EXIF headers in
   the image, interpreted.  An EXIF header is an APP1 marker.
-----------------------------------------------------------------------------*/
    struct jpeg_marker_struct * markerP;
    boolean found_one;

    found_one = FALSE;  /* initial value */

    for (markerP = cinfo.marker_list; markerP; markerP = markerP->next) 
        if (is_exif(*markerP)) {
            pm_message("EXIF INFO:");
            print_exif_info(*markerP);
            found_one = TRUE;
        }
    if (!found_one)
        pm_message("No EXIF info in image.");
}



static void
save_exif(struct jpeg_decompress_struct const cinfo, 
          const char *                  const exif_filespec) {
/*----------------------------------------------------------------------------
  Write the contents of the first Exif header in the image into the
  file with filespec 'exif_filespec'.  Start with the two byte length
  field.  If 'exif_filespec' is "-", write to standard output.

  If there is no Exif header in the image, write just zero, as a two
  byte pure binary integer.
-----------------------------------------------------------------------------*/
    FILE * exif_file;
    struct jpeg_marker_struct * markerP;

    exif_file = pm_openw(exif_filespec);

    for (markerP = cinfo.marker_list; 
         markerP && !is_exif(*markerP);
         markerP = markerP->next);

    if (markerP) {
        pm_writebigshort(exif_file, markerP->data_length+2);
        if (ferror(exif_file))
            pm_error("Write of Exif header to %s failed on first byte.",
                     exif_filespec);
        else {
            int rc;

            rc = fwrite(markerP->data, 1, markerP->data_length, exif_file);
            if (rc != markerP->data_length)
                pm_error("Write of Exif header to '%s' failed.  Wrote "
                         "length successfully, but then failed after "
                         "%d characters of data.", exif_filespec, rc);
        }
    } else {
        /* There is no Exif header in the image */
        pm_writebigshort(exif_file, 0);
        if (ferror(exif_file))
            pm_error("Write of Exif header file '%s' failed.", exif_filespec);
    }
    pm_close(exif_file);
}



static void
tellDetails(struct jpeg_decompress_struct const cinfo,
            xelval                        const maxval,
            int                           const output_type) {

    print_verbose_info_about_header(cinfo);

    pm_message("Input image data precision = %d bits", 
               cinfo.data_precision);
    pm_message("Output file will have format %c%c "
               "with max sample value of %d.", 
               (char) (output_type/256), (char) (output_type % 256),
               maxval);
}  



static enum colorspace
computeColorSpace(struct jpeg_decompress_struct * const cinfoP,
                  enum inklevel                   const inklevel) {
    
    enum colorspace colorSpace;

    if (cinfoP->out_color_space == JCS_GRAYSCALE)
        colorSpace = GRAYSCALE_COLORSPACE;
    else if (cinfoP->out_color_space == JCS_RGB)
        colorSpace = RGB_COLORSPACE;
    else if (cinfoP->out_color_space == JCS_CMYK) {
        switch (inklevel) {
        case ADOBE:
            colorSpace = CMYK_ADOBE_COLORSPACE; break;
        case NORMAL:
            colorSpace = CMYK_NORMAL_COLORSPACE; break;
        case GUESS:
            colorSpace = CMYK_ADOBE_COLORSPACE; break;
        }
    } else
        pm_error("Internal error: unacceptable output color space from "
                 "JPEG decompressor.");

    return colorSpace;
}



static void
convertRaster(struct jpeg_decompress_struct * const cinfoP,
              enum colorspace                 const color_space,
              FILE *                          const ofP,
              xelval                          const format,
              unsigned int                    const maxval) {
              
    JSAMPROW jpegbuffer;  /* Input buffer.  Filled by jpeg_scanlines() */

    jpegbuffer = ((*cinfoP->mem->alloc_sarray)
                  ((j_common_ptr) cinfoP, JPOOL_IMAGE,
                   cinfoP->output_width * cinfoP->output_components, 
                   (JDIMENSION) 1)
        )[0];

    while (cinfoP->output_scanline < cinfoP->output_height) {
        jpeg_read_scanlines(cinfoP, &jpegbuffer, 1);
        if (ofP)
            copyPixelRow(jpegbuffer, cinfoP->output_width, 
                         cinfoP->out_color_components,
                         color_space, ofP, format, maxval);
    }
}



static void
convertImage(FILE *                          const ofP, 
             struct cmdlineInfo              const cmdline,
             struct jpeg_decompress_struct * const cinfoP) {

    int format;
        /* The type of output file, PGM or PPM.  Value is either PPM_TYPE
           or PGM_TYPE, which conveniently also pass as format values
           PPM_FORMAT and PGM_FORMAT.
        */
    xelval maxval;  
        /* The maximum value of a sample (color component), both in the input
           and the output.
        */
    enum colorspace color_space;
        /* The color space of the pixels coming out of the JPEG decompressor */

    beginJpegInput(cinfoP, cmdline.verbose, 
                   cmdline.dct_method, 
                   cmdline.max_memory_to_use, cmdline.nosmooth);
                   
    set_color_spaces(cinfoP->jpeg_color_space, &format,
                     &cinfoP->out_color_space);

    maxval = pm_bitstomaxval(cinfoP->data_precision);

    if (cmdline.verbose) 
        tellDetails(*cinfoP, maxval, format);

    /* Calculate output image dimensions so we can allocate space */
    jpeg_calc_output_dimensions(cinfoP);

    /* Start decompressor */
    jpeg_start_decompress(cinfoP);

    if (ofP)
        /* Write pnm output header */
        pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height,
                         maxval, format, FALSE);

    pnmbuffer = pnm_allocrow(cinfoP->output_width);
    
    color_space = computeColorSpace(cinfoP, cmdline.inklevel);
    
    convertRaster(cinfoP, color_space, ofP, format, maxval);

    if (cmdline.comments)
        print_comments(*cinfoP);
    if (cmdline.dumpexif)
        dump_exif(*cinfoP);
    if (cmdline.exif_filespec)
        save_exif(*cinfoP, cmdline.exif_filespec);

    pnm_freerow(pnmbuffer);

    /* Finish decompression and release decompressor memory. */
    jpeg_finish_decompress(cinfoP);
}




static void
saveMarkers(struct jpeg_decompress_struct * const cinfoP) {

    unsigned int app_type;
    /* Get all the miscellaneous markers (COM and APPn) saved for our
       later access.
    */
    jpeg_save_markers(cinfoP, JPEG_COM, 65535);
    for (app_type = 0; app_type <= 15; ++app_type) {
        if (app_type == 0 || app_type == 14) {
            /* The jpeg library uses APP0 and APP14 internally (see
               libjpeg.doc), so we don't mess with those.
            */
        } else
            jpeg_save_markers(cinfoP, JPEG_APP0 + app_type, 65535);
    }
}



static void
convertImages(FILE *                          const ofP,
              struct cmdlineInfo              const cmdline,
              struct jpeg_decompress_struct * const cinfoP,
              struct sourceManager *          const sourceManagerP) {
              
    if (cmdline.multiple) {
        unsigned int imageSequence;
        for (imageSequence = 0; dsDataLeft(sourceManagerP); ++imageSequence) {
            if (cmdline.verbose)
                pm_message("Reading Image %u", imageSequence);
            convertImage(ofP, cmdline, cinfoP);
        }
    } else {
        if (dsDataLeft(sourceManagerP)) {
            convertImage(ofP, cmdline, cinfoP);
        } else
            pm_error("Input stream is empty");
    }
    if (dsPrematureEof(sourceManagerP)) {
        if (cmdline.repair)
            pm_message("Premature EOF on input; repaired by padding end "
                       "of image.");
        else
            pm_error("Premature EOF on input.  Use -repair to salvage.");
    }
}



int
main(int argc, char **argv) {

    FILE * ofP;
    struct cmdlineInfo cmdline;
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    struct sourceManager * sourceManagerP;

    pnm_init(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    if (cmdline.exif_filespec && streq(cmdline.exif_filespec, "-"))
        /* He's got exif going to stdout, so there can be no image output */
        ofP = NULL;
    else
        ofP = stdout;

    displayComments = cmdline.comments;

    /* Initialize the JPEG decompression object with default error handling. */
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);

    if (cmdline.trace_level == 0 && cmdline.verbose) 
        cinfo.err->trace_level = 1;
    else 
        cinfo.err->trace_level = cmdline.trace_level;
    
    saveMarkers(&cinfo);

    sourceManagerP = dsCreateSource(cmdline.input_filespec);

    cinfo.src = dsJpegSourceMgr(sourceManagerP);

    convertImages(ofP, cmdline, &cinfo, sourceManagerP);

    jpeg_destroy_decompress(&cinfo);

    if (ofP) {
        int rc;
        rc = fclose(ofP);
        if (rc == EOF) 
            pm_error("Error writing output file.  Errno = %s (%d).",
                     strerror(errno), errno);
    }

    dsDestroySource(sourceManagerP);

    free(cmdline.input_filespec);
  
    exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS);
}