Test initialisation of MUIA_List_AdjustWidth and MUIA_List_AdjustHeight, and
[AROS.git] / workbench / libs / lcms2 / utils / jpgicc / jpgicc.c
blob3b18b540b77798a4988c6c4680ad008699ddc97e
1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2010 Marti Maria Saguer
5 //
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
28 #include "utils.h"
30 #include "jpeglib.h"
31 #include "iccjpeg.h"
33 // Flags
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;
58 static FILE * InFile;
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
70 } ErrorHandler;
73 cmsUInt16Number Alarm[4] = {128,128,128,0};
75 // Out of mem
76 static
77 void OutOfMem(size_t size)
79 FatalError("Out of memory on allocating %d bytes.", size);
83 static
84 void my_error_exit (j_common_ptr cinfo)
86 char buffer[JMSG_LENGTH_MAX];
88 (*cinfo->err->format_message) (cinfo, buffer);
89 FatalError(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
104 marker.
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
118 static
119 cmsBool IsITUFax(jpeg_saved_marker_ptr ptr)
121 while (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;
130 ptr = ptr -> next;
133 return FALSE;
136 // Save a ITU T.42/Fax marker with defaults on boundaries. This is the only mode we support right now.
137 static
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
151 // L* = [0, 100]
152 // a* = [–85, 85]
153 // b* = [–75, 125]
156 // These functions does convert the encoding of ITUFAX to floating point
157 // and vice-versa. No gamut mapping is performed yet.
159 static
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.;
167 static
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
185 static
186 int PCS2ITU(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
188 cmsCIELab Lab;
190 cmsLabEncoded2Float(&Lab, In);
191 cmsDesaturateLab(&Lab, 85, -85, 125, -75); // This function does the necessary gamut remapping
192 Lab2ITU(&Lab, Out);
193 return TRUE;
195 UTILS_UNUSED_PARAMETER(Cargo);
199 static
200 int ITU2PCS( register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
202 cmsCIELab Lab;
204 ITU2Lab(In, &Lab);
205 cmsFloat2LabEncoded(Out, &Lab);
206 return TRUE;
208 UTILS_UNUSED_PARAMETER(Cargo);
211 // This function does create the virtual input profile, which decodes ITU to the profile connection space
212 static
213 cmsHPROFILE CreateITU2PCS_ICC(void)
215 cmsHPROFILE hProfile;
216 cmsPipeline* AToB0;
217 cmsStage* ColorMap;
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);
231 return NULL;
234 cmsWriteTag(hProfile, cmsSigAToB0Tag, AToB0);
235 cmsSetColorSpace(hProfile, cmsSigLabData);
236 cmsSetPCS(hProfile, cmsSigLabData);
237 cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass);
238 cmsPipelineFree(AToB0);
240 return hProfile;
244 // This function does create the virtual output profile, with the necessary gamut mapping
245 static
246 cmsHPROFILE CreatePCS2ITU_ICC(void)
248 cmsHPROFILE hProfile;
249 cmsPipeline* BToA0;
250 cmsStage* ColorMap;
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);
264 return NULL;
267 cmsWriteTag(hProfile, cmsSigBToA0Tag, BToA0);
268 cmsSetColorSpace(hProfile, cmsSigLabData);
269 cmsSetPCS(hProfile, cmsSigLabData);
270 cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass);
272 cmsPipelineFree(BToA0);
274 return hProfile;
279 #define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))
281 static
282 cmsBool ProcessPhotoshopAPP13(JOCTET FAR *data, int datalen)
284 int i;
286 for (i = 14; i < datalen; )
288 long len;
289 unsigned int type;
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]);
307 i += 4; // Size
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;
321 return TRUE;
325 i += len + ((len & 1) ? 1 : 0); // Alignment
327 return FALSE;
331 static
332 cmsBool HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
334 while (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);
351 return TRUE;
355 ptr = ptr -> next;
358 return FALSE;
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
372 static
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
383 static
384 uint32_t read32(uint8_t* arr, int pos, int swapBytes)
387 if(!swapBytes) {
389 return (arr[pos] << 24) |
390 (arr[pos+1] << 16) |
391 (arr[pos+2] << 8) |
392 arr[pos+3];
395 return arr[pos] |
396 (arr[pos+1] << 8) |
397 (arr[pos+2] << 16) |
398 (arr[pos+3] << 24);
403 static
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
411 uint32_t offset;
413 // sanity
414 if (components != 1) return 0;
416 if (format == 3)
417 offset = pos + 8;
418 else
419 offset = read32(arr, pos + 8, swapBytes);
421 switch (format) {
423 case 5: // Rational
425 double num = read32(arr, offset, swapBytes);
426 double den = read32(arr, offset + 4, swapBytes);
427 *(double *) dest = num / den;
429 break;
431 case 3: // uint 16
432 *(int*) dest = read16(arr, offset, swapBytes);
433 break;
435 default: return 0;
438 return 1;
443 // Handler for EXIF data
444 static
445 cmsBool HandleEXIF(struct jpeg_decompress_struct* cinfo)
447 jpeg_saved_marker_ptr ptr;
448 uint32_t ifd_ofs;
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
466 pos = 0;
467 if (read16(data, pos, 0) == INTEL_BYTE_ORDER) {
468 swapBytes = 1;
471 pos += 2;
473 // next two bytes are always 0x002A (TIFF version)
474 pos += 2;
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);
487 switch (tag) {
489 case RESOLUTION_UNIT:
490 if (!read_tag(data, entryOffset, swapBytes, &Unit)) return FALSE;
491 break;
493 case XRESOLUTION:
494 if (!read_tag(data, entryOffset, swapBytes, &XRes)) return FALSE;
495 break;
497 case YRESOLUTION:
498 if (!read_tag(data, entryOffset, swapBytes, &YRes)) return FALSE;
499 break;
501 default:;
506 // Proceed if all found
508 if (XRes != -1 && YRes != -1)
511 // 1 = None
512 // 2 = inches
513 // 3 = cm
515 switch (Unit) {
517 case 2:
519 cinfo ->X_density = (UINT16) floor(XRes + 0.5);
520 cinfo ->Y_density = (UINT16) floor(YRes + 0.5);
521 break;
523 case 1:
525 cinfo ->X_density = (UINT16) floor(XRes * 2.54 + 0.5);
526 cinfo ->Y_density = (UINT16) floor(YRes * 2.54 + 0.5);
527 break;
529 default: return FALSE;
532 cinfo ->density_unit = 1; /* 1 for dots/inch, or 2 for dots/cm.*/
540 return FALSE;
544 static
545 cmsBool OpenInput(const char* FileName)
547 int m;
549 lIsITUFax = FALSE;
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);
571 return TRUE;
575 static
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);
593 return TRUE;
596 static
597 cmsBool Done(void)
599 jpeg_destroy_decompress(&Decompressor);
600 jpeg_destroy_compress(&Compressor);
601 return fclose(InFile) + fclose(OutFile);
606 // Build up the pixeltype descriptor
608 static
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
619 bps = 1; // 8 bits
620 Flavor = 0; // Vanilla
622 if (lIsITUFax) {
624 space = PT_Lab;
625 Decompressor.out_color_space = JCS_YCbCr; // Fake to don't touch
627 else
628 switch (Decompressor.jpeg_color_space) {
630 case JCS_GRAYSCALE: // monochrome
631 space = PT_GRAY;
632 Decompressor.out_color_space = JCS_GRAYSCALE;
633 break;
635 case JCS_RGB: // red/green/blue
636 space = PT_RGB;
637 Decompressor.out_color_space = JCS_RGB;
638 break;
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;
643 break;
645 case JCS_CMYK: // C/M/Y/K
646 space = PT_CMYK;
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
650 break;
652 case JCS_YCCK: // Y/Cb/Cr/K
653 space = PT_CMYK;
654 Decompressor.out_color_space = JCS_CMYK;
655 if (Decompressor.saw_Adobe_marker) // ditto
656 Flavor = 1;
657 break;
659 default:
660 FatalError("Unsupported color space (0x%x)", Decompressor.jpeg_color_space);
661 return 0;
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
669 static
670 cmsUInt32Number ComputeOutputFormatDescriptor(cmsUInt32Number dwInput, int OutColorSpace)
672 int IsPlanar = T_PLANAR(dwInput);
673 int Channels = 0;
674 int Flavor = 0;
676 switch (OutColorSpace) {
678 case PT_GRAY:
679 Channels = 1;
680 break;
681 case PT_RGB:
682 case PT_CMY:
683 case PT_Lab:
684 case PT_YUV:
685 case PT_YCbCr:
686 Channels = 3;
687 break;
689 case PT_CMYK:
690 if (Compressor.write_Adobe_marker) // Adobe keeps CMYK inverted, so change flavor to chocolate
691 Flavor = 1;
692 Channels = 4;
693 break;
694 default:
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
703 static
704 int GetProfileColorSpace(cmsHPROFILE hProfile)
706 cmsColorSpaceSignature ProfileSpace = cmsGetColorSpace(hProfile);
708 return _cmsLCMScolorSpace(ProfileSpace);
711 static
712 int GetDevicelinkColorSpace(cmsHPROFILE hProfile)
714 cmsColorSpaceSignature ProfileSpace = cmsGetPCS(hProfile);
716 return _cmsLCMScolorSpace(ProfileSpace);
720 // From TRANSUPP
722 static
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);
759 static
760 void WriteOutputFields(int OutputColorSpace)
762 J_COLOR_SPACE in_space, jpeg_space;
763 int components;
765 switch (OutputColorSpace) {
767 case PT_GRAY: in_space = jpeg_space = JCS_GRAYSCALE;
768 components = 1;
769 break;
771 case PT_RGB: in_space = JCS_RGB;
772 jpeg_space = JCS_YCbCr;
773 components = 3;
774 break; // red/green/blue
776 case PT_YCbCr: in_space = jpeg_space = JCS_YCbCr;
777 components = 3;
778 break; // Y/Cb/Cr (also known as YUV)
780 case PT_CMYK: in_space = JCS_CMYK;
781 jpeg_space = JCS_YCCK;
782 components = 4;
783 break; // C/M/Y/components
785 case PT_Lab: in_space = jpeg_space = JCS_YCbCr;
786 components = 3;
787 break; // Fake to don't touch
788 default:
789 FatalError("Unsupported output color space");
790 return;
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) {
815 int i;
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;
827 static
828 void DoEmbedProfile(const char* ProfileFile)
830 FILE* f;
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);
840 fclose(f);
841 EmbedBuffer[EmbedLen] = 0;
843 write_icc_profile (&Compressor, EmbedBuffer, EmbedLen);
844 free(EmbedBuffer);
849 static
850 int DoTransform(cmsHTRANSFORM hXForm, int OutputColorSpace)
852 JSAMPROW ScanLineIn;
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);
887 free(ScanLineIn);
888 free(ScanLineOut);
890 jpeg_finish_decompress(&Decompressor);
891 jpeg_finish_compress(&Compressor);
893 return TRUE;
898 // Transform one image
900 static
901 int TransformImage(char *cDefInpProf, char *cOutProf)
903 cmsHPROFILE hIn, hOut, hProof;
904 cmsHTRANSFORM xform;
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;
925 default:;
929 if (GamutCheck) {
930 dwFlags |= cmsFLAGS_GAMUTCHECK;
931 cmsSetAlarmCodes(Alarm);
934 // Take input color space
935 wInput = GetInputPixelType();
937 if (lIsDeviceLink) {
939 hIn = cmsOpenProfileFromFile(cDefInpProf, "r");
940 hOut = NULL;
941 hProof = NULL;
943 else {
945 if (!IgnoreEmbedded && read_icc_profile(&Decompressor, &EmbedBuffer, &EmbedLen))
947 hIn = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
949 if (Verbose) {
951 fprintf(stdout, " (Embedded profile found)\n");
952 PrintProfileInformation(hIn);
953 fflush(stdout);
956 if (hIn != NULL && SaveEmbedded != NULL)
957 SaveMemoryBlock(EmbedBuffer, EmbedLen, SaveEmbedded);
959 free(EmbedBuffer);
961 else
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();
969 else
970 hIn = OpenStockProfile(0, cDefInpProf);
973 if (cOutProf != NULL && cmsstrcasecmp(cOutProf, "*lab") == 0)
974 hOut = CreatePCS2ITU_ICC();
975 else
976 hOut = OpenStockProfile(0, cOutProf);
978 hProof = NULL;
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;
989 if (!hIn)
990 FatalError("Input profile couldn't be read.");
991 if (!hOut)
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);
1004 else {
1005 OutputColorSpace = GetProfileColorSpace(hOut);
1008 jpeg_copy_critical_parameters(&Decompressor, &Compressor);
1010 WriteOutputFields(OutputColorSpace);
1012 wOutput = ComputeOutputFormatDescriptor(wInput, OutputColorSpace);
1015 xform = cmsCreateProofingTransform(hIn, wInput,
1016 hOut, wOutput,
1017 hProof, Intent,
1018 ProofingIntent, dwFlags);
1019 if (xform == NULL)
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);
1032 return 1;
1036 // Simply print help
1038 static
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);
1043 switch(level) {
1045 default:
1046 case 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);
1079 break;
1081 case 1:
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);
1095 break;
1097 case 2:
1098 PrintBuiltins();
1099 break;
1101 case 3:
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");
1108 break;
1111 exit(0);
1115 // The toggles stuff
1117 static
1118 void HandleSwitches(int argc, char *argv[])
1120 int s;
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) {
1124 switch (s)
1127 case 'b':
1128 case 'B':
1129 BlackPointCompensation = TRUE;
1130 break;
1132 case 'd':
1133 case 'D': ObserverAdaptationState = atof(xoptarg);
1134 if (ObserverAdaptationState < 0 ||
1135 ObserverAdaptationState > 1.0)
1136 FatalError("Adaptation state should be 0..1");
1137 break;
1139 case 'v':
1140 case 'V':
1141 Verbose = TRUE;
1142 break;
1144 case 'i':
1145 case 'I':
1146 if (lIsDeviceLink)
1147 FatalError("Device-link already specified");
1149 cInpProf = xoptarg;
1150 break;
1152 case 'o':
1153 case 'O':
1154 if (lIsDeviceLink)
1155 FatalError("Device-link already specified");
1157 cOutProf = xoptarg;
1158 break;
1160 case 'l':
1161 case 'L':
1162 if (cInpProf != NULL || cOutProf != NULL)
1163 FatalError("input/output profiles already specified");
1165 cInpProf = xoptarg;
1166 lIsDeviceLink = TRUE;
1167 break;
1169 case 'p':
1170 case 'P':
1171 cProofing = xoptarg;
1172 break;
1174 case 't':
1175 case 'T':
1176 Intent = atoi(xoptarg);
1177 break;
1179 case 'N':
1180 case 'n':
1181 IgnoreEmbedded = TRUE;
1182 break;
1184 case 'e':
1185 case 'E':
1186 EmbedProfile = TRUE;
1187 break;
1190 case 'g':
1191 case 'G':
1192 GamutCheck = TRUE;
1193 break;
1195 case 'c':
1196 case 'C':
1197 PrecalcMode = atoi(xoptarg);
1198 if (PrecalcMode < 0 || PrecalcMode > 2)
1199 FatalError("Unknown precalc mode '%d'", PrecalcMode);
1200 break;
1202 case 'H':
1203 case 'h': {
1205 int a = atoi(xoptarg);
1206 Help(a);
1208 break;
1210 case 'q':
1211 case 'Q':
1212 jpegQuality = atoi(xoptarg);
1213 if (jpegQuality > 100) jpegQuality = 100;
1214 if (jpegQuality < 0) jpegQuality = 0;
1215 break;
1217 case 'm':
1218 case 'M':
1219 ProofingIntent = atoi(xoptarg);
1220 break;
1222 case 's':
1223 case 'S': SaveEmbedded = xoptarg;
1224 break;
1226 case '!':
1227 if (sscanf(xoptarg, "%hu,%hu,%hu", &Alarm[0], &Alarm[1], &Alarm[2]) == 3) {
1228 int i;
1229 for (i=0; i < 3; i++) {
1230 Alarm[i] = (Alarm[i] << 8) | Alarm[i];
1233 break;
1235 default:
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) {
1251 Help(0);
1254 OpenInput(argv[xoptind]);
1255 OpenOutput(argv[xoptind+1]);
1257 TransformImage(cInpProf, cOutProf);
1260 if (Verbose) { fprintf(stdout, "\n"); fflush(stdout); }
1262 Done();
1264 return 0;