comparison mupdf-source/thirdparty/lcms2/utils/jpgicc/jpgicc.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2023 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:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
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.
23 //
24
25 // This program does apply profiles to (some) JPEG files
26
27
28 #include "utils.h"
29
30 #include "jpeglib.h"
31 #include "iccjpeg.h"
32
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;
42
43 static const char* SaveEmbedded = NULL;
44
45 static int Intent = INTENT_PERCEPTUAL;
46 static int ProofingIntent = INTENT_PERCEPTUAL;
47 static int PrecalcMode = 1;
48
49 static int jpegQuality = 75;
50
51 static cmsFloat64Number ObserverAdaptationState = 0;
52
53
54 static char *cInpProf = NULL;
55 static char *cOutProf = NULL;
56 static char *cProofing = NULL;
57
58 static FILE * InFile;
59 static FILE * OutFile;
60
61 static struct jpeg_decompress_struct Decompressor;
62 static struct jpeg_compress_struct Compressor;
63
64
65 static struct my_error_mgr {
66
67 struct jpeg_error_mgr pub; // "public" fields
68 void* Cargo; // "private" fields
69
70 } ErrorHandler;
71
72
73 cmsUInt16Number Alarm[cmsMAXCHANNELS] = {128,128,128,0};
74
75
76 static
77 void my_error_exit (j_common_ptr cinfo)
78 {
79 char buffer[JMSG_LENGTH_MAX];
80
81 (*cinfo->err->format_message) (cinfo, buffer);
82 FatalError(buffer);
83 }
84
85 /*
86 Definition of the APPn Markers Defined for continuous-tone G3FAX
87
88 The application code APP1 initiates identification of the image as
89 a G3FAX application and defines the spatial resolution and subsampling.
90 This marker directly follows the SOI marker. The data format will be as follows:
91
92 X'FFE1' (APP1), length, FAX identifier, version, spatial resolution.
93
94 The above terms are defined as follows:
95
96 Length: (Two octets) Total APP1 field octet count including the octet count itself, but excluding the APP1
97 marker.
98
99 FAX identifier: (Six octets) X'47', X'33', X'46', X'41', X'58', X'00'. This X'00'-terminated string "G3FAX"
100 uniquely identifies this APP1 marker.
101
102 Version: (Two octets) X'07CA'. This string specifies the year of approval of the standard, for identification
103 in the case of future revision (for example, 1994).
104
105 Spatial Resolution: (Two octets) Lightness pixel density in pels/25.4 mm. The basic value is 200. Allowed values are
106 100, 200, 300, 400, 600 and 1200 pels/25.4 mm, with square (or equivalent) pels.
107
108 NOTE - The functional equivalence of inch-based and mm-based resolutions is maintained. For example, the 200 x 200
109 */
110
111 static
112 cmsBool IsITUFax(jpeg_saved_marker_ptr ptr)
113 {
114 while (ptr)
115 {
116 if (ptr -> marker == (JPEG_APP0 + 1) && ptr -> data_length > 5) {
117
118 const char* data = (const char*) ptr -> data;
119
120 if (strcmp(data, "G3FAX") == 0) return TRUE;
121 }
122
123 ptr = ptr -> next;
124 }
125
126 return FALSE;
127 }
128
129 // Save a ITU T.42/Fax marker with defaults on boundaries. This is the only mode we support right now.
130 static
131 void SetITUFax(j_compress_ptr cinfo)
132 {
133 unsigned char Marker[] = "G3FAX\x00\0x07\xCA\x00\xC8";
134
135 jpeg_write_marker(cinfo, (JPEG_APP0 + 1), Marker, 10);
136 }
137
138
139 // Build a profile for decoding ITU T.42/Fax JPEG streams.
140 // The profile has an additional ability in the input direction of
141 // gamut compress values between 85 < a < -85 and -75 < b < 125. This conforms
142 // the default range for ITU/T.42 -- See RFC 2301, section 6.2.3 for details
143
144 // L* = [0, 100]
145 // a* = [-85, 85]
146 // b* = [-75, 125]
147
148
149 // These functions does convert the encoding of ITUFAX to floating point
150 // and vice-versa. No gamut mapping is performed yet.
151
152 static
153 void ITU2Lab(const cmsUInt16Number In[3], cmsCIELab* Lab)
154 {
155 Lab -> L = (double) In[0] / 655.35;
156 Lab -> a = (double) 170.* (In[1] - 32768.) / 65535.;
157 Lab -> b = (double) 200.* (In[2] - 24576.) / 65535.;
158 }
159
160 static
161 void Lab2ITU(const cmsCIELab* Lab, cmsUInt16Number Out[3])
162 {
163 Out[0] = (cmsUInt16Number) floor((double) (Lab -> L / 100.)* 65535. );
164 Out[1] = (cmsUInt16Number) floor((double) (Lab -> a / 170.)* 65535. + 32768. );
165 Out[2] = (cmsUInt16Number) floor((double) (Lab -> b / 200.)* 65535. + 24576. );
166 }
167
168 // These are the samplers-- They are passed as callbacks to cmsStageSampleCLut16bit()
169 // then, cmsSample3DGrid() will sweel whole Lab gamut calling these functions
170 // once for each node. In[] will contain the Lab PCS value to convert to ITUFAX
171 // on PCS2ITU, or the ITUFAX value to convert to Lab in ITU2PCS
172 // You can change the number of sample points if desired, the algorithm will
173 // remain same. 33 points gives good accuracy, but you can reduce to 22 or less
174 // is space is critical
175
176 #define GRID_POINTS 33
177
178 static
179 int PCS2ITU(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
180 {
181 cmsCIELab Lab;
182
183 cmsLabEncoded2Float(NULL, &Lab, In);
184 cmsDesaturateLab(NULL, &Lab, 85, -85, 125, -75); // This function does the necessary gamut remapping
185 Lab2ITU(&Lab, Out);
186 return TRUE;
187
188 UTILS_UNUSED_PARAMETER(Cargo);
189 UTILS_UNUSED_PARAMETER(ContextID);
190 }
191
192
193 static
194 int ITU2PCS(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
195 {
196 cmsCIELab Lab;
197
198 ITU2Lab(In, &Lab);
199 cmsFloat2LabEncoded(NULL, Out, &Lab);
200 return TRUE;
201
202 UTILS_UNUSED_PARAMETER(Cargo);
203 UTILS_UNUSED_PARAMETER(ContextID);
204 }
205
206 // This function does create the virtual input profile, which decodes ITU to the profile connection space
207 static
208 cmsHPROFILE CreateITU2PCS_ICC(void)
209 {
210 cmsHPROFILE hProfile;
211 cmsPipeline* AToB0;
212 cmsStage* ColorMap;
213
214 AToB0 = cmsPipelineAlloc(0, 3, 3);
215 if (AToB0 == NULL) return NULL;
216
217 ColorMap = cmsStageAllocCLut16bit(0, GRID_POINTS, 3, 3, NULL);
218 if (ColorMap == NULL) return NULL;
219
220 cmsPipelineInsertStage(NULL, AToB0, cmsAT_BEGIN, ColorMap);
221 cmsStageSampleCLut16bit(NULL, ColorMap, ITU2PCS, NULL, 0);
222
223 hProfile = cmsCreateProfilePlaceholder(0);
224 if (hProfile == NULL) {
225 cmsPipelineFree(NULL, AToB0);
226 return NULL;
227 }
228
229 cmsWriteTag(NULL, hProfile, cmsSigAToB0Tag, AToB0);
230 cmsSetColorSpace(NULL, hProfile, cmsSigLabData);
231 cmsSetPCS(NULL, hProfile, cmsSigLabData);
232 cmsSetDeviceClass(NULL, hProfile, cmsSigColorSpaceClass);
233 cmsPipelineFree(NULL, AToB0);
234
235 return hProfile;
236 }
237
238
239 // This function does create the virtual output profile, with the necessary gamut mapping
240 static
241 cmsHPROFILE CreatePCS2ITU_ICC(void)
242 {
243 cmsHPROFILE hProfile;
244 cmsPipeline* BToA0;
245 cmsStage* ColorMap;
246
247 BToA0 = cmsPipelineAlloc(0, 3, 3);
248 if (BToA0 == NULL) return NULL;
249
250 ColorMap = cmsStageAllocCLut16bit(0, GRID_POINTS, 3, 3, NULL);
251 if (ColorMap == NULL) return NULL;
252
253 cmsPipelineInsertStage(NULL, BToA0, cmsAT_BEGIN, ColorMap);
254 cmsStageSampleCLut16bit(NULL, ColorMap, PCS2ITU, NULL, 0);
255
256 hProfile = cmsCreateProfilePlaceholder(0);
257 if (hProfile == NULL) {
258 cmsPipelineFree(NULL, BToA0);
259 return NULL;
260 }
261
262 cmsWriteTag(NULL, hProfile, cmsSigBToA0Tag, BToA0);
263 cmsSetColorSpace(NULL, hProfile, cmsSigLabData);
264 cmsSetPCS(NULL, hProfile, cmsSigLabData);
265 cmsSetDeviceClass(NULL, hProfile, cmsSigColorSpaceClass);
266
267 cmsPipelineFree(NULL, BToA0);
268
269 return hProfile;
270 }
271
272
273
274 #define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))
275
276 static
277 cmsBool ProcessPhotoshopAPP13(JOCTET *data, int datalen)
278 {
279 int i;
280
281 for (i = 14; i < datalen; )
282 {
283 long len;
284 unsigned int type;
285
286 if (!(GETJOCTET(data[i] ) == 0x38 &&
287 GETJOCTET(data[i+1]) == 0x42 &&
288 GETJOCTET(data[i+2]) == 0x49 &&
289 GETJOCTET(data[i+3]) == 0x4D)) break; // Not recognized
290
291 i += 4; // identifying string
292
293 type = (unsigned int) (GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]));
294
295 i += 2; // resource type
296
297 i += GETJOCTET(data[i]) + ((GETJOCTET(data[i]) & 1) ? 1 : 2); // resource name
298
299 len = ((((GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]))<<8) +
300 GETJOCTET(data[i+2]))<<8) + GETJOCTET(data[i+3]);
301
302 if (len < 0) return FALSE; // Keep bug hunters away
303
304 i += 4; // Size
305
306 if (type == 0x03ED && len >= 16) {
307
308 Decompressor.X_density = (UINT16) PS_FIXED_TO_FLOAT(GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]),
309 GETJOCTET(data[i+2]<<8) + GETJOCTET(data[i+3]));
310 Decompressor.Y_density = (UINT16) PS_FIXED_TO_FLOAT(GETJOCTET(data[i+8]<<8) + GETJOCTET(data[i+9]),
311 GETJOCTET(data[i+10]<<8) + GETJOCTET(data[i+11]));
312
313 // Set the density unit to 1 since the
314 // Vertical and Horizontal resolutions
315 // are specified in Pixels per inch
316
317 Decompressor.density_unit = 0x01;
318 return TRUE;
319
320 }
321
322 i += len + ((len & 1) ? 1 : 0); // Alignment
323 }
324 return FALSE;
325 }
326
327
328 static
329 cmsBool HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
330 {
331 while (ptr) {
332
333 if (ptr -> marker == (JPEG_APP0 + 13) && ptr -> data_length > 9)
334 {
335 JOCTET* data = ptr -> data;
336
337 if(GETJOCTET(data[0]) == 0x50 &&
338 GETJOCTET(data[1]) == 0x68 &&
339 GETJOCTET(data[2]) == 0x6F &&
340 GETJOCTET(data[3]) == 0x74 &&
341 GETJOCTET(data[4]) == 0x6F &&
342 GETJOCTET(data[5]) == 0x73 &&
343 GETJOCTET(data[6]) == 0x68 &&
344 GETJOCTET(data[7]) == 0x6F &&
345 GETJOCTET(data[8]) == 0x70) {
346
347 ProcessPhotoshopAPP13(data, ptr -> data_length);
348 return TRUE;
349 }
350 }
351
352 ptr = ptr -> next;
353 }
354
355 return FALSE;
356 }
357
358
359 typedef unsigned short uint16_t;
360 typedef unsigned char uint8_t;
361 typedef unsigned int uint32_t;
362
363 #define INTEL_BYTE_ORDER 0x4949
364 #define XRESOLUTION 0x011a
365 #define YRESOLUTION 0x011b
366 #define RESOLUTION_UNIT 0x128
367
368 // Abort if crafted file
369 static
370 void craftedFile(void)
371 {
372 FatalError("Corrupted EXIF data");
373 }
374
375 // Read a 16-bit word
376 static
377 uint16_t read16(uint8_t* arr, size_t pos, int swapBytes, size_t max)
378 {
379 if (pos + 2 >= max)
380 {
381 craftedFile();
382 return 0;
383 }
384 else
385 {
386 uint8_t b1 = arr[pos];
387 uint8_t b2 = arr[pos + 1];
388
389 return (swapBytes) ? ((b2 << 8) | b1) : ((b1 << 8) | b2);
390 }
391 }
392
393
394 // Read a 32-bit word
395 static
396 uint32_t read32(uint8_t* arr, size_t pos, int swapBytes, size_t max)
397 {
398
399 if (pos + 4 >= max)
400 {
401 craftedFile();
402 return 0;
403 }
404 else
405 {
406 if (!swapBytes) {
407
408 return (arr[pos] << 24) | (arr[pos + 1] << 16) | (arr[pos + 2] << 8) | arr[pos + 3];
409 }
410
411 return arr[pos] | (arr[pos + 1] << 8) | (arr[pos + 2] << 16) | (arr[pos + 3] << 24);
412 }
413 }
414
415
416
417 static
418 int read_tag(uint8_t* arr, int pos, int swapBytes, void* dest, size_t max)
419 {
420 // Format should be 5 over here (rational)
421 uint32_t format = read16(arr, pos + 2, swapBytes, max);
422 // Components should be 1
423 uint32_t components = read32(arr, pos + 4, swapBytes, max);
424 // Points to the value
425 uint32_t offset;
426
427 // sanity
428 if (components != 1) return 0;
429
430 if (format == 3)
431 offset = pos + 8;
432 else
433 offset = read32(arr, pos + 8, swapBytes, max);
434
435 switch (format) {
436
437 case 5: // Rational
438 {
439 double num = read32(arr, offset, swapBytes, max);
440 double den = read32(arr, offset + 4, swapBytes, max);
441 *(double *) dest = num / den;
442 }
443 break;
444
445 case 3: // uint 16
446 *(int*) dest = read16(arr, offset, swapBytes, max);
447 break;
448
449 default: return 0;
450 }
451
452 return 1;
453 }
454
455
456
457 // Handler for EXIF data
458 static
459 cmsBool HandleEXIF(struct jpeg_decompress_struct* cinfo)
460 {
461 jpeg_saved_marker_ptr ptr;
462 uint32_t ifd_ofs;
463 int pos = 0, swapBytes = 0;
464 uint32_t i, numEntries;
465 double XRes = -1, YRes = -1;
466 int Unit = 2; // Inches
467
468
469 for (ptr = cinfo ->marker_list; ptr; ptr = ptr ->next) {
470
471 if ((ptr ->marker == JPEG_APP0+1) && ptr ->data_length > 6) {
472
473 JOCTET* data = ptr -> data;
474 size_t max = ptr->data_length;
475
476 if (memcmp(data, "Exif\0\0", 6) == 0) {
477
478 data += 6; // Skip EXIF marker
479
480 // 8 byte TIFF header
481 // first two determine byte order
482 pos = 0;
483 if (read16(data, pos, 0, max) == INTEL_BYTE_ORDER) {
484 swapBytes = 1;
485 }
486
487 pos += 2;
488
489 // next two bytes are always 0x002A (TIFF version)
490 pos += 2;
491
492 // offset to Image File Directory (includes the previous 8 bytes)
493 ifd_ofs = read32(data, pos, swapBytes, max);
494
495 // Search the directory for resolution tags
496 numEntries = read16(data, ifd_ofs, swapBytes, max);
497
498 for (i=0; i < numEntries; i++) {
499
500 uint32_t entryOffset = ifd_ofs + 2 + (12 * i);
501 uint32_t tag = read16(data, entryOffset, swapBytes, max);
502
503 switch (tag) {
504
505 case RESOLUTION_UNIT:
506 if (!read_tag(data, entryOffset, swapBytes, &Unit, max)) return FALSE;
507 break;
508
509 case XRESOLUTION:
510 if (!read_tag(data, entryOffset, swapBytes, &XRes, max)) return FALSE;
511 break;
512
513 case YRESOLUTION:
514 if (!read_tag(data, entryOffset, swapBytes, &YRes, max)) return FALSE;
515 break;
516
517 default:;
518 }
519
520 }
521
522 // Proceed if all found
523
524 if (XRes != -1 && YRes != -1)
525 {
526
527 // 1 = None
528 // 2 = inches
529 // 3 = cm
530
531 switch (Unit) {
532
533 case 2:
534
535 cinfo ->X_density = (UINT16) floor(XRes + 0.5);
536 cinfo ->Y_density = (UINT16) floor(YRes + 0.5);
537 break;
538
539 case 1:
540
541 cinfo ->X_density = (UINT16) floor(XRes * 2.54 + 0.5);
542 cinfo ->Y_density = (UINT16) floor(YRes * 2.54 + 0.5);
543 break;
544
545 default: return FALSE;
546 }
547
548 cinfo ->density_unit = 1; /* 1 for dots/inch, or 2 for dots/cm.*/
549
550 }
551
552
553 }
554 }
555 }
556 return FALSE;
557 }
558
559
560 static
561 cmsBool OpenInput(const char* FileName)
562 {
563 int m;
564
565 lIsITUFax = FALSE;
566 InFile = fopen(FileName, "rb");
567 if (InFile == NULL) {
568 FatalError("Cannot open '%s'", FileName);
569 }
570
571 // Now we can initialize the JPEG decompression object.
572 Decompressor.err = jpeg_std_error(&ErrorHandler.pub);
573 ErrorHandler.pub.error_exit = my_error_exit;
574 ErrorHandler.pub.output_message = my_error_exit;
575
576 jpeg_create_decompress(&Decompressor);
577 jpeg_stdio_src(&Decompressor, InFile);
578
579 for (m = 0; m < 16; m++)
580 jpeg_save_markers(&Decompressor, JPEG_APP0 + m, 0xFFFF);
581
582 // setup_read_icc_profile(&Decompressor);
583
584 fseek(InFile, 0, SEEK_SET);
585 jpeg_read_header(&Decompressor, TRUE);
586
587 return TRUE;
588 }
589
590
591 static
592 cmsBool OpenOutput(const char* FileName)
593 {
594
595 OutFile = fopen(FileName, "wb");
596 if (OutFile == NULL) {
597 FatalError("Cannot create '%s'", FileName);
598
599 }
600
601 Compressor.err = jpeg_std_error(&ErrorHandler.pub);
602 ErrorHandler.pub.error_exit = my_error_exit;
603 ErrorHandler.pub.output_message = my_error_exit;
604
605 Compressor.input_components = Compressor.num_components = 4;
606
607 jpeg_create_compress(&Compressor);
608 jpeg_stdio_dest(&Compressor, OutFile);
609 return TRUE;
610 }
611
612 static
613 cmsBool Done(void)
614 {
615 jpeg_destroy_decompress(&Decompressor);
616 jpeg_destroy_compress(&Compressor);
617 return fclose(InFile) + fclose(OutFile);
618
619 }
620
621
622 // Build up the pixeltype descriptor
623
624 static
625 cmsUInt32Number GetInputPixelType(void)
626 {
627 int space, bps, extra, ColorChannels, Flavor;
628
629 lIsITUFax = IsITUFax(Decompressor.marker_list);
630 lIsPhotoshopApp13 = HandlePhotoshopAPP13(Decompressor.marker_list);
631 lIsEXIF = HandleEXIF(&Decompressor);
632
633 ColorChannels = Decompressor.num_components;
634 extra = 0; // Alpha = None
635 bps = 1; // 8 bits
636 Flavor = 0; // Vanilla
637
638 if (lIsITUFax) {
639
640 space = PT_Lab;
641 Decompressor.out_color_space = JCS_YCbCr; // Fake to don't touch
642 }
643 else
644 switch (Decompressor.jpeg_color_space) {
645
646 case JCS_GRAYSCALE: // monochrome
647 space = PT_GRAY;
648 Decompressor.out_color_space = JCS_GRAYSCALE;
649 break;
650
651 case JCS_RGB: // red/green/blue
652 space = PT_RGB;
653 Decompressor.out_color_space = JCS_RGB;
654 break;
655
656 case JCS_YCbCr: // Y/Cb/Cr (also known as YUV)
657 space = PT_RGB; // Let IJG code to do the conversion
658 Decompressor.out_color_space = JCS_RGB;
659 break;
660
661 case JCS_CMYK: // C/M/Y/K
662 space = PT_CMYK;
663 Decompressor.out_color_space = JCS_CMYK;
664 if (Decompressor.saw_Adobe_marker) // Adobe keeps CMYK inverted, so change flavor
665 Flavor = 1; // from vanilla to chocolate
666 break;
667
668 case JCS_YCCK: // Y/Cb/Cr/K
669 space = PT_CMYK;
670 Decompressor.out_color_space = JCS_CMYK;
671 if (Decompressor.saw_Adobe_marker) // ditto
672 Flavor = 1;
673 break;
674
675 default:
676 FatalError("Unsupported color space (0x%x)", Decompressor.jpeg_color_space);
677 return 0;
678 }
679
680 return (EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|COLORSPACE_SH(space)|FLAVOR_SH(Flavor));
681 }
682
683
684 // Rearrange pixel type to build output descriptor
685 static
686 cmsUInt32Number ComputeOutputFormatDescriptor(cmsUInt32Number dwInput, int OutColorSpace)
687 {
688 int IsPlanar = T_PLANAR(dwInput);
689 int Channels = 0;
690 int Flavor = 0;
691
692 switch (OutColorSpace) {
693
694 case PT_GRAY:
695 Channels = 1;
696 break;
697 case PT_RGB:
698 case PT_CMY:
699 case PT_Lab:
700 case PT_YUV:
701 case PT_YCbCr:
702 Channels = 3;
703 break;
704
705 case PT_CMYK:
706 if (Compressor.write_Adobe_marker) // Adobe keeps CMYK inverted, so change flavor to chocolate
707 Flavor = 1;
708 Channels = 4;
709 break;
710 default:
711 FatalError("Unsupported output color space");
712 }
713
714 return (COLORSPACE_SH(OutColorSpace)|PLANAR_SH(IsPlanar)|CHANNELS_SH(Channels)|BYTES_SH(1)|FLAVOR_SH(Flavor));
715 }
716
717
718 // Equivalence between ICC color spaces and lcms color spaces
719 static
720 int GetProfileColorSpace(cmsContext ContextID, cmsHPROFILE hProfile)
721 {
722 cmsColorSpaceSignature ProfileSpace = cmsGetColorSpace(ContextID, hProfile);
723
724 return _cmsLCMScolorSpace(ContextID, ProfileSpace);
725 }
726
727 static
728 int GetDevicelinkColorSpace(cmsContext ContextID, cmsHPROFILE hProfile)
729 {
730 cmsColorSpaceSignature ProfileSpace = cmsGetPCS(ContextID, hProfile);
731
732 return _cmsLCMScolorSpace(ContextID, ProfileSpace);
733 }
734
735
736 // From TRANSUPP
737
738 static
739 void jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo)
740 {
741 jpeg_saved_marker_ptr marker;
742
743 /* In the current implementation, we don't actually need to examine the
744 * option flag here; we just copy everything that got saved.
745 * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
746 * if the encoder library already wrote one.
747 */
748 for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
749
750 if (dstinfo->write_JFIF_header &&
751 marker->marker == JPEG_APP0 &&
752 marker->data_length >= 5 &&
753 GETJOCTET(marker->data[0]) == 0x4A &&
754 GETJOCTET(marker->data[1]) == 0x46 &&
755 GETJOCTET(marker->data[2]) == 0x49 &&
756 GETJOCTET(marker->data[3]) == 0x46 &&
757 GETJOCTET(marker->data[4]) == 0)
758 continue; /* reject duplicate JFIF */
759
760 if (dstinfo->write_Adobe_marker &&
761 marker->marker == JPEG_APP0+14 &&
762 marker->data_length >= 5 &&
763 GETJOCTET(marker->data[0]) == 0x41 &&
764 GETJOCTET(marker->data[1]) == 0x64 &&
765 GETJOCTET(marker->data[2]) == 0x6F &&
766 GETJOCTET(marker->data[3]) == 0x62 &&
767 GETJOCTET(marker->data[4]) == 0x65)
768 continue; /* reject duplicate Adobe */
769
770 jpeg_write_marker(dstinfo, marker->marker,
771 marker->data, marker->data_length);
772 }
773 }
774
775 static
776 void WriteOutputFields(int OutputColorSpace)
777 {
778 J_COLOR_SPACE in_space, jpeg_space;
779 int components;
780
781 switch (OutputColorSpace) {
782
783 case PT_GRAY: in_space = jpeg_space = JCS_GRAYSCALE;
784 components = 1;
785 break;
786
787 case PT_RGB: in_space = JCS_RGB;
788 jpeg_space = JCS_YCbCr;
789 components = 3;
790 break; // red/green/blue
791
792 case PT_YCbCr: in_space = jpeg_space = JCS_YCbCr;
793 components = 3;
794 break; // Y/Cb/Cr (also known as YUV)
795
796 case PT_CMYK: in_space = JCS_CMYK;
797 jpeg_space = JCS_YCCK;
798 components = 4;
799 break; // C/M/Y/components
800
801 case PT_Lab: in_space = jpeg_space = JCS_YCbCr;
802 components = 3;
803 break; // Fake to don't touch
804 default:
805 FatalError("Unsupported output color space");
806 return;
807 }
808
809
810 if (jpegQuality >= 100) {
811
812 // avoid destructive conversion when asking for lossless compression
813 jpeg_space = in_space;
814 }
815
816 Compressor.in_color_space = in_space;
817 Compressor.jpeg_color_space = jpeg_space;
818 Compressor.input_components = Compressor.num_components = components;
819 jpeg_set_defaults(&Compressor);
820 jpeg_set_colorspace(&Compressor, jpeg_space);
821
822
823 // Make sure to pass resolution through
824 if (OutputColorSpace == PT_CMYK)
825 Compressor.write_JFIF_header = 1;
826
827 // Avoid subsampling on high quality factor
828 jpeg_set_quality(&Compressor, jpegQuality, 1);
829 if (jpegQuality >= 70) {
830
831 int i;
832 for(i=0; i < Compressor.num_components; i++) {
833
834 Compressor.comp_info[i].h_samp_factor = 1;
835 Compressor.comp_info[i].v_samp_factor = 1;
836 }
837
838 }
839
840 }
841
842
843 static
844 void DoEmbedProfile(const char* ProfileFile)
845 {
846 FILE* f;
847 size_t size, EmbedLen;
848 cmsUInt8Number* EmbedBuffer;
849
850 f = fopen(ProfileFile, "rb");
851 if (f == NULL) return;
852
853 size = cmsfilelength(f);
854 EmbedBuffer = (cmsUInt8Number*) malloc(size + 1);
855 EmbedLen = fread(EmbedBuffer, 1, size, f);
856 fclose(f);
857 EmbedBuffer[EmbedLen] = 0;
858
859 write_icc_profile (&Compressor, EmbedBuffer, (unsigned int) EmbedLen);
860 free(EmbedBuffer);
861 }
862
863
864
865 static
866 int DoTransform(cmsContext ContextID, cmsHTRANSFORM hXForm, int OutputColorSpace)
867 {
868 JSAMPROW ScanLineIn;
869 JSAMPROW ScanLineOut;
870
871
872 //Preserve resolution values from the original
873 // (Thanks to Robert Bergs for finding out this bug)
874 Compressor.density_unit = Decompressor.density_unit;
875 Compressor.X_density = Decompressor.X_density;
876 Compressor.Y_density = Decompressor.Y_density;
877
878 // Compressor.write_JFIF_header = 1;
879
880 jpeg_start_decompress(&Decompressor);
881 jpeg_start_compress(&Compressor, TRUE);
882
883 if (OutputColorSpace == PT_Lab)
884 SetITUFax(&Compressor);
885
886 // Embed the profile if needed
887 if (EmbedProfile && cOutProf)
888 DoEmbedProfile(cOutProf);
889
890 ScanLineIn = (JSAMPROW) malloc((size_t) Decompressor.output_width * Decompressor.num_components);
891 ScanLineOut = (JSAMPROW) malloc((size_t) Compressor.image_width * Compressor.num_components);
892
893 while (Decompressor.output_scanline <
894 Decompressor.output_height) {
895
896 jpeg_read_scanlines(&Decompressor, &ScanLineIn, 1);
897
898 cmsDoTransform(ContextID, hXForm, ScanLineIn, ScanLineOut, Decompressor.output_width);
899
900 jpeg_write_scanlines(&Compressor, &ScanLineOut, 1);
901 }
902
903 free(ScanLineIn);
904 free(ScanLineOut);
905
906 jpeg_finish_decompress(&Decompressor);
907 jpeg_finish_compress(&Compressor);
908
909 return TRUE;
910 }
911
912
913
914 // Transform one image
915
916 static
917 int TransformImage(cmsContext ContextID, char *cDefInpProf, char *cOutputProf)
918 {
919 cmsHPROFILE hIn, hOut, hProof;
920 cmsHTRANSFORM xform;
921 cmsUInt32Number wInput, wOutput;
922 int OutputColorSpace;
923 cmsUInt32Number dwFlags = 0;
924 cmsUInt32Number EmbedLen;
925 cmsUInt8Number* EmbedBuffer;
926
927
928 cmsSetAdaptationState(ContextID, ObserverAdaptationState);
929
930 if (BlackPointCompensation) {
931
932 dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
933 }
934
935
936 switch (PrecalcMode) {
937
938 case 0: dwFlags |= cmsFLAGS_NOOPTIMIZE; break;
939 case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
940 case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
941 default:;
942 }
943
944
945 if (GamutCheck) {
946 dwFlags |= cmsFLAGS_GAMUTCHECK;
947 cmsSetAlarmCodes(ContextID, Alarm);
948 }
949
950 // Take input color space
951 wInput = GetInputPixelType();
952
953 if (lIsDeviceLink) {
954
955 hIn = cmsOpenProfileFromFile(ContextID, cDefInpProf, "r");
956 hOut = NULL;
957 hProof = NULL;
958 }
959 else {
960
961 if (!IgnoreEmbedded && read_icc_profile(&Decompressor, &EmbedBuffer, &EmbedLen))
962 {
963 hIn = cmsOpenProfileFromMem(ContextID, EmbedBuffer, EmbedLen);
964
965 if (Verbose) {
966
967 fprintf(stdout, " (Embedded profile found)\n");
968 PrintProfileInformation(ContextID, hIn);
969 fflush(stdout);
970 }
971
972 if (hIn != NULL && SaveEmbedded != NULL)
973 SaveMemoryBlock(EmbedBuffer, EmbedLen, SaveEmbedded);
974
975 free(EmbedBuffer);
976 }
977 else
978 {
979 // Default for ITU/Fax
980 if (cDefInpProf == NULL && T_COLORSPACE(wInput) == PT_Lab)
981 cDefInpProf = "*Lab";
982
983 if (cDefInpProf != NULL && cmsstrcasecmp(cDefInpProf, "*lab") == 0)
984 hIn = CreateITU2PCS_ICC();
985 else
986 hIn = OpenStockProfile(0, cDefInpProf);
987 }
988
989 if (cOutputProf != NULL && cmsstrcasecmp(cOutputProf, "*lab") == 0)
990 hOut = CreatePCS2ITU_ICC();
991 else
992 hOut = OpenStockProfile(0, cOutputProf);
993
994 hProof = NULL;
995 if (cProofing != NULL) {
996
997 hProof = OpenStockProfile(0, cProofing);
998 if (hProof == NULL) {
999 FatalError("Proofing profile couldn't be read.");
1000 }
1001 dwFlags |= cmsFLAGS_SOFTPROOFING;
1002 }
1003 }
1004
1005 if (!hIn)
1006 FatalError("Input profile couldn't be read.");
1007 if (!lIsDeviceLink && !hOut)
1008 FatalError("Output profile couldn't be read.");
1009
1010 // Assure both, input profile and input JPEG are on same colorspace
1011 if (cmsGetColorSpace(ContextID, hIn) != _cmsICCcolorSpace(ContextID, T_COLORSPACE(wInput)))
1012 FatalError("Input profile is not operating in proper color space");
1013
1014
1015 // Output colorspace is given by output profile
1016
1017 if (lIsDeviceLink) {
1018 OutputColorSpace = GetDevicelinkColorSpace(ContextID, hIn);
1019 }
1020 else {
1021 OutputColorSpace = GetProfileColorSpace(ContextID, hOut);
1022 }
1023
1024 jpeg_copy_critical_parameters(&Decompressor, &Compressor);
1025
1026 WriteOutputFields(OutputColorSpace);
1027
1028 wOutput = ComputeOutputFormatDescriptor(wInput, OutputColorSpace);
1029
1030
1031 xform = cmsCreateProofingTransform(ContextID, hIn, wInput,
1032 hOut, wOutput,
1033 hProof, Intent,
1034 ProofingIntent, dwFlags);
1035 if (xform == NULL)
1036 FatalError("Cannot transform by using the profiles");
1037
1038 DoTransform(ContextID, xform, OutputColorSpace);
1039
1040
1041 jcopy_markers_execute(&Decompressor, &Compressor);
1042
1043 cmsDeleteTransform(ContextID, xform);
1044 cmsCloseProfile(ContextID, hIn);
1045 cmsCloseProfile(ContextID, hOut);
1046 if (hProof) cmsCloseProfile(ContextID, hProof);
1047
1048 return 1;
1049 }
1050
1051
1052 static
1053 void Help(cmsContext ContextID, int level)
1054 {
1055
1056 UTILS_UNUSED_PARAMETER(level);
1057
1058 fprintf(stderr, "usage: jpgicc [flags] input.jpg output.jpg\n");
1059
1060 fprintf(stderr, "\nflags:\n\n");
1061 fprintf(stderr, "-v - Verbose\n");
1062 fprintf(stderr, "-i<profile> - Input profile (defaults to sRGB)\n");
1063 fprintf(stderr, "-o<profile> - Output profile (defaults to sRGB)\n");
1064
1065 PrintBuiltins();
1066
1067 PrintRenderingIntents(ContextID);
1068
1069
1070 fprintf(stderr, "-b - Black point compensation\n");
1071 fprintf(stderr, "-d<0..1> - Observer adaptation state (abs.col. only)\n");
1072 fprintf(stderr, "-n - Ignore embedded profile\n");
1073 fprintf(stderr, "-e - Embed destination profile\n");
1074 fprintf(stderr, "-s<new profile> - Save embedded profile as <new profile>\n");
1075
1076 fprintf(stderr, "\n");
1077
1078 fprintf(stderr, "-c<0,1,2,3> - Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes) [defaults to 1]\n");
1079 fprintf(stderr, "\n");
1080
1081 fprintf(stderr, "-p<profile> - Soft proof profile\n");
1082 fprintf(stderr, "-m<0,1,2,3> - SoftProof intent\n");
1083 fprintf(stderr, "-g - Marks out-of-gamut colors on softproof\n");
1084 fprintf(stderr, "-!<r>,<g>,<b> - Out-of-gamut marker channel values\n");
1085
1086 fprintf(stderr, "\n");
1087 fprintf(stderr, "-q<0..100> - Output JPEG quality\n");
1088
1089 fprintf(stderr, "Examples:\n\n"
1090 "To color correct from scanner to sRGB:\n"
1091 "\tjpgicc -iscanner.icm in.jpg out.jpg\n"
1092 "To convert from monitor1 to monitor2:\n"
1093 "\tjpgicc -imon1.icm -omon2.icm in.jpg out.jpg\n"
1094 "To make a CMYK separation:\n"
1095 "\tjpgicc -oprinter.icm inrgb.jpg outcmyk.jpg\n"
1096 "To recover sRGB from a CMYK separation:\n"
1097 "\tjpgicc -iprinter.icm incmyk.jpg outrgb.jpg\n"
1098 "To convert from CIELab ITU/Fax JPEG to sRGB\n"
1099 "\tjpgicc in.jpg out.jpg\n\n");
1100
1101
1102 fprintf(stderr, "This program is intended to be a demo of the Little CMS\n"
1103 "color engine. Both lcms and this program are open source.\n"
1104 "You can obtain both in source code at https://www.littlecms.com\n"
1105 "For suggestions, comments, bug reports etc. send mail to\n"
1106 "info@littlecms.com\n\n");
1107
1108 exit(0);
1109 }
1110
1111
1112 // The toggles stuff
1113
1114 static
1115 void HandleSwitches(cmsContext ContextID, int argc, char *argv[])
1116 {
1117 int s;
1118
1119 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) {
1120
1121 switch (s)
1122 {
1123
1124 case '-':
1125 if (strcmp(xoptarg, "help") == 0)
1126 {
1127 Help(ContextID, 0);
1128 }
1129 else
1130 {
1131 FatalError("Unknown option - run without args to see valid ones.\n");
1132 }
1133 break;
1134
1135 case 'b':
1136 case 'B':
1137 BlackPointCompensation = TRUE;
1138 break;
1139
1140 case 'd':
1141 case 'D': ObserverAdaptationState = atof(xoptarg);
1142 if (ObserverAdaptationState < 0 ||
1143 ObserverAdaptationState > 1.0)
1144 FatalError("Adaptation state should be 0..1");
1145 break;
1146
1147 case 'v':
1148 case 'V':
1149 Verbose = TRUE;
1150 break;
1151
1152 case 'i':
1153 case 'I':
1154 if (lIsDeviceLink)
1155 FatalError("Device-link already specified");
1156
1157 cInpProf = xoptarg;
1158 break;
1159
1160 case 'o':
1161 case 'O':
1162 if (lIsDeviceLink)
1163 FatalError("Device-link already specified");
1164
1165 cOutProf = xoptarg;
1166 break;
1167
1168 case 'l':
1169 case 'L':
1170 if (cInpProf != NULL || cOutProf != NULL)
1171 FatalError("input/output profiles already specified");
1172
1173 cInpProf = xoptarg;
1174 lIsDeviceLink = TRUE;
1175 break;
1176
1177 case 'p':
1178 case 'P':
1179 cProofing = xoptarg;
1180 break;
1181
1182 case 't':
1183 case 'T':
1184 Intent = atoi(xoptarg);
1185 break;
1186
1187 case 'N':
1188 case 'n':
1189 IgnoreEmbedded = TRUE;
1190 break;
1191
1192 case 'e':
1193 case 'E':
1194 EmbedProfile = TRUE;
1195 break;
1196
1197
1198 case 'g':
1199 case 'G':
1200 GamutCheck = TRUE;
1201 break;
1202
1203 case 'c':
1204 case 'C':
1205 PrecalcMode = atoi(xoptarg);
1206 if (PrecalcMode < 0 || PrecalcMode > 2)
1207 FatalError("Unknown precalc mode '%d'", PrecalcMode);
1208 break;
1209
1210 case 'H':
1211 case 'h': {
1212
1213 int a = atoi(xoptarg);
1214 Help(ContextID, a);
1215 }
1216 break;
1217
1218 case 'q':
1219 case 'Q':
1220 jpegQuality = atoi(xoptarg);
1221 if (jpegQuality > 100) jpegQuality = 100;
1222 if (jpegQuality < 0) jpegQuality = 0;
1223 break;
1224
1225 case 'm':
1226 case 'M':
1227 ProofingIntent = atoi(xoptarg);
1228 break;
1229
1230 case 's':
1231 case 'S': SaveEmbedded = xoptarg;
1232 break;
1233
1234 case '!':
1235 if (sscanf(xoptarg, "%hu,%hu,%hu", &Alarm[0], &Alarm[1], &Alarm[2]) == 3) {
1236 int i;
1237 for (i=0; i < 3; i++) {
1238 Alarm[i] = (Alarm[i] << 8) | Alarm[i];
1239 }
1240 }
1241 break;
1242
1243 default:
1244
1245 FatalError("Unknown option - run without args to see valid ones");
1246 }
1247
1248 }
1249 }
1250
1251
1252 int main(int argc, char* argv[])
1253 {
1254 cmsContext ContextID = cmsCreateContext(NULL, NULL);
1255
1256 fprintf(stderr, "Little CMS ICC profile applier for JPEG - v3.4 [LittleCMS %2.2f]\n\n", cmsGetEncodedCMMversion() / 1000.0);
1257 fprintf(stderr, "Copyright (c) 1998-2023 Marti Maria Saguer. See COPYING file for details.\n");
1258 fflush(stderr);
1259
1260 InitUtils(ContextID, "jpgicc");
1261
1262 HandleSwitches(ContextID, argc, argv);
1263
1264 if ((argc - xoptind) != 2) {
1265 Help(ContextID, 0);
1266 }
1267
1268 OpenInput(argv[xoptind]);
1269 OpenOutput(argv[xoptind+1]);
1270
1271 TransformImage(ContextID, cInpProf, cOutProf);
1272
1273
1274 if (Verbose) { fprintf(stdout, "\n"); fflush(stdout); }
1275
1276 Done();
1277
1278 cmsDeleteContext(ContextID);
1279
1280 return 0;
1281 }