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