static gboolean set_vcgt_from_data (cmsHPROFILE profile, const guint16 *red, const guint16 *green, const guint16 *blue, guint size) { guint i; gboolean ret = FALSE; cmsToneCurve *vcgt_curve[3]; /* build tone curve */ vcgt_curve[0] = cmsBuildTabulatedToneCurve16 (NULL, size, red); vcgt_curve[1] = cmsBuildTabulatedToneCurve16 (NULL, size, green); vcgt_curve[2] = cmsBuildTabulatedToneCurve16 (NULL, size, blue); /* smooth it */ for (i = 0; i < 3; i++) cmsSmoothToneCurve (vcgt_curve[i], 5); /* write the tag */ ret = cmsWriteTag (profile, cmsSigVcgtType, vcgt_curve); /* free the tonecurves */ for (i = 0; i < 3; i++) cmsFreeToneCurve (vcgt_curve[i]); return ret; }
cmsHPROFILE dt_colorspaces_create_srgb_profile() { cmsHPROFILE hsRGB; cmsCIEXYZTRIPLE Colorants = { {0.436066, 0.222488, 0.013916}, {0.385147, 0.716873, 0.097076}, {0.143066, 0.060608, 0.714096} }; cmsCIEXYZ black = { 0, 0, 0 }; cmsCIEXYZ D65 = { 0.95045, 1, 1.08905 }; cmsToneCurve* transferFunction; transferFunction = cmsBuildTabulatedToneCurve16(NULL, dt_srgb_tone_curve_values_n, dt_srgb_tone_curve_values); hsRGB = cmsCreateProfilePlaceholder(0); cmsSetProfileVersion(hsRGB, 2.1); cmsMLU *mlu0 = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(mlu0, "en", "US", "Public Domain"); cmsMLU *mlu1 = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(mlu1, "en", "US", "sRGB"); cmsMLU *mlu2 = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(mlu2, "en", "US", "Darktable"); cmsMLU *mlu3 = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(mlu3, "en", "US", "sRGB"); // this will only be displayed when the embedded profile is read by for example GIMP cmsWriteTag(hsRGB, cmsSigCopyrightTag, mlu0); cmsWriteTag(hsRGB, cmsSigProfileDescriptionTag, mlu1); cmsWriteTag(hsRGB, cmsSigDeviceMfgDescTag, mlu2); cmsWriteTag(hsRGB, cmsSigDeviceModelDescTag, mlu3); cmsMLUfree(mlu0); cmsMLUfree(mlu1); cmsMLUfree(mlu2); cmsMLUfree(mlu3); cmsSetDeviceClass(hsRGB, cmsSigDisplayClass); cmsSetColorSpace(hsRGB, cmsSigRgbData); cmsSetPCS(hsRGB, cmsSigXYZData); cmsWriteTag(hsRGB, cmsSigMediaWhitePointTag, &D65); cmsWriteTag(hsRGB, cmsSigMediaBlackPointTag, &black); cmsWriteTag(hsRGB, cmsSigRedColorantTag, (void*) &Colorants.Red); cmsWriteTag(hsRGB, cmsSigGreenColorantTag, (void*) &Colorants.Green); cmsWriteTag(hsRGB, cmsSigBlueColorantTag, (void*) &Colorants.Blue); cmsWriteTag(hsRGB, cmsSigRedTRCTag, (void*) transferFunction); cmsLinkTag(hsRGB, cmsSigGreenTRCTag, cmsSigRedTRCTag ); cmsLinkTag(hsRGB, cmsSigBlueTRCTag, cmsSigRedTRCTag ); return hsRGB; }
// 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; }
// 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; }
static cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, int Intent) { cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL); cmsHPROFILE hXYZ = cmsCreateXYZProfile(); cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE); int i; for (i=0; i < 256; i++) { cmsUInt8Number Gray = (cmsUInt8Number) i; cmsCIEXYZ XYZ; cmsDoTransform(xform, &Gray, &XYZ, 1); Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0); } cmsDeleteTransform(xform); cmsCloseProfile(hXYZ); return Out; }
// Reverse a gamma table cmsToneCurve* CMSEXPORT cmsReverseToneCurveEx(cmsInt32Number nResultSamples, const cmsToneCurve* InCurve) { cmsToneCurve *out; cmsFloat64Number a = 0, b = 0, y, x1, y1, x2, y2; int i, j; int Ascending; _cmsAssert(InCurve != NULL); // Try to reverse it analytically whatever possible if (InCurve ->nSegments == 1 && InCurve ->Segments[0].Type > 0 && InCurve -> Segments[0].Type <= 5) { return cmsBuildParametricToneCurve(InCurve ->InterpParams->ContextID, -(InCurve -> Segments[0].Type), InCurve -> Segments[0].Params); } // Nope, reverse the table. out = cmsBuildTabulatedToneCurve16(InCurve ->InterpParams->ContextID, nResultSamples, NULL); if (out == NULL) return NULL; // We want to know if this is an ascending or descending table Ascending = !cmsIsToneCurveDescending(InCurve); // Iterate across Y axis for (i=0; i < nResultSamples; i++) { y = (cmsFloat64Number) i * 65535.0 / (nResultSamples - 1); // Find interval in which y is within. j = GetInterval(y, InCurve->Table16, InCurve->InterpParams); if (j >= 0) { // Get limits of interval x1 = InCurve ->Table16[j]; x2 = InCurve ->Table16[j+1]; y1 = (cmsFloat64Number) (j * 65535.0) / (InCurve ->nEntries - 1); y2 = (cmsFloat64Number) ((j+1) * 65535.0 ) / (InCurve ->nEntries - 1); // If collapsed, then use any if (x1 == x2) { out ->Table16[i] = _cmsQuickSaturateWord(Ascending ? y2 : y1); continue; } else { // Interpolate a = (y2 - y1) / (x2 - x1); b = y2 - a * x2; } } out ->Table16[i] = _cmsQuickSaturateWord(a* y + b); } return out; }