jpgicc.c 35 KB

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