1 //
2 //  Little cms
3 //  Copyright (C) 1998-2007 Marti Maria
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the Software
10 // is furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
17 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 
23 
24 // This program does apply profiles to (some) JPEG files
25 
26 
27 #include "lcms.h"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33 #include <sys/stat.h>
34 
35 #ifndef NON_WINDOWS
36 #    include <io.h>
37 #endif
38 
39 #include "jpeglib.h"
40 #include "iccjpeg.h"
41 
42 #define PROGNAME    "JPEGICC"
43 
44 // xgetopt() interface -----------------------------------------------------
45 
46 extern int   xoptind;
47 extern char *xoptarg;
48 extern int   xopterr;
49 extern char   SW;
50 int    cdecl xgetopt(int argc, char *argv[], char *optionS);
51 
52 // ------------------------------------------------------------------------
53 
54 extern cmsHPROFILE OpenStockProfile(const char* File);
55 
56 
57 // Flags
58 
59 static LCMSBOOL Verbose                = FALSE;
60 static LCMSBOOL BlackPointCompensation = FALSE;
61 static LCMSBOOL IgnoreEmbedded         = FALSE;
62 static LCMSBOOL GamutCheck             = FALSE;
63 static LCMSBOOL lIsITUFax              = FALSE;
64 static LCMSBOOL lIsPhotoshopApp13      = FALSE;
65 static LCMSBOOL lIsDeviceLink          = FALSE;
66 static LCMSBOOL EmbedProfile           = FALSE;
67 static int PreserveBlack		   = 0;
68 
69 static const char* SaveEmbedded = NULL;
70 
71 static int Intent                  = INTENT_PERCEPTUAL;
72 static int ProofingIntent          = INTENT_PERCEPTUAL;
73 static int PrecalcMode             = 1;
74 
75 static int jpegQuality             = 75;
76 
77 static double ObserverAdaptationState = 0;
78 
79 static char *cInpProf  = NULL;
80 static char *cOutProf  = NULL;
81 static char *cProofing = NULL;
82 
83 static FILE * InFile;
84 static FILE * OutFile;
85 
86 static struct jpeg_decompress_struct Decompressor;
87 static struct jpeg_compress_struct   Compressor;
88 
89 
90 static struct my_error_mgr {
91 
92     struct  jpeg_error_mgr pub;  // "public" fields
93     LPVOID  Cargo;               // "private" fields
94 
95 } ErrorHandler;
96 
97 static
ConsoleWarningHandler(const char * module,const char * fmt,va_list ap)98 void ConsoleWarningHandler(const char* module, const char* fmt, va_list ap)
99 {
100     char e[512] = { '\0' };
101     if (module != NULL)
102         strcat(strcpy(e, module), ": ");
103 
104     vsprintf(e+strlen(e), fmt, ap);
105     strcat(e, ".");
106     if (Verbose) {
107 
108         fprintf(stderr, "\nWarning");
109         fprintf(stderr, " %s\n", e);
110     }
111 }
112 
113 static
ConsoleErrorHandler(const char * module,const char * fmt,va_list ap)114 void ConsoleErrorHandler(const char* module, const char* fmt, va_list ap)
115 {
116        char e[512] = { '\0' };
117 
118        if (module != NULL)
119               strcat(strcpy(e, module), ": ");
120 
121        vsprintf(e+strlen(e), fmt, ap);
122        strcat(e, ".");
123        fprintf(stderr, "\nError");
124        fprintf(stderr, " %s\n", e);
125 }
126 
127 // Force an error and exit w/ return code 1
128 
129 static
FatalError(const char * frm,...)130 void FatalError(const char *frm, ...)
131 {
132        va_list args;
133 
134        va_start(args, frm);
135        ConsoleErrorHandler(PROGNAME, frm, args);
136        va_end(args);
137        exit(1);
138 }
139 
140 static
MyErrorHandler(int ErrorCode,const char * ErrorText)141 int MyErrorHandler(int ErrorCode, const char *ErrorText)
142 {
143     FatalError("%s", ErrorText);
144     return 0;
145 }
146 
147 
148 // Out of mem
149 static
OutOfMem(size_t size)150 void OutOfMem(size_t size)
151 {
152     FatalError("Out of memory on allocating %d bytes.", size);
153 }
154 
155 
156 METHODDEF(void)
my_output_message(j_common_ptr cinfo)157 my_output_message (j_common_ptr cinfo)
158 {
159   char buffer[JMSG_LENGTH_MAX];
160 
161   (*cinfo->err->format_message) (cinfo, buffer);
162   FatalError("%s %s", PROGNAME, buffer);
163 }
164 
165 
166 METHODDEF(void)
my_error_exit(j_common_ptr cinfo)167 my_error_exit (j_common_ptr cinfo)
168 {
169   // struct my_error_mgr *myerr = (struct my_error_mgr *) cinfo->err;
170   char buffer[JMSG_LENGTH_MAX];
171 
172   (*cinfo->err->format_message) (cinfo, buffer);
173   FatalError(buffer);
174 }
175 
176 
177 
178 static
IsITUFax(jpeg_saved_marker_ptr ptr)179 LCMSBOOL IsITUFax(jpeg_saved_marker_ptr ptr)
180 {
181     while (ptr) {
182 
183         if (ptr -> marker == (JPEG_APP0 + 1) && ptr -> data_length > 5)
184         {
185             JOCTET FAR* data = ptr -> data;
186 
187             if (GETJOCTET(data[0]) == 0x47 &&
188                 GETJOCTET(data[1]) == 0x33 &&
189                 GETJOCTET(data[2]) == 0x46 &&
190                 GETJOCTET(data[3]) == 0x41 &&
191                 GETJOCTET(data[4]) == 0x58)
192                 return TRUE;
193         }
194 
195         ptr = ptr -> next;
196     }
197 
198    return FALSE;
199 
200 }
201 
202 #define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))
203 
204 static
ProcessPhotoshopAPP13(JOCTET FAR * data,int datalen)205 LCMSBOOL ProcessPhotoshopAPP13(JOCTET FAR *data, int datalen)
206 {
207     int i;
208 
209     for (i = 14; i < datalen; )
210     {
211         long len;
212         unsigned int type;
213 
214         if (!(GETJOCTET(data[i]  ) == 0x38 &&
215               GETJOCTET(data[i+1]) == 0x42 &&
216               GETJOCTET(data[i+2]) == 0x49 &&
217               GETJOCTET(data[i+3]) == 0x4D)) break; // Not recognized
218 
219         i += 4; // identifying string
220 
221         type = (unsigned int) (GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]));
222 
223         i += 2; // resource type
224 
225         i += GETJOCTET(data[i]) + ((GETJOCTET(data[i]) & 1) ? 1 : 2);   // resource name
226 
227         len = ((((GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]))<<8) +
228                          GETJOCTET(data[i+2]))<<8) + GETJOCTET(data[i+3]);
229 
230         i += 4; // Size
231 
232         if (type == 0x03ED && len >= 16) {
233 
234             Decompressor.X_density = (int) PS_FIXED_TO_FLOAT(GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]),
235                                                  GETJOCTET(data[i+2]<<8) + GETJOCTET(data[i+3]));
236             Decompressor.Y_density = (int) PS_FIXED_TO_FLOAT(GETJOCTET(data[i+8]<<8) + GETJOCTET(data[i+9]),
237                                                  GETJOCTET(data[i+10]<<8) + GETJOCTET(data[i+11]));
238 
239             // Set the density unit to 1 since the
240             // Vertical and Horizontal resolutions
241             // are specified in Pixels per inch
242 
243             Decompressor.density_unit = 0x01;
244             return TRUE;
245 
246         }
247 
248         i += len + ((len & 1) ? 1 : 0);   // Alignment
249     }
250     return FALSE;
251 }
252 
253 
254 static
HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)255 LCMSBOOL HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
256 {
257     while (ptr) {
258 
259         if (ptr -> marker == (JPEG_APP0 + 13) && ptr -> data_length > 9)
260         {
261             JOCTET FAR* data = ptr -> data;
262 
263             if(GETJOCTET(data[0]) == 0x50 &&
264                GETJOCTET(data[1]) == 0x68 &&
265                GETJOCTET(data[2]) == 0x6F &&
266                GETJOCTET(data[3]) == 0x74 &&
267                GETJOCTET(data[4]) == 0x6F &&
268                GETJOCTET(data[5]) == 0x73 &&
269                GETJOCTET(data[6]) == 0x68 &&
270                GETJOCTET(data[7]) == 0x6F &&
271                GETJOCTET(data[8]) == 0x70) {
272 
273                 ProcessPhotoshopAPP13(data, ptr -> data_length);
274                 return TRUE;
275             }
276         }
277 
278         ptr = ptr -> next;
279     }
280 
281     return FALSE;
282 }
283 
284 
285 
286 static
OpenInput(const char * FileName)287 LCMSBOOL OpenInput(const char* FileName)
288 {
289     int m;
290 
291     lIsITUFax = FALSE;
292     InFile  = fopen(FileName, "rb");
293     if (InFile == NULL) {
294         FatalError("Cannot open '%s'", FileName);
295         return FALSE;
296     }
297 
298     // Now we can initialize the JPEG decompression object.
299 
300     Decompressor.err                 = jpeg_std_error(&ErrorHandler.pub);
301     ErrorHandler.pub.error_exit      = my_error_exit;
302     ErrorHandler.pub.output_message  = my_output_message;
303 
304     jpeg_create_decompress(&Decompressor);
305     jpeg_stdio_src(&Decompressor, InFile);
306 
307     for (m = 0; m < 16; m++)
308         jpeg_save_markers(&Decompressor, JPEG_APP0 + m, 0xFFFF);
309 
310     setup_read_icc_profile(&Decompressor);
311 
312     fseek(InFile, 0, SEEK_SET);
313     jpeg_read_header(&Decompressor, TRUE);
314 
315     return TRUE;
316 }
317 
318 
319 static
OpenOutput(const char * FileName)320 LCMSBOOL OpenOutput(const char* FileName)
321 {
322 
323         OutFile = fopen(FileName, "wb");
324         if (OutFile == NULL) {
325                     FatalError("Cannot create '%s'", FileName);
326                     return FALSE;
327         }
328 
329         Compressor.err                   = jpeg_std_error(&ErrorHandler.pub);
330         ErrorHandler.pub.error_exit      = my_error_exit;
331         ErrorHandler.pub.output_message  = my_output_message;
332 
333         Compressor.input_components = Compressor.num_components = 4;
334 
335         jpeg_create_compress(&Compressor);
336         jpeg_stdio_dest(&Compressor, OutFile);
337 
338         return TRUE;
339 }
340 
341 static
Done()342 LCMSBOOL Done()
343 {
344        jpeg_destroy_decompress(&Decompressor);
345        jpeg_destroy_compress(&Compressor);
346        return fclose(InFile) + fclose(OutFile);
347 
348 }
349 
350 
351 // Build up the pixeltype descriptor
352 
353 static
GetInputPixelType(void)354 DWORD GetInputPixelType(void)
355 {
356      int space, bps, extra, ColorChannels, Flavor;
357 
358 
359      lIsITUFax         = IsITUFax(Decompressor.marker_list);
360      lIsPhotoshopApp13 = HandlePhotoshopAPP13(Decompressor.marker_list);
361 
362      ColorChannels = Decompressor.num_components;
363      extra  = 0;            // Alpha = None
364      bps    = 1;            // 8 bits
365      Flavor = 0;            // Vanilla
366 
367      if (lIsITUFax) {
368 
369         space = PT_Lab;
370         Decompressor.out_color_space = JCS_YCbCr;  // Fake to don't touch
371      }
372      else
373      switch (Decompressor.jpeg_color_space) {
374 
375      case JCS_GRAYSCALE:        // monochrome
376               space = PT_GRAY;
377               Decompressor.out_color_space = JCS_GRAYSCALE;
378               break;
379 
380      case JCS_RGB:             // red/green/blue
381               space = PT_RGB;
382               Decompressor.out_color_space = JCS_RGB;
383               break;
384 
385      case JCS_YCbCr:               // Y/Cb/Cr (also known as YUV)
386               space = PT_RGB;      // Let IJG code to do the conversion
387               Decompressor.out_color_space = JCS_RGB;
388               break;
389 
390      case JCS_CMYK:            // C/M/Y/K
391               space = PT_CMYK;
392               Decompressor.out_color_space = JCS_CMYK;
393               if (Decompressor.saw_Adobe_marker)            // Adobe keeps CMYK inverted, so change flavor
394                                 Flavor = 1;                 // from vanilla to chocolate
395               break;
396 
397      case JCS_YCCK:            // Y/Cb/Cr/K
398               space = PT_CMYK;
399               Decompressor.out_color_space = JCS_CMYK;
400               if (Decompressor.saw_Adobe_marker)            // ditto
401                                 Flavor = 1;
402               break;
403 
404      default:
405               FatalError("Unsupported color space (0x%x)", Decompressor.jpeg_color_space);
406               return 0;
407      }
408 
409      return (EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|COLORSPACE_SH(space)|FLAVOR_SH(Flavor));
410 }
411 
412 
413 
414 
415 
416 // Rearrange pixel type to build output descriptor
417 
418 static
ComputeOutputFormatDescriptor(DWORD dwInput,int OutColorSpace)419 DWORD ComputeOutputFormatDescriptor(DWORD dwInput, int OutColorSpace)
420 {
421    int IsPlanar  = T_PLANAR(dwInput);
422    int Channels  = 0;
423    int Flavor    = 0;
424 
425    switch (OutColorSpace) {
426 
427    case PT_GRAY:
428                Channels = 1;
429                break;
430    case PT_RGB:
431    case PT_CMY:
432    case PT_Lab:
433    case PT_YUV:
434    case PT_YCbCr:
435                Channels = 3;
436                break;
437 
438    case PT_CMYK:
439                if (Compressor.write_Adobe_marker)   // Adobe keeps CMYK inverted, so change flavor to chocolate
440                         Flavor = 1;
441 
442                Channels = 4;
443                break;
444    default:
445                FatalError("Unsupported output color space");
446    }
447 
448     return (COLORSPACE_SH(OutColorSpace)|PLANAR_SH(IsPlanar)|CHANNELS_SH(Channels)|BYTES_SH(1)|FLAVOR_SH(Flavor));
449 }
450 
451 
452 // Equivalence between ICC color spaces and lcms color spaces
453 
454 static
GetProfileColorSpace(cmsHPROFILE hProfile)455 int GetProfileColorSpace(cmsHPROFILE hProfile)
456 {
457     icColorSpaceSignature ProfileSpace = cmsGetColorSpace(hProfile);
458 
459        switch (ProfileSpace) {
460 
461        case icSigGrayData: return  PT_GRAY;
462        case icSigRgbData:  return  PT_RGB;
463        case icSigCmyData:  return  PT_CMY;
464        case icSigCmykData: return  PT_CMYK;
465        case icSigYCbCrData:return  PT_YCbCr;
466        case icSigLuvData:  return  PT_YUV;
467        case icSigXYZData:  return  PT_XYZ;
468        case icSigLabData:  return  PT_Lab;
469        case icSigLuvKData: return  PT_YUVK;
470        case icSigHsvData:  return  PT_HSV;
471        case icSigHlsData:  return  PT_HLS;
472        case icSigYxyData:  return  PT_Yxy;
473 
474        case icSigHexachromeData: return PT_HiFi;
475 
476 
477        default:  return icMaxEnumData;
478        }
479 }
480 
481 // From TRANSUPP
482 
483 static
jcopy_markers_execute(j_decompress_ptr srcinfo,j_compress_ptr dstinfo)484 void jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo)
485 {
486   jpeg_saved_marker_ptr marker;
487 
488   /* In the current implementation, we don't actually need to examine the
489    * option flag here; we just copy everything that got saved.
490    * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
491    * if the encoder library already wrote one.
492    */
493   for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
494     if (dstinfo->write_JFIF_header &&
495     marker->marker == JPEG_APP0 &&
496     marker->data_length >= 5 &&
497     GETJOCTET(marker->data[0]) == 0x4A &&
498     GETJOCTET(marker->data[1]) == 0x46 &&
499     GETJOCTET(marker->data[2]) == 0x49 &&
500     GETJOCTET(marker->data[3]) == 0x46 &&
501     GETJOCTET(marker->data[4]) == 0)
502       continue;         /* reject duplicate JFIF */
503 
504     if (dstinfo->write_Adobe_marker &&
505     marker->marker == JPEG_APP0+14 &&
506     marker->data_length >= 5 &&
507     GETJOCTET(marker->data[0]) == 0x41 &&
508     GETJOCTET(marker->data[1]) == 0x64 &&
509     GETJOCTET(marker->data[2]) == 0x6F &&
510     GETJOCTET(marker->data[3]) == 0x62 &&
511     GETJOCTET(marker->data[4]) == 0x65)
512       continue;         /* reject duplicate Adobe */
513 
514 #ifdef NEED_FAR_POINTERS
515     /* We could use jpeg_write_marker if the data weren't FAR... */
516     {
517       unsigned int i;
518 
519       jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
520       for (i = 0; i < marker->data_length; i++)
521         jpeg_write_m_byte(dstinfo, marker->data[i]);
522     }
523 #else
524     jpeg_write_marker(dstinfo, marker->marker,
525               marker->data, marker->data_length);
526 #endif
527   }
528 }
529 
530 static
WriteOutputFields(int OutputColorSpace)531 void WriteOutputFields(int OutputColorSpace)
532 {
533     J_COLOR_SPACE in_space, jpeg_space;
534     int components;
535 
536     switch (OutputColorSpace) {
537 
538     case PT_GRAY: in_space = jpeg_space = JCS_GRAYSCALE;
539                   components = 1;
540                   break;
541 
542     case PT_RGB:  in_space = JCS_RGB;
543                   jpeg_space = JCS_YCbCr;
544                   components = 3;
545                   break;       // red/green/blue
546 
547     case PT_YCbCr: in_space = jpeg_space = JCS_YCbCr;
548                    components = 3;
549                    break;               // Y/Cb/Cr (also known as YUV)
550 
551     case PT_CMYK: in_space = JCS_CMYK;
552                   jpeg_space = JCS_YCCK;
553                   components = 4;
554                   break;      // C/M/Y/components
555 
556     case PT_Lab:  in_space = jpeg_space = JCS_YCbCr;
557                   components = 3;
558                   break;                // Fake to don't touch
559     default:
560                 FatalError("Unsupported output color space");
561                 return;
562     }
563 
564 
565     if (jpegQuality >= 100) {
566 
567      // avoid destructive conversion when asking for lossless compression
568         jpeg_space = in_space;
569     }
570 
571     Compressor.in_color_space =  in_space;
572     Compressor.jpeg_color_space = jpeg_space;
573     Compressor.input_components = Compressor.num_components = components;
574     jpeg_set_defaults(&Compressor);
575     jpeg_set_colorspace(&Compressor, jpeg_space);
576 
577     // Make sure to pass resolution through
578     if (OutputColorSpace == PT_CMYK)
579         Compressor.write_JFIF_header = 1;
580 
581     //avoid subsampling on high quality factor
582     jpeg_set_quality(&Compressor, jpegQuality, 1);
583     if (jpegQuality >= 70) {
584 
585       int i;
586       for(i=0; i < Compressor.num_components; i++) {
587 
588 	        Compressor.comp_info[i].h_samp_factor = 1;
589             Compressor.comp_info[i].v_samp_factor = 1;
590       }
591 
592     }
593 
594 }
595 
596 // A replacement for (the nonstandard) filelenght
597 
598 static
xfilelength(int fd)599 int xfilelength(int fd)
600 {
601 #ifdef _MSC_VER
602         return _filelength(fd);
603 #else
604         struct stat sb;
605         if (fstat(fd, &sb) < 0)
606                 return(-1);
607         return(sb.st_size);
608 #endif
609 
610 
611 }
612 
613 static
DoEmbedProfile(const char * ProfileFile)614 void DoEmbedProfile(const char* ProfileFile)
615 {
616     FILE* f;
617     size_t size, EmbedLen;
618     LPBYTE EmbedBuffer;
619 
620         f = fopen(ProfileFile, "rb");
621         if (f == NULL) return;
622 
623         size = xfilelength(fileno(f));
624         EmbedBuffer = (LPBYTE) _cmsMalloc(size + 1);
625         EmbedLen = fread(EmbedBuffer, 1, size, f);
626         fclose(f);
627         EmbedBuffer[EmbedLen] = 0;
628 
629         write_icc_profile (&Compressor, EmbedBuffer, EmbedLen);
630         _cmsFree(EmbedBuffer);
631 }
632 
633 
634 
635 static
DoTransform(cmsHTRANSFORM hXForm)636 int DoTransform(cmsHTRANSFORM hXForm)
637 {
638     JSAMPROW ScanLineIn;
639     JSAMPROW ScanLineOut;
640 
641 
642        //Preserve resolution values from the original
643        // (Thanks to robert bergs for finding out this bug)
644 
645        Compressor.density_unit = Decompressor.density_unit;
646        Compressor.X_density    = Decompressor.X_density;
647        Compressor.Y_density    = Decompressor.Y_density;
648 
649       //  Compressor.write_JFIF_header = 1;
650 
651        jpeg_start_decompress(&Decompressor);
652        jpeg_start_compress(&Compressor, TRUE);
653 
654        // Embed the profile if needed
655        if (EmbedProfile && cOutProf)
656            DoEmbedProfile(cOutProf);
657 
658        ScanLineIn  = (JSAMPROW) _cmsMalloc(Decompressor.output_width * Decompressor.num_components);
659        ScanLineOut = (JSAMPROW) _cmsMalloc(Compressor.image_width * Compressor.num_components);
660 
661        while (Decompressor.output_scanline <
662                             Decompressor.output_height) {
663 
664        jpeg_read_scanlines(&Decompressor, &ScanLineIn, 1);
665 
666        cmsDoTransform(hXForm, ScanLineIn, ScanLineOut, Decompressor.output_width);
667 
668        jpeg_write_scanlines(&Compressor, &ScanLineOut, 1);
669 
670        }
671 
672        _cmsFree(ScanLineIn);
673        _cmsFree(ScanLineOut);
674 
675        jpeg_finish_decompress(&Decompressor);
676        jpeg_finish_compress(&Compressor);
677 
678        return TRUE;
679 }
680 
681 
682 
683 
684 static
SaveMemoryBlock(const BYTE * Buffer,DWORD dwLen,const char * Filename)685 void SaveMemoryBlock(const BYTE* Buffer, DWORD dwLen, const char* Filename)
686 {
687     FILE* out = fopen(Filename, "wb");
688     if (out == NULL)
689            FatalError("Cannot create '%s'", Filename);
690 
691     if (fwrite(Buffer, 1, dwLen, out) != dwLen)
692             FatalError("Cannot write %ld bytes to %s", dwLen, Filename);
693 
694     if (fclose(out) != 0)
695             FatalError("Error flushing file '%s'", Filename);
696 }
697 
698 
699 
700 // Transform one image
701 
702 static
TransformImage(char * cDefInpProf,char * cOutProf)703 int TransformImage(char *cDefInpProf, char *cOutProf)
704 {
705        cmsHPROFILE hIn, hOut, hProof;
706        cmsHTRANSFORM xform;
707        DWORD wInput, wOutput;
708        int OutputColorSpace;
709        DWORD dwFlags = 0;
710        DWORD EmbedLen;
711        LPBYTE EmbedBuffer;
712 
713 	   // Observer adaptation state (only meaningful on absolute colorimetric intent)
714 
715        cmsSetAdaptationState(ObserverAdaptationState);
716 
717 
718        if (BlackPointCompensation) {
719 
720             dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
721        }
722 
723 
724        if (PreserveBlack) {
725 			dwFlags |= cmsFLAGS_PRESERVEBLACK;
726 			if (PrecalcMode == 0) PrecalcMode = 1;
727             cmsSetCMYKPreservationStrategy(PreserveBlack-1);
728 	   }
729 
730 
731        switch (PrecalcMode) {
732 
733        case 0: dwFlags |= cmsFLAGS_NOTPRECALC; break;
734        case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
735        case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
736        default:;
737        }
738 
739 
740        if (GamutCheck)
741             dwFlags |= cmsFLAGS_GAMUTCHECK;
742 
743 
744         if (lIsDeviceLink) {
745 
746             hIn = cmsOpenProfileFromFile(cDefInpProf, "r");
747             hOut = NULL;
748             hProof = NULL;
749        }
750         else {
751 
752         if (!IgnoreEmbedded && read_icc_profile(&Decompressor, &EmbedBuffer, &EmbedLen))
753         {
754               hIn = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
755 
756                if (Verbose) {
757 
758                   fprintf(stdout, " (Embedded profile found)\n");
759                   fprintf(stdout, "Product name: %s\n", cmsTakeProductName(hIn));
760                   fprintf(stdout, "Description : %s\n", cmsTakeProductDesc(hIn));
761                   fflush(stdout);
762               }
763 
764                if (hIn != NULL && SaveEmbedded != NULL)
765                           SaveMemoryBlock(EmbedBuffer, EmbedLen, SaveEmbedded);
766 
767               _cmsFree(EmbedBuffer);
768         }
769         else
770         {
771                 hIn = OpenStockProfile(cDefInpProf);
772        }
773 
774         hOut = OpenStockProfile(cOutProf);
775 
776 
777        hProof = NULL;
778        if (cProofing != NULL) {
779 
780            hProof = OpenStockProfile(cProofing);
781            dwFlags |= cmsFLAGS_SOFTPROOFING;
782           }
783        }
784 
785        // Take input color space
786 
787        wInput = GetInputPixelType();
788 
789        // Assure both, input profile and input JPEG are on same colorspace
790 
791 
792        if (cmsGetColorSpace(hIn) != _cmsICCcolorSpace(T_COLORSPACE(wInput)))
793               FatalError("Input profile is not operating in proper color space");
794 
795 
796        // Output colorspace is given by output profile
797 
798         if (lIsDeviceLink) {
799             OutputColorSpace = T_COLORSPACE(wInput);
800         }
801         else {
802             OutputColorSpace = GetProfileColorSpace(hOut);
803         }
804 
805        jpeg_copy_critical_parameters(&Decompressor, &Compressor);
806 
807        WriteOutputFields(OutputColorSpace);
808 
809        wOutput      = ComputeOutputFormatDescriptor(wInput, OutputColorSpace);
810 
811        xform = cmsCreateProofingTransform(hIn, wInput,
812                                           hOut, wOutput,
813                                           hProof, Intent,
814                                           ProofingIntent, dwFlags);
815 
816        // Handle tile by tile or strip by strip strtok
817 
818        DoTransform(xform);
819 
820 
821        jcopy_markers_execute(&Decompressor, &Compressor);
822 
823        cmsDeleteTransform(xform);
824        cmsCloseProfile(hIn);
825        cmsCloseProfile(hOut);
826        if (hProof) cmsCloseProfile(hProof);
827 
828        return 1;
829 }
830 
831 
832 // Simply print help
833 
834 static
Help(int level)835 void Help(int level)
836 {
837      fprintf(stderr, "little cms ICC profile applier for JPEG - v2.1\n\n");
838 
839      switch(level) {
840 
841      default:
842      case 0:
843 
844      fprintf(stderr, "usage: jpegicc [flags] input.jpg output.jpg\n");
845 
846      fprintf(stderr, "\nflags:\n\n");
847      fprintf(stderr, "%cv - Verbose\n", SW);
848      fprintf(stderr, "%ci<profile> - Input profile (defaults to sRGB)\n", SW);
849      fprintf(stderr, "%co<profile> - Output profile (defaults to sRGB)\n", SW);
850      fprintf(stderr, "%ct<0,1,2,3> - Intent (0=Perceptual, 1=Colorimetric, 2=Saturation, 3=Absolute)\n", SW);
851 
852      fprintf(stderr, "\n");
853 
854      fprintf(stderr, "%cb - Black point compensation\n", SW);
855      fprintf(stderr, "%cf<n> - Preserve black (CMYK only) 0=off, 1=black ink only, 2=full K plane\n", SW);
856      fprintf(stderr, "%cn - Ignore embedded profile\n", SW);
857      fprintf(stderr, "%ce - Embed destination profile\n", SW);
858      fprintf(stderr, "%cs<new profile> - Save embedded profile as <new profile>\n", SW);
859 
860      fprintf(stderr, "\n");
861 
862      fprintf(stderr, "%cc<0,1,2,3> - Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes) [defaults to 1]\n", SW);
863      fprintf(stderr, "\n");
864 
865      fprintf(stderr, "%cp<profile> - Soft proof profile\n", SW);
866      fprintf(stderr, "%cm<0,1,2,3> - SoftProof intent\n", SW);
867      fprintf(stderr, "%cg - Marks out-of-gamut colors on softproof\n", SW);
868 
869      fprintf(stderr, "\n");
870      fprintf(stderr, "%cq<0..100> - Output JPEG quality\n", SW);
871 	 fprintf(stderr, "\n");
872 	 fprintf(stderr, "%cd<0..1> - Observer adaptation state (abs.col. only)\n", SW);
873 
874      fprintf(stderr, "\n");
875      fprintf(stderr, "%ch<0,1,2> - More help\n", SW);
876      break;
877 
878      case 1:
879 
880 
881      fprintf(stderr, "Examples:\n\n"
882                      "To color correct from scanner to sRGB:\n"
883                      "\tjpegicc %ciscanner.icm in.jpg out.jpg\n"
884                      "To convert from monitor1 to monitor2:\n"
885                      "\tjpegicc %cimon1.icm %comon2.icm in.jpg out.jpg\n"
886                      "To make a CMYK separation:\n"
887                      "\tjpegicc %coprinter.icm inrgb.jpg outcmyk.jpg\n"
888                      "To recover sRGB from a CMYK separation:\n"
889                      "\tjpegicc %ciprinter.icm incmyk.jpg outrgb.jpg\n"
890                      "To convert from CIELab ITU/Fax JPEG to sRGB\n"
891                      "\tjpegicc %ciitufax.icm in.jpg out.jpg\n\n",
892                      SW, SW, SW, SW, SW, SW);
893      break;
894 
895      case 2:
896 
897      fprintf(stderr, "This program is intended to be a demo of the little cms\n"
898                      "engine. Both lcms and this program are freeware. You can\n"
899                      "obtain both in source code at http://www.littlecms.com\n"
900                      "For suggestions, comments, bug reports etc. send mail to\n"
901                      "marti@littlecms.com\n\n");
902      break;
903      }
904 
905      exit(0);
906 }
907 
908 
909 // The toggles stuff
910 
911 static
HandleSwitches(int argc,char * argv[])912 void HandleSwitches(int argc, char *argv[])
913 {
914     int s;
915 
916 	while ((s=xgetopt(argc,argv,"bBnNvVGgh:H:i:I:o:O:P:p:t:T:c:C:Q:q:M:m:L:l:eEs:S:F:f:D:d:")) != EOF) {
917 
918         switch (s)
919         {
920 
921         case 'b':
922         case 'B':
923             BlackPointCompensation = TRUE;
924             break;
925 
926         case 'v':
927         case 'V':
928             Verbose = TRUE;
929             break;
930 
931         case 'i':
932         case 'I':
933 
934             if (lIsDeviceLink)
935                 FatalError("Device-link already specified");
936 
937             cInpProf = xoptarg;
938             break;
939 
940         case 'o':
941         case 'O':
942 
943             if (lIsDeviceLink)
944                 FatalError("Device-link already specified");
945 
946             cOutProf = xoptarg;
947             break;
948 
949         case 'l':
950         case 'L':
951             cInpProf = xoptarg;
952             lIsDeviceLink = TRUE;
953             break;
954 
955         case 'p':
956         case 'P':
957             cProofing = xoptarg;
958             break;
959 
960         case 't':
961         case 'T':
962             Intent = atoi(xoptarg);
963             if (Intent > 3) Intent = 3;
964             if (Intent < 0) Intent = 0;
965             break;
966 
967         case 'N':
968         case 'n':
969             IgnoreEmbedded = TRUE;
970             break;
971 
972         case 'e':
973         case 'E':
974             EmbedProfile = TRUE;
975             break;
976 
977        case 'f':
978 	   case 'F':
979 		    PreserveBlack = atoi(xoptarg);
980             if (PreserveBlack < 0 || PreserveBlack > 2)
981                     FatalError("Unknown PreserveBlack '%d'", PreserveBlack);
982 			break;
983 
984         case 'g':
985         case 'G':
986             GamutCheck = TRUE;
987             break;
988 
989         case 'c':
990         case 'C':
991             PrecalcMode = atoi(xoptarg);
992             if (PrecalcMode < 0 || PrecalcMode > 2)
993                 FatalError("Unknown precalc mode '%d'", PrecalcMode);
994             break;
995 
996         case 'H':
997         case 'h':  {
998 
999             int a =  atoi(xoptarg);
1000             Help(a);
1001                    }
1002             break;
1003 
1004         case 'q':
1005         case 'Q':
1006             jpegQuality = atoi(xoptarg);
1007             if (jpegQuality > 100) jpegQuality = 100;
1008             if (jpegQuality < 0)   jpegQuality = 0;
1009             break;
1010 
1011         case 'm':
1012         case 'M':
1013             ProofingIntent = atoi(xoptarg);
1014             if (ProofingIntent > 3) ProofingIntent = 3;
1015             if (ProofingIntent < 0) ProofingIntent = 0;
1016             break;
1017 
1018         case 's':
1019         case 'S': SaveEmbedded = xoptarg;
1020             break;
1021 
1022         case 'd':
1023         case 'D': ObserverAdaptationState = atof(xoptarg);
1024                  if (ObserverAdaptationState != 0 &&
1025                      ObserverAdaptationState != 1.0)
1026 					 fprintf(stderr, "Warning: Adaptation states other that 0 or 1 are not yet implemented\n");
1027                  break;
1028 
1029         default:
1030 
1031             FatalError("Unknown option - run without args to see valid ones");
1032         }
1033 
1034     }
1035 }
1036 
1037 
1038 // The main sink
1039 
main(int argc,char * argv[])1040 int main(int argc, char* argv[])
1041 {
1042       char *Intents[] = {"perceptual",
1043                          "relative colorimetric",
1044                          "saturation",
1045                          "absolute colorimetric" };
1046 
1047       HandleSwitches(argc, argv);
1048 
1049       cmsSetErrorHandler(MyErrorHandler);
1050 
1051       if ((argc - xoptind) != 2) {
1052 
1053               Help(0);
1054               }
1055 
1056 
1057 
1058       if (Verbose) {
1059 
1060           if (lIsDeviceLink)
1061             fprintf(stdout, "%s(device link) -> %s [%s]",
1062                                                 argv[xoptind],
1063                                                 argv[xoptind+1],
1064                                                 Intents[Intent]);
1065 
1066         else
1067             fprintf(stdout, "%s(%s) -> %s(%s) [%s]", argv[xoptind],
1068                                                 (cInpProf == NULL ? "sRGB": cInpProf),
1069                                                 argv[xoptind+1],
1070                                                 (cOutProf == NULL ? "sRGB" : cOutProf),
1071                                                 Intents[Intent]);
1072       }
1073 
1074       OpenInput(argv[xoptind]);
1075       OpenOutput(argv[xoptind+1]);
1076       TransformImage(cInpProf, cOutProf);
1077 
1078 
1079       if (Verbose) fprintf(stdout, "\n");
1080 
1081       Done();
1082 
1083       return 0;
1084 }
1085 
1086 
1087 
1088