static cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile) { cmsToneCurve *GrayTRC, *RevGrayTRC; cmsPipeline* Lut; cmsContext ContextID = cmsGetProfileContextID(hProfile); GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag); if (GrayTRC == NULL) return NULL; RevGrayTRC = cmsReverseToneCurve(GrayTRC); if (RevGrayTRC == NULL) return NULL; Lut = cmsPipelineAlloc(ContextID, 3, 1); if (Lut == NULL) { cmsFreeToneCurve(RevGrayTRC); return NULL; } if (cmsGetPCS(hProfile) == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL)); } else { cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickYMatrix, NULL)); } cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &RevGrayTRC)); cmsFreeToneCurve(RevGrayTRC); return Lut; }
/*static cmsPipeline* _cmsReadFloatInputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat) { cmsContext ContextID = cmsGetProfileContextID(hProfile); cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile); if (Lut == NULL) return NULL; // If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding, // and since the formatter has already accomodated to 0..1.0, we should undo this change if ( spc == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)); } else if (spc == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)); } return Lut; } */ static cmsPipeline* _cmsReadFloatInputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat) { cmsContext ContextID = cmsGetProfileContextID(hProfile); cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile); cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); if (Lut == NULL) return NULL; // input and output of transform are in lcms 0..1 encoding. If XYZ or Lab spaces are used, // these need to be normalized into the appropriate ranges (Lab = 100,0,0, XYZ=1.0,1.0,1.0) if ( spc == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)); } else if (spc == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)); } if ( PCS == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)); } else if( PCS == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)); } return Lut; }
// Read the AToD0 tag, adjusting the encoding of Lab or XYZ if neded static cmsPipeline* _cmsReadFloatDevicelinkTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat) { cmsContext ContextID = cmsGetProfileContextID(hProfile); cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile); if (Lut == NULL) return NULL; if (spc == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)); } else if (spc == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)); } if (PCS == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)); } else if (PCS == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)); } return Lut; }
static cmsPipeline* _cmsReadFloatOutputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat) { cmsContext ContextID = cmsGetProfileContextID(hProfile); cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); cmsColorSpaceSignature dataSpace = cmsGetColorSpace(hProfile); if (Lut == NULL) return NULL; // If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding, // and since the formatter has already accomodated to 0..1.0, we should undo this change if ( PCS == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)); } else if (PCS == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)); } // the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline if ( dataSpace == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)); } else if ( dataSpace == cmsSigXYZData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)); } return Lut; }
// Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc // is adjusted here in order to create a LUT that takes care of all those details cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent) { cmsTagTypeSignature OriginalType; cmsTagSignature tag16 = Device2PCS16[Intent]; cmsTagSignature tagFloat = Device2PCSFloat[Intent]; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence // Floating point LUT are always V4, so no adjustment is required return cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); } // Revert to perceptual if no tag is found if (!cmsIsTag(hProfile, tag16)) { tag16 = Device2PCS16[0]; } if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? // Check profile version and LUT type. Do the necessary adjustments if needed // First read the tag cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL; // After reading it, we have now info about the original type OriginalType = _cmsGetTagTrueType(hProfile, tag16); // The profile owns the Lut, so we need to copy it Lut = cmsPipelineDup(Lut); // We need to adjust data only for Lab16 on output if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) return Lut; // If the input is Lab, add also a conversion at the begin if (cmsGetColorSpace(hProfile) == cmsSigLabData) cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)); // Add a matrix for conversion V2 to V4 Lab PCS cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)); return Lut; } // Lut was not found, try to create a matrix-shaper // Check if this is a grayscale profile. if (cmsGetColorSpace(hProfile) == cmsSigGrayData) { // if so, build appropiate conversion tables. // The tables are the PCS iluminant, scaled across GrayTRC return BuildGrayInputMatrixPipeline(hProfile); } // Not gray, create a normal matrix-shaper return BuildRGBInputMatrixShaper(hProfile); }
static cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsToneCurve *Shapes[3], *InvShapes[3]; cmsMAT3 Mat, Inv; int i, j; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; if (!_cmsMAT3inverse(&Mat, &Inv)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0; for (i=0; i < 3; i++) for (j=0; j < 3; j++) Inv.v[i].n[j] *= OutpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; InvShapes[0] = cmsReverseToneCurve(Shapes[0]); InvShapes[1] = cmsReverseToneCurve(Shapes[1]); InvShapes[2] = cmsReverseToneCurve(Shapes[2]); if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { return NULL; } Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { // Note that it is certainly possible a single profile would have a LUT based // tag for output working in lab and a matrix-shaper for the fallback cases. // This is not allowed by the spec, but this code is tolerant to those cases if (cmsGetPCS(hProfile) == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)); } cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)); cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes)); } cmsFreeToneCurveTriple(InvShapes); return Lut; }
// This one includes abstract profiles as well. Matrix-shaper cannot be obtained on that device class. The // tag name here may default to AToB0 cmsPipeline* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, int Intent) { cmsPipeline* Lut; cmsTagTypeSignature OriginalType; cmsTagSignature tag16 = Device2PCS16[Intent]; cmsTagSignature tagFloat = Device2PCSFloat[Intent]; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence // Floating point LUT are always V4, no adjustment is required return cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); } tagFloat = Device2PCSFloat[0]; if (cmsIsTag(hProfile, tagFloat)) { return cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); } if (!cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? tag16 = Device2PCS16[0]; if (!cmsIsTag(hProfile, tag16)) return NULL; } // Check profile version and LUT type. Do the necessary adjustments if needed // Read the tag Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL; // The profile owns the Lut, so we need to copy it Lut = cmsPipelineDup(Lut); // After reading it, we have info about the original type OriginalType = _cmsGetTagTrueType(hProfile, tag16); // We need to adjust data for Lab16 on output if (OriginalType != cmsSigLut16Type) return Lut; // Here it is possible to get Lab on both sides if (cmsGetPCS(hProfile) == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)); } if (cmsGetColorSpace(hProfile) == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)); } return Lut; }
static cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsToneCurve *Shapes[3], *InvShapes[3]; cmsMAT3 Mat, Inv; int i, j; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; if (!_cmsMAT3inverse(&Mat, &Inv)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0; for (i=0; i < 3; i++) for (j=0; j < 3; j++) Inv.v[i].n[j] *= OutpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; InvShapes[0] = cmsReverseToneCurve(Shapes[0]); InvShapes[1] = cmsReverseToneCurve(Shapes[1]); InvShapes[2] = cmsReverseToneCurve(Shapes[2]); if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { return NULL; } Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)); cmsStage* mpe = cmsStageAllocToneCurves(ContextID, 3, InvShapes); if (mpe) cmsPipelineInsertStage(Lut, cmsAT_END, mpe); else { cmsPipelineFree(Lut); Lut = NULL; } } cmsFreeToneCurveTriple(InvShapes); return Lut; }
// Gray input pipeline static cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) { cmsToneCurve *GrayTRC; cmsPipeline* Lut; cmsContext ContextID = cmsGetProfileContextID(hProfile); GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag); if (GrayTRC == NULL) return NULL; Lut = cmsPipelineAlloc(ContextID, 1, 3); if (Lut == NULL) goto Error; if (cmsGetPCS(hProfile) == cmsSigLabData) { // In this case we implement the profile as an identity matrix plus 3 tone curves cmsUInt16Number Zero[2] = { 0x8080, 0x8080 }; cmsToneCurve* EmptyTab; cmsToneCurve* LabCurves[3]; EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero); if (EmptyTab == NULL) goto Error; LabCurves[0] = GrayTRC; LabCurves[1] = EmptyTab; LabCurves[2] = EmptyTab; if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, OneToThreeInputMatrix, NULL)) || !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, LabCurves))) { cmsFreeToneCurve(EmptyTab); goto Error; } cmsFreeToneCurve(EmptyTab); } else { if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &GrayTRC)) || !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, GrayInputMatrix, NULL))) goto Error; } return Lut; Error: cmsFreeToneCurve(GrayTRC); cmsPipelineFree(Lut); return NULL; }
// RGB Matrix shaper static cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsMAT3 Mat; cmsToneCurve *Shapes[3]; cmsContext ContextID = cmsGetProfileContextID(hProfile); int i, j; if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix output comes in 0..0xffff range, so // we need to adjust the output by a factor of (0x10000/0xffff) to put data in // a 1.16 range, and then a >> 1 to obtain 1.15. The total factor is (65536.0)/(65535.0*2) for (i=0; i < 3; i++) for (j=0; j < 3; j++) Mat.v[i].n[j] *= InpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, Shapes)) || !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Mat, NULL))) goto Error; // Note that it is certainly possible a single profile would have a LUT based // tag for output working in lab and a matrix-shaper for the fallback cases. // This is not allowed by the spec, but this code is tolerant to those cases if (cmsGetPCS(hProfile) == cmsSigLabData) { if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID))) goto Error; } } return Lut; Error: cmsPipelineFree(Lut); return NULL; }
// 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(BToA0, cmsAT_BEGIN, ColorMap); cmsStageSampleCLut16bit(ColorMap, PCS2ITU, NULL, 0); hProfile = cmsCreateProfilePlaceholder(0); if (hProfile == NULL) { cmsPipelineFree(BToA0); return NULL; } cmsWriteTag(hProfile, cmsSigBToA0Tag, BToA0); cmsSetColorSpace(hProfile, cmsSigLabData); cmsSetPCS(hProfile, cmsSigLabData); cmsSetDeviceClass(hProfile, cmsSigColorSpaceClass); cmsPipelineFree(BToA0); return hProfile; }
// Create an output MPE LUT from agiven profile. Version mismatches are handled here cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent) { cmsTagTypeSignature OriginalType; cmsTagSignature tag16 = PCS2Device16[Intent]; cmsTagSignature tagFloat = PCS2DeviceFloat[Intent]; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence // Floating point LUT are always V4, so no adjustment is required return cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); } // Revert to perceptual if no tag is found if (!cmsIsTag(hProfile, tag16)) { tag16 = PCS2Device16[0]; } if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? // Check profile version and LUT type. Do the necessary adjustments if needed // First read the tag cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL; // After reading it, we have info about the original type OriginalType = _cmsGetTagTrueType(hProfile, tag16); // The profile owns the Lut, so we need to copy it Lut = cmsPipelineDup(Lut); if (Lut == NULL) return NULL; // Now it is time for a controversial stuff. I found that for 3D LUTS using // Lab used as indexer space, trilinear interpolation should be used if (cmsGetPCS(hProfile) == cmsSigLabData) ChangeInterpolationToTrilinear(Lut); // We need to adjust data only for Lab and Lut16 type if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) return Lut; // Add a matrix for conversion V4 to V2 Lab PCS cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)); return Lut; } // Lut not found, try to create a matrix-shaper // Check if this is a grayscale profile. if (cmsGetColorSpace(hProfile) == cmsSigGrayData) { // if so, build appropiate conversion tables. // The tables are the PCS iluminant, scaled across GrayTRC return BuildGrayOutputPipeline(hProfile); } // Not gray, create a normal matrix-shaper return BuildRGBOutputMatrixShaper(hProfile); }
static void AddIdentityMatrix(cmsPipeline* lut) { const cmsFloat64Number Identity[] = { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 }; cmsPipelineInsertStage(lut, cmsAT_END, cmsStageAllocMatrix(NULL, 3, 3, Identity, NULL)); }
cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID, cmsColorSpaceSignature ColorSpace, cmsToneCurve* const TransferFunctions[]) { cmsHPROFILE hICC; cmsPipeline* Pipeline; cmsStage* Lin; int nChannels; hICC = cmsCreateProfilePlaceholder(ContextID); if (!hICC) return NULL; cmsSetProfileVersion(hICC, 4.3); cmsSetDeviceClass(hICC, cmsSigLinkClass); cmsSetColorSpace(hICC, ColorSpace); cmsSetPCS(hICC, ColorSpace); cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); // Set up channels nChannels = cmsChannelsOf(ColorSpace); // Creates a Pipeline with prelinearization step only Pipeline = cmsPipelineAlloc(ContextID, nChannels, nChannels); if (Pipeline == NULL) goto Error; // Copy tables to Pipeline Lin = cmsStageAllocToneCurves(ContextID, nChannels, TransferFunctions); if (Lin == NULL) goto Error; cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Lin); // Create tags if (!SetTextTags(hICC, L"Linearization built-in")) goto Error; if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline)) goto Error; if (!SetSeqDescTag(hICC, "Linearization built-in")) goto Error; // Pipeline is already on virtual profile cmsPipelineFree(Pipeline); // Ok, done return hICC; Error: if (hICC) cmsCloseProfile(hICC); return NULL; }
// Creates a fake NULL profile. This profile return 1 channel as always 0. // Is useful only for gamut checking tricks cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID) { cmsHPROFILE hProfile; cmsPipeline* LUT = NULL; cmsStage* PostLin; cmsToneCurve* EmptyTab; cmsUInt16Number Zero[2] = { 0, 0 }; hProfile = cmsCreateProfilePlaceholder(ContextID); if (!hProfile) // can't allocate return NULL; cmsSetProfileVersion(hProfile, 4.3); if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error; cmsSetDeviceClass(hProfile, cmsSigOutputClass); cmsSetColorSpace(hProfile, cmsSigGrayData); cmsSetPCS(hProfile, cmsSigLabData); // An empty LUTs is all we need LUT = cmsPipelineAlloc(ContextID, 1, 1); if (LUT == NULL) goto Error; EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero); PostLin = cmsStageAllocToneCurves(ContextID, 1, &EmptyTab); cmsFreeToneCurve(EmptyTab); if (!cmsPipelineInsertStage(LUT, cmsAT_END, PostLin)) goto Error; if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error; if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error; cmsPipelineFree(LUT); return hProfile; Error: if (LUT != NULL) cmsPipelineFree(LUT); if (hProfile != NULL) cmsCloseProfile(hProfile); return NULL; }
// RGB Matrix shaper static cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsMAT3 Mat; cmsToneCurve *Shapes[3]; cmsContext ContextID = cmsGetProfileContextID(hProfile); int i, j; if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix output comes in 0..0xffff range, so // we need to adjust the output by a factor of (0x10000/0xffff) to put data in // a 1.16 range, and then a >> 1 to obtain 1.15. The total factor is (65536.0)/(65535.0*2) for (i=0; i < 3; i++) for (j=0; j < 3; j++) Mat.v[i].n[j] *= InpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, Shapes)); cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Mat, NULL)); } return Lut; }
// Creates a fake XYZ identity cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID) { cmsHPROFILE hProfile; cmsPipeline* LUT = NULL; hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL); if (hProfile == NULL) return NULL; cmsSetProfileVersion(hProfile, 4.3); cmsSetDeviceClass(hProfile, cmsSigAbstractClass); cmsSetColorSpace(hProfile, cmsSigXYZData); cmsSetPCS(hProfile, cmsSigXYZData); if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error; // An identity LUT is all we need LUT = cmsPipelineAlloc(ContextID, 3, 3); if (LUT == NULL) goto Error; if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3))) goto Error; if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error; cmsPipelineFree(LUT); return hProfile; Error: if (LUT != NULL) cmsPipelineFree(LUT); if (hProfile != NULL) cmsCloseProfile(hProfile); return NULL; }
// Creates a fake Lab identity. cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint) { cmsHPROFILE hProfile; cmsPipeline* LUT = NULL; hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL); if (hProfile == NULL) return NULL; cmsSetProfileVersion(hProfile, 2.1); cmsSetDeviceClass(hProfile, cmsSigAbstractClass); cmsSetColorSpace(hProfile, cmsSigLabData); cmsSetPCS(hProfile, cmsSigLabData); if (!SetTextTags(hProfile, L"Lab identity built-in")) return NULL; // An identity LUT is all we need LUT = cmsPipelineAlloc(ContextID, 3, 3); if (LUT == NULL) goto Error; if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3))) goto Error; if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error; cmsPipelineFree(LUT); return hProfile; Error: if (LUT != NULL) cmsPipelineFree(LUT); if (hProfile != NULL) cmsCloseProfile(hProfile); return NULL; }
cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID, cmsHPROFILE hProfiles[], cmsBool BPC[], cmsUInt32Number Intents[], cmsFloat64Number AdaptationStates[], cmsUInt32Number nGamutPCSposition, cmsHPROFILE hGamut) { cmsHPROFILE hLab; cmsPipeline* Gamut; cmsStage* CLUT; cmsUInt32Number dwFormat; GAMUTCHAIN Chain; int nChannels, nGridpoints; cmsColorSpaceSignature ColorSpace; cmsUInt32Number i; cmsHPROFILE ProfileList[256]; cmsBool BPCList[256]; cmsFloat64Number AdaptationList[256]; cmsUInt32Number IntentList[256]; memset(&Chain, 0, sizeof(GAMUTCHAIN)); if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) { cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition); return NULL; } hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); if (hLab == NULL) return NULL; // The figure of merit. On matrix-shaper profiles, should be almost zero as // the conversion is pretty exact. On LUT based profiles, different resolutions // of input and output CLUT may result in differences. if (cmsIsMatrixShaper(hGamut)) { Chain.Thereshold = 1.0; } else { Chain.Thereshold = ERR_THERESHOLD; } // Create a copy of parameters for (i=0; i < nGamutPCSposition; i++) { ProfileList[i] = hProfiles[i]; BPCList[i] = BPC[i]; AdaptationList[i] = AdaptationStates[i]; IntentList[i] = Intents[i]; } // Fill Lab identity ProfileList[nGamutPCSposition] = hLab; BPCList[nGamutPCSposition] = 0; AdaptationList[nGamutPCSposition] = 1.0; IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC; ColorSpace = cmsGetColorSpace(hGamut); nChannels = cmsChannelsOf(ColorSpace); nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC); dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); // 16 bits to Lab double Chain.hInput = cmsCreateExtendedTransform(ContextID, nGamutPCSposition + 1, ProfileList, BPCList, IntentList, AdaptationList, NULL, 0, dwFormat, TYPE_Lab_DBL, cmsFLAGS_NOCACHE); // Does create the forward step. Lab double to device dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); Chain.hForward = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_DBL, hGamut, dwFormat, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOCACHE); // Does create the backwards step Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOCACHE); // All ok? if (Chain.hInput && Chain.hForward && Chain.hReverse) { // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing // dE when doing a transform back and forth on the colorimetric intent. Gamut = cmsPipelineAlloc(ContextID, 3, 1); if (Gamut != NULL) { CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL); if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) { cmsPipelineFree(Gamut); Gamut = NULL; } else { cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0); } } } else Gamut = NULL; // Didn't work... // Free all needed stuff. if (Chain.hInput) cmsDeleteTransform(Chain.hInput); if (Chain.hForward) cmsDeleteTransform(Chain.hForward); if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse); if (hLab) cmsCloseProfile(hLab); // And return computed hull return Gamut; }
// This is the entry for black-preserving K-only intents, which are non-ICC static cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID, cmsUInt32Number nProfiles, cmsUInt32Number TheIntents[], cmsHPROFILE hProfiles[], cmsBool BPC[], cmsFloat64Number AdaptationStates[], cmsUInt32Number dwFlags) { GrayOnlyParams bp; cmsPipeline* Result; cmsUInt32Number ICCIntents[256]; cmsStage* CLUT; cmsUInt32Number i, nGridPoints; // Sanity check if (nProfiles < 1 || nProfiles > 255) return NULL; // Translate black-preserving intents to ICC ones for (i=0; i < nProfiles; i++) ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); // Check for non-cmyk profiles if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData) return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); memset(&bp, 0, sizeof(bp)); // Allocate an empty LUT for holding the result Result = cmsPipelineAlloc(ContextID, 4, 4); if (Result == NULL) return NULL; // Create a LUT holding normal ICC transform bp.cmyk2cmyk = DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); if (bp.cmyk2cmyk == NULL) goto Error; // Now, compute the tone curve bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); if (bp.KTone == NULL) goto Error; // How many gridpoints are we going to use? nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); // Create the CLUT. 16 bits CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); if (CLUT == NULL) goto Error; // This is the one and only MPE in this LUT if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT)) goto Error; // Sample it. We cannot afford pre/post linearization this time. if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) goto Error; // Get rid of xform and tone curve cmsPipelineFree(bp.cmyk2cmyk); cmsFreeToneCurve(bp.KTone); return Result; Error: if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk); if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone); if (Result != NULL) cmsPipelineFree(Result); return NULL; }
// This is the entry for black-plane preserving, which are non-ICC static cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID, cmsUInt32Number nProfiles, cmsUInt32Number TheIntents[], cmsHPROFILE hProfiles[], cmsBool BPC[], cmsFloat64Number AdaptationStates[], cmsUInt32Number dwFlags) { PreserveKPlaneParams bp; cmsPipeline* Result = NULL; cmsUInt32Number ICCIntents[256]; cmsStage* CLUT; cmsUInt32Number i, nGridPoints; cmsHPROFILE hLab; // Sanity check if (nProfiles < 1 || nProfiles > 255) return NULL; // Translate black-preserving intents to ICC ones for (i=0; i < nProfiles; i++) ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]); // Check for non-cmyk profiles if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData || cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass)) return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); // Allocate an empty LUT for holding the result Result = cmsPipelineAlloc(ContextID, 4, 4); if (Result == NULL) return NULL; memset(&bp, 0, sizeof(bp)); // We need the input LUT of the last profile, assuming this one is responsible of // black generation. This LUT will be seached in inverse order. bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC); if (bp.LabK2cmyk == NULL) goto Cleanup; // Get total area coverage (in 0..1 domain) bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0; if (bp.MaxTAC <= 0) goto Cleanup; // Create a LUT holding normal ICC transform bp.cmyk2cmyk = DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); if (bp.cmyk2cmyk == NULL) goto Cleanup; // Now the tone curve bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags); if (bp.KTone == NULL) goto Cleanup; // To measure the output, Last profile to Lab hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); if ( bp.hProofOutput == NULL) goto Cleanup; // Same as anterior, but lab in the 0..1 range bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1], FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab, FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4), INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); if (bp.cmyk2Lab == NULL) goto Cleanup; cmsCloseProfile(hLab); // Error estimation (for debug only) bp.MaxError = 0; // How many gridpoints are we going to use? nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags); CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); if (CLUT == NULL) goto Cleanup; if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT)) goto Cleanup; cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0); Cleanup: if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk); if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab); if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput); if (bp.KTone) cmsFreeToneCurve(bp.KTone); if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk); return Result; }
// Default handler for ICC-style intents static cmsPipeline* DefaultICCintents(cmsContext ContextID, cmsUInt32Number nProfiles, cmsUInt32Number TheIntents[], cmsHPROFILE hProfiles[], cmsBool BPC[], cmsFloat64Number AdaptationStates[], cmsUInt32Number dwFlags) { cmsPipeline* Lut = NULL; cmsPipeline* Result; cmsHPROFILE hProfile; cmsMAT3 m; cmsVEC3 off; cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace; cmsProfileClassSignature ClassSig; cmsUInt32Number i, Intent; // For safety if (nProfiles == 0) return NULL; // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined' Result = cmsPipelineAlloc(ContextID, 0, 0); if (Result == NULL) return NULL; CurrentColorSpace = cmsGetColorSpace(hProfiles[0]); for (i=0; i < nProfiles; i++) { cmsBool lIsDeviceLink, lIsInput; hProfile = hProfiles[i]; ClassSig = cmsGetDeviceClass(hProfile); lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass ); // First profile is used as input unless devicelink or abstract if ((i == 0) && !lIsDeviceLink) { lIsInput = TRUE; } else { // Else use profile in the input direction if current space is not PCS lIsInput = (CurrentColorSpace != cmsSigXYZData) && (CurrentColorSpace != cmsSigLabData); } Intent = TheIntents[i]; if (lIsInput || lIsDeviceLink) { ColorSpaceIn = cmsGetColorSpace(hProfile); ColorSpaceOut = cmsGetPCS(hProfile); } else { ColorSpaceIn = cmsGetPCS(hProfile); ColorSpaceOut = cmsGetColorSpace(hProfile); } if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch"); goto Error; } // If devicelink is found, then no custom intent is allowed and we can // read the LUT to be applied. Settings don't apply here. if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) { // Get the involved LUT from the profile Lut = _cmsReadDevicelinkLUT(hProfile, Intent); if (Lut == NULL) goto Error; // What about abstract profiles? if (ClassSig == cmsSigAbstractClass && i > 0) { if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; } else { _cmsMAT3identity(&m); _cmsVEC3init(&off, 0, 0, 0); } if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; } else { if (lIsInput) { // Input direction means non-pcs connection, so proceed like devicelinks Lut = _cmsReadInputLUT(hProfile, Intent); if (Lut == NULL) goto Error; } else { // Output direction means PCS connection. Intent may apply here Lut = _cmsReadOutputLUT(hProfile, Intent); if (Lut == NULL) goto Error; if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; } } // Concatenate to the output LUT if (!cmsPipelineCat(Result, Lut)) goto Error; cmsPipelineFree(Lut); Lut = NULL; // Update current space CurrentColorSpace = ColorSpaceOut; } // Check for non-negatives clip if (dwFlags & cmsFLAGS_NONEGATIVES) { if (ColorSpaceOut == cmsSigGrayData || ColorSpaceOut == cmsSigRgbData || ColorSpaceOut == cmsSigCmykData) { cmsStage* clip = _cmsStageClipNegatives(Result->ContextID, cmsChannelsOf(ColorSpaceOut)); if (clip == NULL) goto Error; if (!cmsPipelineInsertStage(Result, cmsAT_END, clip)) goto Error; } } return Result; Error: if (Lut != NULL) cmsPipelineFree(Lut); if (Result != NULL) cmsPipelineFree(Result); return NULL; cmsUNUSED_PARAMETER(dwFlags); }
// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space static cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off) { cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m; cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off; // Handle PCS mismatches. A specialized stage is added to the LUT in such case switch (InPCS) { case cmsSigXYZData: // Input profile operates in XYZ switch (OutPCS) { case cmsSigXYZData: // XYZ -> XYZ if (!IsEmptyLayer(m, off) && !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) return FALSE; break; case cmsSigLabData: // XYZ -> Lab if (!IsEmptyLayer(m, off) && !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) return FALSE; if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID))) return FALSE; break; default: return FALSE; // Colorspace mismatch } break; case cmsSigLabData: // Input profile operates in Lab switch (OutPCS) { case cmsSigXYZData: // Lab -> XYZ if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID))) return FALSE; if (!IsEmptyLayer(m, off) && !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) return FALSE; break; case cmsSigLabData: // Lab -> Lab if (!IsEmptyLayer(m, off)) { if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) || !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) || !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID))) return FALSE; } break; default: return FALSE; // Mismatch } break; // On colorspaces other than PCS, check for same space default: if (InPCS != OutPCS) return FALSE; break; } return TRUE; }
// This one includes abstract profiles as well. Matrix-shaper cannot be obtained on that device class. The // tag name here may default to AToB0 cmsPipeline* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, int Intent) { cmsPipeline* Lut; cmsTagTypeSignature OriginalType; cmsTagSignature tag16; cmsTagSignature tagFloat; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (Intent < INTENT_PERCEPTUAL || Intent > INTENT_ABSOLUTE_COLORIMETRIC) return NULL; tag16 = Device2PCS16[Intent]; tagFloat = Device2PCSFloat[Intent]; // On named color, take the appropriate tag if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*)cmsReadTag(hProfile, cmsSigNamedColor2Tag); if (nc == NULL) return NULL; Lut = cmsPipelineAlloc(ContextID, 0, 0); if (Lut == NULL) goto Error; if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, FALSE))) goto Error; if (cmsGetColorSpace(hProfile) == cmsSigLabData) if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) goto Error; return Lut; Error: cmsPipelineFree(Lut); cmsFreeNamedColorList(nc); return NULL; } if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence // Floating point LUT are always V return _cmsReadFloatDevicelinkTag(hProfile, tagFloat); } tagFloat = Device2PCSFloat[0]; if (cmsIsTag(hProfile, tagFloat)) { return cmsPipelineDup((cmsPipeline*)cmsReadTag(hProfile, tagFloat)); } if (!cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? tag16 = Device2PCS16[0]; if (!cmsIsTag(hProfile, tag16)) return NULL; } // Check profile version and LUT type. Do the necessary adjustments if needed // Read the tag Lut = (cmsPipeline*)cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL; // The profile owns the Lut, so we need to copy it Lut = cmsPipelineDup(Lut); if (Lut == NULL) return NULL; // Now it is time for a controversial stuff. I found that for 3D LUTS using // Lab used as indexer space, trilinear interpolation should be used if (cmsGetPCS(hProfile) == cmsSigLabData) ChangeInterpolationToTrilinear(Lut); // After reading it, we have info about the original type OriginalType = _cmsGetTagTrueType(hProfile, tag16); // We need to adjust data for Lab16 on output if (OriginalType != cmsSigLut16Type) return Lut; // Here it is possible to get Lab on both sides if (cmsGetColorSpace(hProfile) == cmsSigLabData) { if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) goto Error2; } if (cmsGetPCS(hProfile) == cmsSigLabData) { if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) goto Error2; } return Lut; Error2: cmsPipelineFree(Lut); return NULL; }
// Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc // is adjusted here in order to create a LUT that takes care of all those details. // We add intent = -1 as a way to read matrix shaper always, no matter of other LUT cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent) { cmsTagTypeSignature OriginalType; cmsTagSignature tag16; cmsTagSignature tagFloat; cmsContext ContextID = cmsGetProfileContextID(hProfile); // On named color, take the appropriate tag if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { cmsPipeline* Lut; cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*) cmsReadTag(hProfile, cmsSigNamedColor2Tag); if (nc == NULL) return NULL; Lut = cmsPipelineAlloc(ContextID, 0, 0); if (Lut == NULL) { cmsFreeNamedColorList(nc); return NULL; } if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, TRUE)) || !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) { cmsPipelineFree(Lut); return NULL; } return Lut; } // This is an attempt to reuse this function to retrieve the matrix-shaper as pipeline no // matter other LUT are present and have precedence. Intent = -1 means just this. if (Intent >= INTENT_PERCEPTUAL && Intent <= INTENT_ABSOLUTE_COLORIMETRIC) { tag16 = Device2PCS16[Intent]; tagFloat = Device2PCSFloat[Intent]; if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence // Floating point LUT are always V4, but the encoding range is no // longer 0..1.0, so we need to add an stage depending on the color space return _cmsReadFloatInputTag(hProfile, tagFloat); } // Revert to perceptual if no tag is found if (!cmsIsTag(hProfile, tag16)) { tag16 = Device2PCS16[0]; } if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? // Check profile version and LUT type. Do the necessary adjustments if needed // First read the tag cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL; // After reading it, we have now info about the original type OriginalType = _cmsGetTagTrueType(hProfile, tag16); // The profile owns the Lut, so we need to copy it Lut = cmsPipelineDup(Lut); // We need to adjust data only for Lab16 on output if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) return Lut; // If the input is Lab, add also a conversion at the begin if (cmsGetColorSpace(hProfile) == cmsSigLabData && !cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) goto Error; // Add a matrix for conversion V2 to V4 Lab PCS if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) goto Error; return Lut; Error: cmsPipelineFree(Lut); return NULL; } } // Lut was not found, try to create a matrix-shaper // Check if this is a grayscale profile. if (cmsGetColorSpace(hProfile) == cmsSigGrayData) { // if so, build appropriate conversion tables. // The tables are the PCS iluminant, scaled across GrayTRC return BuildGrayInputMatrixPipeline(hProfile); } // Not gray, create a normal matrix-shaper return BuildRGBInputMatrixShaper(hProfile); }
// Does convert a transform into a device link profile cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags) { cmsHPROFILE hProfile = NULL; cmsUInt32Number FrmIn, FrmOut, ChansIn, ChansOut; cmsUInt32Number ColorSpaceBitsIn, ColorSpaceBitsOut; _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform; cmsPipeline* LUT = NULL; cmsStage* mpe; cmsContext ContextID = cmsGetTransformContextID(hTransform); const cmsAllowedLUT* AllowedLUT; cmsTagSignature DestinationTag; cmsProfileClassSignature deviceClass; _cmsAssert(hTransform != NULL); // Get the first mpe to check for named color mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut); // Check if is a named color transform if (mpe != NULL) { if (cmsStageType(mpe) == cmsSigNamedColorElemType) { return CreateNamedColorDevicelink(hTransform); } } // First thing to do is to get a copy of the transformation LUT = cmsPipelineDup(xform ->Lut); if (LUT == NULL) return NULL; // Time to fix the Lab2/Lab4 issue. if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) { if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID))) goto Error; } // On the output side too if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) { if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID))) goto Error; } hProfile = cmsCreateProfilePlaceholder(ContextID); if (!hProfile) goto Error; // can't allocate cmsSetProfileVersion(hProfile, Version); FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags); // Optimize the LUT and precalculate a devicelink ChansIn = cmsChannelsOf(xform -> EntryColorSpace); ChansOut = cmsChannelsOf(xform -> ExitColorSpace); ColorSpaceBitsIn = _cmsLCMScolorSpace(xform -> EntryColorSpace); ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace); FrmIn = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2); FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2); deviceClass = cmsGetDeviceClass(hProfile); if (deviceClass == cmsSigOutputClass) DestinationTag = cmsSigBToA0Tag; else DestinationTag = cmsSigAToB0Tag; // Check if the profile/version can store the result if (dwFlags & cmsFLAGS_FORCE_CLUT) AllowedLUT = NULL; else AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); if (AllowedLUT == NULL) { // Try to optimize _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags); AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); } // If no way, then force CLUT that for sure can be written if (AllowedLUT == NULL) { dwFlags |= cmsFLAGS_FORCE_CLUT; _cmsOptimizePipeline(ContextID, &LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags); // Put identity curves if needed if (cmsPipelineGetPtrToFirstStage(LUT) ->Type != cmsSigCurveSetElemType) if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn))) goto Error; if (cmsPipelineGetPtrToLastStage(LUT) ->Type != cmsSigCurveSetElemType) if (!cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, ChansOut))) goto Error; AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag); } // Somethings is wrong... if (AllowedLUT == NULL) { goto Error; } if (dwFlags & cmsFLAGS_8BITS_DEVICELINK) cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE); // Tag profile with information if (!SetTextTags(hProfile, L"devicelink")) goto Error; // Store result if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error; if (xform -> InputColorant != NULL) { if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error; } if (xform -> OutputColorant != NULL) { if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error; } if ((deviceClass == cmsSigLinkClass) && (xform ->Sequence != NULL)) { if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error; } // Set the white point if (deviceClass == cmsSigInputClass) { if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->EntryWhitePoint)) goto Error; } else { if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xform ->ExitWhitePoint)) goto Error; } // Per 7.2.15 in spec 4.3 cmsSetHeaderRenderingIntent(hProfile, xform ->RenderingIntent); cmsPipelineFree(LUT); return hProfile; Error: if (LUT != NULL) cmsPipelineFree(LUT); cmsCloseProfile(hProfile); return NULL; }
cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID, cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit) { cmsHPROFILE hICC; cmsPipeline* LUT; cmsStage* CLUT; int nChannels; if (ColorSpace != cmsSigCmykData) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "InkLimiting: Only CMYK currently supported"); return NULL; } if (Limit < 0.0 || Limit > 400) { cmsSignalError(ContextID, cmsERROR_RANGE, "InkLimiting: Limit should be between 0..400"); if (Limit < 0) Limit = 0; if (Limit > 400) Limit = 400; } hICC = cmsCreateProfilePlaceholder(ContextID); if (!hICC) // can't allocate return NULL; cmsSetProfileVersion(hICC, 4.3); cmsSetDeviceClass(hICC, cmsSigLinkClass); cmsSetColorSpace(hICC, ColorSpace); cmsSetPCS(hICC, ColorSpace); cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); // Creates a Pipeline with 3D grid only LUT = cmsPipelineAlloc(ContextID, 4, 4); if (LUT == NULL) goto Error; nChannels = cmsChannelsOf(ColorSpace); CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL); if (CLUT == NULL) goto Error; if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error; if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels)) || !cmsPipelineInsertStage(LUT, cmsAT_END, CLUT) || !cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels))) goto Error; // Create tags if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error; if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT)) goto Error; if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error; // cmsPipeline is already on virtual profile cmsPipelineFree(LUT); // Ok, done return hICC; Error: if (LUT != NULL) cmsPipelineFree(LUT); if (hICC != NULL) cmsCloseProfile(hICC); return NULL; }
cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID, int nLUTPoints, cmsFloat64Number Bright, cmsFloat64Number Contrast, cmsFloat64Number Hue, cmsFloat64Number Saturation, int TempSrc, int TempDest) { cmsHPROFILE hICC; cmsPipeline* Pipeline; BCHSWADJUSTS bchsw; cmsCIExyY WhitePnt; cmsStage* CLUT; cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS]; int i; bchsw.Brightness = Bright; bchsw.Contrast = Contrast; bchsw.Hue = Hue; bchsw.Saturation = Saturation; cmsWhitePointFromTemp(&WhitePnt, TempSrc ); cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt); cmsWhitePointFromTemp(&WhitePnt, TempDest); cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt); hICC = cmsCreateProfilePlaceholder(ContextID); if (!hICC) // can't allocate return NULL; cmsSetDeviceClass(hICC, cmsSigAbstractClass); cmsSetColorSpace(hICC, cmsSigLabData); cmsSetPCS(hICC, cmsSigLabData); cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL); // Creates a Pipeline with 3D grid only Pipeline = cmsPipelineAlloc(ContextID, 3, 3); if (Pipeline == NULL) { cmsCloseProfile(hICC); return NULL; } for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints; CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL); if (CLUT == NULL) return NULL; if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) { // Shouldn't reach here goto Error; } if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) { goto Error; } // Create tags if (!SetTextTags(hICC, L"BCHS built-in")) return NULL; cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ()); cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline); // Pipeline is already on virtual profile cmsPipelineFree(Pipeline); // Ok, done return hICC; Error: cmsPipelineFree(Pipeline); cmsCloseProfile(hICC); return NULL; }