/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
// This file is available under and governed by the GNU General Public // License version 2 only, as published by the Free Software Foundation. // However, the following notice accompanied the original version of this // file: // //--------------------------------------------------------------------------------- // // Little Color Management System // Copyright (c) 1998-2022 Marti Maria Saguer // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // //--------------------------------------------------------------------------------- //
#include"lcms2_internal.h"
// Read tags using low-level functions, provides necessary glue code to adapt versions, etc.
// Factors to convert from 1.15 fixed point to 0..1.0 range and vice-versa #define InpAdj (1.0/MAX_ENCODEABLE_XYZ) // (65536.0/(65535.0*2.0)) #define OutpAdj (MAX_ENCODEABLE_XYZ) // ((2.0*65535.0)/65536.0)
// 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(); returnTRUE;
}
// V2 display profiles should give D50 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) {
// Auxiliary, read colorants as a MAT3 structure. Used by any function that needs a matrix-shaper static
cmsBool ReadICCMatrixRGB2XYZ(cmsMAT3* r, cmsHPROFILE hProfile)
{
cmsCIEXYZ *PtrRed, *PtrGreen, *PtrBlue;
// 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];
if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL;
// XYZ PCS in encoded in 1.15 format, and the matrix output comes in 0..0xffff range, so // we need to adjust the output by a factor of (0x10000/0xffff) to put data in // a 1.16 range, and then a >> 1 to obtain 1.15. The total factor is (65536.0)/(65535.0*2)
for (i=0; i < 3; i++) for (j=0; j < 3; j++)
Mat.v[i].n[j] *= InpAdj;
// Note that it is certainly possible a single profile would have a LUT based // tag for output working in lab and a matrix-shaper for the fallback cases. // This is not allowed by the spec, but this code is tolerant to those cases if (cmsGetPCS(hProfile) == cmsSigLabData) {
if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID))) goto Error;
}
}
return Lut;
Error:
cmsPipelineFree(Lut); return NULL;
}
// Read the DToAX tag, adjusting the encoding of Lab or XYZ if needed static
cmsPipeline* _cmsReadFloatInputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
{
cmsContext ContextID = cmsGetProfileContextID(hProfile);
cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile);
cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
if (Lut == NULL) return NULL;
// input and output of transform are in lcms 0..1 encoding. If XYZ or Lab spaces are used, // these need to be normalized into the appropriate ranges (Lab = 100,0,0, XYZ=1.0,1.0,1.0) if ( spc == cmsSigLabData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID))) goto Error;
} elseif (spc == cmsSigXYZData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID))) goto Error;
}
if ( PCS == cmsSigLabData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID))) goto Error;
} elseif( PCS == cmsSigXYZData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID))) goto Error;
}
return Lut;
Error:
cmsPipelineFree(Lut); return NULL;
}
// Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc // is adjusted here in order to create a LUT that takes care of all those details. // We add intent = 0xffffffff as a way to read matrix shaper always, no matter of other LUT
cmsPipeline* CMSEXPORT _cmsReadInputLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
{
cmsTagTypeSignature OriginalType;
cmsTagSignature tag16;
cmsTagSignature tagFloat;
cmsContext ContextID = cmsGetProfileContextID(hProfile);
// On named color, take the appropriate tag if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
// 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 = 0xffffffff can be used for that. if (Intent <= INTENT_ABSOLUTE_COLORIMETRIC) {
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;
// 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);
}
// Gray output pipeline. // XYZ -> Gray or Lab -> Gray. Since we only know the GrayTRC, we need to do some assumptions. Gray component will be // given by Y on XYZ PCS and by L* on Lab PCS, Both across inverse TRC curve. // The complete pipeline on XYZ is Matrix[3:1] -> Tone curve and in Lab Matrix[3:1] -> Tone Curve as well.
static
cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile)
{
cmsPipeline* Lut;
cmsToneCurve *Shapes[3], *InvShapes[3];
cmsMAT3 Mat, Inv; int i, j;
cmsContext ContextID = cmsGetProfileContextID(hProfile);
if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL;
if (!_cmsMAT3inverse(&Mat, &Inv)) return NULL;
// XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0;
for (i=0; i < 3; i++) for (j=0; j < 3; j++)
Inv.v[i].n[j] *= OutpAdj;
if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { return NULL;
}
Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) {
// Note that it is certainly possible a single profile would have a LUT based // tag for output working in lab and a matrix-shaper for the fallback cases. // This is not allowed by the spec, but this code is tolerant to those cases if (cmsGetPCS(hProfile) == cmsSigLabData) {
if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID))) goto Error;
}
// Read the DToAX tag, adjusting the encoding of Lab or XYZ if needed static
cmsPipeline* _cmsReadFloatOutputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
{
cmsContext ContextID = cmsGetProfileContextID(hProfile);
cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
cmsColorSpaceSignature dataSpace = cmsGetColorSpace(hProfile);
if (Lut == NULL) return NULL;
// If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding, // and since the formatter has already accommodated to 0..1.0, we should undo this change if ( PCS == cmsSigLabData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID))) goto Error;
} else if (PCS == cmsSigXYZData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID))) goto Error;
}
// the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline if ( dataSpace == cmsSigLabData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID))) goto Error;
} elseif (dataSpace == cmsSigXYZData)
{ if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID))) goto Error;
}
return Lut;
Error:
cmsPipelineFree(Lut); return NULL;
}
// Create an output MPE LUT from agiven profile. Version mismatches are handled here
cmsPipeline* CMSEXPORT _cmsReadOutputLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
{
cmsTagTypeSignature OriginalType;
cmsTagSignature tag16;
cmsTagSignature tagFloat;
cmsContext ContextID = cmsGetProfileContextID(hProfile);
if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence
// Floating point LUT are always V4 return _cmsReadFloatOutputTag(hProfile, tagFloat);
}
// Revert to perceptual if no tag is found if (!cmsIsTag(hProfile, tag16)) {
tag16 = PCS2Device16[0];
}
if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table?
// Check profile version and LUT type. Do the necessary adjustments if needed
// First read the tag
cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); if (Lut == NULL) return NULL;
// After reading it, we have info about the original type
OriginalType = _cmsGetTagTrueType(hProfile, tag16);
// The profile owns the Lut, so we need to copy it
Lut = cmsPipelineDup(Lut); if (Lut == NULL) return NULL;
// Now it is time for a controversial stuff. I found that for 3D LUTS using // Lab used as indexer space, trilinear interpolation should be used if (cmsGetPCS(hProfile) == cmsSigLabData)
ChangeInterpolationToTrilinear(Lut);
// We need to adjust data only for Lab and Lut16 type if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) return Lut;
// Add a matrix for conversion V4 to V2 Lab PCS if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) goto Error;
// If the output is Lab, add also a conversion at the end if (cmsGetColorSpace(hProfile) == cmsSigLabData) if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) goto Error;
// 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* CMSEXPORT _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
{
cmsPipeline* Lut;
cmsTagTypeSignature OriginalType;
cmsTagSignature tag16;
cmsTagSignature tagFloat;
cmsContext ContextID = cmsGetProfileContextID(hProfile);
if (Intent > INTENT_ABSOLUTE_COLORIMETRIC) return NULL;
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;
}
// Returns TRUE if the profile is implemented as matrix-shaper
cmsBool CMSEXPORT cmsIsMatrixShaper(cmsHPROFILE hProfile)
{ switch (cmsGetColorSpace(hProfile)) {
// 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); returnFALSE;
}
return cmsIsTag(hProfile, TagTable[Intent]);
}
// Return info about supported intents
cmsBool CMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
cmsUInt32Number Intent, cmsUInt32Number UsedDirection)
{
if (cmsIsCLUT(hProfile, Intent, UsedDirection)) returnTRUE;
// Is there any matrix-shaper? If so, the intent is supported. This is a bit odd, since V2 matrix shaper // does not fully support relative colorimetric because they cannot deal with non-zero black points, but // many profiles claims that, and this is certainly not true for V4 profiles. Lets answer "yes" no matter // the accuracy would be less than optimal in rel.col and v2 case.
// Read both, profile sequence description and profile sequence id if present. Then combine both to // create qa unique structure holding both. Shame on ICC to store things in such complicated way.
cmsSEQ* _cmsReadProfileSequence(cmsHPROFILE hProfile)
{
cmsSEQ* ProfileSeq;
cmsSEQ* ProfileId;
cmsSEQ* NewSeq;
cmsUInt32Number i;
// Take profile sequence description first
ProfileSeq = (cmsSEQ*) cmsReadTag(hProfile, cmsSigProfileSequenceDescTag);
// Take profile sequence ID
ProfileId = (cmsSEQ*) cmsReadTag(hProfile, cmsSigProfileSequenceIdTag);
if (ProfileSeq == NULL && ProfileId == NULL) return NULL;
if (ProfileSeq == NULL) return cmsDupProfileSequenceDescription(ProfileId); if (ProfileId == NULL) return cmsDupProfileSequenceDescription(ProfileSeq);
// We have to mix both together. For that they must agree if (ProfileSeq ->n != ProfileId ->n) return cmsDupProfileSequenceDescription(ProfileSeq);
// Dump the contents of profile sequence in both tags (if v4 available)
cmsBool _cmsWriteProfileSequence(cmsHPROFILE hProfile, const cmsSEQ* seq)
{ if (!cmsWriteTag(hProfile, cmsSigProfileSequenceDescTag, seq)) returnFALSE;
if (cmsGetEncodedICCversion(hProfile) >= 0x4000000) {
if (!cmsWriteTag(hProfile, cmsSigProfileSequenceIdTag, seq)) returnFALSE;
}
returnTRUE;
}
// Auxiliary, read and duplicate a MLU if found. static
cmsMLU* GetMLUFromProfile(cmsHPROFILE h, cmsTagSignature sig)
{
cmsMLU* mlu = (cmsMLU*) cmsReadTag(h, sig); if (mlu == NULL) return NULL;
return cmsMLUdup(mlu);
}
// Create a sequence description out of an array of profiles
cmsSEQ* _cmsCompileProfileSequence(cmsContext ContextID, cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[])
{
cmsUInt32Number i;
cmsSEQ* seq = cmsAllocProfileSequenceDescription(ContextID, nProfiles);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.