Path: blob/master/src/java.desktop/share/native/liblcms/cmsgmt.c
41149 views
/*1* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.2*3* This code is free software; you can redistribute it and/or modify it4* under the terms of the GNU General Public License version 2 only, as5* published by the Free Software Foundation. Oracle designates this6* particular file as subject to the "Classpath" exception as provided7* by Oracle in the LICENSE file that accompanied this code.8*9* This code is distributed in the hope that it will be useful, but WITHOUT10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License12* version 2 for more details (a copy is included in the LICENSE file that13* accompanied this code).14*15* You should have received a copy of the GNU General Public License version16* 2 along with this work; if not, write to the Free Software Foundation,17* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.18*19* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA20* or visit www.oracle.com if you need additional information or have any21* questions.22*/2324// This file is available under and governed by the GNU General Public25// License version 2 only, as published by the Free Software Foundation.26// However, the following notice accompanied the original version of this27// file:28//29//---------------------------------------------------------------------------------30//31// Little Color Management System32// Copyright (c) 1998-2020 Marti Maria Saguer33//34// Permission is hereby granted, free of charge, to any person obtaining35// a copy of this software and associated documentation files (the "Software"),36// to deal in the Software without restriction, including without limitation37// the rights to use, copy, modify, merge, publish, distribute, sublicense,38// and/or sell copies of the Software, and to permit persons to whom the Software39// is furnished to do so, subject to the following conditions:40//41// The above copyright notice and this permission notice shall be included in42// all copies or substantial portions of the Software.43//44// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,45// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO46// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND47// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE48// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION49// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION50// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.51//52//---------------------------------------------------------------------------------53//5455#include "lcms2_internal.h"565758// Auxiliary: append a Lab identity after the given sequence of profiles59// and return the transform. Lab profile is closed, rest of profiles are kept open.60cmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID,61cmsUInt32Number nProfiles,62cmsUInt32Number InputFormat,63cmsUInt32Number OutputFormat,64const cmsUInt32Number Intents[],65const cmsHPROFILE hProfiles[],66const cmsBool BPC[],67const cmsFloat64Number AdaptationStates[],68cmsUInt32Number dwFlags)69{70cmsHTRANSFORM xform;71cmsHPROFILE hLab;72cmsHPROFILE ProfileList[256];73cmsBool BPCList[256];74cmsFloat64Number AdaptationList[256];75cmsUInt32Number IntentList[256];76cmsUInt32Number i;7778// This is a rather big number and there is no need of dynamic memory79// since we are adding a profile, 254 + 1 = 255 and this is the limit80if (nProfiles > 254) return NULL;8182// The output space83hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);84if (hLab == NULL) return NULL;8586// Create a copy of parameters87for (i=0; i < nProfiles; i++) {8889ProfileList[i] = hProfiles[i];90BPCList[i] = BPC[i];91AdaptationList[i] = AdaptationStates[i];92IntentList[i] = Intents[i];93}9495// Place Lab identity at chain's end.96ProfileList[nProfiles] = hLab;97BPCList[nProfiles] = 0;98AdaptationList[nProfiles] = 1.0;99IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC;100101// Create the transform102xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList,103BPCList,104IntentList,105AdaptationList,106NULL, 0,107InputFormat,108OutputFormat,109dwFlags);110111cmsCloseProfile(hLab);112113return xform;114}115116117// Compute K -> L* relationship. Flags may include black point compensation. In this case,118// the relationship is assumed from the profile with BPC to a black point zero.119static120cmsToneCurve* ComputeKToLstar(cmsContext ContextID,121cmsUInt32Number nPoints,122cmsUInt32Number nProfiles,123const cmsUInt32Number Intents[],124const cmsHPROFILE hProfiles[],125const cmsBool BPC[],126const cmsFloat64Number AdaptationStates[],127cmsUInt32Number dwFlags)128{129cmsToneCurve* out = NULL;130cmsUInt32Number i;131cmsHTRANSFORM xform;132cmsCIELab Lab;133cmsFloat32Number cmyk[4];134cmsFloat32Number* SampledPoints;135136xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags);137if (xform == NULL) return NULL;138139SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number));140if (SampledPoints == NULL) goto Error;141142for (i=0; i < nPoints; i++) {143144cmyk[0] = 0;145cmyk[1] = 0;146cmyk[2] = 0;147cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1));148149cmsDoTransform(xform, cmyk, &Lab, 1);150SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation151}152153out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints);154155Error:156157cmsDeleteTransform(xform);158if (SampledPoints) _cmsFree(ContextID, SampledPoints);159160return out;161}162163164// Compute Black tone curve on a CMYK -> CMYK transform. This is done by165// using the proof direction on both profiles to find K->L* relationship166// then joining both curves. dwFlags may include black point compensation.167cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID,168cmsUInt32Number nPoints,169cmsUInt32Number nProfiles,170const cmsUInt32Number Intents[],171const cmsHPROFILE hProfiles[],172const cmsBool BPC[],173const cmsFloat64Number AdaptationStates[],174cmsUInt32Number dwFlags)175{176cmsToneCurve *in, *out, *KTone;177178// Make sure CMYK -> CMYK179if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||180cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL;181182183// Make sure last is an output profile184if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL;185186// Create individual curves. BPC works also as each K to L* is187// computed as a BPC to zero black point in case of L*188in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags);189if (in == NULL) return NULL;190191out = ComputeKToLstar(ContextID, nPoints, 1,192Intents + (nProfiles - 1),193&hProfiles [nProfiles - 1],194BPC + (nProfiles - 1),195AdaptationStates + (nProfiles - 1),196dwFlags);197if (out == NULL) {198cmsFreeToneCurve(in);199return NULL;200}201202// Build the relationship. This effectively limits the maximum accuracy to 16 bits, but203// since this is used on black-preserving LUTs, we are not losing accuracy in any case204KTone = cmsJoinToneCurve(ContextID, in, out, nPoints);205206// Get rid of components207cmsFreeToneCurve(in); cmsFreeToneCurve(out);208209// Something went wrong...210if (KTone == NULL) return NULL;211212// Make sure it is monotonic213if (!cmsIsToneCurveMonotonic(KTone)) {214cmsFreeToneCurve(KTone);215return NULL;216}217218return KTone;219}220221222// Gamut LUT Creation -----------------------------------------------------------------------------------------223224// Used by gamut & softproofing225226typedef struct {227228cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL229cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back230cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut231232} GAMUTCHAIN;233234// This sampler does compute gamut boundaries by comparing original235// values with a transform going back and forth. Values above ERR_THERESHOLD236// of maximum are considered out of gamut.237238#define ERR_THERESHOLD 5239240241static242int GamutSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)243{244GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo;245cmsCIELab LabIn1, LabOut1;246cmsCIELab LabIn2, LabOut2;247cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS];248cmsFloat64Number dE1, dE2, ErrorRatio;249250// Assume in-gamut by default.251ErrorRatio = 1.0;252253// Convert input to Lab254cmsDoTransform(t -> hInput, In, &LabIn1, 1);255256// converts from PCS to colorant. This always257// does return in-gamut values,258cmsDoTransform(t -> hForward, &LabIn1, Proof, 1);259260// Now, do the inverse, from colorant to PCS.261cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1);262263memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab));264265// Try again, but this time taking Check as input266cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1);267cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1);268269// Take difference of direct value270dE1 = cmsDeltaE(&LabIn1, &LabOut1);271272// Take difference of converted value273dE2 = cmsDeltaE(&LabIn2, &LabOut2);274275276// if dE1 is small and dE2 is small, value is likely to be in gamut277if (dE1 < t->Thereshold && dE2 < t->Thereshold)278Out[0] = 0;279else {280281// if dE1 is small and dE2 is big, undefined. Assume in gamut282if (dE1 < t->Thereshold && dE2 > t->Thereshold)283Out[0] = 0;284else285// dE1 is big and dE2 is small, clearly out of gamut286if (dE1 > t->Thereshold && dE2 < t->Thereshold)287Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5);288else {289290// dE1 is big and dE2 is also big, could be due to perceptual mapping291// so take error ratio292if (dE2 == 0.0)293ErrorRatio = dE1;294else295ErrorRatio = dE1 / dE2;296297if (ErrorRatio > t->Thereshold)298Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5);299else300Out[0] = 0;301}302}303304305return TRUE;306}307308// Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs309// the dE obtained is then annotated on the LUT. Values truly out of gamut are clipped to dE = 0xFFFE310// and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well.311//312// **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors,313// of course, many perceptual and saturation intents does not work in such way, but relativ. ones should.314315cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID,316cmsHPROFILE hProfiles[],317cmsBool BPC[],318cmsUInt32Number Intents[],319cmsFloat64Number AdaptationStates[],320cmsUInt32Number nGamutPCSposition,321cmsHPROFILE hGamut)322{323cmsHPROFILE hLab;324cmsPipeline* Gamut;325cmsStage* CLUT;326cmsUInt32Number dwFormat;327GAMUTCHAIN Chain;328cmsUInt32Number nChannels, nGridpoints;329cmsColorSpaceSignature ColorSpace;330cmsUInt32Number i;331cmsHPROFILE ProfileList[256];332cmsBool BPCList[256];333cmsFloat64Number AdaptationList[256];334cmsUInt32Number IntentList[256];335336memset(&Chain, 0, sizeof(GAMUTCHAIN));337338339if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) {340cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition);341return NULL;342}343344hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);345if (hLab == NULL) return NULL;346347348// The figure of merit. On matrix-shaper profiles, should be almost zero as349// the conversion is pretty exact. On LUT based profiles, different resolutions350// of input and output CLUT may result in differences.351352if (cmsIsMatrixShaper(hGamut)) {353354Chain.Thereshold = 1.0;355}356else {357Chain.Thereshold = ERR_THERESHOLD;358}359360361// Create a copy of parameters362for (i=0; i < nGamutPCSposition; i++) {363ProfileList[i] = hProfiles[i];364BPCList[i] = BPC[i];365AdaptationList[i] = AdaptationStates[i];366IntentList[i] = Intents[i];367}368369// Fill Lab identity370ProfileList[nGamutPCSposition] = hLab;371BPCList[nGamutPCSposition] = 0;372AdaptationList[nGamutPCSposition] = 1.0;373IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC;374375376ColorSpace = cmsGetColorSpace(hGamut);377378nChannels = cmsChannelsOf(ColorSpace);379nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC);380dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));381382// 16 bits to Lab double383Chain.hInput = cmsCreateExtendedTransform(ContextID,384nGamutPCSposition + 1,385ProfileList,386BPCList,387IntentList,388AdaptationList,389NULL, 0,390dwFormat, TYPE_Lab_DBL,391cmsFLAGS_NOCACHE);392393394// Does create the forward step. Lab double to device395dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2));396Chain.hForward = cmsCreateTransformTHR(ContextID,397hLab, TYPE_Lab_DBL,398hGamut, dwFormat,399INTENT_RELATIVE_COLORIMETRIC,400cmsFLAGS_NOCACHE);401402// Does create the backwards step403Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat,404hLab, TYPE_Lab_DBL,405INTENT_RELATIVE_COLORIMETRIC,406cmsFLAGS_NOCACHE);407408409// All ok?410if (Chain.hInput && Chain.hForward && Chain.hReverse) {411412// Go on, try to compute gamut LUT from PCS. This consist on a single channel containing413// dE when doing a transform back and forth on the colorimetric intent.414415Gamut = cmsPipelineAlloc(ContextID, 3, 1);416if (Gamut != NULL) {417418CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL);419if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) {420cmsPipelineFree(Gamut);421Gamut = NULL;422}423else {424cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0);425}426}427}428else429Gamut = NULL; // Didn't work...430431// Free all needed stuff.432if (Chain.hInput) cmsDeleteTransform(Chain.hInput);433if (Chain.hForward) cmsDeleteTransform(Chain.hForward);434if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse);435if (hLab) cmsCloseProfile(hLab);436437// And return computed hull438return Gamut;439}440441// Total Area Coverage estimation ----------------------------------------------------------------442443typedef struct {444cmsUInt32Number nOutputChans;445cmsHTRANSFORM hRoundTrip;446cmsFloat32Number MaxTAC;447cmsFloat32Number MaxInput[cmsMAXCHANNELS];448449} cmsTACestimator;450451452// This callback just accounts the maximum ink dropped in the given node. It does not populate any453// memory, as the destination table is NULL. Its only purpose it to know the global maximum.454static455int EstimateTAC(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void * Cargo)456{457cmsTACestimator* bp = (cmsTACestimator*) Cargo;458cmsFloat32Number RoundTrip[cmsMAXCHANNELS];459cmsUInt32Number i;460cmsFloat32Number Sum;461462463// Evaluate the xform464cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1);465466// All all amounts of ink467for (Sum=0, i=0; i < bp ->nOutputChans; i++)468Sum += RoundTrip[i];469470// If above maximum, keep track of input values471if (Sum > bp ->MaxTAC) {472473bp ->MaxTAC = Sum;474475for (i=0; i < bp ->nOutputChans; i++) {476bp ->MaxInput[i] = In[i];477}478}479480return TRUE;481482cmsUNUSED_PARAMETER(Out);483}484485486// Detect Total area coverage of the profile487cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile)488{489cmsTACestimator bp;490cmsUInt32Number dwFormatter;491cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS];492cmsHPROFILE hLab;493cmsContext ContextID = cmsGetProfileContextID(hProfile);494495// TAC only works on output profiles496if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) {497return 0;498}499500// Create a fake formatter for result501dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE);502503bp.nOutputChans = T_CHANNELS(dwFormatter);504bp.MaxTAC = 0; // Initial TAC is 0505506// for safety507if (bp.nOutputChans >= cmsMAXCHANNELS) return 0;508509hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);510if (hLab == NULL) return 0;511// Setup a roundtrip on perceptual intent in output profile for TAC estimation512bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16,513hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE);514515cmsCloseProfile(hLab);516if (bp.hRoundTrip == NULL) return 0;517518// For L* we only need black and white. For C* we need many points519GridPoints[0] = 6;520GridPoints[1] = 74;521GridPoints[2] = 74;522523524if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) {525bp.MaxTAC = 0;526}527528cmsDeleteTransform(bp.hRoundTrip);529530// Results in %531return bp.MaxTAC;532}533534535// Carefully, clamp on CIELab space.536537cmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab,538double amax, double amin,539double bmax, double bmin)540{541542// Whole Luma surface to zero543544if (Lab -> L < 0) {545546Lab-> L = Lab->a = Lab-> b = 0.0;547return FALSE;548}549550// Clamp white, DISCARD HIGHLIGHTS. This is done551// in such way because icc spec doesn't allow the552// use of L>100 as a highlight means.553554if (Lab->L > 100)555Lab -> L = 100;556557// Check out gamut prism, on a, b faces558559if (Lab -> a < amin || Lab->a > amax||560Lab -> b < bmin || Lab->b > bmax) {561562cmsCIELCh LCh;563double h, slope;564565// Falls outside a, b limits. Transports to LCh space,566// and then do the clipping567568569if (Lab -> a == 0.0) { // Is hue exactly 90?570571// atan will not work, so clamp here572Lab -> b = Lab->b < 0 ? bmin : bmax;573return TRUE;574}575576cmsLab2LCh(&LCh, Lab);577578slope = Lab -> b / Lab -> a;579h = LCh.h;580581// There are 4 zones582583if ((h >= 0. && h < 45.) ||584(h >= 315 && h <= 360.)) {585586// clip by amax587Lab -> a = amax;588Lab -> b = amax * slope;589}590else591if (h >= 45. && h < 135.)592{593// clip by bmax594Lab -> b = bmax;595Lab -> a = bmax / slope;596}597else598if (h >= 135. && h < 225.) {599// clip by amin600Lab -> a = amin;601Lab -> b = amin * slope;602603}604else605if (h >= 225. && h < 315.) {606// clip by bmin607Lab -> b = bmin;608Lab -> a = bmin / slope;609}610else {611cmsSignalError(0, cmsERROR_RANGE, "Invalid angle");612return FALSE;613}614615}616617return TRUE;618}619620621