static int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, int Intent, cmsUInt32Number dwFlags) { cmsHTRANSFORM xform; int i, nColors, nColorant; cmsUInt32Number OutputFormat; char ColorName[32]; char Colorant[128]; cmsNAMEDCOLORLIST* NamedColorList; OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2, FALSE); nColorant = T_CHANNELS(OutputFormat); xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags); if (xform == NULL) return 0; NamedColorList = cmsGetNamedColorList(xform); if (NamedColorList == NULL) return 0; _cmsIOPrintf(m, "<<\n"); _cmsIOPrintf(m, "(colorlistcomment) (%s) \n", "Named profile"); _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n"); _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n"); nColors = cmsNamedColorCount(NamedColorList); for (i=0; i < nColors; i++) { cmsUInt16Number In[1]; cmsUInt16Number Out[cmsMAXCHANNELS]; In[0] = (cmsUInt16Number) i; if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) continue; cmsDoTransform(xform, In, Out, 1); BuildColorantList(Colorant, nColorant, Out); _cmsIOPrintf(m, " (%s) [ %s ]\n", ColorName, Colorant); } _cmsIOPrintf(m, " >>"); if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { _cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n"); } cmsDeleteTransform(xform); return 1; }
// Detect Total area coverage of the profile cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile) { cmsTACestimator bp; cmsUInt32Number dwFormatter; cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS]; cmsHPROFILE hLab; cmsContext ContextID = cmsGetProfileContextID(hProfile); // TAC only works on output profiles if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) { return 0; } // Create a fake formatter for result dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE); bp.nOutputChans = T_CHANNELS(dwFormatter); bp.MaxTAC = 0; // Initial TAC is 0 // for safety if (bp.nOutputChans >= cmsMAXCHANNELS) return 0; hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); if (hLab == NULL) return 0; // Setup a roundtrip on perceptual intent in output profile for TAC estimation bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16, hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); cmsCloseProfile(hLab); if (bp.hRoundTrip == NULL) return 0; // For L* we only need black and white. For C* we need many points GridPoints[0] = 6; GridPoints[1] = 74; GridPoints[2] = 74; if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) { bp.MaxTAC = 0; } cmsDeleteTransform(bp.hRoundTrip); // Results in % return bp.MaxTAC; }
static int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags) { cmsHPROFILE hLab; cmsHTRANSFORM xform; cmsUInt32Number nChannels; cmsUInt32Number InputFormat; int rc; cmsHPROFILE Profiles[2]; cmsCIEXYZ BlackPointAdaptedToD50; // Does create a device-link based transform. // The DeviceLink is next dumped as working CSA. InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE); nChannels = T_CHANNELS(InputFormat); cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0); // Adjust output to Lab4 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); Profiles[0] = hProfile; Profiles[1] = hLab; xform = cmsCreateMultiprofileTransform(Profiles, 2, InputFormat, TYPE_Lab_DBL, Intent, 0); cmsCloseProfile(hLab); if (xform == NULL) { cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab"); return 0; } // Only 1, 3 and 4 channels are allowed switch (nChannels) { case 1: { cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent); EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50); cmsFreeToneCurve(Gray2Y); } break; case 3: case 4: { cmsUInt32Number OutFrm = TYPE_Lab_16; cmsPipeline* DeviceLink; _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform; DeviceLink = cmsPipelineDup(v ->Lut); if (DeviceLink == NULL) return 0; dwFlags |= cmsFLAGS_FORCE_CLUT; _cmsOptimizePipeline(&DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags); rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50); cmsPipelineFree(DeviceLink); } break; default: cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels supported for CSA. This profile has %d channels.", nChannels); return 0; } cmsDeleteTransform(xform); return 1; }
static int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags) { cmsHPROFILE hLab; cmsHTRANSFORM xform; int i, nChannels; cmsUInt32Number OutputFormat; _cmsTRANSFORM* v; cmsPipeline* DeviceLink; cmsHPROFILE Profiles[3]; cmsCIEXYZ BlackPointAdaptedToD50; cmsBool lDoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION); cmsBool lFixWhite = !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP); cmsUInt32Number InFrm = TYPE_Lab_16; int RelativeEncodingIntent; cmsColorSpaceSignature ColorSpace; hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); if (hLab == NULL) return 0; OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE); nChannels = T_CHANNELS(OutputFormat); ColorSpace = cmsGetColorSpace(hProfile); // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision. RelativeEncodingIntent = Intent; if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC) RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC; // Use V4 Lab always Profiles[0] = hLab; Profiles[1] = hProfile; xform = cmsCreateMultiprofileTransformTHR(m ->ContextID, Profiles, 2, TYPE_Lab_DBL, OutputFormat, RelativeEncodingIntent, 0); cmsCloseProfile(hLab); if (xform == NULL) { cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation"); return 0; } // Get a copy of the internal devicelink v = (_cmsTRANSFORM*) xform; DeviceLink = cmsPipelineDup(v ->Lut); if (DeviceLink == NULL) return 0; // We need a CLUT dwFlags |= cmsFLAGS_FORCE_CLUT; _cmsOptimizePipeline(&DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags); _cmsIOPrintf(m, "<<\n"); _cmsIOPrintf(m, "/ColorRenderingType 1\n"); cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0); // Emit headers, etc. EmitWhiteBlackD50(m, &BlackPointAdaptedToD50); EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC); EmitXYZ2Lab(m); // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127, // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to // zero. This would sacrifice a bit of highlights, but failure to do so would cause // scum dot. Ouch. if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) lFixWhite = FALSE; _cmsIOPrintf(m, "/RenderTable "); WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace); _cmsIOPrintf(m, " %d {} bind ", nChannels); for (i=1; i < nChannels; i++) _cmsIOPrintf(m, "dup "); _cmsIOPrintf(m, "]\n"); EmitIntent(m, Intent); _cmsIOPrintf(m, ">>\n"); if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n"); } cmsPipelineFree(DeviceLink); cmsDeleteTransform(xform); return 1; }
// Creates all needed color transforms static cmsBool OpenTransforms(void) { cmsHPROFILE hInput, hOutput, hProof; cmsUInt32Number dwIn, dwOut, dwFlags; cmsNAMEDCOLORLIST* List; int i; // We don't need cache dwFlags = cmsFLAGS_NOCACHE; if (lIsDeviceLink) { hInput = OpenStockProfile(0, cInProf); if (hInput == NULL) return FALSE; hOutput = NULL; hProof = NULL; if (cmsGetDeviceClass(hInput) == cmsSigNamedColorClass) { OutputColorSpace = cmsGetColorSpace(hInput); InputColorSpace = cmsGetPCS(hInput); } else { InputColorSpace = cmsGetColorSpace(hInput); OutputColorSpace = cmsGetPCS(hInput); } // Read colorant tables if present if (cmsIsTag(hInput, cmsSigColorantTableTag)) { List = cmsReadTag(hInput, cmsSigColorantTableTag); InputColorant = cmsDupNamedColorList(List); InputRange = 1; } else InputColorant = ComponentNames(InputColorSpace, TRUE); if (cmsIsTag(hInput, cmsSigColorantTableOutTag)){ List = cmsReadTag(hInput, cmsSigColorantTableOutTag); OutputColorant = cmsDupNamedColorList(List); OutputRange = 1; } else OutputColorant = ComponentNames(OutputColorSpace, FALSE); } else { hInput = OpenStockProfile(0, cInProf); if (hInput == NULL) return FALSE; hOutput = OpenStockProfile(0, cOutProf); if (hOutput == NULL) return FALSE; hProof = NULL; if (cmsGetDeviceClass(hInput) == cmsSigLinkClass || cmsGetDeviceClass(hOutput) == cmsSigLinkClass) FatalError("Use %cl flag for devicelink profiles!\n", SW); InputColorSpace = cmsGetColorSpace(hInput); OutputColorSpace = cmsGetColorSpace(hOutput); // Read colorant tables if present if (cmsIsTag(hInput, cmsSigColorantTableTag)) { List = cmsReadTag(hInput, cmsSigColorantTableTag); InputColorant = cmsDupNamedColorList(List); if (cmsNamedColorCount(InputColorant) <= 3) SetRange(255, TRUE); else SetRange(1, TRUE); // Inks are already divided by 100 in the formatter } else InputColorant = ComponentNames(InputColorSpace, TRUE); if (cmsIsTag(hOutput, cmsSigColorantTableTag)){ List = cmsReadTag(hOutput, cmsSigColorantTableTag); OutputColorant = cmsDupNamedColorList(List); if (cmsNamedColorCount(OutputColorant) <= 3) SetRange(255, FALSE); else SetRange(1, FALSE); // Inks are already divided by 100 in the formatter } else OutputColorant = ComponentNames(OutputColorSpace, FALSE); if (cProofing != NULL) { hProof = OpenStockProfile(0, cProofing); if (hProof == NULL) return FALSE; dwFlags |= cmsFLAGS_SOFTPROOFING; } } // Print information on profiles if (Verbose > 2) { printf("Profile:\n"); PrintProfileInformation(hInput); if (hOutput) { printf("Output profile:\n"); PrintProfileInformation(hOutput); } if (hProof != NULL) { printf("Proofing profile:\n"); PrintProfileInformation(hProof); } } // Input is always in floating point dwIn = cmsFormatterForColorspaceOfProfile(hInput, 0, TRUE); if (lIsDeviceLink) { dwOut = cmsFormatterForPCSOfProfile(hInput, lIsFloat ? 0 : 2, lIsFloat); } else { // 16 bits or floating point (only on output) dwOut = cmsFormatterForColorspaceOfProfile(hOutput, lIsFloat ? 0 : 2, lIsFloat); } // For named color, there is a specialized formatter if (cmsGetDeviceClass(hInput) == cmsSigNamedColorClass) { dwIn = TYPE_NAMED_COLOR_INDEX; InputNamedColor = TRUE; } // Precision mode switch (PrecalcMode) { case 0: dwFlags |= cmsFLAGS_NOOPTIMIZE; break; case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break; case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break; case 1: break; default: FatalError("Unknown precalculation mode '%d'", PrecalcMode); } if (BlackPointCompensation) dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; if (GamutCheck) { cmsUInt16Number Alarm[cmsMAXCHANNELS]; if (hProof == NULL) FatalError("I need proofing profile -p for gamut checking!"); for (i=0; i < cmsMAXCHANNELS; i++) Alarm[i] = 0xFFFF; cmsSetAlarmCodes(Alarm); dwFlags |= cmsFLAGS_GAMUTCHECK; } // The main transform hTrans = cmsCreateProofingTransform(hInput, dwIn, hOutput, dwOut, hProof, Intent, ProofingIntent, dwFlags); if (hProof) cmsCloseProfile(hProof); if (hTrans == NULL) return FALSE; // PCS Dump if requested hTransXYZ = NULL; hTransLab = NULL; if (hOutput && Verbose > 1) { cmsHPROFILE hXYZ = cmsCreateXYZProfile(); cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); hTransXYZ = cmsCreateTransform(hInput, dwIn, hXYZ, lIsFloat ? TYPE_XYZ_DBL : TYPE_XYZ_16, Intent, cmsFLAGS_NOCACHE); if (hTransXYZ == NULL) return FALSE; hTransLab = cmsCreateTransform(hInput, dwIn, hLab, lIsFloat? TYPE_Lab_DBL : TYPE_Lab_16, Intent, cmsFLAGS_NOCACHE); if (hTransLab == NULL) return FALSE; cmsCloseProfile(hXYZ); cmsCloseProfile(hLab); } if (hInput) cmsCloseProfile(hInput); if (hOutput) cmsCloseProfile(hOutput); return TRUE; }
// Use darker colorants to obtain black point. This works in the relative colorimetric intent and // assumes more ink results in darker colors. No ink limit is assumed. static cmsBool BlackPointAsDarkerColorant(cmsHPROFILE hInput, cmsUInt32Number Intent, cmsCIEXYZ* BlackPoint, cmsUInt32Number dwFlags) { cmsUInt16Number *Black; cmsHTRANSFORM xform; cmsColorSpaceSignature Space; cmsUInt32Number nChannels; cmsUInt32Number dwFormat; cmsHPROFILE hLab; cmsCIELab Lab; cmsCIEXYZ BlackXYZ; cmsContext ContextID = cmsGetProfileContextID(hInput); // If the profile does not support input direction, assume Black point 0 if (!cmsIsIntentSupported(hInput, Intent, LCMS_USED_AS_INPUT)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Create a formatter which has n channels and floating point dwFormat = cmsFormatterForColorspaceOfProfile(hInput, 2); // Try to get black by using black colorant Space = cmsGetColorSpace(hInput); // This function returns darker colorant in 16 bits for several spaces if (!_cmsEndPointsBySpace(Space, NULL, &Black, &nChannels)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } if (nChannels != T_CHANNELS(dwFormat)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Lab will be used as the output space, but lab2 will avoid recursion hLab = cmsCreateLab2ProfileTHR(ContextID, NULL); if (hLab == NULL) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Create the transform xform = cmsCreateTransformTHR(ContextID, hInput, dwFormat, hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); cmsCloseProfile(hLab); if (xform == NULL) { // Something went wrong. Get rid of open resources and return zero as black BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Convert black to Lab cmsDoTransform(xform, Black, &Lab, 1); // Force it to be neutral, clip to max. L* of 50 Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; // Free the resources cmsDeleteTransform(xform); // Convert from Lab (which is now clipped) to XYZ. cmsLab2XYZ(NULL, &BlackXYZ, &Lab); if (BlackPoint != NULL) *BlackPoint = BlackXYZ; return TRUE; cmsUNUSED_PARAMETER(dwFlags); }