1 //---------------------------------------------------------------------------------
3 // Little Color Management System
4 // Copyright (c) 1998-2010 Marti Maria Saguer
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 // This program does apply profiles to (some) JPEG files
34 static cmsBool BlackPointCompensation
= FALSE
;
35 static cmsBool IgnoreEmbedded
= FALSE
;
36 static cmsBool GamutCheck
= FALSE
;
37 static cmsBool lIsITUFax
= FALSE
;
38 static cmsBool lIsPhotoshopApp13
= FALSE
;
39 static cmsBool lIsEXIF
;
40 static cmsBool lIsDeviceLink
= FALSE
;
41 static cmsBool EmbedProfile
= FALSE
;
43 static const char* SaveEmbedded
= NULL
;
45 static int Intent
= INTENT_PERCEPTUAL
;
46 static int ProofingIntent
= INTENT_PERCEPTUAL
;
47 static int PrecalcMode
= 1;
49 static int jpegQuality
= 75;
51 static cmsFloat64Number ObserverAdaptationState
= 0;
54 static char *cInpProf
= NULL
;
55 static char *cOutProf
= NULL
;
56 static char *cProofing
= NULL
;
59 static FILE * OutFile
;
61 static struct jpeg_decompress_struct Decompressor
;
62 static struct jpeg_compress_struct Compressor
;
65 static struct my_error_mgr
{
67 struct jpeg_error_mgr pub
; // "public" fields
68 void* Cargo
; // "private" fields
73 cmsUInt16Number Alarm
[4] = {128,128,128,0};
77 void OutOfMem(size_t size
)
79 FatalError("Out of memory on allocating %d bytes.", size
);
84 void my_error_exit (j_common_ptr cinfo
)
86 char buffer
[JMSG_LENGTH_MAX
];
88 (*cinfo
->err
->format_message
) (cinfo
, buffer
);
93 Definition of the APPn Markers Defined for continuous-tone G3FAX
95 The application code APP1 initiates identification of the image as
96 a G3FAX application and defines the spatial resolution and subsampling.
97 This marker directly follows the SOI marker. The data format will be as follows:
99 X'FFE1' (APP1), length, FAX identifier, version, spatial resolution.
101 The above terms are defined as follows:
103 Length: (Two octets) Total APP1 field octet count including the octet count itself, but excluding the APP1
106 FAX identifier: (Six octets) X'47', X'33', X'46', X'41', X'58', X'00'. This X'00'-terminated string "G3FAX"
107 uniquely identifies this APP1 marker.
109 Version: (Two octets) X'07CA'. This string specifies the year of approval of the standard, for identification
110 in the case of future revision (for example, 1994).
112 Spatial Resolution: (Two octets) Lightness pixel density in pels/25.4 mm. The basic value is 200. Allowed values are
113 100, 200, 300, 400, 600 and 1200 pels/25.4 mm, with square (or equivalent) pels.
115 NOTE – The functional equivalence of inch-based and mm-based resolutions is maintained. For example, the 200 × 200
119 cmsBool
IsITUFax(jpeg_saved_marker_ptr ptr
)
123 if (ptr
-> marker
== (JPEG_APP0
+ 1) && ptr
-> data_length
> 5) {
125 const char* data
= (const char*) ptr
-> data
;
127 if (strcmp(data
, "G3FAX") == 0) return TRUE
;
136 // Save a ITU T.42/Fax marker with defaults on boundaries. This is the only mode we support right now.
138 void SetITUFax(j_compress_ptr cinfo
)
140 unsigned char Marker
[] = "G3FAX\x00\0x07\xCA\x00\xC8";
142 jpeg_write_marker(cinfo
, (JPEG_APP0
+ 1), Marker
, 10);
146 // Build a profile for decoding ITU T.42/Fax JPEG streams.
147 // The profile has an additional ability in the input direction of
148 // gamut compress values between 85 < a < -85 and -75 < b < 125. This conforms
149 // the default range for ITU/T.42 -- See RFC 2301, section 6.2.3 for details
156 // These functions does convert the encoding of ITUFAX to floating point
157 // and vice-versa. No gamut mapping is performed yet.
160 void ITU2Lab(const cmsUInt16Number In
[3], cmsCIELab
* Lab
)
162 Lab
-> L
= (double) In
[0] / 655.35;
163 Lab
-> a
= (double) 170.* (In
[1] - 32768.) / 65535.;
164 Lab
-> b
= (double) 200.* (In
[2] - 24576.) / 65535.;
168 void Lab2ITU(const cmsCIELab
* Lab
, cmsUInt16Number Out
[3])
170 Out
[0] = (cmsUInt16Number
) floor((double) (Lab
-> L
/ 100.)* 65535. );
171 Out
[1] = (cmsUInt16Number
) floor((double) (Lab
-> a
/ 170.)* 65535. + 32768. );
172 Out
[2] = (cmsUInt16Number
) floor((double) (Lab
-> b
/ 200.)* 65535. + 24576. );
175 // These are the samplers-- They are passed as callbacks to cmsStageSampleCLut16bit()
176 // then, cmsSample3DGrid() will sweel whole Lab gamut calling these functions
177 // once for each node. In[] will contain the Lab PCS value to convert to ITUFAX
178 // on PCS2ITU, or the ITUFAX value to convert to Lab in ITU2PCS
179 // You can change the number of sample points if desired, the algorithm will
180 // remain same. 33 points gives good accurancy, but you can reduce to 22 or less
181 // is space is critical
183 #define GRID_POINTS 33
186 int PCS2ITU(register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void* Cargo
)
190 cmsLabEncoded2Float(&Lab
, In
);
191 cmsDesaturateLab(&Lab
, 85, -85, 125, -75); // This function does the necessary gamut remapping
195 UTILS_UNUSED_PARAMETER(Cargo
);
200 int ITU2PCS( register const cmsUInt16Number In
[], register cmsUInt16Number Out
[], register void* Cargo
)
205 cmsFloat2LabEncoded(Out
, &Lab
);
208 UTILS_UNUSED_PARAMETER(Cargo
);
211 // This function does create the virtual input profile, which decodes ITU to the profile connection space
213 cmsHPROFILE
CreateITU2PCS_ICC(void)
215 cmsHPROFILE hProfile
;
219 AToB0
= cmsPipelineAlloc(0, 3, 3);
220 if (AToB0
== NULL
) return NULL
;
222 ColorMap
= cmsStageAllocCLut16bit(0, GRID_POINTS
, 3, 3, NULL
);
223 if (ColorMap
== NULL
) return NULL
;
225 cmsPipelineInsertStage(AToB0
, cmsAT_BEGIN
, ColorMap
);
226 cmsStageSampleCLut16bit(ColorMap
, ITU2PCS
, NULL
, 0);
228 hProfile
= cmsCreateProfilePlaceholder(0);
229 if (hProfile
== NULL
) {
230 cmsPipelineFree(AToB0
);
234 cmsWriteTag(hProfile
, cmsSigAToB0Tag
, AToB0
);
235 cmsSetColorSpace(hProfile
, cmsSigLabData
);
236 cmsSetPCS(hProfile
, cmsSigLabData
);
237 cmsSetDeviceClass(hProfile
, cmsSigColorSpaceClass
);
238 cmsPipelineFree(AToB0
);
244 // This function does create the virtual output profile, with the necessary gamut mapping
246 cmsHPROFILE
CreatePCS2ITU_ICC(void)
248 cmsHPROFILE hProfile
;
252 BToA0
= cmsPipelineAlloc(0, 3, 3);
253 if (BToA0
== NULL
) return NULL
;
255 ColorMap
= cmsStageAllocCLut16bit(0, GRID_POINTS
, 3, 3, NULL
);
256 if (ColorMap
== NULL
) return NULL
;
258 cmsPipelineInsertStage(BToA0
, cmsAT_BEGIN
, ColorMap
);
259 cmsStageSampleCLut16bit(ColorMap
, PCS2ITU
, NULL
, 0);
261 hProfile
= cmsCreateProfilePlaceholder(0);
262 if (hProfile
== NULL
) {
263 cmsPipelineFree(BToA0
);
267 cmsWriteTag(hProfile
, cmsSigBToA0Tag
, BToA0
);
268 cmsSetColorSpace(hProfile
, cmsSigLabData
);
269 cmsSetPCS(hProfile
, cmsSigLabData
);
270 cmsSetDeviceClass(hProfile
, cmsSigColorSpaceClass
);
272 cmsPipelineFree(BToA0
);
279 #define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))
282 cmsBool
ProcessPhotoshopAPP13(JOCTET FAR
*data
, int datalen
)
286 for (i
= 14; i
< datalen
; )
291 if (!(GETJOCTET(data
[i
] ) == 0x38 &&
292 GETJOCTET(data
[i
+1]) == 0x42 &&
293 GETJOCTET(data
[i
+2]) == 0x49 &&
294 GETJOCTET(data
[i
+3]) == 0x4D)) break; // Not recognized
296 i
+= 4; // identifying string
298 type
= (unsigned int) (GETJOCTET(data
[i
]<<8) + GETJOCTET(data
[i
+1]));
300 i
+= 2; // resource type
302 i
+= GETJOCTET(data
[i
]) + ((GETJOCTET(data
[i
]) & 1) ? 1 : 2); // resource name
304 len
= ((((GETJOCTET(data
[i
]<<8) + GETJOCTET(data
[i
+1]))<<8) +
305 GETJOCTET(data
[i
+2]))<<8) + GETJOCTET(data
[i
+3]);
309 if (type
== 0x03ED && len
>= 16) {
311 Decompressor
.X_density
= (UINT16
) PS_FIXED_TO_FLOAT(GETJOCTET(data
[i
]<<8) + GETJOCTET(data
[i
+1]),
312 GETJOCTET(data
[i
+2]<<8) + GETJOCTET(data
[i
+3]));
313 Decompressor
.Y_density
= (UINT16
) PS_FIXED_TO_FLOAT(GETJOCTET(data
[i
+8]<<8) + GETJOCTET(data
[i
+9]),
314 GETJOCTET(data
[i
+10]<<8) + GETJOCTET(data
[i
+11]));
316 // Set the density unit to 1 since the
317 // Vertical and Horizontal resolutions
318 // are specified in Pixels per inch
320 Decompressor
.density_unit
= 0x01;
325 i
+= len
+ ((len
& 1) ? 1 : 0); // Alignment
332 cmsBool
HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr
)
336 if (ptr
-> marker
== (JPEG_APP0
+ 13) && ptr
-> data_length
> 9)
338 JOCTET FAR
* data
= ptr
-> data
;
340 if(GETJOCTET(data
[0]) == 0x50 &&
341 GETJOCTET(data
[1]) == 0x68 &&
342 GETJOCTET(data
[2]) == 0x6F &&
343 GETJOCTET(data
[3]) == 0x74 &&
344 GETJOCTET(data
[4]) == 0x6F &&
345 GETJOCTET(data
[5]) == 0x73 &&
346 GETJOCTET(data
[6]) == 0x68 &&
347 GETJOCTET(data
[7]) == 0x6F &&
348 GETJOCTET(data
[8]) == 0x70) {
350 ProcessPhotoshopAPP13(data
, ptr
-> data_length
);
362 typedef unsigned short uint16_t;
363 typedef unsigned char uint8_t;
364 typedef unsigned int uint32_t;
366 #define INTEL_BYTE_ORDER 0x4949
367 #define XRESOLUTION 0x011a
368 #define YRESOLUTION 0x011b
369 #define RESOLUTION_UNIT 0x128
371 // Read a 16-bit word
373 uint16_t read16(uint8_t* arr
, int pos
, int swapBytes
)
375 uint8_t b1
= arr
[pos
];
376 uint8_t b2
= arr
[pos
+1];
378 return (swapBytes
) ? ((b2
<< 8) | b1
) : ((b1
<< 8) | b2
);
382 // Read a 32-bit word
384 uint32_t read32(uint8_t* arr
, int pos
, int swapBytes
)
389 return (arr
[pos
] << 24) |
404 int read_tag(uint8_t* arr
, int pos
, int swapBytes
, void* dest
)
406 // Format should be 5 over here (rational)
407 uint32_t format
= read16(arr
, pos
+ 2, swapBytes
);
408 // Components should be 1
409 uint32_t components
= read32(arr
, pos
+ 4, swapBytes
);
410 // Points to the value
414 if (components
!= 1) return 0;
419 offset
= read32(arr
, pos
+ 8, swapBytes
);
425 double num
= read32(arr
, offset
, swapBytes
);
426 double den
= read32(arr
, offset
+ 4, swapBytes
);
427 *(double *) dest
= num
/ den
;
432 *(int*) dest
= read16(arr
, offset
, swapBytes
);
443 // Handler for EXIF data
445 cmsBool
HandleEXIF(struct jpeg_decompress_struct
* cinfo
)
447 jpeg_saved_marker_ptr ptr
;
449 int pos
= 0, swapBytes
= 0;
450 uint32_t i
, numEntries
;
451 double XRes
= -1, YRes
= -1;
452 int Unit
= 2; // Inches
455 for (ptr
= cinfo
->marker_list
; ptr
; ptr
= ptr
->next
) {
457 if ((ptr
->marker
== JPEG_APP0
+1) && ptr
->data_length
> 6) {
458 JOCTET FAR
* data
= ptr
-> data
;
460 if (memcmp(data
, "Exif\0\0", 6) == 0) {
462 data
+= 6; // Skip EXIF marker
464 // 8 byte TIFF header
465 // first two determine byte order
467 if (read16(data
, pos
, 0) == INTEL_BYTE_ORDER
) {
473 // next two bytes are always 0x002A (TIFF version)
476 // offset to Image File Directory (includes the previous 8 bytes)
477 ifd_ofs
= read32(data
, pos
, swapBytes
);
479 // Search the directory for resolution tags
480 numEntries
= read16(data
, ifd_ofs
, swapBytes
);
482 for (i
=0; i
< numEntries
; i
++) {
484 uint32_t entryOffset
= ifd_ofs
+ 2 + (12 * i
);
485 uint32_t tag
= read16(data
, entryOffset
, swapBytes
);
489 case RESOLUTION_UNIT
:
490 if (!read_tag(data
, entryOffset
, swapBytes
, &Unit
)) return FALSE
;
494 if (!read_tag(data
, entryOffset
, swapBytes
, &XRes
)) return FALSE
;
498 if (!read_tag(data
, entryOffset
, swapBytes
, &YRes
)) return FALSE
;
506 // Proceed if all found
508 if (XRes
!= -1 && YRes
!= -1)
519 cinfo
->X_density
= (UINT16
) floor(XRes
+ 0.5);
520 cinfo
->Y_density
= (UINT16
) floor(YRes
+ 0.5);
525 cinfo
->X_density
= (UINT16
) floor(XRes
* 2.54 + 0.5);
526 cinfo
->Y_density
= (UINT16
) floor(YRes
* 2.54 + 0.5);
529 default: return FALSE
;
532 cinfo
->density_unit
= 1; /* 1 for dots/inch, or 2 for dots/cm.*/
545 cmsBool
OpenInput(const char* FileName
)
550 InFile
= fopen(FileName
, "rb");
551 if (InFile
== NULL
) {
552 FatalError("Cannot open '%s'", FileName
);
555 // Now we can initialize the JPEG decompression object.
556 Decompressor
.err
= jpeg_std_error(&ErrorHandler
.pub
);
557 ErrorHandler
.pub
.error_exit
= my_error_exit
;
558 ErrorHandler
.pub
.output_message
= my_error_exit
;
560 jpeg_create_decompress(&Decompressor
);
561 jpeg_stdio_src(&Decompressor
, InFile
);
563 for (m
= 0; m
< 16; m
++)
564 jpeg_save_markers(&Decompressor
, JPEG_APP0
+ m
, 0xFFFF);
566 // setup_read_icc_profile(&Decompressor);
568 fseek(InFile
, 0, SEEK_SET
);
569 jpeg_read_header(&Decompressor
, TRUE
);
576 cmsBool
OpenOutput(const char* FileName
)
579 OutFile
= fopen(FileName
, "wb");
580 if (OutFile
== NULL
) {
581 FatalError("Cannot create '%s'", FileName
);
585 Compressor
.err
= jpeg_std_error(&ErrorHandler
.pub
);
586 ErrorHandler
.pub
.error_exit
= my_error_exit
;
587 ErrorHandler
.pub
.output_message
= my_error_exit
;
589 Compressor
.input_components
= Compressor
.num_components
= 4;
591 jpeg_create_compress(&Compressor
);
592 jpeg_stdio_dest(&Compressor
, OutFile
);
599 jpeg_destroy_decompress(&Decompressor
);
600 jpeg_destroy_compress(&Compressor
);
601 return fclose(InFile
) + fclose(OutFile
);
606 // Build up the pixeltype descriptor
609 cmsUInt32Number
GetInputPixelType(void)
611 int space
, bps
, extra
, ColorChannels
, Flavor
;
613 lIsITUFax
= IsITUFax(Decompressor
.marker_list
);
614 lIsPhotoshopApp13
= HandlePhotoshopAPP13(Decompressor
.marker_list
);
615 lIsEXIF
= HandleEXIF(&Decompressor
);
617 ColorChannels
= Decompressor
.num_components
;
618 extra
= 0; // Alpha = None
620 Flavor
= 0; // Vanilla
625 Decompressor
.out_color_space
= JCS_YCbCr
; // Fake to don't touch
628 switch (Decompressor
.jpeg_color_space
) {
630 case JCS_GRAYSCALE
: // monochrome
632 Decompressor
.out_color_space
= JCS_GRAYSCALE
;
635 case JCS_RGB
: // red/green/blue
637 Decompressor
.out_color_space
= JCS_RGB
;
640 case JCS_YCbCr
: // Y/Cb/Cr (also known as YUV)
641 space
= PT_RGB
; // Let IJG code to do the conversion
642 Decompressor
.out_color_space
= JCS_RGB
;
645 case JCS_CMYK
: // C/M/Y/K
647 Decompressor
.out_color_space
= JCS_CMYK
;
648 if (Decompressor
.saw_Adobe_marker
) // Adobe keeps CMYK inverted, so change flavor
649 Flavor
= 1; // from vanilla to chocolate
652 case JCS_YCCK
: // Y/Cb/Cr/K
654 Decompressor
.out_color_space
= JCS_CMYK
;
655 if (Decompressor
.saw_Adobe_marker
) // ditto
660 FatalError("Unsupported color space (0x%x)", Decompressor
.jpeg_color_space
);
664 return (EXTRA_SH(extra
)|CHANNELS_SH(ColorChannels
)|BYTES_SH(bps
)|COLORSPACE_SH(space
)|FLAVOR_SH(Flavor
));
668 // Rearrange pixel type to build output descriptor
670 cmsUInt32Number
ComputeOutputFormatDescriptor(cmsUInt32Number dwInput
, int OutColorSpace
)
672 int IsPlanar
= T_PLANAR(dwInput
);
676 switch (OutColorSpace
) {
690 if (Compressor
.write_Adobe_marker
) // Adobe keeps CMYK inverted, so change flavor to chocolate
695 FatalError("Unsupported output color space");
698 return (COLORSPACE_SH(OutColorSpace
)|PLANAR_SH(IsPlanar
)|CHANNELS_SH(Channels
)|BYTES_SH(1)|FLAVOR_SH(Flavor
));
702 // Equivalence between ICC color spaces and lcms color spaces
704 int GetProfileColorSpace(cmsHPROFILE hProfile
)
706 cmsColorSpaceSignature ProfileSpace
= cmsGetColorSpace(hProfile
);
708 return _cmsLCMScolorSpace(ProfileSpace
);
712 int GetDevicelinkColorSpace(cmsHPROFILE hProfile
)
714 cmsColorSpaceSignature ProfileSpace
= cmsGetPCS(hProfile
);
716 return _cmsLCMScolorSpace(ProfileSpace
);
723 void jcopy_markers_execute(j_decompress_ptr srcinfo
, j_compress_ptr dstinfo
)
725 jpeg_saved_marker_ptr marker
;
727 /* In the current implementation, we don't actually need to examine the
728 * option flag here; we just copy everything that got saved.
729 * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
730 * if the encoder library already wrote one.
732 for (marker
= srcinfo
->marker_list
; marker
!= NULL
; marker
= marker
->next
) {
734 if (dstinfo
->write_JFIF_header
&&
735 marker
->marker
== JPEG_APP0
&&
736 marker
->data_length
>= 5 &&
737 GETJOCTET(marker
->data
[0]) == 0x4A &&
738 GETJOCTET(marker
->data
[1]) == 0x46 &&
739 GETJOCTET(marker
->data
[2]) == 0x49 &&
740 GETJOCTET(marker
->data
[3]) == 0x46 &&
741 GETJOCTET(marker
->data
[4]) == 0)
742 continue; /* reject duplicate JFIF */
744 if (dstinfo
->write_Adobe_marker
&&
745 marker
->marker
== JPEG_APP0
+14 &&
746 marker
->data_length
>= 5 &&
747 GETJOCTET(marker
->data
[0]) == 0x41 &&
748 GETJOCTET(marker
->data
[1]) == 0x64 &&
749 GETJOCTET(marker
->data
[2]) == 0x6F &&
750 GETJOCTET(marker
->data
[3]) == 0x62 &&
751 GETJOCTET(marker
->data
[4]) == 0x65)
752 continue; /* reject duplicate Adobe */
754 jpeg_write_marker(dstinfo
, marker
->marker
,
755 marker
->data
, marker
->data_length
);
760 void WriteOutputFields(int OutputColorSpace
)
762 J_COLOR_SPACE in_space
, jpeg_space
;
765 switch (OutputColorSpace
) {
767 case PT_GRAY
: in_space
= jpeg_space
= JCS_GRAYSCALE
;
771 case PT_RGB
: in_space
= JCS_RGB
;
772 jpeg_space
= JCS_YCbCr
;
774 break; // red/green/blue
776 case PT_YCbCr
: in_space
= jpeg_space
= JCS_YCbCr
;
778 break; // Y/Cb/Cr (also known as YUV)
780 case PT_CMYK
: in_space
= JCS_CMYK
;
781 jpeg_space
= JCS_YCCK
;
783 break; // C/M/Y/components
785 case PT_Lab
: in_space
= jpeg_space
= JCS_YCbCr
;
787 break; // Fake to don't touch
789 FatalError("Unsupported output color space");
794 if (jpegQuality
>= 100) {
796 // avoid destructive conversion when asking for lossless compression
797 jpeg_space
= in_space
;
800 Compressor
.in_color_space
= in_space
;
801 Compressor
.jpeg_color_space
= jpeg_space
;
802 Compressor
.input_components
= Compressor
.num_components
= components
;
803 jpeg_set_defaults(&Compressor
);
804 jpeg_set_colorspace(&Compressor
, jpeg_space
);
807 // Make sure to pass resolution through
808 if (OutputColorSpace
== PT_CMYK
)
809 Compressor
.write_JFIF_header
= 1;
811 // Avoid subsampling on high quality factor
812 jpeg_set_quality(&Compressor
, jpegQuality
, 1);
813 if (jpegQuality
>= 70) {
816 for(i
=0; i
< Compressor
.num_components
; i
++) {
818 Compressor
.comp_info
[i
].h_samp_factor
= 1;
819 Compressor
.comp_info
[i
].v_samp_factor
= 1;
828 void DoEmbedProfile(const char* ProfileFile
)
831 size_t size
, EmbedLen
;
832 cmsUInt8Number
* EmbedBuffer
;
834 f
= fopen(ProfileFile
, "rb");
835 if (f
== NULL
) return;
837 size
= cmsfilelength(f
);
838 EmbedBuffer
= (cmsUInt8Number
*) malloc(size
+ 1);
839 EmbedLen
= fread(EmbedBuffer
, 1, size
, f
);
841 EmbedBuffer
[EmbedLen
] = 0;
843 write_icc_profile (&Compressor
, EmbedBuffer
, EmbedLen
);
850 int DoTransform(cmsHTRANSFORM hXForm
, int OutputColorSpace
)
853 JSAMPROW ScanLineOut
;
856 //Preserve resolution values from the original
857 // (Thanks to Robert Bergs for finding out this bug)
858 Compressor
.density_unit
= Decompressor
.density_unit
;
859 Compressor
.X_density
= Decompressor
.X_density
;
860 Compressor
.Y_density
= Decompressor
.Y_density
;
862 // Compressor.write_JFIF_header = 1;
864 jpeg_start_decompress(&Decompressor
);
865 jpeg_start_compress(&Compressor
, TRUE
);
867 if (OutputColorSpace
== PT_Lab
)
868 SetITUFax(&Compressor
);
870 // Embed the profile if needed
871 if (EmbedProfile
&& cOutProf
)
872 DoEmbedProfile(cOutProf
);
874 ScanLineIn
= (JSAMPROW
) malloc(Decompressor
.output_width
* Decompressor
.num_components
);
875 ScanLineOut
= (JSAMPROW
) malloc(Compressor
.image_width
* Compressor
.num_components
);
877 while (Decompressor
.output_scanline
<
878 Decompressor
.output_height
) {
880 jpeg_read_scanlines(&Decompressor
, &ScanLineIn
, 1);
882 cmsDoTransform(hXForm
, ScanLineIn
, ScanLineOut
, Decompressor
.output_width
);
884 jpeg_write_scanlines(&Compressor
, &ScanLineOut
, 1);
890 jpeg_finish_decompress(&Decompressor
);
891 jpeg_finish_compress(&Compressor
);
898 // Transform one image
901 int TransformImage(char *cDefInpProf
, char *cOutProf
)
903 cmsHPROFILE hIn
, hOut
, hProof
;
905 cmsUInt32Number wInput
, wOutput
;
906 int OutputColorSpace
;
907 cmsUInt32Number dwFlags
= 0;
908 cmsUInt32Number EmbedLen
;
909 cmsUInt8Number
* EmbedBuffer
;
912 cmsSetAdaptationState(ObserverAdaptationState
);
914 if (BlackPointCompensation
) {
916 dwFlags
|= cmsFLAGS_BLACKPOINTCOMPENSATION
;
920 switch (PrecalcMode
) {
922 case 0: dwFlags
|= cmsFLAGS_NOOPTIMIZE
; break;
923 case 2: dwFlags
|= cmsFLAGS_HIGHRESPRECALC
; break;
924 case 3: dwFlags
|= cmsFLAGS_LOWRESPRECALC
; break;
930 dwFlags
|= cmsFLAGS_GAMUTCHECK
;
931 cmsSetAlarmCodes(Alarm
);
934 // Take input color space
935 wInput
= GetInputPixelType();
939 hIn
= cmsOpenProfileFromFile(cDefInpProf
, "r");
945 if (!IgnoreEmbedded
&& read_icc_profile(&Decompressor
, &EmbedBuffer
, &EmbedLen
))
947 hIn
= cmsOpenProfileFromMem(EmbedBuffer
, EmbedLen
);
951 fprintf(stdout
, " (Embedded profile found)\n");
952 PrintProfileInformation(hIn
);
956 if (hIn
!= NULL
&& SaveEmbedded
!= NULL
)
957 SaveMemoryBlock(EmbedBuffer
, EmbedLen
, SaveEmbedded
);
963 // Default for ITU/Fax
964 if (cDefInpProf
== NULL
&& T_COLORSPACE(wInput
) == PT_Lab
)
965 cDefInpProf
= "*Lab";
967 if (cDefInpProf
!= NULL
&& cmsstrcasecmp(cDefInpProf
, "*lab") == 0)
968 hIn
= CreateITU2PCS_ICC();
970 hIn
= OpenStockProfile(0, cDefInpProf
);
973 if (cOutProf
!= NULL
&& cmsstrcasecmp(cOutProf
, "*lab") == 0)
974 hOut
= CreatePCS2ITU_ICC();
976 hOut
= OpenStockProfile(0, cOutProf
);
979 if (cProofing
!= NULL
) {
981 hProof
= OpenStockProfile(0, cProofing
);
982 if (hProof
== NULL
) {
983 FatalError("Proofing profile couldn't be read.");
985 dwFlags
|= cmsFLAGS_SOFTPROOFING
;
990 FatalError("Input profile couldn't be read.");
992 FatalError("Output profile couldn't be read.");
994 // Assure both, input profile and input JPEG are on same colorspace
995 if (cmsGetColorSpace(hIn
) != _cmsICCcolorSpace(T_COLORSPACE(wInput
)))
996 FatalError("Input profile is not operating in proper color space");
999 // Output colorspace is given by output profile
1001 if (lIsDeviceLink
) {
1002 OutputColorSpace
= GetDevicelinkColorSpace(hIn
);
1005 OutputColorSpace
= GetProfileColorSpace(hOut
);
1008 jpeg_copy_critical_parameters(&Decompressor
, &Compressor
);
1010 WriteOutputFields(OutputColorSpace
);
1012 wOutput
= ComputeOutputFormatDescriptor(wInput
, OutputColorSpace
);
1015 xform
= cmsCreateProofingTransform(hIn
, wInput
,
1018 ProofingIntent
, dwFlags
);
1020 FatalError("Cannot transform by using the profiles");
1022 DoTransform(xform
, OutputColorSpace
);
1025 jcopy_markers_execute(&Decompressor
, &Compressor
);
1027 cmsDeleteTransform(xform
);
1028 cmsCloseProfile(hIn
);
1029 cmsCloseProfile(hOut
);
1030 if (hProof
) cmsCloseProfile(hProof
);
1036 // Simply print help
1039 void Help(int level
)
1041 fprintf(stderr
, "little cms ICC profile applier for JPEG - v3.2 [LittleCMS %2.2f]\n\n", LCMS_VERSION
/ 1000.0);
1048 fprintf(stderr
, "usage: jpgicc [flags] input.jpg output.jpg\n");
1050 fprintf(stderr
, "\nflags:\n\n");
1051 fprintf(stderr
, "%cv - Verbose\n", SW
);
1052 fprintf(stderr
, "%ci<profile> - Input profile (defaults to sRGB)\n", SW
);
1053 fprintf(stderr
, "%co<profile> - Output profile (defaults to sRGB)\n", SW
);
1055 PrintRenderingIntents();
1058 fprintf(stderr
, "%cb - Black point compensation\n", SW
);
1059 fprintf(stderr
, "%cd<0..1> - Observer adaptation state (abs.col. only)\n", SW
);
1060 fprintf(stderr
, "%cn - Ignore embedded profile\n", SW
);
1061 fprintf(stderr
, "%ce - Embed destination profile\n", SW
);
1062 fprintf(stderr
, "%cs<new profile> - Save embedded profile as <new profile>\n", SW
);
1064 fprintf(stderr
, "\n");
1066 fprintf(stderr
, "%cc<0,1,2,3> - Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes) [defaults to 1]\n", SW
);
1067 fprintf(stderr
, "\n");
1069 fprintf(stderr
, "%cp<profile> - Soft proof profile\n", SW
);
1070 fprintf(stderr
, "%cm<0,1,2,3> - SoftProof intent\n", SW
);
1071 fprintf(stderr
, "%cg - Marks out-of-gamut colors on softproof\n", SW
);
1072 fprintf(stderr
, "%c!<r>,<g>,<b> - Out-of-gamut marker channel values\n", SW
);
1074 fprintf(stderr
, "\n");
1075 fprintf(stderr
, "%cq<0..100> - Output JPEG quality\n", SW
);
1077 fprintf(stderr
, "\n");
1078 fprintf(stderr
, "%ch<0,1,2,3> - More help\n", SW
);
1083 fprintf(stderr
, "Examples:\n\n"
1084 "To color correct from scanner to sRGB:\n"
1085 "\tjpgicc %ciscanner.icm in.jpg out.jpg\n"
1086 "To convert from monitor1 to monitor2:\n"
1087 "\tjpgicc %cimon1.icm %comon2.icm in.jpg out.jpg\n"
1088 "To make a CMYK separation:\n"
1089 "\tjpgicc %coprinter.icm inrgb.jpg outcmyk.jpg\n"
1090 "To recover sRGB from a CMYK separation:\n"
1091 "\tjpgicc %ciprinter.icm incmyk.jpg outrgb.jpg\n"
1092 "To convert from CIELab ITU/Fax JPEG to sRGB\n"
1093 "\tjpgicc in.jpg out.jpg\n\n",
1094 SW
, SW
, SW
, SW
, SW
);
1103 fprintf(stderr
, "This program is intended to be a demo of the little cms\n"
1104 "engine. Both lcms and this program are freeware. You can\n"
1105 "obtain both in source code at http://www.littlecms.com\n"
1106 "For suggestions, comments, bug reports etc. send mail to\n"
1107 "marti@littlecms.com\n\n");
1115 // The toggles stuff
1118 void HandleSwitches(int argc
, char *argv
[])
1122 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:!:D:d:")) != EOF
) {
1129 BlackPointCompensation
= TRUE
;
1133 case 'D': ObserverAdaptationState
= atof(xoptarg
);
1134 if (ObserverAdaptationState
< 0 ||
1135 ObserverAdaptationState
> 1.0)
1136 FatalError("Adaptation state should be 0..1");
1147 FatalError("Device-link already specified");
1155 FatalError("Device-link already specified");
1162 if (cInpProf
!= NULL
|| cOutProf
!= NULL
)
1163 FatalError("input/output profiles already specified");
1166 lIsDeviceLink
= TRUE
;
1171 cProofing
= xoptarg
;
1176 Intent
= atoi(xoptarg
);
1181 IgnoreEmbedded
= TRUE
;
1186 EmbedProfile
= TRUE
;
1197 PrecalcMode
= atoi(xoptarg
);
1198 if (PrecalcMode
< 0 || PrecalcMode
> 2)
1199 FatalError("Unknown precalc mode '%d'", PrecalcMode
);
1205 int a
= atoi(xoptarg
);
1212 jpegQuality
= atoi(xoptarg
);
1213 if (jpegQuality
> 100) jpegQuality
= 100;
1214 if (jpegQuality
< 0) jpegQuality
= 0;
1219 ProofingIntent
= atoi(xoptarg
);
1223 case 'S': SaveEmbedded
= xoptarg
;
1227 if (sscanf(xoptarg
, "%hu,%hu,%hu", &Alarm
[0], &Alarm
[1], &Alarm
[2]) == 3) {
1229 for (i
=0; i
< 3; i
++) {
1230 Alarm
[i
] = (Alarm
[i
] << 8) | Alarm
[i
];
1237 FatalError("Unknown option - run without args to see valid ones");
1244 int main(int argc
, char* argv
[])
1246 InitUtils("jpgicc");
1248 HandleSwitches(argc
, argv
);
1250 if ((argc
- xoptind
) != 2) {
1254 OpenInput(argv
[xoptind
]);
1255 OpenOutput(argv
[xoptind
+1]);
1257 TransformImage(cInpProf
, cOutProf
);
1260 if (Verbose
) { fprintf(stdout
, "\n"); fflush(stdout
); }