| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281 |
- //---------------------------------------------------------------------------------
- //
- // Little Color Management System
- // Copyright (c) 1998-2023 Marti Maria Saguer
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the "Software"),
- // to deal in the Software without restriction, including without limitation
- // the rights to use, copy, modify, merge, publish, distribute, sublicense,
- // and/or sell copies of the Software, and to permit persons to whom the Software
- // is furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
- // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- // This program does apply profiles to (some) JPEG files
- #include "utils.h"
- #include "jpeglib.h"
- #include "iccjpeg.h"
- // Flags
- static cmsBool BlackPointCompensation = FALSE;
- static cmsBool IgnoreEmbedded = FALSE;
- static cmsBool GamutCheck = FALSE;
- static cmsBool lIsITUFax = FALSE;
- static cmsBool lIsPhotoshopApp13 = FALSE;
- static cmsBool lIsEXIF;
- static cmsBool lIsDeviceLink = FALSE;
- static cmsBool EmbedProfile = FALSE;
- static const char* SaveEmbedded = NULL;
- static int Intent = INTENT_PERCEPTUAL;
- static int ProofingIntent = INTENT_PERCEPTUAL;
- static int PrecalcMode = 1;
- static int jpegQuality = 75;
- static cmsFloat64Number ObserverAdaptationState = 0;
- static char *cInpProf = NULL;
- static char *cOutProf = NULL;
- static char *cProofing = NULL;
- static FILE * InFile;
- static FILE * OutFile;
- static struct jpeg_decompress_struct Decompressor;
- static struct jpeg_compress_struct Compressor;
- static struct my_error_mgr {
- struct jpeg_error_mgr pub; // "public" fields
- void* Cargo; // "private" fields
- } ErrorHandler;
- cmsUInt16Number Alarm[cmsMAXCHANNELS] = {128,128,128,0};
- static
- void my_error_exit (j_common_ptr cinfo)
- {
- char buffer[JMSG_LENGTH_MAX];
- (*cinfo->err->format_message) (cinfo, buffer);
- FatalError(buffer);
- }
- /*
- Definition of the APPn Markers Defined for continuous-tone G3FAX
- The application code APP1 initiates identification of the image as
- a G3FAX application and defines the spatial resolution and subsampling.
- This marker directly follows the SOI marker. The data format will be as follows:
- X'FFE1' (APP1), length, FAX identifier, version, spatial resolution.
- The above terms are defined as follows:
- Length: (Two octets) Total APP1 field octet count including the octet count itself, but excluding the APP1
- marker.
- FAX identifier: (Six octets) X'47', X'33', X'46', X'41', X'58', X'00'. This X'00'-terminated string "G3FAX"
- uniquely identifies this APP1 marker.
- Version: (Two octets) X'07CA'. This string specifies the year of approval of the standard, for identification
- in the case of future revision (for example, 1994).
- Spatial Resolution: (Two octets) Lightness pixel density in pels/25.4 mm. The basic value is 200. Allowed values are
- 100, 200, 300, 400, 600 and 1200 pels/25.4 mm, with square (or equivalent) pels.
- NOTE - The functional equivalence of inch-based and mm-based resolutions is maintained. For example, the 200 x 200
- */
- static
- cmsBool IsITUFax(jpeg_saved_marker_ptr ptr)
- {
- while (ptr)
- {
- if (ptr -> marker == (JPEG_APP0 + 1) && ptr -> data_length > 5) {
- const char* data = (const char*) ptr -> data;
- if (strcmp(data, "G3FAX") == 0) return TRUE;
- }
- ptr = ptr -> next;
- }
- return FALSE;
- }
- // Save a ITU T.42/Fax marker with defaults on boundaries. This is the only mode we support right now.
- static
- void SetITUFax(j_compress_ptr cinfo)
- {
- unsigned char Marker[] = "G3FAX\x00\0x07\xCA\x00\xC8";
- jpeg_write_marker(cinfo, (JPEG_APP0 + 1), Marker, 10);
- }
- // Build a profile for decoding ITU T.42/Fax JPEG streams.
- // The profile has an additional ability in the input direction of
- // gamut compress values between 85 < a < -85 and -75 < b < 125. This conforms
- // the default range for ITU/T.42 -- See RFC 2301, section 6.2.3 for details
- // L* = [0, 100]
- // a* = [-85, 85]
- // b* = [-75, 125]
- // These functions does convert the encoding of ITUFAX to floating point
- // and vice-versa. No gamut mapping is performed yet.
- static
- void ITU2Lab(const cmsUInt16Number In[3], cmsCIELab* Lab)
- {
- Lab -> L = (double) In[0] / 655.35;
- Lab -> a = (double) 170.* (In[1] - 32768.) / 65535.;
- Lab -> b = (double) 200.* (In[2] - 24576.) / 65535.;
- }
- static
- void Lab2ITU(const cmsCIELab* Lab, cmsUInt16Number Out[3])
- {
- Out[0] = (cmsUInt16Number) floor((double) (Lab -> L / 100.)* 65535. );
- Out[1] = (cmsUInt16Number) floor((double) (Lab -> a / 170.)* 65535. + 32768. );
- Out[2] = (cmsUInt16Number) floor((double) (Lab -> b / 200.)* 65535. + 24576. );
- }
- // These are the samplers-- They are passed as callbacks to cmsStageSampleCLut16bit()
- // then, cmsSample3DGrid() will sweel whole Lab gamut calling these functions
- // once for each node. In[] will contain the Lab PCS value to convert to ITUFAX
- // on PCS2ITU, or the ITUFAX value to convert to Lab in ITU2PCS
- // You can change the number of sample points if desired, the algorithm will
- // remain same. 33 points gives good accuracy, but you can reduce to 22 or less
- // is space is critical
- #define GRID_POINTS 33
- static
- int PCS2ITU(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
- {
- cmsCIELab Lab;
- cmsLabEncoded2Float(NULL, &Lab, In);
- cmsDesaturateLab(NULL, &Lab, 85, -85, 125, -75); // This function does the necessary gamut remapping
- Lab2ITU(&Lab, Out);
- return TRUE;
- UTILS_UNUSED_PARAMETER(Cargo);
- UTILS_UNUSED_PARAMETER(ContextID);
- }
- static
- int ITU2PCS(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
- {
- cmsCIELab Lab;
- ITU2Lab(In, &Lab);
- cmsFloat2LabEncoded(NULL, Out, &Lab);
- return TRUE;
- UTILS_UNUSED_PARAMETER(Cargo);
- UTILS_UNUSED_PARAMETER(ContextID);
- }
- // This function does create the virtual input profile, which decodes ITU to the profile connection space
- static
- cmsHPROFILE CreateITU2PCS_ICC(void)
- {
- cmsHPROFILE hProfile;
- cmsPipeline* AToB0;
- cmsStage* ColorMap;
- AToB0 = cmsPipelineAlloc(0, 3, 3);
- if (AToB0 == NULL) return NULL;
- ColorMap = cmsStageAllocCLut16bit(0, GRID_POINTS, 3, 3, NULL);
- if (ColorMap == NULL) return NULL;
- cmsPipelineInsertStage(NULL, AToB0, cmsAT_BEGIN, ColorMap);
- cmsStageSampleCLut16bit(NULL, ColorMap, ITU2PCS, NULL, 0);
- hProfile = cmsCreateProfilePlaceholder(0);
- if (hProfile == NULL) {
- cmsPipelineFree(NULL, AToB0);
- return NULL;
- }
- cmsWriteTag(NULL, hProfile, cmsSigAToB0Tag, AToB0);
- cmsSetColorSpace(NULL, hProfile, cmsSigLabData);
- cmsSetPCS(NULL, hProfile, cmsSigLabData);
- cmsSetDeviceClass(NULL, hProfile, cmsSigColorSpaceClass);
- cmsPipelineFree(NULL, AToB0);
- return hProfile;
- }
- // This function does create the virtual output profile, with the necessary gamut mapping
- static
- cmsHPROFILE CreatePCS2ITU_ICC(void)
- {
- cmsHPROFILE hProfile;
- cmsPipeline* BToA0;
- cmsStage* ColorMap;
- BToA0 = cmsPipelineAlloc(0, 3, 3);
- if (BToA0 == NULL) return NULL;
- ColorMap = cmsStageAllocCLut16bit(0, GRID_POINTS, 3, 3, NULL);
- if (ColorMap == NULL) return NULL;
- cmsPipelineInsertStage(NULL, BToA0, cmsAT_BEGIN, ColorMap);
- cmsStageSampleCLut16bit(NULL, ColorMap, PCS2ITU, NULL, 0);
- hProfile = cmsCreateProfilePlaceholder(0);
- if (hProfile == NULL) {
- cmsPipelineFree(NULL, BToA0);
- return NULL;
- }
- cmsWriteTag(NULL, hProfile, cmsSigBToA0Tag, BToA0);
- cmsSetColorSpace(NULL, hProfile, cmsSigLabData);
- cmsSetPCS(NULL, hProfile, cmsSigLabData);
- cmsSetDeviceClass(NULL, hProfile, cmsSigColorSpaceClass);
- cmsPipelineFree(NULL, BToA0);
- return hProfile;
- }
- #define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))
- static
- cmsBool ProcessPhotoshopAPP13(JOCTET *data, int datalen)
- {
- int i;
- for (i = 14; i < datalen; )
- {
- long len;
- unsigned int type;
- if (!(GETJOCTET(data[i] ) == 0x38 &&
- GETJOCTET(data[i+1]) == 0x42 &&
- GETJOCTET(data[i+2]) == 0x49 &&
- GETJOCTET(data[i+3]) == 0x4D)) break; // Not recognized
- i += 4; // identifying string
- type = (unsigned int) (GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]));
- i += 2; // resource type
- i += GETJOCTET(data[i]) + ((GETJOCTET(data[i]) & 1) ? 1 : 2); // resource name
- len = ((((GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]))<<8) +
- GETJOCTET(data[i+2]))<<8) + GETJOCTET(data[i+3]);
- if (len < 0) return FALSE; // Keep bug hunters away
- i += 4; // Size
- if (type == 0x03ED && len >= 16) {
- Decompressor.X_density = (UINT16) PS_FIXED_TO_FLOAT(GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]),
- GETJOCTET(data[i+2]<<8) + GETJOCTET(data[i+3]));
- Decompressor.Y_density = (UINT16) PS_FIXED_TO_FLOAT(GETJOCTET(data[i+8]<<8) + GETJOCTET(data[i+9]),
- GETJOCTET(data[i+10]<<8) + GETJOCTET(data[i+11]));
- // Set the density unit to 1 since the
- // Vertical and Horizontal resolutions
- // are specified in Pixels per inch
- Decompressor.density_unit = 0x01;
- return TRUE;
- }
- i += len + ((len & 1) ? 1 : 0); // Alignment
- }
- return FALSE;
- }
- static
- cmsBool HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
- {
- while (ptr) {
- if (ptr -> marker == (JPEG_APP0 + 13) && ptr -> data_length > 9)
- {
- JOCTET* data = ptr -> data;
- if(GETJOCTET(data[0]) == 0x50 &&
- GETJOCTET(data[1]) == 0x68 &&
- GETJOCTET(data[2]) == 0x6F &&
- GETJOCTET(data[3]) == 0x74 &&
- GETJOCTET(data[4]) == 0x6F &&
- GETJOCTET(data[5]) == 0x73 &&
- GETJOCTET(data[6]) == 0x68 &&
- GETJOCTET(data[7]) == 0x6F &&
- GETJOCTET(data[8]) == 0x70) {
- ProcessPhotoshopAPP13(data, ptr -> data_length);
- return TRUE;
- }
- }
- ptr = ptr -> next;
- }
- return FALSE;
- }
- typedef unsigned short uint16_t;
- typedef unsigned char uint8_t;
- typedef unsigned int uint32_t;
- #define INTEL_BYTE_ORDER 0x4949
- #define XRESOLUTION 0x011a
- #define YRESOLUTION 0x011b
- #define RESOLUTION_UNIT 0x128
- // Abort if crafted file
- static
- void craftedFile(void)
- {
- FatalError("Corrupted EXIF data");
- }
- // Read a 16-bit word
- static
- uint16_t read16(uint8_t* arr, size_t pos, int swapBytes, size_t max)
- {
- if (pos + 2 >= max)
- {
- craftedFile();
- return 0;
- }
- else
- {
- uint8_t b1 = arr[pos];
- uint8_t b2 = arr[pos + 1];
- return (swapBytes) ? ((b2 << 8) | b1) : ((b1 << 8) | b2);
- }
- }
- // Read a 32-bit word
- static
- uint32_t read32(uint8_t* arr, size_t pos, int swapBytes, size_t max)
- {
- if (pos + 4 >= max)
- {
- craftedFile();
- return 0;
- }
- else
- {
- if (!swapBytes) {
- return (arr[pos] << 24) | (arr[pos + 1] << 16) | (arr[pos + 2] << 8) | arr[pos + 3];
- }
- return arr[pos] | (arr[pos + 1] << 8) | (arr[pos + 2] << 16) | (arr[pos + 3] << 24);
- }
- }
- static
- int read_tag(uint8_t* arr, int pos, int swapBytes, void* dest, size_t max)
- {
- // Format should be 5 over here (rational)
- uint32_t format = read16(arr, pos + 2, swapBytes, max);
- // Components should be 1
- uint32_t components = read32(arr, pos + 4, swapBytes, max);
- // Points to the value
- uint32_t offset;
- // sanity
- if (components != 1) return 0;
- if (format == 3)
- offset = pos + 8;
- else
- offset = read32(arr, pos + 8, swapBytes, max);
- switch (format) {
- case 5: // Rational
- {
- double num = read32(arr, offset, swapBytes, max);
- double den = read32(arr, offset + 4, swapBytes, max);
- *(double *) dest = num / den;
- }
- break;
- case 3: // uint 16
- *(int*) dest = read16(arr, offset, swapBytes, max);
- break;
- default: return 0;
- }
- return 1;
- }
- // Handler for EXIF data
- static
- cmsBool HandleEXIF(struct jpeg_decompress_struct* cinfo)
- {
- jpeg_saved_marker_ptr ptr;
- uint32_t ifd_ofs;
- int pos = 0, swapBytes = 0;
- uint32_t i, numEntries;
- double XRes = -1, YRes = -1;
- int Unit = 2; // Inches
- for (ptr = cinfo ->marker_list; ptr; ptr = ptr ->next) {
- if ((ptr ->marker == JPEG_APP0+1) && ptr ->data_length > 6) {
- JOCTET* data = ptr -> data;
- size_t max = ptr->data_length;
- if (memcmp(data, "Exif\0\0", 6) == 0) {
- data += 6; // Skip EXIF marker
- // 8 byte TIFF header
- // first two determine byte order
- pos = 0;
- if (read16(data, pos, 0, max) == INTEL_BYTE_ORDER) {
- swapBytes = 1;
- }
- pos += 2;
- // next two bytes are always 0x002A (TIFF version)
- pos += 2;
- // offset to Image File Directory (includes the previous 8 bytes)
- ifd_ofs = read32(data, pos, swapBytes, max);
- // Search the directory for resolution tags
- numEntries = read16(data, ifd_ofs, swapBytes, max);
- for (i=0; i < numEntries; i++) {
- uint32_t entryOffset = ifd_ofs + 2 + (12 * i);
- uint32_t tag = read16(data, entryOffset, swapBytes, max);
- switch (tag) {
- case RESOLUTION_UNIT:
- if (!read_tag(data, entryOffset, swapBytes, &Unit, max)) return FALSE;
- break;
- case XRESOLUTION:
- if (!read_tag(data, entryOffset, swapBytes, &XRes, max)) return FALSE;
- break;
- case YRESOLUTION:
- if (!read_tag(data, entryOffset, swapBytes, &YRes, max)) return FALSE;
- break;
- default:;
- }
- }
- // Proceed if all found
- if (XRes != -1 && YRes != -1)
- {
- // 1 = None
- // 2 = inches
- // 3 = cm
- switch (Unit) {
- case 2:
- cinfo ->X_density = (UINT16) floor(XRes + 0.5);
- cinfo ->Y_density = (UINT16) floor(YRes + 0.5);
- break;
- case 1:
- cinfo ->X_density = (UINT16) floor(XRes * 2.54 + 0.5);
- cinfo ->Y_density = (UINT16) floor(YRes * 2.54 + 0.5);
- break;
- default: return FALSE;
- }
- cinfo ->density_unit = 1; /* 1 for dots/inch, or 2 for dots/cm.*/
- }
- }
- }
- }
- return FALSE;
- }
- static
- cmsBool OpenInput(const char* FileName)
- {
- int m;
- lIsITUFax = FALSE;
- InFile = fopen(FileName, "rb");
- if (InFile == NULL) {
- FatalError("Cannot open '%s'", FileName);
- }
- // Now we can initialize the JPEG decompression object.
- Decompressor.err = jpeg_std_error(&ErrorHandler.pub);
- ErrorHandler.pub.error_exit = my_error_exit;
- ErrorHandler.pub.output_message = my_error_exit;
- jpeg_create_decompress(&Decompressor);
- jpeg_stdio_src(&Decompressor, InFile);
- for (m = 0; m < 16; m++)
- jpeg_save_markers(&Decompressor, JPEG_APP0 + m, 0xFFFF);
- // setup_read_icc_profile(&Decompressor);
- fseek(InFile, 0, SEEK_SET);
- jpeg_read_header(&Decompressor, TRUE);
- return TRUE;
- }
- static
- cmsBool OpenOutput(const char* FileName)
- {
- OutFile = fopen(FileName, "wb");
- if (OutFile == NULL) {
- FatalError("Cannot create '%s'", FileName);
- }
- Compressor.err = jpeg_std_error(&ErrorHandler.pub);
- ErrorHandler.pub.error_exit = my_error_exit;
- ErrorHandler.pub.output_message = my_error_exit;
- Compressor.input_components = Compressor.num_components = 4;
- jpeg_create_compress(&Compressor);
- jpeg_stdio_dest(&Compressor, OutFile);
- return TRUE;
- }
- static
- cmsBool Done(void)
- {
- jpeg_destroy_decompress(&Decompressor);
- jpeg_destroy_compress(&Compressor);
- return fclose(InFile) + fclose(OutFile);
- }
- // Build up the pixeltype descriptor
- static
- cmsUInt32Number GetInputPixelType(void)
- {
- int space, bps, extra, ColorChannels, Flavor;
- lIsITUFax = IsITUFax(Decompressor.marker_list);
- lIsPhotoshopApp13 = HandlePhotoshopAPP13(Decompressor.marker_list);
- lIsEXIF = HandleEXIF(&Decompressor);
- ColorChannels = Decompressor.num_components;
- extra = 0; // Alpha = None
- bps = 1; // 8 bits
- Flavor = 0; // Vanilla
- if (lIsITUFax) {
- space = PT_Lab;
- Decompressor.out_color_space = JCS_YCbCr; // Fake to don't touch
- }
- else
- switch (Decompressor.jpeg_color_space) {
- case JCS_GRAYSCALE: // monochrome
- space = PT_GRAY;
- Decompressor.out_color_space = JCS_GRAYSCALE;
- break;
- case JCS_RGB: // red/green/blue
- space = PT_RGB;
- Decompressor.out_color_space = JCS_RGB;
- break;
- case JCS_YCbCr: // Y/Cb/Cr (also known as YUV)
- space = PT_RGB; // Let IJG code to do the conversion
- Decompressor.out_color_space = JCS_RGB;
- break;
- case JCS_CMYK: // C/M/Y/K
- space = PT_CMYK;
- Decompressor.out_color_space = JCS_CMYK;
- if (Decompressor.saw_Adobe_marker) // Adobe keeps CMYK inverted, so change flavor
- Flavor = 1; // from vanilla to chocolate
- break;
- case JCS_YCCK: // Y/Cb/Cr/K
- space = PT_CMYK;
- Decompressor.out_color_space = JCS_CMYK;
- if (Decompressor.saw_Adobe_marker) // ditto
- Flavor = 1;
- break;
- default:
- FatalError("Unsupported color space (0x%x)", Decompressor.jpeg_color_space);
- return 0;
- }
- return (EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|COLORSPACE_SH(space)|FLAVOR_SH(Flavor));
- }
- // Rearrange pixel type to build output descriptor
- static
- cmsUInt32Number ComputeOutputFormatDescriptor(cmsUInt32Number dwInput, int OutColorSpace)
- {
- int IsPlanar = T_PLANAR(dwInput);
- int Channels = 0;
- int Flavor = 0;
- switch (OutColorSpace) {
- case PT_GRAY:
- Channels = 1;
- break;
- case PT_RGB:
- case PT_CMY:
- case PT_Lab:
- case PT_YUV:
- case PT_YCbCr:
- Channels = 3;
- break;
- case PT_CMYK:
- if (Compressor.write_Adobe_marker) // Adobe keeps CMYK inverted, so change flavor to chocolate
- Flavor = 1;
- Channels = 4;
- break;
- default:
- FatalError("Unsupported output color space");
- }
- return (COLORSPACE_SH(OutColorSpace)|PLANAR_SH(IsPlanar)|CHANNELS_SH(Channels)|BYTES_SH(1)|FLAVOR_SH(Flavor));
- }
- // Equivalence between ICC color spaces and lcms color spaces
- static
- int GetProfileColorSpace(cmsContext ContextID, cmsHPROFILE hProfile)
- {
- cmsColorSpaceSignature ProfileSpace = cmsGetColorSpace(ContextID, hProfile);
- return _cmsLCMScolorSpace(ContextID, ProfileSpace);
- }
- static
- int GetDevicelinkColorSpace(cmsContext ContextID, cmsHPROFILE hProfile)
- {
- cmsColorSpaceSignature ProfileSpace = cmsGetPCS(ContextID, hProfile);
- return _cmsLCMScolorSpace(ContextID, ProfileSpace);
- }
- // From TRANSUPP
- static
- void jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo)
- {
- jpeg_saved_marker_ptr marker;
- /* In the current implementation, we don't actually need to examine the
- * option flag here; we just copy everything that got saved.
- * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
- * if the encoder library already wrote one.
- */
- for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
- if (dstinfo->write_JFIF_header &&
- marker->marker == JPEG_APP0 &&
- marker->data_length >= 5 &&
- GETJOCTET(marker->data[0]) == 0x4A &&
- GETJOCTET(marker->data[1]) == 0x46 &&
- GETJOCTET(marker->data[2]) == 0x49 &&
- GETJOCTET(marker->data[3]) == 0x46 &&
- GETJOCTET(marker->data[4]) == 0)
- continue; /* reject duplicate JFIF */
- if (dstinfo->write_Adobe_marker &&
- marker->marker == JPEG_APP0+14 &&
- marker->data_length >= 5 &&
- GETJOCTET(marker->data[0]) == 0x41 &&
- GETJOCTET(marker->data[1]) == 0x64 &&
- GETJOCTET(marker->data[2]) == 0x6F &&
- GETJOCTET(marker->data[3]) == 0x62 &&
- GETJOCTET(marker->data[4]) == 0x65)
- continue; /* reject duplicate Adobe */
- jpeg_write_marker(dstinfo, marker->marker,
- marker->data, marker->data_length);
- }
- }
- static
- void WriteOutputFields(int OutputColorSpace)
- {
- J_COLOR_SPACE in_space, jpeg_space;
- int components;
- switch (OutputColorSpace) {
- case PT_GRAY: in_space = jpeg_space = JCS_GRAYSCALE;
- components = 1;
- break;
- case PT_RGB: in_space = JCS_RGB;
- jpeg_space = JCS_YCbCr;
- components = 3;
- break; // red/green/blue
- case PT_YCbCr: in_space = jpeg_space = JCS_YCbCr;
- components = 3;
- break; // Y/Cb/Cr (also known as YUV)
- case PT_CMYK: in_space = JCS_CMYK;
- jpeg_space = JCS_YCCK;
- components = 4;
- break; // C/M/Y/components
- case PT_Lab: in_space = jpeg_space = JCS_YCbCr;
- components = 3;
- break; // Fake to don't touch
- default:
- FatalError("Unsupported output color space");
- return;
- }
- if (jpegQuality >= 100) {
- // avoid destructive conversion when asking for lossless compression
- jpeg_space = in_space;
- }
- Compressor.in_color_space = in_space;
- Compressor.jpeg_color_space = jpeg_space;
- Compressor.input_components = Compressor.num_components = components;
- jpeg_set_defaults(&Compressor);
- jpeg_set_colorspace(&Compressor, jpeg_space);
- // Make sure to pass resolution through
- if (OutputColorSpace == PT_CMYK)
- Compressor.write_JFIF_header = 1;
- // Avoid subsampling on high quality factor
- jpeg_set_quality(&Compressor, jpegQuality, 1);
- if (jpegQuality >= 70) {
- int i;
- for(i=0; i < Compressor.num_components; i++) {
- Compressor.comp_info[i].h_samp_factor = 1;
- Compressor.comp_info[i].v_samp_factor = 1;
- }
- }
- }
- static
- void DoEmbedProfile(const char* ProfileFile)
- {
- FILE* f;
- size_t size, EmbedLen;
- cmsUInt8Number* EmbedBuffer;
- f = fopen(ProfileFile, "rb");
- if (f == NULL) return;
- size = cmsfilelength(f);
- EmbedBuffer = (cmsUInt8Number*) malloc(size + 1);
- EmbedLen = fread(EmbedBuffer, 1, size, f);
- fclose(f);
- EmbedBuffer[EmbedLen] = 0;
- write_icc_profile (&Compressor, EmbedBuffer, (unsigned int) EmbedLen);
- free(EmbedBuffer);
- }
- static
- int DoTransform(cmsContext ContextID, cmsHTRANSFORM hXForm, int OutputColorSpace)
- {
- JSAMPROW ScanLineIn;
- JSAMPROW ScanLineOut;
- //Preserve resolution values from the original
- // (Thanks to Robert Bergs for finding out this bug)
- Compressor.density_unit = Decompressor.density_unit;
- Compressor.X_density = Decompressor.X_density;
- Compressor.Y_density = Decompressor.Y_density;
- // Compressor.write_JFIF_header = 1;
- jpeg_start_decompress(&Decompressor);
- jpeg_start_compress(&Compressor, TRUE);
- if (OutputColorSpace == PT_Lab)
- SetITUFax(&Compressor);
- // Embed the profile if needed
- if (EmbedProfile && cOutProf)
- DoEmbedProfile(cOutProf);
- ScanLineIn = (JSAMPROW) malloc((size_t) Decompressor.output_width * Decompressor.num_components);
- ScanLineOut = (JSAMPROW) malloc((size_t) Compressor.image_width * Compressor.num_components);
- while (Decompressor.output_scanline <
- Decompressor.output_height) {
- jpeg_read_scanlines(&Decompressor, &ScanLineIn, 1);
- cmsDoTransform(ContextID, hXForm, ScanLineIn, ScanLineOut, Decompressor.output_width);
- jpeg_write_scanlines(&Compressor, &ScanLineOut, 1);
- }
- free(ScanLineIn);
- free(ScanLineOut);
- jpeg_finish_decompress(&Decompressor);
- jpeg_finish_compress(&Compressor);
- return TRUE;
- }
- // Transform one image
- static
- int TransformImage(cmsContext ContextID, char *cDefInpProf, char *cOutputProf)
- {
- cmsHPROFILE hIn, hOut, hProof;
- cmsHTRANSFORM xform;
- cmsUInt32Number wInput, wOutput;
- int OutputColorSpace;
- cmsUInt32Number dwFlags = 0;
- cmsUInt32Number EmbedLen;
- cmsUInt8Number* EmbedBuffer;
- cmsSetAdaptationState(ContextID, ObserverAdaptationState);
- if (BlackPointCompensation) {
- dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
- }
- switch (PrecalcMode) {
- case 0: dwFlags |= cmsFLAGS_NOOPTIMIZE; break;
- case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
- case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
- default:;
- }
- if (GamutCheck) {
- dwFlags |= cmsFLAGS_GAMUTCHECK;
- cmsSetAlarmCodes(ContextID, Alarm);
- }
- // Take input color space
- wInput = GetInputPixelType();
- if (lIsDeviceLink) {
- hIn = cmsOpenProfileFromFile(ContextID, cDefInpProf, "r");
- hOut = NULL;
- hProof = NULL;
- }
- else {
- if (!IgnoreEmbedded && read_icc_profile(&Decompressor, &EmbedBuffer, &EmbedLen))
- {
- hIn = cmsOpenProfileFromMem(ContextID, EmbedBuffer, EmbedLen);
- if (Verbose) {
- fprintf(stdout, " (Embedded profile found)\n");
- PrintProfileInformation(ContextID, hIn);
- fflush(stdout);
- }
- if (hIn != NULL && SaveEmbedded != NULL)
- SaveMemoryBlock(EmbedBuffer, EmbedLen, SaveEmbedded);
- free(EmbedBuffer);
- }
- else
- {
- // Default for ITU/Fax
- if (cDefInpProf == NULL && T_COLORSPACE(wInput) == PT_Lab)
- cDefInpProf = "*Lab";
- if (cDefInpProf != NULL && cmsstrcasecmp(cDefInpProf, "*lab") == 0)
- hIn = CreateITU2PCS_ICC();
- else
- hIn = OpenStockProfile(0, cDefInpProf);
- }
- if (cOutputProf != NULL && cmsstrcasecmp(cOutputProf, "*lab") == 0)
- hOut = CreatePCS2ITU_ICC();
- else
- hOut = OpenStockProfile(0, cOutputProf);
- hProof = NULL;
- if (cProofing != NULL) {
- hProof = OpenStockProfile(0, cProofing);
- if (hProof == NULL) {
- FatalError("Proofing profile couldn't be read.");
- }
- dwFlags |= cmsFLAGS_SOFTPROOFING;
- }
- }
- if (!hIn)
- FatalError("Input profile couldn't be read.");
- if (!lIsDeviceLink && !hOut)
- FatalError("Output profile couldn't be read.");
- // Assure both, input profile and input JPEG are on same colorspace
- if (cmsGetColorSpace(ContextID, hIn) != _cmsICCcolorSpace(ContextID, T_COLORSPACE(wInput)))
- FatalError("Input profile is not operating in proper color space");
- // Output colorspace is given by output profile
- if (lIsDeviceLink) {
- OutputColorSpace = GetDevicelinkColorSpace(ContextID, hIn);
- }
- else {
- OutputColorSpace = GetProfileColorSpace(ContextID, hOut);
- }
- jpeg_copy_critical_parameters(&Decompressor, &Compressor);
- WriteOutputFields(OutputColorSpace);
- wOutput = ComputeOutputFormatDescriptor(wInput, OutputColorSpace);
- xform = cmsCreateProofingTransform(ContextID, hIn, wInput,
- hOut, wOutput,
- hProof, Intent,
- ProofingIntent, dwFlags);
- if (xform == NULL)
- FatalError("Cannot transform by using the profiles");
- DoTransform(ContextID, xform, OutputColorSpace);
- jcopy_markers_execute(&Decompressor, &Compressor);
- cmsDeleteTransform(ContextID, xform);
- cmsCloseProfile(ContextID, hIn);
- cmsCloseProfile(ContextID, hOut);
- if (hProof) cmsCloseProfile(ContextID, hProof);
- return 1;
- }
- static
- void Help(cmsContext ContextID, int level)
- {
- UTILS_UNUSED_PARAMETER(level);
- fprintf(stderr, "usage: jpgicc [flags] input.jpg output.jpg\n");
- fprintf(stderr, "\nflags:\n\n");
- fprintf(stderr, "-v - Verbose\n");
- fprintf(stderr, "-i<profile> - Input profile (defaults to sRGB)\n");
- fprintf(stderr, "-o<profile> - Output profile (defaults to sRGB)\n");
- PrintBuiltins();
- PrintRenderingIntents(ContextID);
- fprintf(stderr, "-b - Black point compensation\n");
- fprintf(stderr, "-d<0..1> - Observer adaptation state (abs.col. only)\n");
- fprintf(stderr, "-n - Ignore embedded profile\n");
- fprintf(stderr, "-e - Embed destination profile\n");
- fprintf(stderr, "-s<new profile> - Save embedded profile as <new profile>\n");
- fprintf(stderr, "\n");
- fprintf(stderr, "-c<0,1,2,3> - Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes) [defaults to 1]\n");
- fprintf(stderr, "\n");
- fprintf(stderr, "-p<profile> - Soft proof profile\n");
- fprintf(stderr, "-m<0,1,2,3> - SoftProof intent\n");
- fprintf(stderr, "-g - Marks out-of-gamut colors on softproof\n");
- fprintf(stderr, "-!<r>,<g>,<b> - Out-of-gamut marker channel values\n");
- fprintf(stderr, "\n");
- fprintf(stderr, "-q<0..100> - Output JPEG quality\n");
- fprintf(stderr, "Examples:\n\n"
- "To color correct from scanner to sRGB:\n"
- "\tjpgicc -iscanner.icm in.jpg out.jpg\n"
- "To convert from monitor1 to monitor2:\n"
- "\tjpgicc -imon1.icm -omon2.icm in.jpg out.jpg\n"
- "To make a CMYK separation:\n"
- "\tjpgicc -oprinter.icm inrgb.jpg outcmyk.jpg\n"
- "To recover sRGB from a CMYK separation:\n"
- "\tjpgicc -iprinter.icm incmyk.jpg outrgb.jpg\n"
- "To convert from CIELab ITU/Fax JPEG to sRGB\n"
- "\tjpgicc in.jpg out.jpg\n\n");
- fprintf(stderr, "This program is intended to be a demo of the Little CMS\n"
- "color engine. Both lcms and this program are open source.\n"
- "You can obtain both in source code at https://www.littlecms.com\n"
- "For suggestions, comments, bug reports etc. send mail to\n"
- "info@littlecms.com\n\n");
- exit(0);
- }
- // The toggles stuff
- static
- void HandleSwitches(cmsContext ContextID, int argc, char *argv[])
- {
- int s;
- 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) {
- switch (s)
- {
- case '-':
- if (strcmp(xoptarg, "help") == 0)
- {
- Help(ContextID, 0);
- }
- else
- {
- FatalError("Unknown option - run without args to see valid ones.\n");
- }
- break;
- case 'b':
- case 'B':
- BlackPointCompensation = TRUE;
- break;
- case 'd':
- case 'D': ObserverAdaptationState = atof(xoptarg);
- if (ObserverAdaptationState < 0 ||
- ObserverAdaptationState > 1.0)
- FatalError("Adaptation state should be 0..1");
- break;
- case 'v':
- case 'V':
- Verbose = TRUE;
- break;
- case 'i':
- case 'I':
- if (lIsDeviceLink)
- FatalError("Device-link already specified");
- cInpProf = xoptarg;
- break;
- case 'o':
- case 'O':
- if (lIsDeviceLink)
- FatalError("Device-link already specified");
- cOutProf = xoptarg;
- break;
- case 'l':
- case 'L':
- if (cInpProf != NULL || cOutProf != NULL)
- FatalError("input/output profiles already specified");
- cInpProf = xoptarg;
- lIsDeviceLink = TRUE;
- break;
- case 'p':
- case 'P':
- cProofing = xoptarg;
- break;
- case 't':
- case 'T':
- Intent = atoi(xoptarg);
- break;
- case 'N':
- case 'n':
- IgnoreEmbedded = TRUE;
- break;
- case 'e':
- case 'E':
- EmbedProfile = TRUE;
- break;
- case 'g':
- case 'G':
- GamutCheck = TRUE;
- break;
- case 'c':
- case 'C':
- PrecalcMode = atoi(xoptarg);
- if (PrecalcMode < 0 || PrecalcMode > 2)
- FatalError("Unknown precalc mode '%d'", PrecalcMode);
- break;
- case 'H':
- case 'h': {
- int a = atoi(xoptarg);
- Help(ContextID, a);
- }
- break;
- case 'q':
- case 'Q':
- jpegQuality = atoi(xoptarg);
- if (jpegQuality > 100) jpegQuality = 100;
- if (jpegQuality < 0) jpegQuality = 0;
- break;
- case 'm':
- case 'M':
- ProofingIntent = atoi(xoptarg);
- break;
- case 's':
- case 'S': SaveEmbedded = xoptarg;
- break;
- case '!':
- if (sscanf(xoptarg, "%hu,%hu,%hu", &Alarm[0], &Alarm[1], &Alarm[2]) == 3) {
- int i;
- for (i=0; i < 3; i++) {
- Alarm[i] = (Alarm[i] << 8) | Alarm[i];
- }
- }
- break;
- default:
- FatalError("Unknown option - run without args to see valid ones");
- }
- }
- }
- int main(int argc, char* argv[])
- {
- cmsContext ContextID = cmsCreateContext(NULL, NULL);
- fprintf(stderr, "Little CMS ICC profile applier for JPEG - v3.4 [LittleCMS %2.2f]\n\n", cmsGetEncodedCMMversion() / 1000.0);
- fprintf(stderr, "Copyright (c) 1998-2023 Marti Maria Saguer. See COPYING file for details.\n");
- fflush(stderr);
- InitUtils(ContextID, "jpgicc");
- HandleSwitches(ContextID, argc, argv);
- if ((argc - xoptind) != 2) {
- Help(ContextID, 0);
- }
- OpenInput(argv[xoptind]);
- OpenOutput(argv[xoptind+1]);
- TransformImage(ContextID, cInpProf, cOutProf);
- if (Verbose) { fprintf(stdout, "\n"); fflush(stdout); }
- Done();
- cmsDeleteContext(ContextID);
- return 0;
- }
|