bool LcmsColorProfileContainer::init() { if (d->profile) cmsCloseProfile(d->profile); d->profile = cmsOpenProfileFromMem((void*)d->data->rawData().constData(), d->data->rawData().size()); #ifndef NDEBUG if (d->data->rawData().size() == 4096) { warnPigment << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; } #endif if (d->profile) { wchar_t buffer[_BUFFER_SIZE_]; d->colorSpaceSignature = cmsGetColorSpace(d->profile); d->deviceClass = cmsGetDeviceClass(d->profile); cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->productDescription = QString::fromWCharArray(buffer); d->valid = true; cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->name = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->manufacturer = QString::fromWCharArray(buffer); // Check if the profile can convert (something->this) d->suitableForOutput = cmsIsMatrixShaper(d->profile) || ( cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) && cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT) ); return true; } return false; }
int cmsDetectBlackPoint(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, int Intent, DWORD dwFlags) { // v4 + perceptual & saturation intents does have its own black point if ((cmsGetProfileICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { return GetV4PerceptualBlack(BlackPoint, hProfile, dwFlags); } #ifdef HONOR_BLACK_POINT_TAG // v2, v4 rel/abs colorimetric if (cmsIsTag(hProfile, icSigMediaBlackPointTag) && Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; cmsCIELab Lab; // If black point is specified, then use it, cmsTakeMediaBlackPoint(&BlackXYZ, hProfile); cmsTakeMediaWhitePoint(&MediaWhite, hProfile); // Black point is absolute XYZ, so adapt to D50 to get PCS value cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); // Force a=b=0 to get rid of any chroma cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); // Return BP as D50 relative or absolute XYZ (depends on flags) if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)) cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &TrustedBlackPoint); else *BlackPoint = TrustedBlackPoint; } #endif // If output profile, discount ink-limiting if (Intent == INTENT_RELATIVE_COLORIMETRIC && (cmsGetDeviceClass(hProfile) == icSigOutputClass) && (cmsGetColorSpace(hProfile) == icSigCmykData)) return BlackPointUsingPerceptualBlack(BlackPoint, hProfile, dwFlags); // Nope, compute BP using current intent. return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); }
// Get a media white point fixing some issues found in certain old profiles cmsBool _cmsReadMediaWhitePoint(cmsCIEXYZ* Dest, cmsHPROFILE hProfile) { cmsCIEXYZ* Tag; _cmsAssert(Dest != NULL); Tag = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag); // If no wp, take D50 if (Tag == NULL) { *Dest = *cmsD50_XYZ(); return TRUE; } // V2 display profiles should give D50 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) { if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) { *Dest = *cmsD50_XYZ(); return TRUE; } } // All seems ok *Dest = *Tag; return TRUE; }
// Chromatic adaptation matrix. Fix some issues as well cmsBool _cmsReadCHAD(cmsMAT3* Dest, cmsHPROFILE hProfile) { cmsMAT3* Tag; _cmsAssert(Dest != NULL); Tag = (cmsMAT3*) cmsReadTag(hProfile, cmsSigChromaticAdaptationTag); if (Tag != NULL) { *Dest = *Tag; return TRUE; } // No CHAD available, default it to identity _cmsMAT3identity(Dest); // V2 display profiles should give D50 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) { if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) { cmsCIEXYZ* White = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag); if (White == NULL) { _cmsMAT3identity(Dest); return TRUE; } return _cmsAdaptationMatrix(Dest, NULL, White, cmsD50_XYZ()); } } return TRUE; }
// Returns TRUE if the intent is implemented as CLUT cmsBool CMSEXPORT cmsIsCLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number UsedDirection) { const cmsTagSignature* TagTable; // For devicelinks, the supported intent is that one stated in the header if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) { return (cmsGetHeaderRenderingIntent(hProfile) == Intent); } switch (UsedDirection) { case LCMS_USED_AS_INPUT: TagTable = Device2PCS16; break; case LCMS_USED_AS_OUTPUT:TagTable = PCS2Device16; break; // For proofing, we need rel. colorimetric in output. Let's do some recursion case LCMS_USED_AS_PROOF: return cmsIsIntentSupported(hProfile, Intent, LCMS_USED_AS_INPUT) && cmsIsIntentSupported(hProfile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_OUTPUT); default: cmsSignalError(cmsGetProfileContextID(hProfile), cmsERROR_RANGE, "Unexpected direction (%d)", UsedDirection); return FALSE; } return cmsIsTag(hProfile, TagTable[Intent]); }
// Compute Black tone curve on a CMYK -> CMYK transform. This is done by // using the proof direction on both profiles to find K->L* relationship // then joining both curves. dwFlags may include black point compensation. cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID, cmsUInt32Number nPoints, cmsUInt32Number nProfiles, const cmsUInt32Number Intents[], const cmsHPROFILE hProfiles[], const cmsBool BPC[], const cmsFloat64Number AdaptationStates[], cmsUInt32Number dwFlags) { cmsToneCurve *in, *out, *KTone; // Make sure CMYK -> CMYK if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL; // Make sure last is an output profile if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL; // Create individual curves. BPC works also as each K to L* is // computed as a BPC to zero black point in case of L* in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags); if (in == NULL) return NULL; out = ComputeKToLstar(ContextID, nPoints, 1, Intents + (nProfiles - 1), hProfiles + (nProfiles - 1), BPC + (nProfiles - 1), AdaptationStates + (nProfiles - 1), dwFlags); if (out == NULL) { cmsFreeToneCurve(in); return NULL; } // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but // since this is used on black-preserving LUTs, we are not loosing accuracy in any case KTone = cmsJoinToneCurve(ContextID, in, out, nPoints); // Get rid of components cmsFreeToneCurve(in); cmsFreeToneCurve(out); // Something went wrong... if (KTone == NULL) return NULL; // Make sure it is monotonic if (!cmsIsToneCurveMonotonic(KTone)) { cmsFreeToneCurve(KTone); return NULL; } return KTone; }
static cmsBool GetXFormColorSpaces(int nProfiles, cmsHPROFILE hProfiles[], cmsColorSpaceSignature* Input, cmsColorSpaceSignature* Output) { cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut; cmsColorSpaceSignature PostColorSpace; int i; if (nProfiles <= 0) return FALSE; if (hProfiles[0] == NULL) return FALSE; *Input = PostColorSpace = cmsGetColorSpace(hProfiles[0]); for (i=0; i < nProfiles; i++) { cmsProfileClassSignature cls; cmsHPROFILE hProfile = hProfiles[i]; int lIsInput = (PostColorSpace != cmsSigXYZData) && (PostColorSpace != cmsSigLabData); if (hProfile == NULL) return FALSE; cls = cmsGetDeviceClass(hProfile); if (cls == cmsSigNamedColorClass) { ColorSpaceIn = cmsSig1colorData; ColorSpaceOut = (nProfiles > 1) ? cmsGetPCS(hProfile) : cmsGetColorSpace(hProfile); } else if (lIsInput || (cls == cmsSigLinkClass)) { ColorSpaceIn = cmsGetColorSpace(hProfile); ColorSpaceOut = cmsGetPCS(hProfile); } else { ColorSpaceIn = cmsGetPCS(hProfile); ColorSpaceOut = cmsGetColorSpace(hProfile); } if (i==0) *Input = ColorSpaceIn; PostColorSpace = ColorSpaceOut; } *Output = PostColorSpace; return TRUE; }
IccProfile::ProfileType IccProfile::type() { if (!d) { return InvalidType; } if (d->type != InvalidType) { return d->type; } if (!open()) { return InvalidType; } LcmsLock lock; switch ((int)cmsGetDeviceClass(d->handle)) { case icSigInputClass: case 0x6e6b7066: // 'nkbf', proprietary in Nikon profiles d->type = Input; break; case icSigDisplayClass: d->type = Display; break; case icSigOutputClass: d->type = Output; break; case icSigColorSpaceClass: d->type = ColorSpace; break; case icSigLinkClass: d->type = DeviceLink; break; case icSigAbstractClass: d->type = Abstract; break; case icSigNamedColorClass: d->type = NamedColor; break; default: break; } return d->type; }
static cmsHPROFILE OpenProfile(const char* File) { cmsHPROFILE h; if (!File) return cmsCreate_sRGBProfile(); if (stricmp(File, "*Lab") == 0) return cmsCreateLabProfile(NULL); if (stricmp(File, "*XYZ") == 0) return cmsCreateXYZProfile(); if (stricmp(File, "*srgb") == 0) return cmsCreate_sRGBProfile(); if (stricmp(File, "*Gray22") == 0) { LPGAMMATABLE Gamma = cmsBuildGamma(256, 2.2); cmsHPROFILE hProfile = cmsCreateGrayProfile(cmsD50_xyY(), Gamma); cmsFreeGamma(Gamma); return hProfile; } if (stricmp(File, "*Lin2222") == 0) { LPGAMMATABLE Gamma = cmsBuildGamma(256, 2.2); LPGAMMATABLE Gamma4[4]; cmsHPROFILE hProfile; Gamma4[0] = Gamma4[1] = Gamma4[2] = Gamma4[3] = Gamma; hProfile = cmsCreateLinearizationDeviceLink(icSigCmykData, Gamma4); cmsFreeGamma(Gamma); return hProfile; } h = cmsOpenProfileFromFile(File, "r"); if (cmsGetDeviceClass(h) == icSigNamedColorClass) FatalError("ERROR: Cannot make devicelink of named color profiles!"); return h; }
// 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 cmsUInt32Number GenerateCRD(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags, cmsIOHANDLER* mem) { cmsUInt32Number dwBytesUsed; if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile); } // Is a named color profile? if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) { return 0; } } else { // CRD are always implemented as LUT if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) { return 0; } } if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { _cmsIOPrintf(mem, "%%%%EndResource\n"); _cmsIOPrintf(mem, "\n%% CRD End\n"); } // Done, keep memory usage dwBytesUsed = mem ->UsedSpace; // Finally, return used byte count return dwBytesUsed; cmsUNUSED_PARAMETER(ContextID); }
icProfileClassSignature ColorProfile::GetDeviceClass() const { ASSERT(profile_ != 0); return cmsGetDeviceClass(profile_); }
// 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, 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; } return Result; Error: if (Lut != NULL) cmsPipelineFree(Lut); if (Result != NULL) cmsPipelineFree(Result); return NULL; cmsUNUSED_PARAMETER(dwFlags); }
// New to lcms 2.0 -- have all parameters available. cmsHTRANSFORM CMSEXPORT cmsCreateExtendedTransform(cmsContext ContextID, cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[], cmsBool BPC[], cmsUInt32Number Intents[], cmsFloat64Number AdaptationStates[], cmsHPROFILE hGamutProfile, cmsUInt32Number nGamutPCSposition, cmsUInt32Number InputFormat, cmsUInt32Number OutputFormat, cmsUInt32Number dwFlags) { _cmsTRANSFORM* xform; cmsColorSpaceSignature EntryColorSpace; cmsColorSpaceSignature ExitColorSpace; cmsPipeline* Lut; cmsUInt32Number LastIntent = Intents[nProfiles-1]; // If it is a fake transform if (dwFlags & cmsFLAGS_NULLTRANSFORM) { return AllocEmptyTransform(ContextID, NULL, INTENT_PERCEPTUAL, &InputFormat, &OutputFormat, &dwFlags); } // If gamut check is requested, make sure we have a gamut profile if (dwFlags & cmsFLAGS_GAMUTCHECK) { if (hGamutProfile == NULL) dwFlags &= ~cmsFLAGS_GAMUTCHECK; } // On floating point transforms, inhibit cache if (_cmsFormatterIsFloat(InputFormat) || _cmsFormatterIsFloat(OutputFormat)) dwFlags |= cmsFLAGS_NOCACHE; // Mark entry/exit spaces if (!GetXFormColorSpaces(nProfiles, hProfiles, &EntryColorSpace, &ExitColorSpace)) { cmsSignalError(ContextID, cmsERROR_NULL, "NULL input profiles on transform"); return NULL; } // Check if proper colorspaces if (!IsProperColorSpace(EntryColorSpace, InputFormat)) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong input color space on transform"); return NULL; } if (!IsProperColorSpace(ExitColorSpace, OutputFormat)) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong output color space on transform"); return NULL; } // Create a pipeline with all transformations Lut = _cmsLinkProfiles(ContextID, nProfiles, Intents, hProfiles, BPC, AdaptationStates, dwFlags); if (Lut == NULL) { cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Couldn't link the profiles"); return NULL; } // Check channel count if ((cmsChannelsOf(EntryColorSpace) != cmsPipelineInputChannels(Lut)) || (cmsChannelsOf(ExitColorSpace) != cmsPipelineOutputChannels(Lut))) { cmsPipelineFree(Lut); cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Channel count doesn't match. Profile is corrupted"); return NULL; } // All seems ok xform = AllocEmptyTransform(ContextID, Lut, LastIntent, &InputFormat, &OutputFormat, &dwFlags); if (xform == NULL) { return NULL; } // Keep values xform ->EntryColorSpace = EntryColorSpace; xform ->ExitColorSpace = ExitColorSpace; xform ->RenderingIntent = Intents[nProfiles-1]; // Take white points SetWhitePoint(&xform->EntryWhitePoint, (cmsCIEXYZ*) cmsReadTag(hProfiles[0], cmsSigMediaWhitePointTag)); SetWhitePoint(&xform->ExitWhitePoint, (cmsCIEXYZ*) cmsReadTag(hProfiles[nProfiles-1], cmsSigMediaWhitePointTag)); // Create a gamut check LUT if requested if (hGamutProfile != NULL && (dwFlags & cmsFLAGS_GAMUTCHECK)) xform ->GamutCheck = _cmsCreateGamutCheckPipeline(ContextID, hProfiles, BPC, Intents, AdaptationStates, nGamutPCSposition, hGamutProfile); // Try to read input and output colorant table if (cmsIsTag(hProfiles[0], cmsSigColorantTableTag)) { // Input table can only come in this way. xform ->InputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[0], cmsSigColorantTableTag)); } // Output is a little bit more complex. if (cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigLinkClass) { // This tag may exist only on devicelink profiles. if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag)) { // It may be NULL if error xform ->OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag)); } } else { if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableTag)) { xform -> OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableTag)); } } // Store the sequence of profiles if (dwFlags & cmsFLAGS_KEEP_SEQUENCE) { xform ->Sequence = _cmsCompileProfileSequence(ContextID, nProfiles, hProfiles); } else xform ->Sequence = NULL; // If this is a cached transform, init first value, which is zero (16 bits only) if (!(dwFlags & cmsFLAGS_NOCACHE)) { memset(&xform ->Cache.CacheIn, 0, sizeof(xform ->Cache.CacheIn)); if (xform ->GamutCheck != NULL) { TransformOnePixelWithGamutCheck(xform, xform ->Cache.CacheIn, xform->Cache.CacheOut); } else { xform ->Lut ->Eval16Fn(xform ->Cache.CacheIn, xform->Cache.CacheOut, xform -> Lut->Data); } } return (cmsHTRANSFORM) xform; }
cmsHPROFILE LCMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, DWORD dwFlags) { cmsHPROFILE hICC; _LPcmsTRANSFORM v = (_LPcmsTRANSFORM) hTransform; LPLUT Lut; LCMSBOOL MustFreeLUT; LPcmsNAMEDCOLORLIST InputColorant = NULL; LPcmsNAMEDCOLORLIST OutputColorant = NULL; // Check if is a named color transform if (cmsGetDeviceClass(v ->InputProfile) == icSigNamedColorClass) { return CreateNamedColorDevicelink(hTransform); } if (v ->DeviceLink) { Lut = v -> DeviceLink; MustFreeLUT = FALSE; } else { Lut = _cmsPrecalculateDeviceLink(hTransform, dwFlags); if (!Lut) return NULL; MustFreeLUT = TRUE; } hICC = _cmsCreateProfilePlaceholder(); if (!hICC) { // can't allocate if (MustFreeLUT) cmsFreeLUT(Lut); return NULL; } FixColorSpaces(hICC, v -> EntryColorSpace, v -> ExitColorSpace, dwFlags); cmsSetRenderingIntent(hICC, v -> Intent); // Implement devicelink profile using following tags: // // 1 icSigProfileDescriptionTag // 2 icSigMediaWhitePointTag // 3 icSigAToB0Tag cmsAddTag(hICC, icSigDeviceMfgDescTag, (LPVOID) "LittleCMS"); cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "Device link"); cmsAddTag(hICC, icSigDeviceModelDescTag, (LPVOID) "Device link"); cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ()); if (cmsGetDeviceClass(hICC) == icSigOutputClass) { cmsAddTag(hICC, icSigBToA0Tag, (LPVOID) Lut); } else cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut); // Try to read input and output colorant table if (cmsIsTag(v ->InputProfile, icSigColorantTableTag)) { // Input table can only come in this way. InputColorant = cmsReadColorantTable(v ->InputProfile, icSigColorantTableTag); } // Output is a little bit more complex. if (cmsGetDeviceClass(v ->OutputProfile) == icSigLinkClass) { // This tag may exist only on devicelink profiles. if (cmsIsTag(v ->OutputProfile, icSigColorantTableOutTag)) { OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableOutTag); } } else { if (cmsIsTag(v ->OutputProfile, icSigColorantTableTag)) { OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableTag); } } if (InputColorant) cmsAddTag(hICC, icSigColorantTableTag, InputColorant); if (OutputColorant) cmsAddTag(hICC, icSigColorantTableOutTag, OutputColorant); if (MustFreeLUT) cmsFreeLUT(Lut); if (InputColorant) cmsFreeNamedColorList(InputColorant); if (OutputColorant) cmsFreeNamedColorList(OutputColorant); return hICC; }
icProfileClassSignature dkCmsGetDeviceClass(cmsHPROFILE hProfile) { return static_cast<icProfileClassSignature>( cmsGetDeviceClass(hProfile) ); }
// 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; }
// 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; }
// Does create a Color Space Array on XYZ colorspace for PostScript usage static cmsUInt32Number GenerateCSA(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags, cmsIOHANDLER* mem) { cmsUInt32Number dwBytesUsed; cmsPipeline* lut = NULL; cmsStage* Matrix, *Shaper; // Is a named color profile? if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error; } else { // Any profile class are allowed (including devicelink), but // output (PCS) colorspace must be XYZ or Lab cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile); if (ColorSpace != cmsSigXYZData && ColorSpace != cmsSigLabData) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space"); goto Error; } // Read the lut with all necessary conversion stages lut = _cmsReadInputLUT(hProfile, Intent); if (lut == NULL) goto Error; // Tone curves + matrix can be implemented without any LUT if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) { if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error; } else { // We need a LUT for the rest if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error; } } // Done, keep memory usage dwBytesUsed = mem ->UsedSpace; // Get rid of LUT if (lut != NULL) cmsPipelineFree(lut); // Finally, return used byte count return dwBytesUsed; Error: if (lut != NULL) cmsPipelineFree(lut); return 0; }
// 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); }
QList<ScColorProfileInfo> ScLcmsColorMgmtEngineImpl::getAvailableProfileInfo(const QString& directory, bool recursive) { QList<ScColorProfileInfo> profileInfos; QDir d(directory, "*", QDir::Name, QDir::Files | QDir::Readable | QDir::Dirs | QDir::NoSymLinks); if ((!d.exists()) || (d.count() == 0)) return profileInfos; QString nam = ""; cmsHPROFILE hIn = NULL; for (uint dc = 0; dc < d.count(); ++dc) { QString file = d[dc]; if (file == "." || file == "..") continue; QFileInfo fi(directory + "/" + file); if (fi.isDir() && !recursive) continue; else if (fi.isDir() && !file.startsWith('.')) { QList<ScColorProfileInfo> profileInfos2 = getAvailableProfileInfo(fi.filePath()+"/", true); profileInfos.append(profileInfos2); continue; } ScColorProfileInfo profileInfo; profileInfo.file = fi.filePath(); QFile f(fi.filePath()); QByteArray bb(40, ' '); if (!f.open(QIODevice::ReadOnly)) { profileInfo.debug = QString("couldn't open %1 as color profile").arg(fi.filePath()); profileInfos.append(profileInfo); continue; } int len = f.read(bb.data(), 40); f.close(); if (len == 40 && bb[36] == 'a' && bb[37] == 'c' && bb[38] == 's' && bb[39] == 'p') { const QByteArray profilePath( QString(directory + "/" + file).toLocal8Bit() ); cmsSetErrorHandler(&cmsErrorHandler); try { hIn = cmsOpenProfileFromFile(profilePath.data(), "r"); if (hIn == NULL) continue; const char* profileDescriptor = cmsTakeProductDesc(hIn); profileInfo.description = QString(profileDescriptor); if (profileInfo.description.isEmpty()) { cmsCloseProfile(hIn); profileInfo.debug = QString("Color profile %1 is broken : no valid description").arg(fi.filePath()); profileInfos.append(profileInfo); continue; } profileInfo.colorSpace = translateLcmsColorSpaceType( cmsGetColorSpace(hIn) ); profileInfo.deviceClass = translateLcmsProfileClass( cmsGetDeviceClass(hIn) ); profileInfos.append(profileInfo); cmsCloseProfile(hIn); hIn = NULL; } catch (lcmsException&) { // Profile is broken if (hIn) { cmsCloseProfile(hIn); hIn = NULL; } profileInfo.debug = QString("Color profile %1 is broken").arg(fi.filePath()); profileInfos.append(profileInfo); } cmsSetErrorHandler(NULL); } } cmsSetErrorHandler(NULL); return profileInfos; }
// 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); // On named color, take the appropiate 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; } cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, TRUE)); cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)); return Lut; } 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); }
bool LcmsColorProfileContainer::init() { if (d->profile) { cmsCloseProfile(d->profile); } d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size()); #ifndef NDEBUG if (d->data->rawData().size() == 4096) { qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; } #endif if (d->profile) { wchar_t buffer[_BUFFER_SIZE_]; d->colorSpaceSignature = cmsGetColorSpace(d->profile); d->deviceClass = cmsGetDeviceClass(d->profile); cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->name = QString::fromWCharArray(buffer); //apparantly this should give us a localised string??? Not sure about this. cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->productDescription = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->manufacturer = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->copyright = QString::fromWCharArray(buffer); cmsProfileClassSignature profile_class; profile_class = cmsGetDeviceClass(d->profile); d->valid = (profile_class != cmsSigNamedColorClass); //This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is //present. This is necessary for profiles following the v4 spec. cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption. if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)) { d->mediaWhitePoint = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag)); baseMediaWhitePoint = d->mediaWhitePoint; cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)) { //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50. cmsCIEXYZ *CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag); //We first put all our data into structures we can manipulate. double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z}; QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy); QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z); //we then abuse QTransform's invert function because it probably does matrix invertion 20 times better than I can program. //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeble //effect when we start multiplying. QTransform invertedDummy = invertDummy.inverted(); //we then put the QTransform into a generic 3x3 matrix. double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(), invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(), invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33() }; QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy); //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint. QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix; //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it //uses column/row for initialising. d->mediaWhitePoint.X = result(0, 0); d->mediaWhitePoint.Y = result(1, 0); d->mediaWhitePoint.Z = result(2, 0); cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); } } //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles. if (cmsIsTag(d->profile, cmsSigRedColorantTag)) { cmsCIEXYZTRIPLE tempColorants; tempColorants.Red = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag)); tempColorants.Green = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag)); tempColorants.Blue = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag)); //convert to d65, this is useless. cmsAdaptToIlluminant(&d->colorants.Red, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Red); cmsAdaptToIlluminant(&d->colorants.Green, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Green); cmsAdaptToIlluminant(&d->colorants.Blue, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Blue); //d->colorants = tempColorants; d->hasColorants = true; } else { //qDebug()<<d->name<<": has no colorants"; d->hasColorants = false; } //retrieve TRC. if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) { d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag)); d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag)); d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag)); d->redTRCReverse = cmsReverseToneCurve(d->redTRC); d->greenTRCReverse = cmsReverseToneCurve(d->greenTRC); d->blueTRCReverse = cmsReverseToneCurve(d->blueTRC); d->hasTRC = true; } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag)); d->grayTRCReverse = cmsReverseToneCurve(d->grayTRC); d->hasTRC = true; } else { d->hasTRC = false; } // Check if the profile can convert (something->this) d->suitableForOutput = cmsIsMatrixShaper(d->profile) || (cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) && cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)); d->version = cmsGetProfileVersion(d->profile); d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile); d->isMatrixShaper = cmsIsMatrixShaper(d->profile); d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT); d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT); return true; } return false; }
// 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; }
// 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; }
cmsBool CMSEXPORT cmsDetectBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) { // Zero for black point if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // v4 + perceptual & saturation intents does have its own black point, and it is // well specified enough to use it. Black point tag is deprecated in V4. if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { // Matrix shaper share MRC & perceptual intents if (cmsIsMatrixShaper(hProfile)) return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; return TRUE; } #ifdef CMS_USE_PROFILE_BLACK_POINT_TAG // v2, v4 rel/abs colorimetric if (cmsIsTag(hProfile, cmsSigMediaBlackPointTag) && Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ *BlackPtr, BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; cmsCIELab Lab; // If black point is specified, then use it, BlackPtr = cmsReadTag(hProfile, cmsSigMediaBlackPointTag); if (BlackPtr != NULL) { BlackXYZ = *BlackPtr; _cmsReadMediaWhitePoint(&MediaWhite, hProfile); // Black point is absolute XYZ, so adapt to D50 to get PCS value cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); // Force a=b=0 to get rid of any chroma cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); if (BlackPoint != NULL) *BlackPoint = TrustedBlackPoint; return TRUE; } } #endif // That is about v2 profiles. // If output profile, discount ink-limiting and that's all if (Intent == INTENT_RELATIVE_COLORIMETRIC && (cmsGetDeviceClass(hProfile) == cmsSigOutputClass) && (cmsGetColorSpace(hProfile) == cmsSigCmykData)) return BlackPointUsingPerceptualBlack(BlackPoint, hProfile); // Nope, compute BP using current intent. return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); }
// Calculates the black point of a destination profile. // This algorithm comes from the Adobe paper disclosing its black point compensation method. cmsBool CMSEXPORT cmsDetectDestinationBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) { cmsColorSpaceSignature ColorSpace; cmsHTRANSFORM hRoundTrip = NULL; cmsCIELab InitialLab, destLab, Lab; cmsFloat64Number inRamp[256], outRamp[256]; cmsFloat64Number MinL, MaxL; cmsBool NearlyStraightMidrange = TRUE; cmsFloat64Number yRamp[256]; cmsFloat64Number x[256], y[256]; cmsFloat64Number lo, hi; int n, l; cmsProfileClassSignature devClass; // Make sure the device class is adequate devClass = cmsGetDeviceClass(hProfile); if (devClass == cmsSigLinkClass || devClass == cmsSigAbstractClass || devClass == cmsSigNamedColorClass) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Make sure intent is adequate if (Intent != INTENT_PERCEPTUAL && Intent != INTENT_RELATIVE_COLORIMETRIC && Intent != INTENT_SATURATION) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // v4 + perceptual & saturation intents does have its own black point, and it is // well specified enough to use it. Black point tag is deprecated in V4. if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { // Matrix shaper share MRC & perceptual intents if (cmsIsMatrixShaper(hProfile)) return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; return TRUE; } // Check if the profile is lut based and gray, rgb or cmyk (7.2 in Adobe's document) ColorSpace = cmsGetColorSpace(hProfile); if (!cmsIsCLUT(hProfile, Intent, LCMS_USED_AS_OUTPUT ) || (ColorSpace != cmsSigGrayData && ColorSpace != cmsSigRgbData && ColorSpace != cmsSigCmykData)) { // In this case, handle as input case return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags); } // It is one of the valid cases!, use Adobe algorithm // Set a first guess, that should work on good profiles. if (Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ IniXYZ; // calculate initial Lab as source black point if (!cmsDetectBlackPoint(&IniXYZ, hProfile, Intent, dwFlags)) { return FALSE; } // convert the XYZ to lab cmsXYZ2Lab(NULL, &InitialLab, &IniXYZ); } else { // set the initial Lab to zero, that should be the black point for perceptual and saturation InitialLab.L = 0; InitialLab.a = 0; InitialLab.b = 0; } // Step 2 // ====== // Create a roundtrip. Define a Transform BT for all x in L*a*b* hRoundTrip = CreateRoundtripXForm(hProfile, Intent); if (hRoundTrip == NULL) return FALSE; // Compute ramps for (l=0; l < 256; l++) { Lab.L = (cmsFloat64Number) (l * 100.0) / 255.0; Lab.a = cmsmin(50, cmsmax(-50, InitialLab.a)); Lab.b = cmsmin(50, cmsmax(-50, InitialLab.b)); cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); inRamp[l] = Lab.L; outRamp[l] = destLab.L; } // Make monotonic for (l = 254; l > 0; --l) { outRamp[l] = cmsmin(outRamp[l], outRamp[l+1]); } // Check if (! (outRamp[0] < outRamp[255])) { cmsDeleteTransform(hRoundTrip); BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Test for mid range straight (only on relative colorimetric) NearlyStraightMidrange = TRUE; MinL = outRamp[0]; MaxL = outRamp[255]; if (Intent == INTENT_RELATIVE_COLORIMETRIC) { for (l=0; l < 256; l++) { if (! ((inRamp[l] <= MinL + 0.2 * (MaxL - MinL) ) || (fabs(inRamp[l] - outRamp[l]) < 4.0 ))) NearlyStraightMidrange = FALSE; } // If the mid range is straight (as determined above) then the // DestinationBlackPoint shall be the same as initialLab. // Otherwise, the DestinationBlackPoint shall be determined // using curve fitting. if (NearlyStraightMidrange) { cmsLab2XYZ(NULL, BlackPoint, &InitialLab); cmsDeleteTransform(hRoundTrip); return TRUE; } } // curve fitting: The round-trip curve normally looks like a nearly constant section at the black point, // with a corner and a nearly straight line to the white point. for (l=0; l < 256; l++) { yRamp[l] = (outRamp[l] - MinL) / (MaxL - MinL); } // find the black point using the least squares error quadratic curve fitting if (Intent == INTENT_RELATIVE_COLORIMETRIC) { lo = 0.1; hi = 0.5; } else { // Perceptual and saturation lo = 0.03; hi = 0.25; } // Capture shadow points for the fitting. n = 0; for (l=0; l < 256; l++) { cmsFloat64Number ff = yRamp[l]; if (ff >= lo && ff < hi) { x[n] = inRamp[l]; y[n] = yRamp[l]; n++; } } // No suitable points if (n < 3 ) { cmsDeleteTransform(hRoundTrip); BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // fit and get the vertex of quadratic curve Lab.L = RootOfLeastSquaresFitQuadraticCurve(n, x, y); if (Lab.L < 0.0) { // clip to zero L* if the vertex is negative Lab.L = 0; } Lab.a = InitialLab.a; Lab.b = InitialLab.b; cmsLab2XYZ(NULL, BlackPoint, &Lab); cmsDeleteTransform(hRoundTrip); return TRUE; }
void SPImage::apply_profile(Inkscape::Pixbuf *pixbuf) { // TODO: this will prevent using MIME data when exporting. // Integrate color correction into loading. pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); int imagewidth = pixbuf->width(); int imageheight = pixbuf->height(); int rowstride = pixbuf->rowstride();; guchar* px = pixbuf->pixels(); if ( px ) { DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" ); guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN; cmsHPROFILE prof = Inkscape::CMSSystem::getHandle( this->document, &profIntent, this->color_profile ); if ( prof ) { cmsProfileClassSignature profileClass = cmsGetDeviceClass( prof ); if ( profileClass != cmsSigNamedColorClass ) { int intent = INTENT_PERCEPTUAL; switch ( profIntent ) { case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: intent = INTENT_RELATIVE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_SATURATION: intent = INTENT_SATURATION; break; case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: intent = INTENT_ABSOLUTE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_PERCEPTUAL: case Inkscape::RENDERING_INTENT_UNKNOWN: case Inkscape::RENDERING_INTENT_AUTO: default: intent = INTENT_PERCEPTUAL; } cmsHPROFILE destProf = cmsCreate_sRGBProfile(); cmsHTRANSFORM transf = cmsCreateTransform( prof, TYPE_RGBA_8, destProf, TYPE_RGBA_8, intent, 0 ); if ( transf ) { guchar* currLine = px; for ( int y = 0; y < imageheight; y++ ) { // Since the types are the same size, we can do the transformation in-place cmsDoTransform( transf, currLine, currLine, imagewidth ); currLine += rowstride; } cmsDeleteTransform( transf ); } else { DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." ); } cmsCloseProfile( destProf ); } else { DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." ); } } else { DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." ); } } }
eProfileClass ScLcms2ColorProfileImpl::deviceClass() const { if (m_profileHandle) return ScLcms2ColorMgmtEngineImpl::translateLcmsProfileClass( cmsGetDeviceClass(m_profileHandle) ); return Class_Unknown; }
// Simplified version of multiprofile transform creator // Flags are removed from arguments. // Gamut checking and named color profiles are not supported. // WARNING: I/O pixel formats should be specified for the created transform later by caller. cmsHTRANSFORM cmmCreateMultiprofileTransform(cmsHPROFILE hProfiles[], int nProfiles, int Intent) { DWORD inFmt, outFmt; cmsHPROFILE hTargetProfile, hProfile; icColorSpaceSignature csIn, csOut; LPLUT Grid = NULL; int nGridPoints, nInChannels, nOutChannels = 3, i = 0; _LPcmsTRANSFORM p; // Resulting transform cmsHTRANSFORM transforms[255]; // Cannot merge more than 255 profiles ZeroMemory(transforms, sizeof(transforms)); if (nProfiles > 255) { return NULL; // Too many profiles } // Check if there are any named color profiles for (i=0; i < nProfiles; i++) { if (cmsGetDeviceClass(hProfiles[i]) == icSigNamedColorClass || cmsGetDeviceClass(hProfiles[i]) == icSigLinkClass) { return NULL; // Unsupported named color and device link profiles } } // Create a placeholder transform with dummy I/O formats to place LUT in it p = (_LPcmsTRANSFORM) cmsCreateTransform(NULL, TYPE_RGB_8, NULL, TYPE_RGB_8, Intent, cmsFLAGS_NULLTRANSFORM); p->EntryColorSpace = cmsGetColorSpace(hProfiles[0]); // Gater information about first input profile hProfile = hProfiles[0]; csIn = cmsGetColorSpace(hProfile); nInChannels = _cmsChannelsOf(csIn); inFmt = BYTES_SH(2) | CHANNELS_SH(nInChannels); // Create a sequence for (i=1; i < nProfiles; i++) { // Gather output parameters hTargetProfile = hProfiles[i]; csOut = cmsGetColorSpace(hTargetProfile); nOutChannels = _cmsChannelsOf(csOut); outFmt = BYTES_SH(2)|CHANNELS_SH(nOutChannels); transforms[i-1] = cmsCreateTransform( hProfile, inFmt, hTargetProfile, outFmt, Intent, cmsFLAGS_NOTPRECALC | cmsFLAGS_NOTCACHE ); if(transforms[i-1] == NULL) CLEANUP_AND_RETURN(NULL); // Incompatible profiles? // Assign output parameters to input hProfile = hTargetProfile; csIn = csOut; nInChannels = nOutChannels; inFmt = outFmt; } p->ExitColorSpace = csOut; transforms[i] = NULL; // End marker p->InputProfile = hProfiles[0]; p->OutputProfile = hProfiles[nProfiles-1]; nGridPoints = _cmsReasonableGridpointsByColorspace(p->EntryColorSpace, 0); nInChannels = _cmsChannelsOf(cmsGetColorSpace(p->InputProfile)); // Create 3DCLUT if (! (Grid = cmsAllocLUT())) CLEANUP_AND_RETURN(NULL); Grid = cmsAlloc3DGrid(Grid, nGridPoints, nInChannels, nOutChannels); _cmsComputePrelinearizationTablesFromXFORM(transforms, nProfiles-1, Grid); // Compute device link on 16-bit basis if (!cmsSample3DGrid(Grid, cmmMultiprofileSampler, (LPVOID) transforms, Grid -> wFlags)) CLEANUP_AND_RETURN(NULL); // Put the new LUT into resulting transform p->DeviceLink = Grid; // Set transform method p->xform = cmmPrecalculatedXformImpl; // Commented out since it is not clear if it is correct or not // Sequential transforms gives same result as multiprofile with this call commented out /* if(Intent != INTENT_ABSOLUTE_COLORIMETRIC) _cmsFixWhiteMisalignment(p); */ // Don't clean LUT Grid = NULL; CLEANUP_AND_RETURN((cmsHTRANSFORM) p); }