From f4419a8d200b1e534af01dbf8c65e70e410f286b Mon Sep 17 00:00:00 2001 From: xsooy <38968580+xsooy@users.noreply.github.com> Date: Sat, 29 Jan 2022 12:34:02 +0800 Subject: [PATCH 01/16] Update Type1CharString.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复文字显示异常问题 --- .../main/java/com/tom_roush/fontbox/cff/Type1CharString.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java index 350ae2d7..26ec76ae 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java @@ -123,7 +123,9 @@ public Path getPath() { render(); } - return path; + Path clone = new Path(); + clone.addPath(path); + return clone; } /** From b1059d02d3159f28a2e3a5af5848d3d9e2a9b44f Mon Sep 17 00:00:00 2001 From: "1131777939@qq.com" Date: Mon, 26 Sep 2022 16:14:04 +0800 Subject: [PATCH 02/16] support ICCBase,libIcc --- library/CMakeLists.txt | 14 + library/build.gradle | 12 + library/src/main/cpp/icc/CMakeLists.txt | 47 + library/src/main/cpp/icc/IccApplyBPC.cpp | 621 ++ library/src/main/cpp/icc/IccApplyBPC.h | 135 + library/src/main/cpp/icc/IccCmm.cpp | 6158 +++++++++++++++ library/src/main/cpp/icc/IccCmm.h | 1141 +++ library/src/main/cpp/icc/IccConvertUTF.cpp | 541 ++ library/src/main/cpp/icc/IccConvertUTF.h | 158 + library/src/main/cpp/icc/IccDefs.h | 136 + library/src/main/cpp/icc/IccDemo.cpp | 91 + library/src/main/cpp/icc/IccEval.cpp | 208 + library/src/main/cpp/icc/IccEval.h | 99 + library/src/main/cpp/icc/IccIO.cpp | 712 ++ library/src/main/cpp/icc/IccIO.h | 243 + library/src/main/cpp/icc/IccMpeACS.cpp | 491 ++ library/src/main/cpp/icc/IccMpeACS.h | 175 + library/src/main/cpp/icc/IccMpeBasic.cpp | 2657 +++++++ library/src/main/cpp/icc/IccMpeBasic.h | 417 + library/src/main/cpp/icc/IccMpeFactory.cpp | 195 + library/src/main/cpp/icc/IccMpeFactory.h | 296 + library/src/main/cpp/icc/IccPrmg.cpp | 298 + library/src/main/cpp/icc/IccPrmg.h | 105 + library/src/main/cpp/icc/IccProfLib.dsp | 270 + library/src/main/cpp/icc/IccProfLib.vcproj | 534 ++ library/src/main/cpp/icc/IccProfLibConf.h | 173 + library/src/main/cpp/icc/IccProfLibVer.h | 3 + .../src/main/cpp/icc/IccProfLib_CRTDLL.dsp | 258 + .../src/main/cpp/icc/IccProfLib_CRTDLL.vcproj | 532 ++ .../main/cpp/icc/IccProfLib_CRTDLL_v7.vcproj | 394 + .../main/cpp/icc/IccProfLib_CRTDLL_v8.vcproj | 531 ++ library/src/main/cpp/icc/IccProfLib_v7.vcproj | 401 + library/src/main/cpp/icc/IccProfLib_v8.vcproj | 533 ++ library/src/main/cpp/icc/IccProfile.cpp | 2458 ++++++ library/src/main/cpp/icc/IccProfile.h | 225 + library/src/main/cpp/icc/IccTag.h | 94 + library/src/main/cpp/icc/IccTagBasic.cpp | 6959 +++++++++++++++++ library/src/main/cpp/icc/IccTagBasic.h | 1187 +++ library/src/main/cpp/icc/IccTagDict.cpp | 1467 ++++ library/src/main/cpp/icc/IccTagDict.h | 229 + library/src/main/cpp/icc/IccTagFactory.cpp | 579 ++ library/src/main/cpp/icc/IccTagFactory.h | 322 + library/src/main/cpp/icc/IccTagLut.cpp | 4842 ++++++++++++ library/src/main/cpp/icc/IccTagLut.h | 533 ++ library/src/main/cpp/icc/IccTagMPE.cpp | 1440 ++++ library/src/main/cpp/icc/IccTagMPE.h | 410 + library/src/main/cpp/icc/IccTagProfSeqId.cpp | 693 ++ library/src/main/cpp/icc/IccTagProfSeqId.h | 157 + library/src/main/cpp/icc/IccUtil.cpp | 2026 +++++ library/src/main/cpp/icc/IccUtil.h | 363 + library/src/main/cpp/icc/IccXformFactory.cpp | 171 + library/src/main/cpp/icc/IccXformFactory.h | 248 + library/src/main/cpp/icc/MainPage.h | 457 ++ library/src/main/cpp/icc/Makefile.am | 58 + library/src/main/cpp/icc/Makefile.in | 728 ++ library/src/main/cpp/icc/icProfileHeader.h | 1666 ++++ library/src/main/cpp/icc/md5.cpp | 291 + library/src/main/cpp/icc/md5.h | 61 + .../com/tom_roush/pdfbox/cos/COSArray.java | 19 + .../graphics/color/PDCIEBasedColorSpace.java | 84 + .../color/PDCIEDictionaryBasedColorSpace.java | 177 + .../pdmodel/graphics/color/PDColorSpace.java | 75 +- .../pdmodel/graphics/color/PDICCBased.java | 607 ++ .../graphics/image/SampledImageReader.java | 2 + .../src/main/java/com/xsooy/icc/IccUtils.java | 42 + 65 files changed, 47246 insertions(+), 3 deletions(-) create mode 100644 library/CMakeLists.txt create mode 100644 library/src/main/cpp/icc/CMakeLists.txt create mode 100644 library/src/main/cpp/icc/IccApplyBPC.cpp create mode 100644 library/src/main/cpp/icc/IccApplyBPC.h create mode 100644 library/src/main/cpp/icc/IccCmm.cpp create mode 100644 library/src/main/cpp/icc/IccCmm.h create mode 100644 library/src/main/cpp/icc/IccConvertUTF.cpp create mode 100644 library/src/main/cpp/icc/IccConvertUTF.h create mode 100644 library/src/main/cpp/icc/IccDefs.h create mode 100644 library/src/main/cpp/icc/IccDemo.cpp create mode 100644 library/src/main/cpp/icc/IccEval.cpp create mode 100644 library/src/main/cpp/icc/IccEval.h create mode 100644 library/src/main/cpp/icc/IccIO.cpp create mode 100644 library/src/main/cpp/icc/IccIO.h create mode 100644 library/src/main/cpp/icc/IccMpeACS.cpp create mode 100644 library/src/main/cpp/icc/IccMpeACS.h create mode 100644 library/src/main/cpp/icc/IccMpeBasic.cpp create mode 100644 library/src/main/cpp/icc/IccMpeBasic.h create mode 100644 library/src/main/cpp/icc/IccMpeFactory.cpp create mode 100644 library/src/main/cpp/icc/IccMpeFactory.h create mode 100644 library/src/main/cpp/icc/IccPrmg.cpp create mode 100644 library/src/main/cpp/icc/IccPrmg.h create mode 100644 library/src/main/cpp/icc/IccProfLib.dsp create mode 100644 library/src/main/cpp/icc/IccProfLib.vcproj create mode 100644 library/src/main/cpp/icc/IccProfLibConf.h create mode 100644 library/src/main/cpp/icc/IccProfLibVer.h create mode 100644 library/src/main/cpp/icc/IccProfLib_CRTDLL.dsp create mode 100644 library/src/main/cpp/icc/IccProfLib_CRTDLL.vcproj create mode 100644 library/src/main/cpp/icc/IccProfLib_CRTDLL_v7.vcproj create mode 100644 library/src/main/cpp/icc/IccProfLib_CRTDLL_v8.vcproj create mode 100644 library/src/main/cpp/icc/IccProfLib_v7.vcproj create mode 100644 library/src/main/cpp/icc/IccProfLib_v8.vcproj create mode 100644 library/src/main/cpp/icc/IccProfile.cpp create mode 100644 library/src/main/cpp/icc/IccProfile.h create mode 100644 library/src/main/cpp/icc/IccTag.h create mode 100644 library/src/main/cpp/icc/IccTagBasic.cpp create mode 100644 library/src/main/cpp/icc/IccTagBasic.h create mode 100644 library/src/main/cpp/icc/IccTagDict.cpp create mode 100644 library/src/main/cpp/icc/IccTagDict.h create mode 100644 library/src/main/cpp/icc/IccTagFactory.cpp create mode 100644 library/src/main/cpp/icc/IccTagFactory.h create mode 100644 library/src/main/cpp/icc/IccTagLut.cpp create mode 100644 library/src/main/cpp/icc/IccTagLut.h create mode 100644 library/src/main/cpp/icc/IccTagMPE.cpp create mode 100644 library/src/main/cpp/icc/IccTagMPE.h create mode 100644 library/src/main/cpp/icc/IccTagProfSeqId.cpp create mode 100644 library/src/main/cpp/icc/IccTagProfSeqId.h create mode 100644 library/src/main/cpp/icc/IccUtil.cpp create mode 100644 library/src/main/cpp/icc/IccUtil.h create mode 100644 library/src/main/cpp/icc/IccXformFactory.cpp create mode 100644 library/src/main/cpp/icc/IccXformFactory.h create mode 100644 library/src/main/cpp/icc/MainPage.h create mode 100644 library/src/main/cpp/icc/Makefile.am create mode 100644 library/src/main/cpp/icc/Makefile.in create mode 100644 library/src/main/cpp/icc/icProfileHeader.h create mode 100644 library/src/main/cpp/icc/md5.cpp create mode 100644 library/src/main/cpp/icc/md5.h create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEDictionaryBasedColorSpace.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDICCBased.java create mode 100644 library/src/main/java/com/xsooy/icc/IccUtils.java diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt new file mode 100644 index 00000000..79812b6f --- /dev/null +++ b/library/CMakeLists.txt @@ -0,0 +1,14 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + + +add_subdirectory(src/main/cpp/icc) diff --git a/library/build.gradle b/library/build.gradle index e810f6ed..bd1a40df 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -18,6 +18,18 @@ android { targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-proguard-rules.txt' + + externalNativeBuild { + cmake{ + abiFilters "armeabi-v7a" ,"arm64-v8a" + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } } buildTypes { diff --git a/library/src/main/cpp/icc/CMakeLists.txt b/library/src/main/cpp/icc/CMakeLists.txt new file mode 100644 index 00000000..23ae5927 --- /dev/null +++ b/library/src/main/cpp/icc/CMakeLists.txt @@ -0,0 +1,47 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +set(TARGET icc) +set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/ + # opencv/include +) + +#add_library(libopencv_java4 SHARED IMPORTED) +#set_target_properties(libopencv_java4 PROPERTIES IMPORTED_LOCATION +# ${CMAKE_CURRENT_SOURCE_DIR}/opencv/${ANDROID_ABI}/libopencv_java4.so) + + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +aux_source_directory(${SRC_DIR} DIR_LIB_SOURCE) + +add_library(${TARGET} SHARED ${DIR_LIB_SOURCE}) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +target_link_libraries(${TARGET} log android) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +#if(${ANDROID_ABI} STREQUAL x86 OR ${ANDROID_ABI} STREQUAL x86_64) +#target_link_libraries(${TARGET} opencv_imgproc opencv_core ippiw ippicv ittnotify tbb cpufeatures) +#else() +#target_link_libraries(${TARGET}) +#endif() + diff --git a/library/src/main/cpp/icc/IccApplyBPC.cpp b/library/src/main/cpp/icc/IccApplyBPC.cpp new file mode 100644 index 00000000..9da173e6 --- /dev/null +++ b/library/src/main/cpp/icc/IccApplyBPC.cpp @@ -0,0 +1,621 @@ +/** @file +File: IccApplyBPC.cpp + +Contains: Implementation of Black Point Compensation calculations. + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2003-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Rohit Patil 12-10-2008 +// +////////////////////////////////////////////////////////////////////// + +#include "IccApplyBPC.h" +#include + +#define IsSpacePCS(x) ((x)==icSigXYZData || (x)==icSigLabData) + +/** +************************************************************************** +* Name: CIccApplyBPCHint::GetNewAdjustPCSXform +* +* Purpose: +* Returns a new CIccApplyBPC object. Returned object should be deleted +* by the caller. +* +************************************************************************** +*/ +IIccAdjustPCSXform* CIccApplyBPCHint::GetNewAdjustPCSXform() const +{ + return new CIccApplyBPC(); +} + +////////////////////////////////////////////////////////////////////// +// CIccApplyBPC utility functions +////////////////////////////////////////////////////////////////////// + +// converts lab to pcs +void CIccApplyBPC::lab2pcs(icFloatNumber* pixel, const CIccProfile* pProfile) const +{ + switch (pProfile->m_Header.pcs) + { + case icSigLabData: + icLabToPcs(pixel); + break; + + case icSigXYZData: + icLabtoXYZ(pixel); + icXyzToPcs(pixel); + break; + + default: + break; + } +} + +// converts pcs to lab +void CIccApplyBPC::pcs2lab(icFloatNumber* pixel, const CIccProfile* pProfile) const +{ + switch (pProfile->m_Header.pcs) + { + case icSigLabData: + icLabFromPcs(pixel); + break; + + case icSigXYZData: + icXyzFromPcs(pixel); + icXYZtoLab(pixel); + break; + + default: + break; + } +} + +// calculates sum of product of x^j and y^k polynomials +icFloatNumber CIccApplyBPC::calcsum(icFloatNumber* x, icFloatNumber* y, int n, int j, int k) const +{ + icFloatNumber dSum = 0.0; + + int i; + if (j && k) { + for (i=0; i2) { // need at least three points to solve three linear equations + icFloatNumber s00, s10, s20, s30, s40, s01, s11, s21, denom; + s00 = calcsum(x, y, n, 0, 0); + s10 = calcsum(x, y, n, 1, 0); + s20 = calcsum(x, y, n, 2, 0); + s30 = calcsum(x, y, n, 3, 0); + s40 = calcsum(x, y, n, 4, 0); + s01 = calcsum(x, y, n, 0, 1); + s11 = calcsum(x, y, n, 1, 1); + s21 = calcsum(x, y, n, 2, 1); + denom = (icFloatNumber)(s00*s20*s40 - s10*s10*s40 - s00*s30*s30 + 2.0*s10*s20*s30 - s20*s20*s20); + if (fabs(denom)>0.0) { + // t and u are the coefficients of the quadratic equation y = tx^2 + ux + c + // the three equations with 3 unknowns can be written as + // [s40 s30 s20][t] [s21] + // [s30 s20 s10][u] = [s11] + // [s20 s10 s00][c] [s01] + icFloatNumber t = (s01*s10*s30 - s11*s00*s30 - s01*s20*s20 + s11*s10*s20 + s21*s00*s20 - s21*s10*s10)/denom; + + icFloatNumber u = (s11*s00*s40 - s01*s10*s40 + s01*s20*s30 - s21*s00*s30 - s11*s20*s20 + s21*s10*s20)/denom; + + icFloatNumber c = (s01*s20*s40 - s11*s10*s40 - s01*s30*s30 + s11*s20*s30 + s21*s10*s30 - s21*s20*s20)/denom; + + // vertex is (-u + sqrt(u^2 - 4tc))/2t + vert = (icFloatNumber)((-1.0 * u + sqrt(u*u - 4*t*c)) / (2.0 * t)); + } + } + + return vert; +} + +/** +************************************************************************** +* Name: CIccApplyBPC::CalculateFactors +* +* Purpose: +* This function does the suitable calculations to setup black point +* compensation. +* +* Args: +* pXform = pointer to the Xform object that calls this function +* +* Return: +* true = all calculations done +* false = an error occurred +************************************************************************** +*/ +bool CIccApplyBPC::CalcFactors(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* Scale, icFloatNumber* Offset) const +{ + if (!pProfile || !pXform) + return false; + + if (pXform->GetIntent()==icAbsoluteColorimetric) { // black point compensation not supported + return false; + } + + switch (pProfile->m_Header.deviceClass) + { // These profile classes not supported + case icSigLinkClass: + case icSigAbstractClass: + //case icSigColorSpaceClass: + case icSigNamedColorClass: + return false; + default: + break; + } + + icFloatNumber XYZbp[3]; // storage for black point XYZ + + // calculate the black point + if (!calcBlackPoint(pProfile, pXform, XYZbp)) { + return false; + } + + // calculate the scale and offset + if (pXform->IsInput()) { // use PRM black as destination black + Scale[0] = (icFloatNumber)((1.0 - icPerceptualRefBlackY)/(1.0 - XYZbp[1])); + } + else { // use PRM black as source black + Scale[0] = (icFloatNumber)((1.0 - XYZbp[1])/(1.0 - icPerceptualRefBlackY)); + } + + Scale[1] = Scale[0]; + Scale[2] = Scale[0]; + + Offset[0] = (icFloatNumber)((1.0 - Scale[0]) * icPerceptualRefWhiteX); + Offset[1] = (icFloatNumber)((1.0 - Scale[1]) * icPerceptualRefWhiteY); + Offset[2] = (icFloatNumber)((1.0 - Scale[2]) * icPerceptualRefWhiteZ); + + icXyzToPcs(Offset); + + return true; +} + +/** +************************************************************************** +* Name: CIccApplyBPC::calcBlackPoint +* +* Purpose: +* Calculates the black point of a profile +* +************************************************************************** +*/ +bool CIccApplyBPC::calcBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const +{ + if (pXform->IsInput()) { // profile used as input/source profile + return calcSrcBlackPoint(pProfile, pXform, XYZb); + } + else { // profile used as output profile + return calcDstBlackPoint(pProfile, pXform, XYZb); + } + + return true; +} + +/** +************************************************************************** +* Name: CIccApplyBPC::calcSrcBlackPoint +* +* Purpose: +* Calculates the black point of a source profile +* +************************************************************************** +*/ +bool CIccApplyBPC::calcSrcBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const +{ + icFloatNumber Pixel[16]; + if ((pProfile->m_Header.colorSpace == icSigCmykData) && (pProfile->m_Header.deviceClass == icSigOutputClass)) { + + // calculate intermediate CMYK + XYZb[0] = XYZb[1] = XYZb[2] = 0.0; + + // convert the Lab of 0,0,0 to relevant PCS + lab2pcs(XYZb, pProfile); + + //convert the PCS value to CMYK + if (!pixelXfm(Pixel, XYZb, pProfile->m_Header.pcs, icPerceptual, pProfile)) { + return false; + } + } + else { + switch (pProfile->m_Header.colorSpace) { + case icSigRgbData: + Pixel[0] = 0.0; + Pixel[1] = 0.0; + Pixel[2] = 0.0; + break; + + case icSigGrayData: + Pixel[0] = 0.0; + break; + + case icSigCmykData: + case icSigCmyData: + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + { + icUInt32Number nSamples = icGetSpaceSamples(pProfile->m_Header.colorSpace); + for (icUInt32Number i=0; im_Header.colorSpace, pXform->GetIntent(), pProfile)) { + return false; + } + + // convert PCS to Lab + pcs2lab(XYZb, pProfile); + + // set a* b* to zero for cmyk profiles + if (pProfile->m_Header.colorSpace == icSigCmykData) { + XYZb[1] = XYZb[2] = 0.0; + } + + // clip L* to 50 + if (XYZb[0]>50.0) { + XYZb[0] = 50.0; + } + + // convert Lab to XYZ + icLabtoXYZ(XYZb); + return true; +} + +/** +************************************************************************** +* Name: CIccApplyBPC::calcDstBlackPoint +* +* Purpose: +* Calculates the black point of a destination profile +* +************************************************************************** +*/ +bool CIccApplyBPC::calcDstBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const +{ + icRenderingIntent nIntent = pXform->GetIntent(); + icFloatNumber Pixel[3]; + icFloatNumber pcsPixel[3]; + + // check if the profile is lut based gray, rgb or cmyk + if (pProfile->IsTagPresent(icSigBToA0Tag) && + (pProfile->m_Header.colorSpace==icSigGrayData || pProfile->m_Header.colorSpace==icSigRgbData || pProfile->m_Header.colorSpace==icSigCmykData)) + { // do the complicated and lengthy black point estimation + + // get the black transform + CIccCmm* pCmm = getBlackXfm(nIntent, pProfile); + if (!pCmm) { + return false; + } + + // set the initial Lab + icFloatNumber iniLab[3] = {0.0, 0.0, 0.0}; + + // calculate minL + pcsPixel[0] = 0.0; + pcsPixel[1] = iniLab[1]; + pcsPixel[2] = iniLab[2]; + lab2pcs(pcsPixel, pProfile); + if (pCmm->Apply(Pixel, pcsPixel)!=icCmmStatOk) { + delete pCmm; + return false; + } + pcs2lab(Pixel, pProfile); + icFloatNumber MinL = Pixel[0]; + + // calculate MaxL + pcsPixel[0] = 100.0; + pcsPixel[1] = iniLab[1]; + pcsPixel[2] = iniLab[2]; + lab2pcs(pcsPixel, pProfile); + if (pCmm->Apply(Pixel, pcsPixel)!=icCmmStatOk) { + delete pCmm; + return false; + } + pcs2lab(Pixel, pProfile); + icFloatNumber MaxL = Pixel[0]; + + // check if quadratic estimation needs to be done + bool bStraightMidRange = false; + + // if the intent is relative + if (nIntent==icRelativeColorimetric) + { + // calculate initial Lab as source black point + if (!calcSrcBlackPoint(pProfile, pXform, iniLab)) { + delete pCmm; + return false; + } + + // convert the XYZ to lab + icXYZtoLab(iniLab); + + // check mid range L* values + icFloatNumber lcnt=0.0, roundtripL; + bStraightMidRange = true; + while (lcnt<100.1) + { + pcsPixel[0] = icFloatNumber(lcnt); + pcsPixel[1] = iniLab[1]; + pcsPixel[2] = iniLab[2]; + lab2pcs(pcsPixel, pProfile); + if (pCmm->Apply(Pixel, pcsPixel)!=icCmmStatOk) { + delete pCmm; + return false; + } + pcs2lab(Pixel, pProfile); + roundtripL = Pixel[0]; + + if (roundtripL>(MinL + 0.2 * (MaxL - MinL))) { + if (fabs(roundtripL - lcnt)>4.0) { + bStraightMidRange = false; + break; + } + } + + lcnt += 1.0; + } + } + + // quadratic estimation is not needed + if (bStraightMidRange) { // initial Lab is the destination black point + XYZb[0] = iniLab[0]; + XYZb[1] = iniLab[1]; + XYZb[2] = iniLab[2]; + icLabtoXYZ(XYZb); + delete pCmm; + return true; + } + + // find the black point using the least squares error quadratic curve fitting + + // calculate y values + icFloatNumber x[101], y[101]; + icFloatNumber lo=0.03f, hi=0.25f; + int i, n; + if (nIntent==icRelativeColorimetric) { + lo = 0.1f; + hi = 0.5f; + } + + for (i=0; i<101; i++) { + x[i] = icFloatNumber(i); + pcsPixel[0] = x[i]; + pcsPixel[1] = iniLab[1]; + pcsPixel[2] = iniLab[2]; + lab2pcs(pcsPixel, pProfile); + if (pCmm->Apply(Pixel, pcsPixel)!=icCmmStatOk) { + delete pCmm; + return false; + } + pcs2lab(Pixel, pProfile); + y[i] = (Pixel[0] - MinL)/(MaxL - MinL); + } + + // check for y values in the range and rearrange + n = 0; + for (i=0; i<101; i++) { + if (y[i]>=lo && y[i] PCS round trip transform, always uses relative intent on the device -> pcs transform +* +************************************************************************** +*/ +CIccCmm* CIccApplyBPC::getBlackXfm(icRenderingIntent nIntent, const CIccProfile *pProfile) const +{ + // create the cmm object + CIccCmm* pCmm = new CIccCmm(pProfile->m_Header.pcs, icSigUnknownData, false); + if (!pCmm) return NULL; + + // first create a copy of the profile because the copy will be owned by the cmm + CIccProfile* pICC1 = new CIccProfile(*pProfile); + if (!pICC1) { + delete pCmm; + return NULL; + } + + // add the xform + if (pCmm->AddXform(pICC1, nIntent, icInterpTetrahedral)!=icCmmStatOk) { + delete pICC1; + delete pCmm; + return NULL; + } + + // create another copy of the profile because the copy will be owned by the cmm + CIccProfile* pICC2 = new CIccProfile(*pProfile); + if (!pICC2) { + delete pCmm; + return NULL; + } + + // add the xform + if (pCmm->AddXform(pICC2, icRelativeColorimetric, icInterpTetrahedral)!=icCmmStatOk) { // uses the relative intent on the device to Lab side + delete pICC2; + delete pCmm; + return NULL; + } + + // get the cmm ready to do transforms + if (pCmm->Begin()!=icCmmStatOk) { + delete pCmm; + return NULL; + } + + return pCmm; +} diff --git a/library/src/main/cpp/icc/IccApplyBPC.h b/library/src/main/cpp/icc/IccApplyBPC.h new file mode 100644 index 00000000..6e82a16a --- /dev/null +++ b/library/src/main/cpp/icc/IccApplyBPC.h @@ -0,0 +1,135 @@ +/** @file +File: IccApplyBPC.h + +Contains: Header file for implementation of Black Point Compensation calculations. + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2003-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Rohit Patil 12-8-2008 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCAPPLYBPC_H) +#define _ICCAPPLYBPC_H + +#include "IccCmm.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Interface and hint for creating a BPC xform object +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyBPCHint : public CIccCreateAdjustPCSXformHint +{ +public: + virtual const char *GetAdjustPCSType() const { return "CIccApplyBPCHint"; } + virtual IIccAdjustPCSXform* GetNewAdjustPCSXform() const; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the hint for applying black point compensation. + * Also does the calculations to setup actual application of BPC. + * + ************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyBPC : public IIccAdjustPCSXform +{ +public: + // virtual IIccAdjustPCSXform functions + // does all the calculations for BPC and returns the scale and offset in the arguments passed + virtual bool CalcFactors(const CIccProfile* pProfile, const CIccXform* pXfm, icFloatNumber* Scale, icFloatNumber* Offset) const; + +private: + // utility functions + void lab2pcs(icFloatNumber* pixel, const CIccProfile* pProfile) const; + void pcs2lab(icFloatNumber* pixel, const CIccProfile* pProfile) const; + icFloatNumber calcsum(icFloatNumber* x, icFloatNumber* y, int n, int j, int k) const; + icFloatNumber calcQuadraticVertex(icFloatNumber* x, icFloatNumber* y, int n) const; + + // worker functions + bool calcBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const; + bool calcSrcBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const; + bool calcDstBlackPoint(const CIccProfile* pProfile, const CIccXform* pXform, icFloatNumber* XYZb) const; + + bool pixelXfm(icFloatNumber *DstPixel, icFloatNumber *SrcPixel, icColorSpaceSignature SrcSpace, + icRenderingIntent nIntent, const CIccProfile *pProfile) const; + + // PCS -> PCS round trip transform, always uses relative intent on the device -> pcs transform + CIccCmm* getBlackXfm(icRenderingIntent nIntent, const CIccProfile *pProfile) const; +}; + +#ifdef USESAMPLEICCNAMESPACE +}; //namespace sampleICC +#endif + +#endif // _ICCAPPLYBPC_H + diff --git a/library/src/main/cpp/icc/IccCmm.cpp b/library/src/main/cpp/icc/IccCmm.cpp new file mode 100644 index 00000000..8b639cc9 --- /dev/null +++ b/library/src/main/cpp/icc/IccCmm.cpp @@ -0,0 +1,6158 @@ +/** @file + File: IccCmm.cpp + + Contains: Implementation of the CIccCmm class. + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// -Added support for Monochrome ICC profile apply by Rohit Patil 12-03-2008 +// -Integrated changes for PCS adjustment by George Pawle 12-09-2008 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include "IccXformFactory.h" +#include "IccTag.h" +#include "IccIO.h" +#include "IccApplyBPC.h" +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +//// +// Useful Macros +//// + +#define IsSpacePCS(x) ((x)==icSigXYZData || (x)==icSigLabData) +#define IsSpaceCMYK(x) ((x)==icSigCmykData || (x)==icSig4colorData) + +#define IsCompatSpace(x, y) ((x)==(y) || (IsSpacePCS(x) && IsSpacePCS(y)) || (IsSpaceCMYK(x) && IsSpaceCMYK(y))) + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +/** + ************************************************************************** + * Class CIccPCS Constructor + * + * Purpose: + * This is a class constructor. + * + ************************************************************************** + */ +CIccPCS::CIccPCS() +{ + m_bIsV2Lab = false; + m_Space = icSigUnknownData; +} + +/** +************************************************************************** +* Name: CIccPCS::Reset +* +* Purpose: +* This is called with the initial color space and a bool +* argument which is true if the PCS is version 2. +* +* Args: +* Startpsace = Starting Colorspace +* bUseLegacyPCS = legacy PCS flag +************************************************************************** +*/ +void CIccPCS::Reset(icColorSpaceSignature StartSpace, bool bUseLegacyPCS) +{ + m_bIsV2Lab = IsSpacePCS(StartSpace) && bUseLegacyPCS; + m_Space = StartSpace; +} + +/** + ************************************************************************** + * Name: CIccPCS::Check + * + * Purpose: + * This is called before the apply of each profile's xform to adjust the PCS + * to the xform's needed PCS. + * + * Args: + * SrcPixel = source pixel data (this may need adjusting), + * pXform = the xform that who's Apply function will shortly be called + * + * Return: + * SrcPixel or ptr to adjusted pixel data (we dont want to modify the source data). + ************************************************************************** + */ +const icFloatNumber *CIccPCS::Check(const icFloatNumber *SrcPixel, const CIccXform *pXform) +{ + icColorSpaceSignature NextSpace = pXform->GetSrcSpace(); + bool bIsV2 = pXform->UseLegacyPCS(); + bool bIsNextV2Lab = bIsV2 && (NextSpace == icSigLabData); + const icFloatNumber *rv; + bool bNoClip = pXform->NoClipPCS(); + + if (m_bIsV2Lab && !bIsNextV2Lab) { + Lab2ToLab4(m_Convert, SrcPixel, bNoClip); + if (NextSpace==icSigXYZData) { + LabToXyz(m_Convert, m_Convert, bNoClip); + } + rv = m_Convert; + } + else if (!m_bIsV2Lab && bIsNextV2Lab) { + if (m_Space==icSigXYZData) { + XyzToLab(m_Convert, SrcPixel, bNoClip); + SrcPixel = m_Convert; + } + Lab4ToLab2(m_Convert, SrcPixel); + rv = m_Convert; + } + else if (m_Space==NextSpace) { + rv = SrcPixel; + } + else if (m_Space==icSigXYZData && NextSpace==icSigLabData) { + XyzToLab(m_Convert, SrcPixel, bNoClip); + rv = m_Convert; + } + else if (m_Space==icSigLabData && NextSpace==icSigXYZData) { + LabToXyz(m_Convert, SrcPixel, bNoClip); + rv = m_Convert; + } + else { + rv = SrcPixel; + } + + m_Space = pXform->GetDstSpace(); + m_bIsV2Lab = bIsV2 && (m_Space == icSigLabData); + + return rv; +} + +/** + ************************************************************************** + * Name: CIccPCS::CheckLast + * + * Purpose: + * Called after all xforms are applied to adjust PCS to final space if needed + * Note: space will always be V4. + * + * Args: + * Pixel = Pixel data, + * DestSpace = destination color space + * bNoClip = indicates whether PCS should be clipped + ************************************************************************** + */ +void CIccPCS::CheckLast(icFloatNumber *Pixel, icColorSpaceSignature DestSpace, bool bNoClip) +{ + if (m_bIsV2Lab) { + Lab2ToLab4(Pixel, Pixel, bNoClip); + if (DestSpace==icSigXYZData) { + LabToXyz(Pixel, Pixel, bNoClip); + } + } + else if (m_Space==DestSpace) { + return; + } + else if (m_Space==icSigXYZData) { + XyzToLab(Pixel, Pixel, bNoClip); + } + else if (m_Space==icSigLabData) { + LabToXyz(Pixel, Pixel, bNoClip); + } +} + +/** + ************************************************************************** + * Name: CIccPCS::UnitClip + * + * Purpose: + * Convert a double to an icUInt16Number with clipping + ************************************************************************** + */ +icFloatNumber CIccPCS::UnitClip(icFloatNumber v) +{ + if (v<0) + v = 0; + if (v>1.0) + v = 1.0; + + return v; +} + +/** + ************************************************************************** + * Name: CIccPCS::NegClip + * + * Purpose: + * Convert a double to an icUInt16Number with clipping of negative numbers + ************************************************************************** + */ +icFloatNumber CIccPCS::NegClip(icFloatNumber v) +{ + if (v<0) + v=0; + + return v; +} + +/** + ************************************************************************** + * Name: CIccPCS::LabToXyz + * + * Purpose: + * Convert Lab to XYZ + ************************************************************************** + */ +void CIccPCS::LabToXyz(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip) +{ + icFloatNumber Lab[3]; + + memcpy(&Lab,Src,sizeof(Lab)); + + icLabFromPcs(Lab); + + icLabtoXYZ(Lab); + + icXyzToPcs(Lab); + + if (!bNoClip) { + Dst[0] = UnitClip(Lab[0]); + Dst[1] = UnitClip(Lab[1]); + Dst[2] = UnitClip(Lab[2]); + } + else { + Dst[0] = Lab[0]; + Dst[1] = Lab[1]; + Dst[2] = Lab[2]; + } +} + + +/** + ************************************************************************** + * Name: CIccPCS::XyzToLab + * + * Purpose: + * Convert XYZ to Lab + ************************************************************************** + */ +void CIccPCS::XyzToLab(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip) +{ + icFloatNumber XYZ[3]; + + + if (!bNoClip) { + XYZ[0] = UnitClip(Src[0]); + XYZ[1] = UnitClip(Src[1]); + XYZ[2] = UnitClip(Src[2]); + } + else { + XYZ[0] = Src[0]; + XYZ[1] = Src[1]; + XYZ[2] = Src[2]; + } + + icXyzFromPcs(XYZ); + + icXYZtoLab(XYZ); + + icLabToPcs(XYZ); + + if (!bNoClip) { + Dst[0] = UnitClip(XYZ[0]); + Dst[1] = UnitClip(XYZ[1]); + Dst[2] = UnitClip(XYZ[2]); + } + else { + Dst[0] = XYZ[0]; + Dst[1] = XYZ[1]; + Dst[2] = XYZ[2]; + } +} + + +/** + ************************************************************************** + * Name: CIccPCS::Lab2ToXyz + * + * Purpose: + * Convert version 2 Lab to XYZ + ************************************************************************** + */ +void CIccPCS::Lab2ToXyz(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip) +{ + Lab2ToLab4(Dst, Src, bNoClip); + LabToXyz(Dst, Dst, bNoClip); +} + + +/** + ************************************************************************** + * Name: CIccPCS::XyzToLab2 + * + * Purpose: + * Convert XYZ to version 2 Lab + ************************************************************************** + */ +void CIccPCS::XyzToLab2(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip) +{ + XyzToLab(Dst, Src, bNoClip); + Lab4ToLab2(Dst, Dst); +} + + +/** + ************************************************************************** + * Name: CIccPCS::Lab2ToLab4 + * + * Purpose: + * Convert version 2 Lab to version 4 Lab + ************************************************************************** + */ +void CIccPCS::Lab2ToLab4(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip) +{ + if (bNoClip) { + Dst[0] = (icFloatNumber)(Src[0] * 65535.0f / 65280.0f); + Dst[1] = (icFloatNumber)(Src[1] * 65535.0f / 65280.0f); + Dst[2] = (icFloatNumber)(Src[2] * 65535.0f / 65280.0f); + } + else { + Dst[0] = UnitClip((icFloatNumber)(Src[0] * 65535.0f / 65280.0f)); + Dst[1] = UnitClip((icFloatNumber)(Src[1] * 65535.0f / 65280.0f)); + Dst[2] = UnitClip((icFloatNumber)(Src[2] * 65535.0f / 65280.0f)); + } +} + +/** + ************************************************************************** + * Name: CIccPCS::Lab4ToLab2 + * + * Purpose: + * Convert version 4 Lab to version 2 Lab + ************************************************************************** + */ +void CIccPCS::Lab4ToLab2(icFloatNumber *Dst, const icFloatNumber *Src) +{ + Dst[0] = (icFloatNumber)(Src[0] * 65280.0f / 65535.0f); + Dst[1] = (icFloatNumber)(Src[1] * 65280.0f / 65535.0f); + Dst[2] = (icFloatNumber)(Src[2] * 65280.0f / 65535.0f); +} + +/** +************************************************************************** +* Name: CIccCreateXformHintManager::CIccCreateXformHintManager +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccCreateXformHintManager::~CIccCreateXformHintManager() +{ + if (m_pList) { + IIccCreateXformHintList::iterator i; + + for (i=m_pList->begin(); i!=m_pList->end(); i++) { + if (i->ptr) + delete i->ptr; + } + + delete m_pList; + } +} + +/** +************************************************************************** +* Name: CIccCreateXformHintManager::AddHint +* +* Purpose: +* Adds and owns the passed named hint to it's list. +* +* Args: +* pHint = pointer to the hint object to be added +* +* Return: +* true = hint added to the list +* false = hint not added +************************************************************************** +*/ +bool CIccCreateXformHintManager::AddHint(IIccCreateXformHint* pHint) +{ + if (!m_pList) { + m_pList = new IIccCreateXformHintList; + } + + if (pHint) { + if (GetHint(pHint->GetHintType())) { + delete pHint; + return false; + } + IIccCreateXformHintPtr Hint; + Hint.ptr = pHint; + m_pList->push_back(Hint); + return true; + } + + return false; +} + +/** +************************************************************************** +* Name: CIccCreateXformHintManager::DeleteHint +* +* Purpose: +* Deletes the object referenced by the passed named hint pointer +* and removes it from the list. +* +* Args: +* pHint = pointer to the hint object to be deleted +* +* Return: +* true = hint found and deleted +* false = hint not found +************************************************************************** +*/ +bool CIccCreateXformHintManager::DeleteHint(IIccCreateXformHint* pHint) +{ + if (m_pList && pHint) { + IIccCreateXformHintList::iterator i; + for (i=m_pList->begin(); i!=m_pList->end(); i++) { + if (i->ptr) { + if (i->ptr == pHint) { + delete pHint; + pHint = NULL; + m_pList->erase(i); + return true; + } + } + } + } + + return false; +} + +/** +************************************************************************** +* Name: CIccCreateXformHintManager::GetHint +* +* Purpose: +* Finds and returns a pointer to the named hint. +* +* Args: +* hintName = name of the desired hint +* +* Return: +* Appropriate IIccCreateXformHint pointer +************************************************************************** +*/ +IIccCreateXformHint* CIccCreateXformHintManager::GetHint(const char* hintName) +{ + IIccCreateXformHint* pHint=NULL; + + if (m_pList) { + IIccCreateXformHintList::iterator i; + for (i=m_pList->begin(); i!=m_pList->end(); i++) { + if (i->ptr) { + if (!strcmp(i->ptr->GetHintType(), hintName)) { + pHint = i->ptr; + break; + } + } + } + } + + return pHint; +} + +/** + ************************************************************************** + * Name: CIccXform::CIccXform + * + * Purpose: + * Constructor + ************************************************************************** + */ +CIccXform::CIccXform() +{ + m_pProfile = NULL; + m_bInput = true; + m_nIntent = icUnknownIntent; + m_pAdjustPCS = NULL; + m_bAdjustPCS = false; +} + + +/** + ************************************************************************** + * Name: CIccXform::~CIccXform + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXform::~CIccXform() +{ + if (m_pProfile) + delete m_pProfile; + + if (m_pAdjustPCS) { + delete m_pAdjustPCS; + } + +} + + +/** + ************************************************************************** + * Name: CIccXform::Create + * + * Purpose: + * This is a static Creation function that creates derived CIccXform objects and + * initializes them. + * + * Args: + * pProfile = pointer to a CIccProfile object that will be owned by the transform. This object will + * be destroyed when the returned CIccXform object is destroyed. The means that the CIccProfile + * object needs to be allocated on the heap. + * bInput = flag to indicate whether to use the input or output side of the profile, + * nIntent = the rendering intent to apply to the profile, + * nInterp = the interpolation algorithm to use for N-D luts. + * nLutType = selection of which transform lut to use + * bUseMpeTags = flag to indicate the use MPE flags if available + * pHintManager = pointer to object that contains xform creation hints + * + * Return: + * A suitable pXform object + ************************************************************************** + */ +CIccXform *CIccXform::Create(CIccProfile *pProfile, bool bInput/* =true */, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, icXformLutType nLutType/* =icXformLutColor */, + bool bUseMpeTags/* =true */, CIccCreateXformHintManager *pHintManager/* =NULL */) +{ + CIccXform *rv = NULL; + icRenderingIntent nTagIntent = nIntent; + + if (pProfile->m_Header.deviceClass==icSigLinkClass && nIntent==icAbsoluteColorimetric) { + nIntent = icPerceptual; + } + + if (nTagIntent == icUnknownIntent) + nTagIntent = icPerceptual; + + switch (nLutType) { + case icXformLutColor: + if (bInput) { + CIccTag *pTag = NULL; + if (bUseMpeTags) { + pTag = pProfile->FindTag(icSigDToB0Tag + nTagIntent); + + if (!pTag && nTagIntent ==icAbsoluteColorimetric) { + pTag = pProfile->FindTag(icSigDToB1Tag); + if (pTag) + nTagIntent = icRelativeColorimetric; + } + + //Apparently Using DtoB0 is not prescribed here by the ICC Specification + //if (!pTag) { + // pTag = pProfile->FindTag(icSigDToB0Tag); + //} + + //Unsupported elements cause fall back behavior + if (pTag && !pTag->IsSupported()) + pTag = NULL; + } + + if (!pTag) { + if (nTagIntent == icAbsoluteColorimetric) + nTagIntent = icRelativeColorimetric; + pTag = pProfile->FindTag(icSigAToB0Tag + nTagIntent); + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigAToB0Tag); + } + + if (!pTag) { + if (pProfile->m_Header.colorSpace == icSigRgbData) { + rv = CIccXformCreator::CreateXform(icXformTypeMatrixTRC, NULL, pHintManager); + } + else if (pProfile->m_Header.colorSpace == icSigGrayData) { + rv = CIccXformCreator::CreateXform(icXformTypeMonochrome, NULL, pHintManager); + } + else + return NULL; + } + else if (pTag->GetType()==icSigMultiProcessElementType) { + rv = CIccXformCreator::CreateXform(icXformTypeMpe, pTag, pHintManager); + } + else { + switch(pProfile->m_Header.colorSpace) { + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigRgbData: + case icSigHsvData: + case icSigHlsData: + case icSigCmyData: + case icSig3colorData: + rv = CIccXformCreator::CreateXform(icXformType3DLut, pTag, pHintManager); + break; + + case icSigCmykData: + case icSig4colorData: + rv = CIccXformCreator::CreateXform(icXformType4DLut, pTag, pHintManager); + break; + + default: + rv = CIccXformCreator::CreateXform(icXformTypeNDLut, pTag, pHintManager); + break; + } + } + } + else { + CIccTag *pTag = NULL; + + if (bUseMpeTags) { + pTag = pProfile->FindTag(icSigBToD0Tag + nTagIntent); + + if (!pTag && nTagIntent ==icAbsoluteColorimetric) { + pTag = pProfile->FindTag(icSigBToD1Tag); + if (pTag) + nTagIntent = icRelativeColorimetric; + } + + //Apparently Using BtoD0 is not prescribed here by the ICC Specification + //if (!pTag) { + // pTag = pProfile->FindTag(icSigBToD0Tag); + //} + + //Unsupported elements cause fall back behavior + if (pTag && !pTag->IsSupported()) + pTag = NULL; + } + + if (!pTag) { + if (nTagIntent == icAbsoluteColorimetric) + nTagIntent = icRelativeColorimetric; + pTag = pProfile->FindTag(icSigBToA0Tag + nTagIntent); + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigBToA0Tag); + } + + if (!pTag) { + if (pProfile->m_Header.colorSpace == icSigRgbData) { + rv = CIccXformCreator::CreateXform(icXformTypeMatrixTRC, pTag, pHintManager); + } + else if (pProfile->m_Header.colorSpace == icSigGrayData) { + rv = CIccXformCreator::CreateXform(icXformTypeMonochrome, NULL, pHintManager); + } + else + return NULL; + } + else if (pTag->GetType()==icSigMultiProcessElementType) { + rv = CIccXformCreator::CreateXform(icXformTypeMpe, pTag, pHintManager); + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = CIccXformCreator::CreateXform(icXformType3DLut, pTag, pHintManager); + break; + + default: + break; + } + } + } + break; + + case icXformLutNamedColor: + { + CIccTag *pTag = pProfile->FindTag(icSigNamedColor2Tag); + if (!pTag) + return NULL; + + CIccCreateNamedColorXformHint* pNamedColorHint = new CIccCreateNamedColorXformHint(); + pNamedColorHint->csPcs = pProfile->m_Header.pcs; + pNamedColorHint->csDevice = pProfile->m_Header.colorSpace; + if (pHintManager) { + pHintManager->AddHint(pNamedColorHint); + rv = CIccXformCreator::CreateXform(icXformTypeNamedColor, pTag, pHintManager); + pHintManager->DeleteHint(pNamedColorHint); + } + else { + CIccCreateXformHintManager HintManager; + HintManager.AddHint(pNamedColorHint); + rv = CIccXformCreator::CreateXform(icXformTypeNamedColor, pTag, &HintManager); + } + } + break; + + case icXformLutPreview: + { + bInput = false; + CIccTag *pTag = pProfile->FindTag(icSigPreview0Tag + nTagIntent); + if (!pTag) { + pTag = pProfile->FindTag(icSigPreview0Tag); + } + if (!pTag) { + return NULL; + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = CIccXformCreator::CreateXform(icXformType3DLut, pTag, pHintManager); + + default: + break; + } + } + } + break; + + case icXformLutGamut: + { + bInput = false; + CIccTag *pTag = pProfile->FindTag(icSigGamutTag); + if (!pTag) { + return NULL; + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = CIccXformCreator::CreateXform(icXformType3DLut, pTag, pHintManager); + + default: + break; + } + } + } + break; + } + + if (rv) { + rv->SetParams(pProfile, bInput, nIntent, nInterp, pHintManager); + } + + return rv; +} + +/** + ****************************************************************************** + * Name: CIccXform::SetParams + * + * Purpose: + * This is an accessor function to set private values. + * + * Args: + * pProfile = pointer to profile associated with transform + * bInput = indicates whether profile is input profile + * nIntent = rendering intent to apply to the profile + * nInterp = the interpolation algorithm to use for N-D luts + ******************************************************************************/ +void CIccXform::SetParams(CIccProfile *pProfile, bool bInput, icRenderingIntent nIntent, + icXformInterp nInterp, CIccCreateXformHintManager *pHintManager/* =NULL */) +{ + m_pProfile = pProfile; + m_bInput = bInput; + m_nIntent = nIntent; + m_nInterp = nInterp; + m_pAdjustPCS = NULL; + + IIccCreateXformHint *pHint=NULL; + if (pHintManager && (pHint = pHintManager->GetHint("CIccCreateAdjustPCSXformHint"))){ + CIccCreateAdjustPCSXformHint *pAdjustPCSHint = (CIccCreateAdjustPCSXformHint*)pHint; + m_pAdjustPCS = pAdjustPCSHint->GetNewAdjustPCSXform(); + } +} + +/** + ************************************************************************** + * Name: CIccXform::Create + * + * Purpose: + * This is a static Creation function that creates derived CIccXform objects and + * initializes them. + * + * Args: + * Profile = reference to a CIccProfile object that will be used to create the transform. + * A copy of the CIccProfile object will be created and passed to the pointer based Create(). + * The copied object will be destroyed when the returned CIccXform object is destroyed. + * bInput = flag to indicate whether to use the input or output side of the profile, + * nIntent = the rendering intent to apply to the profile, + * nInterp = the interpolation algorithm to use for N-D luts. + * nLutType = selection of which transform lut to use + * bUseMpeTags = flag to indicate the use MPE flags if available + * pHint = pointer to object passed to CIccXform creation functionality + * + * Return: + * A suitable pXform object + ************************************************************************** + */ +CIccXform *CIccXform::Create(CIccProfile &Profile, bool bInput/* =true */, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, icXformLutType nLutType/* =icXformLutColor */, + bool bUseMpeTags/* =true */, CIccCreateXformHintManager *pHintManager/* =NULL */) +{ + CIccProfile *pProfile = new CIccProfile(Profile); + CIccXform *pXform = Create(pProfile, bInput, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (!pXform) + delete pProfile; + + return pXform; +} + + +/** + ************************************************************************** + * Name: CIccXform::Begin + * + * Purpose: + * This function will be called before the xform is applied. Derived objects + * should also call this base class function to initialize for Absolute Colorimetric + * Intent handling which is performed through the use of the CheckSrcAbs and + * CheckDstAbs functions. + ************************************************************************** + */ +icStatusCMM CIccXform::Begin() +{ + if (m_nIntent==icAbsoluteColorimetric) { + CIccTag *pTag = m_pProfile->FindTag(icSigMediaWhitePointTag); + + if (!pTag || pTag->GetType()!=icSigXYZType) + return icCmmStatInvalidProfile; + + CIccTagXYZ *pXyzTag = (CIccTagXYZ*)pTag; + + m_MediaXYZ = (*pXyzTag)[0]; + } + + // set up for any needed PCS adjustment + if (m_nIntent == icAbsoluteColorimetric && + (m_MediaXYZ.X != m_pProfile->m_Header.illuminant.X || + m_MediaXYZ.Y != m_pProfile->m_Header.illuminant.Y || + m_MediaXYZ.Z != m_pProfile->m_Header.illuminant.Z)) { + + icColorSpaceSignature Space = m_pProfile->m_Header.pcs; + + if (IsSpacePCS(Space)) { + m_bAdjustPCS = true; // turn ON PCS adjustment + + // scale factors depend upon media white point + // set up for input transform + m_PCSScale[0] = (icFloatNumber) m_MediaXYZ.X / m_pProfile->m_Header.illuminant.X; // convert to icFloat to avoid precision errors + m_PCSScale[1] = (icFloatNumber) m_MediaXYZ.Y / m_pProfile->m_Header.illuminant.Y; + m_PCSScale[2] = (icFloatNumber) m_MediaXYZ.Z / m_pProfile->m_Header.illuminant.Z; + + if (!m_bInput) { + m_PCSScale[0] = (icFloatNumber) 1.0 / m_PCSScale[0]; // inverse for output transform + m_PCSScale[1] = (icFloatNumber) 1.0 / m_PCSScale[1]; + m_PCSScale[2] = (icFloatNumber) 1.0 / m_PCSScale[2]; + } + + m_PCSOffset[0] = 0.0; + m_PCSOffset[1] = 0.0; + m_PCSOffset[2] = 0.0; + } + } + else if (m_nIntent == icPerceptual && (IsVersion2() || !HasPerceptualHandling())) { + icColorSpaceSignature Space = m_pProfile->m_Header.pcs; + + if (IsSpacePCS(Space) && m_pProfile->m_Header.deviceClass!=icSigAbstractClass) { + m_bAdjustPCS = true; // turn ON PCS adjustment + + // set up for input transform, which needs version 2 black point to version 4 + m_PCSScale[0] = (icFloatNumber) (1.0 - icPerceptualRefBlackX / icPerceptualRefWhiteX); // scale factors + m_PCSScale[1] = (icFloatNumber) (1.0 - icPerceptualRefBlackY / icPerceptualRefWhiteY); + m_PCSScale[2] = (icFloatNumber) (1.0 - icPerceptualRefBlackZ / icPerceptualRefWhiteZ); + + m_PCSOffset[0] = (icFloatNumber) (icPerceptualRefBlackX * 32768.0 / 65535.0); // offset factors + m_PCSOffset[1] = (icFloatNumber) (icPerceptualRefBlackY * 32768.0 / 65535.0); + m_PCSOffset[2] = (icFloatNumber) (icPerceptualRefBlackZ * 32768.0 / 65535.0); + + if (!m_bInput) { // output transform must convert version 4 black point to version 2 + m_PCSScale[0] = (icFloatNumber) 1.0 / m_PCSScale[0]; // invert scale factors + m_PCSScale[1] = (icFloatNumber) 1.0 / m_PCSScale[1]; + m_PCSScale[2] = (icFloatNumber) 1.0 / m_PCSScale[2]; + + m_PCSOffset[0] = - m_PCSOffset[0] * m_PCSScale[0]; // negate offset factors + m_PCSOffset[1] = - m_PCSOffset[1] * m_PCSScale[1]; + m_PCSOffset[2] = - m_PCSOffset[2] * m_PCSScale[2]; + } + } + } + + + if (m_pAdjustPCS) { + CIccProfile ProfileCopy(*m_pProfile); + + // need to read in all the tags, so that a copy of the profile can be made + if (!ProfileCopy.ReadTags(m_pProfile)) { + return icCmmStatInvalidProfile; + } + + if (!m_pAdjustPCS->CalcFactors(&ProfileCopy, this, m_PCSScale, m_PCSOffset)) { + return icCmmStatIncorrectApply; + } + + m_bAdjustPCS = true; + delete m_pAdjustPCS; + m_pAdjustPCS = NULL; + } + + return icCmmStatOk; +} + +/** +************************************************************************** +* Name: CIccXform::GetNewApply +* +* Purpose: +* This Factory function allocates data specific for the application of the xform. +* This allows multiple threads to simultaneously use the same xform. +************************************************************************** +*/ +CIccApplyXform *CIccXform::GetNewApply(icStatusCMM &status) +{ + CIccApplyXform *rv = new CIccApplyXform(this); + + if (!rv) { + status = icCmmStatAllocErr; + return NULL; + } + + status = icCmmStatOk; + return rv; +} + +/** + ************************************************************************** +* Name: CIccXform::AdjustPCS + * + * Purpose: +* This function will take care of any PCS adjustments +* needed by the xform (the PCS is always version 4 relative). + * + * Args: +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. + * + ************************************************************************** + */ +void CIccXform::AdjustPCS(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icColorSpaceSignature Space = m_pProfile->m_Header.pcs; + + if (Space==icSigLabData) { + if (UseLegacyPCS()) { + CIccPCS::Lab2ToXyz(DstPixel, SrcPixel, true); + } + else { + CIccPCS::LabToXyz(DstPixel, SrcPixel, true); + } + } + else { + DstPixel[0] = SrcPixel[0]; + DstPixel[1] = SrcPixel[1]; + DstPixel[2] = SrcPixel[2]; + } + + DstPixel[0] = (icFloatNumber)(DstPixel[0] * m_PCSScale[0] + m_PCSOffset[0]); + DstPixel[1] = (icFloatNumber)(DstPixel[1] * m_PCSScale[1] + m_PCSOffset[1]); + DstPixel[2] = (icFloatNumber)(DstPixel[2] * m_PCSScale[2] + m_PCSOffset[2]); + + if (Space==icSigLabData) { + if (UseLegacyPCS()) { + CIccPCS::XyzToLab2(DstPixel, DstPixel, true); + } + else { + CIccPCS::XyzToLab(DstPixel, DstPixel, true); + } + } +#ifndef SAMPLEICC_NOCLIPLABTOXYZ + else { + DstPixel[0] = CIccPCS::NegClip(DstPixel[0]); + DstPixel[1] = CIccPCS::NegClip(DstPixel[1]); + DstPixel[2] = CIccPCS::NegClip(DstPixel[2]); + } +#endif +} + +/** + ************************************************************************** + * Name: CIccXform::CheckSrcAbs + * + * Purpose: + * This function will be called by a derived CIccXform object's Apply() function + * BEFORE the actual xform is performed to take care of Absolute to Relative + * adjustments needed by the xform (IE the PCS is always version 4 relative). + * + * Args: + * Pixel = src pixel data (will not be modified) + * + * Return: + * returns Pixel or adjusted pixel data. + ************************************************************************** + */ +const icFloatNumber *CIccXform::CheckSrcAbs(CIccApplyXform *pApply, const icFloatNumber *Pixel) const +{ + icFloatNumber *pAbsLab = pApply->m_AbsLab; + if (m_bAdjustPCS && !m_bInput) { + AdjustPCS(pAbsLab, Pixel); + return pAbsLab; + } + + return Pixel; +} + +/** + ************************************************************************** + * Name: CIccXform::CheckDstAbs + * + * Purpose: + * This function will be called by a derived CIccXform object's Apply() function + * AFTER the actual xform is performed to take care of Absolute to Relative + * adjustments needed by the xform (IE the PCS is always version 4 relative). + * + * Args: + * Pixel = source pixel data which will be modified + * + ************************************************************************** + */ +void CIccXform::CheckDstAbs(icFloatNumber *Pixel) const +{ + if (m_bAdjustPCS && m_bInput) { + AdjustPCS(Pixel, Pixel); + } + } + +/** +************************************************************************** +* Name: CIccXformMatrixTRC::GetSrcSpace +* +* Purpose: +* Return the color space that is input to the transform. +* If a device space is either XYZ/Lab it is changed to dXYZ/dLab to avoid +* confusion with PCS encoding of these spaces. Device encoding of XYZ +* and Lab spaces left to the device. +************************************************************************** +*/ +icColorSpaceSignature CIccXform::GetSrcSpace() const +{ + icColorSpaceSignature rv; + icProfileClassSignature deviceClass = m_pProfile->m_Header.deviceClass; + + if (m_bInput) { + rv = m_pProfile->m_Header.colorSpace; + + if (deviceClass != icSigAbstractClass) { + //convert signature to device colorspace signature (avoid confusion about encoding). + if (rv==icSigXYZData) { + rv = icSigDevXYZData; + } + else if (rv==icSigLabData) { + rv = icSigDevLabData; + } + } + } + else { + rv = m_pProfile->m_Header.pcs; + } + + return rv; +} + +/** +************************************************************************** +* Name: CIccXformMatrixTRC::GetDstSpace +* +* Purpose: +* Return the color space that is output by the transform. +* If a device space is either XYZ/Lab it is changed to dXYZ/dLab to avoid +* confusion with PCS encoding of these spaces. Device encoding of XYZ +* and Lab spaces left to the device. +************************************************************************** +*/ +icColorSpaceSignature CIccXform::GetDstSpace() const +{ + icColorSpaceSignature rv; + icProfileClassSignature deviceClass = m_pProfile->m_Header.deviceClass; + + if (m_bInput) { + rv = m_pProfile->m_Header.pcs; + } + else { + rv = m_pProfile->m_Header.colorSpace; + + //convert signature to device colorspace signature (avoid confusion about encoding). + if (deviceClass != icSigAbstractClass) { + if (rv==icSigXYZData) { + rv = icSigDevXYZData; + } + else if (rv==icSigLabData) { + rv = icSigDevLabData; + } + } + } + + return rv; +} + +/** +************************************************************************** +* Name: CIccApplyXform::CIccApplyXform +* +* Purpose: +* Constructor +************************************************************************** +*/ +CIccApplyXform::CIccApplyXform(CIccXform *pXform) +{ + m_pXform = pXform; +} + +/** +************************************************************************** +* Name: CIccApplyXform::CIccApplyXform +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccApplyXform::~CIccApplyXform() +{ +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::CIccXformMonochrome +* +* Purpose: +* Constructor +************************************************************************** +*/ +CIccXformMonochrome::CIccXformMonochrome() +{ + m_Curve = NULL; + m_ApplyCurvePtr = NULL; + m_bFreeCurve = false; +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::~CIccXformMonochrome +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccXformMonochrome::~CIccXformMonochrome() +{ + if (m_bFreeCurve && m_Curve) { + delete m_Curve; + } +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::Begin +* +* Purpose: +* Does the initialization of the Xform before Apply() is called. +* Must be called before Apply(). +* +************************************************************************** +*/ +icStatusCMM CIccXformMonochrome::Begin() +{ + icStatusCMM status; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) + return status; + + m_ApplyCurvePtr = NULL; + + if (m_bInput) { + m_Curve = GetCurve(icSigGrayTRCTag); + + if (!m_Curve) { + return icCmmStatProfileMissingTag; + } + } + else { + m_Curve = GetInvCurve(icSigGrayTRCTag); + m_bFreeCurve = true; + + if (!m_Curve) { + return icCmmStatProfileMissingTag; + } + } + + m_Curve->Begin(); + if (!m_Curve->IsIdentity()) { + m_ApplyCurvePtr = m_Curve; + } + + return icCmmStatOk; +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::Apply +* +* Purpose: +* Does the actual application of the Xform. +* +* Args: +* pApply = ApplyXform object containing temporary storage used during Apply +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +void CIccXformMonochrome::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icFloatNumber Pixel[3]; + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + + if (m_bInput) { + Pixel[0] = SrcPixel[0]; + + if (m_ApplyCurvePtr) { + Pixel[0] = m_ApplyCurvePtr->Apply(Pixel[0]); + } + + DstPixel[0] = icFloatNumber(icPerceptualRefWhiteX); + DstPixel[1] = icFloatNumber(icPerceptualRefWhiteY); + DstPixel[2] = icFloatNumber(icPerceptualRefWhiteZ); + + icXyzToPcs(DstPixel); + + if (m_pProfile->m_Header.pcs==icSigLabData) { + if (UseLegacyPCS()) { + CIccPCS::XyzToLab2(DstPixel, DstPixel, true); + } + else { + CIccPCS::XyzToLab(DstPixel, DstPixel, true); + } + } + + DstPixel[0] *= Pixel[0]; + DstPixel[1] *= Pixel[0]; + DstPixel[2] *= Pixel[0]; + } + else { + Pixel[0] = icFloatNumber(icPerceptualRefWhiteX); + Pixel[1] = icFloatNumber(icPerceptualRefWhiteY); + Pixel[2] = icFloatNumber(icPerceptualRefWhiteZ); + + icXyzToPcs(Pixel); + + if (m_pProfile->m_Header.pcs==icSigLabData) { + if (UseLegacyPCS()) { + CIccPCS::XyzToLab2(Pixel, Pixel, true); + } + else { + CIccPCS::XyzToLab(Pixel, Pixel, true); + } + DstPixel[0] = SrcPixel[0]/Pixel[0]; + } + else { + DstPixel[0] = SrcPixel[1]/Pixel[1]; + } + + if (m_ApplyCurvePtr) { + DstPixel[0] = m_ApplyCurvePtr->Apply(DstPixel[0]); + } + } + + CheckDstAbs(DstPixel); +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::GetCurve +* +* Purpose: +* Gets the curve having the passed signature, from the profile. +* +* Args: +* sig = signature of the curve to be found +* +* Return: +* Pointer to the curve. +************************************************************************** +*/ +CIccCurve *CIccXformMonochrome::GetCurve(icSignature sig) const +{ + CIccTag *pTag = m_pProfile->FindTag(sig); + + if (pTag && (pTag->GetType()==icSigCurveType || pTag->GetType()==icSigParametricCurveType)) { + return (CIccCurve*)pTag; + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::GetInvCurve +* +* Purpose: +* Gets the inverted curve having the passed signature, from the profile. +* +* Args: +* sig = signature of the curve to be inverted +* +* Return: +* Pointer to the inverted curve. +************************************************************************** +*/ +CIccCurve *CIccXformMonochrome::GetInvCurve(icSignature sig) const +{ + CIccCurve *pCurve; + CIccTagCurve *pInvCurve; + + if (!(pCurve = GetCurve(sig))) + return NULL; + + pCurve->Begin(); + + pInvCurve = new CIccTagCurve(2048); + + int i; + icFloatNumber x; + icFloatNumber *Lut = &(*pInvCurve)[0]; + + for (i=0; i<2048; i++) { + x=(icFloatNumber)i / 2047; + + Lut[i] = pCurve->Find(x); + } + + return pInvCurve; +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::ExtractInputCurves +* +* Purpose: +* Gets the input curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the input curves. +************************************************************************** +*/ +LPIccCurve* CIccXformMonochrome::ExtractInputCurves() +{ + if (m_bInput) { + if (m_Curve) { + LPIccCurve* Curve = new LPIccCurve[1]; + Curve[0] = (LPIccCurve)(m_Curve->NewCopy()); + m_ApplyCurvePtr = NULL; + return Curve; + } + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXformMonochrome::ExtractOutputCurves +* +* Purpose: +* Gets the output curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the output curves. +************************************************************************** +*/ +LPIccCurve* CIccXformMonochrome::ExtractOutputCurves() +{ + if (!m_bInput) { + if (m_Curve) { + LPIccCurve* Curve = new LPIccCurve[1]; + Curve[0] = (LPIccCurve)(m_Curve->NewCopy()); + m_ApplyCurvePtr = NULL; + return Curve; + } + } + + return NULL; +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::CIccXformMatrixTRC + * + * Purpose: + * Constructor + ************************************************************************** + */ +CIccXformMatrixTRC::CIccXformMatrixTRC() +{ + m_Curve[0] = m_Curve[1] = m_Curve[2] = NULL; + m_ApplyCurvePtr = NULL; + m_bFreeCurve = false; +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::~CIccXformMatrixTRC + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXformMatrixTRC::~CIccXformMatrixTRC() +{ + if (m_bFreeCurve) { + if (m_Curve[0]) + delete m_Curve[0]; + if (m_Curve[1]) + delete m_Curve[1]; + if (m_Curve[2]) + delete m_Curve[2]; + } +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::Begin + * + * Purpose: + * Does the initialization of the Xform before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ +icStatusCMM CIccXformMatrixTRC::Begin() +{ + icStatusCMM status; + const CIccTagXYZ *pXYZ; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) + return status; + + pXYZ = GetColumn(icSigRedMatrixColumnTag); + if (!pXYZ) { + return icCmmStatProfileMissingTag; + } + + m_e[0] = icFtoD((*pXYZ)[0].X); + m_e[3] = icFtoD((*pXYZ)[0].Y); + m_e[6] = icFtoD((*pXYZ)[0].Z); + + pXYZ = GetColumn(icSigGreenMatrixColumnTag); + if (!pXYZ) { + return icCmmStatProfileMissingTag; + } + + m_e[1] = icFtoD((*pXYZ)[0].X); + m_e[4] = icFtoD((*pXYZ)[0].Y); + m_e[7] = icFtoD((*pXYZ)[0].Z); + + pXYZ = GetColumn(icSigBlueMatrixColumnTag); + if (!pXYZ) { + return icCmmStatProfileMissingTag; + } + + m_e[2] = icFtoD((*pXYZ)[0].X); + m_e[5] = icFtoD((*pXYZ)[0].Y); + m_e[8] = icFtoD((*pXYZ)[0].Z); + + m_ApplyCurvePtr = NULL; + + if (m_bInput) { + m_Curve[0] = GetCurve(icSigRedTRCTag); + m_Curve[1] = GetCurve(icSigGreenTRCTag); + m_Curve[2] = GetCurve(icSigBlueTRCTag); + + if (!m_Curve[0] || !m_Curve[1] || !m_Curve[2]) { + return icCmmStatProfileMissingTag; + } + + } + else { + if (m_pProfile->m_Header.pcs!=icSigXYZData) { + return icCmmStatBadSpaceLink; + } + + m_Curve[0] = GetInvCurve(icSigRedTRCTag); + m_Curve[1] = GetInvCurve(icSigGreenTRCTag); + m_Curve[2] = GetInvCurve(icSigBlueTRCTag); + + m_bFreeCurve = true; + + if (!m_Curve[0] || !m_Curve[1] || !m_Curve[2]) { + return icCmmStatProfileMissingTag; + } + + if (!icMatrixInvert3x3(m_e)) { + return icCmmStatInvalidProfile; + } + } + + m_Curve[0]->Begin(); + m_Curve[1]->Begin(); + m_Curve[2]->Begin(); + + if (!m_Curve[0]->IsIdentity() || !m_Curve[1]->IsIdentity() || !m_Curve[2]->IsIdentity()) { + m_ApplyCurvePtr = m_Curve; + } + + return icCmmStatOk; +} + + +static icFloatNumber XYZScale(icFloatNumber v) +{ + v = (icFloatNumber)(v * 32768.0 / 65535.0); + return v; +} + +static icFloatNumber XYZDescale(icFloatNumber v) +{ + return (icFloatNumber)(v * 65535.0 / 32768.0); +} + +static icFloatNumber RGBClip(icFloatNumber v, CIccCurve *pCurve) +{ + if (v<=0) + return(pCurve->Apply(0)); + else if (v>=1.0) + return (pCurve->Apply(1.0)); + + return pCurve->Apply(v); +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::Apply + * + * Purpose: + * Does the actual application of the Xform. + * + * Args: + * pApply = ApplyXform object containging temporary storage used during Apply + * DstPixel = Destination pixel where the result is stored, + * SrcPixel = Source pixel which is to be applied. + ************************************************************************** + */ +void CIccXformMatrixTRC::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icFloatNumber Pixel[3]; + + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + Pixel[0] = SrcPixel[0]; + Pixel[1] = SrcPixel[1]; + Pixel[2] = SrcPixel[2]; + + if (m_bInput) { + + double LinR, LinG, LinB; + if (m_ApplyCurvePtr) { + LinR = m_ApplyCurvePtr[0]->Apply(Pixel[0]); + LinG = m_ApplyCurvePtr[1]->Apply(Pixel[1]); + LinB = m_ApplyCurvePtr[2]->Apply(Pixel[2]); + } + else { + LinR = Pixel[0]; + LinG = Pixel[1]; + LinB = Pixel[2]; + } + + DstPixel[0] = XYZScale((icFloatNumber)(m_e[0] * LinR + m_e[1] * LinG + m_e[2] * LinB)); + DstPixel[1] = XYZScale((icFloatNumber)(m_e[3] * LinR + m_e[4] * LinG + m_e[5] * LinB)); + DstPixel[2] = XYZScale((icFloatNumber)(m_e[6] * LinR + m_e[7] * LinG + m_e[8] * LinB)); + } + else { + double X = XYZDescale(Pixel[0]); + double Y = XYZDescale(Pixel[1]); + double Z = XYZDescale(Pixel[2]); + + if (m_ApplyCurvePtr) { + DstPixel[0] = RGBClip((icFloatNumber)(m_e[0] * X + m_e[1] * Y + m_e[2] * Z), m_ApplyCurvePtr[0]); + DstPixel[1] = RGBClip((icFloatNumber)(m_e[3] * X + m_e[4] * Y + m_e[5] * Z), m_ApplyCurvePtr[1]); + DstPixel[2] = RGBClip((icFloatNumber)(m_e[6] * X + m_e[7] * Y + m_e[8] * Z), m_ApplyCurvePtr[2]); + } + else { + DstPixel[0] = (icFloatNumber)(m_e[0] * X + m_e[1] * Y + m_e[2] * Z); + DstPixel[1] = (icFloatNumber)(m_e[3] * X + m_e[4] * Y + m_e[5] * Z); + DstPixel[2] = (icFloatNumber)(m_e[6] * X + m_e[7] * Y + m_e[8] * Z); + } + } + + CheckDstAbs(DstPixel); +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::GetCurve + * + * Purpose: + * Gets the curve having the passed signature, from the profile. + * + * Args: + * sig = signature of the curve to be found + * + * Return: + * Pointer to the curve. + ************************************************************************** + */ +CIccCurve *CIccXformMatrixTRC::GetCurve(icSignature sig) const +{ + CIccTag *pTag = m_pProfile->FindTag(sig); + + if (pTag->GetType()==icSigCurveType || pTag->GetType()==icSigParametricCurveType) { + return (CIccCurve*)pTag; + } + + return NULL; +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::GetColumn + * + * Purpose: + * Gets the XYZ tag from the profile. + * + * Args: + * sig = signature of the XYZ tag to be found. + * + * Return: + * Pointer to the XYZ tag. + ************************************************************************** + */ +CIccTagXYZ *CIccXformMatrixTRC::GetColumn(icSignature sig) const +{ + CIccTag *pTag = m_pProfile->FindTag(sig); + + if (!pTag || pTag->GetType()!=icSigXYZType) { + return NULL; + } + + return (CIccTagXYZ*)pTag; +} + +/** + ************************************************************************** + * Name: CIccXformMatrixTRC::GetInvCurve + * + * Purpose: + * Gets the inverted curve having the passed signature, from the profile. + * + * Args: + * sig = signature of the curve to be inverted + * + * Return: + * Pointer to the inverted curve. + ************************************************************************** + */ +CIccCurve *CIccXformMatrixTRC::GetInvCurve(icSignature sig) const +{ + CIccCurve *pCurve; + CIccTagCurve *pInvCurve; + + if (!(pCurve = GetCurve(sig))) + return NULL; + + pCurve->Begin(); + + pInvCurve = new CIccTagCurve(2048); + + int i; + icFloatNumber x; + icFloatNumber *Lut = &(*pInvCurve)[0]; + + for (i=0; i<2048; i++) { + x=(icFloatNumber)i / 2047; + + Lut[i] = pCurve->Find(x); + } + + return pInvCurve; +} + +/** +************************************************************************** +* Name: CIccXformMatrixTRC::ExtractInputCurves +* +* Purpose: +* Gets the input curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the input curves. +************************************************************************** +*/ +LPIccCurve* CIccXformMatrixTRC::ExtractInputCurves() +{ + if (m_bInput) { + if (m_Curve[0]) { + LPIccCurve* Curve = new LPIccCurve[3]; + Curve[0] = (LPIccCurve)(m_Curve[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_Curve[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_Curve[2]->NewCopy()); + m_ApplyCurvePtr = NULL; + return Curve; + } + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXformMatrixTRC::ExtractOutputCurves +* +* Purpose: +* Gets the output curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the output curves. +************************************************************************** +*/ +LPIccCurve* CIccXformMatrixTRC::ExtractOutputCurves() +{ + if (!m_bInput) { + if (m_Curve[0]) { + LPIccCurve* Curve = new LPIccCurve[3]; + Curve[0] = (LPIccCurve)(m_Curve[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_Curve[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_Curve[2]->NewCopy()); + m_ApplyCurvePtr = NULL; + return Curve; + } + } + + return NULL; +} + +/** + ************************************************************************** + * Name: CIccXform3DLut::CIccXform3DLut + * + * Purpose: + * Constructor + * + * Args: + * pTag = Pointer to the tag of type CIccMBB + ************************************************************************** + */ +CIccXform3DLut::CIccXform3DLut(CIccTag *pTag) +{ + if (pTag && pTag->IsMBBType()) { + m_pTag = (CIccMBB*)pTag; + } + else + m_pTag = NULL; + + m_ApplyCurvePtrA = m_ApplyCurvePtrB = m_ApplyCurvePtrM = NULL; + m_ApplyMatrixPtr = NULL; +} + +/** + ************************************************************************** + * Name: CIccXform3DLut::~CIccXform3DLut + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXform3DLut::~CIccXform3DLut() +{ +} + +/** + ************************************************************************** + * Name: CIccXform3DLut::Begin + * + * Purpose: + * Does the initialization of the Xform before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ + icStatusCMM CIccXform3DLut::Begin() +{ + icStatusCMM status; + CIccCurve **Curve; + int i; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) + return status; + + if (!m_pTag || + m_pTag->InputChannels()!=3) { + return icCmmStatInvalidLut; + } + + m_ApplyCurvePtrA = NULL; + m_ApplyCurvePtrB = NULL; + m_ApplyCurvePtrM = NULL; + + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + Curve[0]->Begin(); + Curve[1]->Begin(); + Curve[2]->Begin(); + + if (!Curve[0]->IsIdentity() || !Curve[1]->IsIdentity() || !Curve[2]->IsIdentity()) { + m_ApplyCurvePtrB = Curve; + } + } + + if (m_pTag->m_CurvesM) { + Curve = m_pTag->m_CurvesM; + + Curve[0]->Begin(); + Curve[1]->Begin(); + Curve[2]->Begin(); + + if (!Curve[0]->IsIdentity() || !Curve[1]->IsIdentity() || !Curve[2]->IsIdentity()) { + m_ApplyCurvePtrM = Curve; + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrA = Curve; + break; + } + } + } + + } + else { + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + Curve[0]->Begin(); + Curve[1]->Begin(); + Curve[2]->Begin(); + + if (!Curve[0]->IsIdentity() || !Curve[1]->IsIdentity() || !Curve[2]->IsIdentity()) { + m_ApplyCurvePtrA = Curve; + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesM) { + Curve = m_pTag->m_CurvesM; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrM = Curve; + break; + } + } + } + + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrB = Curve; + break; + } + } + } + } + + m_ApplyMatrixPtr = NULL; + if (m_pTag->m_Matrix) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_nInput!=3) { + return icCmmStatInvalidProfile; + } + } + else { + if (m_pTag->m_nOutput!=3) { + return icCmmStatInvalidProfile; + } + } + + if (!m_pTag->m_Matrix->IsIdentity()) { + m_ApplyMatrixPtr = m_pTag->m_Matrix; + } + } + + return icCmmStatOk; +} + +/** + ************************************************************************** + * Name: CIccXform3DLut::Apply + * + * Purpose: + * Does the actual application of the Xform. + * + * Args: + * pApply = ApplyXform object containging temporary storage used during Apply + * DstPixel = Destination pixel where the result is stored, + * SrcPixel = Source pixel which is to be applied. + ************************************************************************** + */ +void CIccXform3DLut::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icFloatNumber Pixel[16]; + int i; + + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + Pixel[0] = SrcPixel[0]; + Pixel[1] = SrcPixel[1]; + Pixel[2] = SrcPixel[2]; + + if (m_pTag->m_bInputMatrix) { + if (m_ApplyCurvePtrB) { + Pixel[0] = m_ApplyCurvePtrB[0]->Apply(Pixel[0]); + Pixel[1] = m_ApplyCurvePtrB[1]->Apply(Pixel[1]); + Pixel[2] = m_ApplyCurvePtrB[2]->Apply(Pixel[2]); + } + + if (m_ApplyMatrixPtr) { + m_ApplyMatrixPtr->Apply(Pixel); + } + + if (m_ApplyCurvePtrM) { + Pixel[0] = m_ApplyCurvePtrM[0]->Apply(Pixel[0]); + Pixel[1] = m_ApplyCurvePtrM[1]->Apply(Pixel[1]); + Pixel[2] = m_ApplyCurvePtrM[2]->Apply(Pixel[2]); + } + + if (m_pTag->m_CLUT) { + if (m_nInterp==icInterpLinear) + m_pTag->m_CLUT->Interp3d(Pixel, Pixel); + else + m_pTag->m_CLUT->Interp3dTetra(Pixel, Pixel); + } + + if (m_ApplyCurvePtrA) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrA[i]->Apply(Pixel[i]); + } + } + + } + else { + if (m_ApplyCurvePtrA) { + Pixel[0] = m_ApplyCurvePtrA[0]->Apply(Pixel[0]); + Pixel[1] = m_ApplyCurvePtrA[1]->Apply(Pixel[1]); + Pixel[2] = m_ApplyCurvePtrA[2]->Apply(Pixel[2]); + } + + if (m_pTag->m_CLUT) { + if (m_nInterp==icInterpLinear) + m_pTag->m_CLUT->Interp3d(Pixel, Pixel); + else + m_pTag->m_CLUT->Interp3dTetra(Pixel, Pixel); + } + + if (m_ApplyCurvePtrM) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrM[i]->Apply(Pixel[i]); + } + } + + if (m_ApplyMatrixPtr) { + m_ApplyMatrixPtr->Apply(Pixel); + } + + if (m_ApplyCurvePtrB) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrB[i]->Apply(Pixel[i]); + } + } + } + + for (i=0; im_nOutput; i++) { + DstPixel[i] = Pixel[i]; + } + + CheckDstAbs(DstPixel); +} + +/** +************************************************************************** +* Name: CIccXform3DLut::ExtractInputCurves +* +* Purpose: +* Gets the input curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the input curves. +************************************************************************** +*/ +LPIccCurve* CIccXform3DLut::ExtractInputCurves() +{ + if (m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[3]; + Curve[0] = (LPIccCurve)(m_pTag->m_CurvesB[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_pTag->m_CurvesB[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_pTag->m_CurvesB[2]->NewCopy()); + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[3]; + Curve[0] = (LPIccCurve)(m_pTag->m_CurvesA[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_pTag->m_CurvesA[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_pTag->m_CurvesA[2]->NewCopy()); + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXform3DLut::ExtractOutputCurves +* +* Purpose: +* Gets the output curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the output curves. +************************************************************************** +*/ +LPIccCurve* CIccXform3DLut::ExtractOutputCurves() +{ + if (!m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesA[i]->NewCopy()); + } + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesB[i]->NewCopy()); + } + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + } + + return NULL; +} + +/** + ************************************************************************** + * Name: CIccXform4DLut::CIccXform4DLut + * + * Purpose: + * Constructor + * + * Args: + * pTag = Pointer to the tag of type CIccMBB + ************************************************************************** + */ +CIccXform4DLut::CIccXform4DLut(CIccTag *pTag) +{ + if (pTag && pTag->IsMBBType()) { + m_pTag = (CIccMBB*)pTag; + } + else + m_pTag = NULL; + + m_ApplyCurvePtrA = m_ApplyCurvePtrB = m_ApplyCurvePtrM = NULL; + m_ApplyMatrixPtr = NULL; +} + + +/** + ************************************************************************** + * Name: CIccXform4DLut::~CIccXform4DLut + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXform4DLut::~CIccXform4DLut() +{ +} + + +/** + ************************************************************************** + * Name: CIccXform4DLut::Begin + * + * Purpose: + * Does the initialization of the Xform before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ +icStatusCMM CIccXform4DLut::Begin() +{ + icStatusCMM status; + CIccCurve **Curve; + int i; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) { + return status; + } + + if (!m_pTag || + m_pTag->InputChannels()!=4) { + return icCmmStatInvalidLut; + } + + m_ApplyCurvePtrA = m_ApplyCurvePtrB = m_ApplyCurvePtrM = NULL; + + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + Curve[0]->Begin(); + Curve[1]->Begin(); + Curve[2]->Begin(); + Curve[3]->Begin(); + + if (!Curve[0]->IsIdentity() || !Curve[1]->IsIdentity() || + !Curve[2]->IsIdentity() || !Curve[3]->IsIdentity()) + { + m_ApplyCurvePtrB = Curve; + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrA = Curve; + break; + } + } + } + + } + else { + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + Curve[0]->Begin(); + Curve[1]->Begin(); + Curve[2]->Begin(); + Curve[3]->Begin(); + + if (!Curve[0]->IsIdentity() || !Curve[1]->IsIdentity() || + !Curve[2]->IsIdentity() || !Curve[3]->IsIdentity()) + { + m_ApplyCurvePtrA = Curve; + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesM) { + Curve = m_pTag->m_CurvesM; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrM = Curve; + break; + } + } + } + + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrB = Curve; + break; + } + } + } + } + + m_ApplyMatrixPtr = NULL; + if (m_pTag->m_Matrix) { + if (m_pTag->m_bInputMatrix) { + return icCmmStatInvalidProfile; + } + else { + if (m_pTag->m_nOutput!=3) { + return icCmmStatInvalidProfile; + } + } + + if (!m_pTag->m_Matrix->IsIdentity()) { + m_ApplyMatrixPtr = m_pTag->m_Matrix; + } + } + + return icCmmStatOk; +} + + +/** + ************************************************************************** + * Name: CIccXform4DLut::Apply + * + * Purpose: + * Does the actual application of the Xform. + * + * Args: + * pApply = ApplyXform object containging temporary storage used during Apply + * DstPixel = Destination pixel where the result is stored, + * SrcPixel = Source pixel which is to be applied. + ************************************************************************** + */ +void CIccXform4DLut::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icFloatNumber Pixel[16]; + int i; + + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + Pixel[0] = SrcPixel[0]; + Pixel[1] = SrcPixel[1]; + Pixel[2] = SrcPixel[2]; + Pixel[3] = SrcPixel[3]; + + if (m_pTag->m_bInputMatrix) { + if (m_ApplyCurvePtrB) { + Pixel[0] = m_ApplyCurvePtrB[0]->Apply(Pixel[0]); + Pixel[1] = m_ApplyCurvePtrB[1]->Apply(Pixel[1]); + Pixel[2] = m_ApplyCurvePtrB[2]->Apply(Pixel[2]); + Pixel[3] = m_ApplyCurvePtrB[3]->Apply(Pixel[3]); + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Interp4d(Pixel, Pixel); + } + + if (m_ApplyCurvePtrA) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrA[i]->Apply(Pixel[i]); + } + } + + } + else { + if (m_ApplyCurvePtrA) { + Pixel[0] = m_ApplyCurvePtrA[0]->Apply(Pixel[0]); + Pixel[1] = m_ApplyCurvePtrA[1]->Apply(Pixel[1]); + Pixel[2] = m_ApplyCurvePtrA[2]->Apply(Pixel[2]); + Pixel[3] = m_ApplyCurvePtrA[3]->Apply(Pixel[3]); + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Interp4d(Pixel, Pixel); + } + + if (m_ApplyCurvePtrM) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrM[i]->Apply(Pixel[i]); + } + } + + if (m_ApplyMatrixPtr) { + m_ApplyMatrixPtr->Apply(Pixel); + } + + if (m_ApplyCurvePtrB) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrB[i]->Apply(Pixel[i]); + } + } + } + + for (i=0; im_nOutput; i++) { + DstPixel[i] = Pixel[i]; + } + + CheckDstAbs(DstPixel); +} + +/** +************************************************************************** +* Name: CIccXform4DLut::ExtractInputCurves +* +* Purpose: +* Gets the input curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the input curves. +************************************************************************** +*/ +LPIccCurve* CIccXform4DLut::ExtractInputCurves() +{ + if (m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[4]; + Curve[0] = (LPIccCurve)(m_pTag->m_CurvesB[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_pTag->m_CurvesB[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_pTag->m_CurvesB[2]->NewCopy()); + Curve[3] = (LPIccCurve)(m_pTag->m_CurvesB[3]->NewCopy()); + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[4]; + Curve[0] = (LPIccCurve)(m_pTag->m_CurvesA[0]->NewCopy()); + Curve[1] = (LPIccCurve)(m_pTag->m_CurvesA[1]->NewCopy()); + Curve[2] = (LPIccCurve)(m_pTag->m_CurvesA[2]->NewCopy()); + Curve[3] = (LPIccCurve)(m_pTag->m_CurvesA[3]->NewCopy()); + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXform4DLut::ExtractOutputCurves +* +* Purpose: +* Gets the output curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the output curves. +************************************************************************** +*/ +LPIccCurve* CIccXform4DLut::ExtractOutputCurves() +{ + if (!m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesA[i]->NewCopy()); + } + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesB[i]->NewCopy()); + } + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + } + + return NULL; +} + + +/** + ************************************************************************** + * Name: CIccXformNDLut::CIccXformNDLut + * + * Purpose: + * Constructor + * + * Args: + * pTag = Pointer to the tag of type CIccMBB + ************************************************************************** + */ +CIccXformNDLut::CIccXformNDLut(CIccTag *pTag) +{ + if (pTag && pTag->IsMBBType()) { + m_pTag = (CIccMBB*)pTag; + } + else + m_pTag = NULL; + + m_ApplyCurvePtrA = m_ApplyCurvePtrB = m_ApplyCurvePtrM = NULL; + m_ApplyMatrixPtr = NULL; +} + + +/** + ************************************************************************** + * Name: CIccXformNDLut::~CIccXformNDLut + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXformNDLut::~CIccXformNDLut() +{ +} + + +/** + ************************************************************************** + * Name: CIccXformNDLut::Begin + * + * Purpose: + * Does the initialization of the Xform before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ +icStatusCMM CIccXformNDLut::Begin() +{ + icStatusCMM status; + CIccCurve **Curve; + int i; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) { + return status; + } + + if (!m_pTag || (m_pTag->InputChannels()>2 && m_pTag->InputChannels()<5)) { + return icCmmStatInvalidLut; + } + + m_nNumInput = m_pTag->m_nInput; + + m_ApplyCurvePtrA = m_ApplyCurvePtrB = m_ApplyCurvePtrM = NULL; + + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + for (i=0; iBegin(); + + for (i=0; iIsIdentity()) { + m_ApplyCurvePtrB = Curve; + break; + } + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrA = Curve; + break; + } + } + } + + } + else { + if (m_pTag->m_CurvesA) { + Curve = m_pTag->m_CurvesA; + + for (i=0; iBegin(); + + for (i=0; iIsIdentity()) { + m_ApplyCurvePtrA = Curve; + break; + } + } + } + + if (m_pTag->m_CLUT) { + m_pTag->m_CLUT->Begin(); + } + + if (m_pTag->m_CurvesM) { + Curve = m_pTag->m_CurvesM; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrM = Curve; + break; + } + } + } + + if (m_pTag->m_CurvesB) { + Curve = m_pTag->m_CurvesB; + + for (i=0; im_nOutput; i++) { + Curve[i]->Begin(); + } + + for (i=0; im_nOutput; i++) { + if (!Curve[i]->IsIdentity()) { + m_ApplyCurvePtrB = Curve; + break; + } + } + } + } + + m_ApplyMatrixPtr = NULL; + if (m_pTag->m_Matrix) { + if (m_pTag->m_bInputMatrix) { + return icCmmStatInvalidProfile; + } + else { + if (m_pTag->m_nOutput!=3) { + return icCmmStatInvalidProfile; + } + } + + if (!m_pTag->m_Matrix->IsIdentity()) { + m_ApplyMatrixPtr = m_pTag->m_Matrix; + } + } + + return icCmmStatOk; +} + + +/** + ************************************************************************** + * Name: CIccXformNDLut::Apply + * + * Purpose: + * Does the actual application of the Xform. + * + * Args: + * pApply = ApplyXform object containging temporary storage used during Apply + * DstPixel = Destination pixel where the result is stored, + * SrcPixel = Source pixel which is to be applied. + ************************************************************************** + */ +void CIccXformNDLut::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + icFloatNumber Pixel[16]; + int i; + + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + for (i=0; im_bInputMatrix) { + if (m_ApplyCurvePtrB) { + for (i=0; iApply(Pixel[i]); + } + + if (m_pTag->m_CLUT) { + switch(m_nNumInput) { + case 5: + m_pTag->m_CLUT->Interp5d(Pixel, Pixel); + break; + case 6: + m_pTag->m_CLUT->Interp6d(Pixel, Pixel); + break; + default: + m_pTag->m_CLUT->InterpND(Pixel, Pixel); + break; + } + } + + if (m_ApplyCurvePtrA) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrA[i]->Apply(Pixel[i]); + } + } + + } + else { + if (m_ApplyCurvePtrA) { + for (i=0; iApply(Pixel[i]); + } + + if (m_pTag->m_CLUT) { + switch(m_nNumInput) { + case 5: + m_pTag->m_CLUT->Interp5d(Pixel, Pixel); + break; + case 6: + m_pTag->m_CLUT->Interp6d(Pixel, Pixel); + break; + default: + m_pTag->m_CLUT->InterpND(Pixel, Pixel); + break; + } + } + + if (m_ApplyCurvePtrM) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrM[i]->Apply(Pixel[i]); + } + } + + if (m_ApplyMatrixPtr) { + m_ApplyMatrixPtr->Apply(Pixel); + } + + if (m_ApplyCurvePtrB) { + for (i=0; im_nOutput; i++) { + Pixel[i] = m_ApplyCurvePtrB[i]->Apply(Pixel[i]); + } + } + } + + for (i=0; im_nOutput; i++) { + DstPixel[i] = Pixel[i]; + } + + CheckDstAbs(DstPixel); +} + +/** +************************************************************************** +* Name: CIccXformNDLut::ExtractInputCurves +* +* Purpose: +* Gets the input curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the input curves. +************************************************************************** +*/ +LPIccCurve* CIccXformNDLut::ExtractInputCurves() +{ + if (m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nInput]; + for (int i=0; im_nInput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesB[i]->NewCopy()); + } + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nInput]; + for (int i=0; im_nInput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesA[i]->NewCopy()); + } + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + } + + return NULL; +} + +/** +************************************************************************** +* Name: CIccXformNDLut::ExtractOutputCurves +* +* Purpose: +* Gets the output curves. Should be called only after Begin() +* has been called. Once the curves are extracted, they will +* not be used by the Apply() function. +* WARNING: caller owns the curves and must be deleted by the caller. +* +* Return: +* Pointer to the output curves. +************************************************************************** +*/ +LPIccCurve* CIccXformNDLut::ExtractOutputCurves() +{ + if (!m_bInput) { + if (m_pTag->m_bInputMatrix) { + if (m_pTag->m_CurvesA) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesA[i]->NewCopy()); + } + m_ApplyCurvePtrA = NULL; + return Curve; + } + } + else { + if (m_pTag->m_CurvesB) { + LPIccCurve* Curve = new LPIccCurve[m_pTag->m_nOutput]; + for (int i=0; im_nOutput; i++) { + Curve[i] = (LPIccCurve)(m_pTag->m_CurvesB[i]->NewCopy()); + } + m_ApplyCurvePtrB = NULL; + return Curve; + } + } + } + + return NULL; +} + +/** + ************************************************************************** + * Name: CIccXformNamedColor::CIccXformNamedColor + * + * Purpose: + * Constructor + * + * Args: + * pTag = Pointer to the tag of type CIccTagNamedColor2, + * csPCS = PCS color space, + * csDevice = Device color space + ************************************************************************** + */ +CIccXformNamedColor::CIccXformNamedColor(CIccTag *pTag, icColorSpaceSignature csPCS, icColorSpaceSignature csDevice) +{ + if (pTag && pTag->GetType()==icSigNamedColor2Type) { + m_pTag = (CIccTagNamedColor2*)pTag; + m_pTag->SetColorSpaces(csPCS, csDevice); + } + else + m_pTag = NULL; + + m_nSrcSpace = icSigUnknownData; + m_nDestSpace = icSigUnknownData; +} + + +/** + ************************************************************************** + * Name: CIccXformNamedColor::CIccXformNamedColor + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccXformNamedColor::~CIccXformNamedColor() +{ +} + +/** + ************************************************************************** + * Name: CIccXformNamedColor::Begin + * + * Purpose: + * Does the initialization of the Xform before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ +icStatusCMM CIccXformNamedColor::Begin() +{ + icStatusCMM status; + + status = CIccXform::Begin(); + if (status != icCmmStatOk) + return status; + + if (m_pTag == NULL) { + return icCmmStatProfileMissingTag; + } + + if (m_nSrcSpace==icSigUnknownData || + m_nDestSpace==icSigUnknownData) { + return icCmmStatIncorrectApply; + } + + if (m_nSrcSpace != icSigNamedData) { + if (m_nDestSpace != icSigNamedData) { + m_nApplyInterface = icApplyPixel2Pixel; + } + else { + m_nApplyInterface = icApplyPixel2Named; + } + } + else { + if (m_nDestSpace != icSigNamedData) { + m_nApplyInterface = icApplyNamed2Pixel; + } + else { + return icCmmStatIncorrectApply; + } + } + + if (!m_pTag->InitFindCachedPCSColor()) + return icCmmStatAllocErr; + + return icCmmStatOk; +} + + + +/** + ************************************************************************** + * Name: CIccXformNamedColor::Apply + * + * Purpose: + * Does the actual application of the Xform. + * + * Args: + * pApply = ApplyXform object containging temporary storage used during Apply + * DstColorName = Destination string where the color name result is stored, + * SrcPixel = Source pixel which is to be applied. + ************************************************************************** + */ +icStatusCMM CIccXformNamedColor::Apply(CIccApplyXform* pApply, icChar *DstColorName, const icFloatNumber *SrcPixel) const +{ + const CIccTagNamedColor2 *pTag = m_pTag; + if (pTag == NULL) + return icCmmStatBadXform; + + icFloatNumber DevicePix[16], PCSPix[3]; + std::string NamedColor; + icUInt32Number i, j; + + if (IsSrcPCS()) { + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + for(i=0; i<3; i++) + PCSPix[i] = SrcPixel[i]; + + j = pTag->FindCachedPCSColor(PCSPix); + pTag->GetColorName(NamedColor, j); + } + else { + for(i=0; iGetDeviceCoords(); i++) + DevicePix[i] = SrcPixel[i]; + + j = pTag->FindDeviceColor(DevicePix); + pTag->GetColorName(NamedColor, j); + } + + sprintf(DstColorName, "%s", NamedColor.c_str()); + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccXformNamedColor::Apply +* +* Purpose: +* Does the actual application of the Xform. +* +* Args: +* pApply = ApplyXform object containging temporary storage used during Apply +* DstPixel = Destination pixel where the result is stored, +* SrcColorName = Source color name which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccXformNamedColor::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icChar *SrcColorName) const +{ + const CIccTagNamedColor2 *pTag = m_pTag; + + if (pTag == NULL) + return icCmmStatProfileMissingTag; + + icUInt32Number j; + + if (m_nSrcSpace != icSigNamedData) + return icCmmStatBadSpaceLink; + + if (IsDestPCS()) { + + j = pTag->FindColor(SrcColorName); + if (j<0) + return icCmmStatColorNotFound; + + if (m_nDestSpace == icSigLabData) { + memcpy(DstPixel, pTag->GetEntry(j)->pcsCoords, 3*sizeof(icFloatNumber)); + } + else { + memcpy(DstPixel, pTag->GetEntry(j)->pcsCoords, 3*sizeof(icFloatNumber)); + } + CheckDstAbs(DstPixel); + } + else { + j = pTag->FindColor(SrcColorName); + if (j<0) + return icCmmStatColorNotFound; + memcpy(DstPixel, pTag->GetEntry(j)->deviceCoords, pTag->GetDeviceCoords()*sizeof(icFloatNumber)); + } + + return icCmmStatOk; +} + +/** + ************************************************************************** + * Name: CIccXformNamedColor::SetSrcSpace + * + * Purpose: + * Sets the source space of the Xform + * + * Args: + * nSrcSpace = signature of the color space to be set + ************************************************************************** + */ +icStatusCMM CIccXformNamedColor::SetSrcSpace(icColorSpaceSignature nSrcSpace) +{ + if (nSrcSpace!=m_pTag->GetPCS()) + if (nSrcSpace!=m_pTag->GetDeviceSpace()) + if (nSrcSpace!=icSigNamedData) + return icCmmStatBadSpaceLink; + + m_nSrcSpace = nSrcSpace; + + return icCmmStatOk; +} + +/** + ************************************************************************** + * Name: CIccXformNamedColor::SetSrcSpace + * + * Purpose: + * Sets the destination space of the Xform + * + * Args: + * nDestSpace = signature of the color space to be set + ************************************************************************** + */ +icStatusCMM CIccXformNamedColor::SetDestSpace(icColorSpaceSignature nDestSpace) +{ + if (m_nSrcSpace == nDestSpace) + return icCmmStatBadSpaceLink; + + if (nDestSpace!=m_pTag->GetPCS()) + if (nDestSpace!=m_pTag->GetDeviceSpace()) + if (nDestSpace!=icSigNamedData) + return icCmmStatBadSpaceLink; + + m_nDestSpace = nDestSpace; + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccXformMPE::CIccXformMPE +* +* Purpose: +* Constructor +************************************************************************** +*/ +CIccXformMpe::CIccXformMpe(CIccTag *pTag) +{ + if (pTag && pTag->GetType()==icSigMultiProcessElementType) + m_pTag = (CIccTagMultiProcessElement*)pTag; + else + m_pTag = NULL; + + m_bUsingAcs = false; +} + +/** +************************************************************************** +* Name: CIccXformMPE::~CIccXformMPE +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccXformMpe::~CIccXformMpe() +{ +} + +/** +************************************************************************** +* Name: CIccXformMPE::Create +* +* Purpose: +* This is a static Creation function that creates derived CIccXform objects and +* initializes them. +* +* Args: +* pProfile = pointer to a CIccProfile object that will be owned by the transform. This object will +* be destroyed when the returned CIccXform object is destroyed. The means that the CIccProfile +* object needs to be allocated on the heap. +* bInput = flag to indicate whether to use the input or output side of the profile, +* nIntent = the rendering intent to apply to the profile, +* nInterp = the interpolation algorithm to use for N-D luts. +* nLutType = selection of which transform lut to use +* pHintManager = hints for creating the xform +* +* Return: +* A suitable pXform object +************************************************************************** +*/ +CIccXform *CIccXformMpe::Create(CIccProfile *pProfile, bool bInput/* =true */, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, icXformLutType nLutType/* =icXformLutColor */, + CIccCreateXformHintManager *pHintManager/* =NULL */) +{ + CIccXform *rv = NULL; + icRenderingIntent nTagIntent = nIntent; + + if (nTagIntent == icUnknownIntent) + nTagIntent = icPerceptual; + + switch (nLutType) { + case icXformLutColor: + if (bInput) { + CIccTag *pTag = pProfile->FindTag(icSigDToB0Tag + nTagIntent); + + if (!pTag && nTagIntent ==icAbsoluteColorimetric) { + pTag = pProfile->FindTag(icSigDToB1Tag); + if (pTag) + nTagIntent = icRelativeColorimetric; + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigDToB0Tag); + } + + //Unsupported elements cause fall back behavior + if (pTag && !pTag->IsSupported()) + pTag = NULL; + + if (!pTag) { + if (nTagIntent == icAbsoluteColorimetric) + nTagIntent = icRelativeColorimetric; + pTag = pProfile->FindTag(icSigAToB0Tag + nTagIntent); + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigAToB0Tag); + } + + if (!pTag) { + if (pProfile->m_Header.colorSpace == icSigRgbData) { + rv = new CIccXformMatrixTRC(); + } + else + return NULL; + } + else if (pTag->GetType()==icSigMultiProcessElementType) { + rv = new CIccXformMpe(pTag); + } + else { + switch(pProfile->m_Header.colorSpace) { + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigRgbData: + case icSigHsvData: + case icSigHlsData: + case icSigCmyData: + case icSig3colorData: + rv = new CIccXform3DLut(pTag); + break; + + case icSigCmykData: + case icSig4colorData: + rv = new CIccXform4DLut(pTag); + break; + + default: + rv = new CIccXformNDLut(pTag); + break; + } + } + } + else { + CIccTag *pTag = pProfile->FindTag(icSigBToD0Tag + nTagIntent); + + if (!pTag && nTagIntent ==icAbsoluteColorimetric) { + pTag = pProfile->FindTag(icSigBToD1Tag); + if (pTag) + nTagIntent = icRelativeColorimetric; + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigBToD0Tag); + } + + //Unsupported elements cause fall back behavior + if (pTag && !pTag->IsSupported()) + pTag = NULL; + + if (!pTag) { + if (nTagIntent == icAbsoluteColorimetric) + nTagIntent = icRelativeColorimetric; + pTag = pProfile->FindTag(icSigBToA0Tag + nTagIntent); + } + + if (!pTag) { + pTag = pProfile->FindTag(icSigBToA0Tag); + } + + if (!pTag) { + if (pProfile->m_Header.colorSpace == icSigRgbData) { + rv = new CIccXformMatrixTRC(); + } + else + return NULL; + } + if (pTag->GetType()==icSigMultiProcessElementType) { + rv = new CIccXformMpe(pTag); + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = new CIccXform3DLut(pTag); + + default: + break; + } + } + } + break; + + case icXformLutNamedColor: + { + CIccTag *pTag = pProfile->FindTag(icSigNamedColor2Tag); + if (!pTag) + return NULL; + + rv = new CIccXformNamedColor(pTag, pProfile->m_Header.pcs, pProfile->m_Header.colorSpace); + } + break; + + case icXformLutPreview: + { + bInput = false; + CIccTag *pTag = pProfile->FindTag(icSigPreview0Tag + nTagIntent); + if (!pTag) { + pTag = pProfile->FindTag(icSigPreview0Tag); + } + if (!pTag) { + return NULL; + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = new CIccXform3DLut(pTag); + + default: + break; + } + } + } + break; + + case icXformLutGamut: + { + bInput = false; + CIccTag *pTag = pProfile->FindTag(icSigGamutTag); + if (!pTag) { + return NULL; + } + else { + switch(pProfile->m_Header.pcs) { + case icSigXYZData: + case icSigLabData: + rv = new CIccXform3DLut(pTag); + + default: + break; + } + } + } + break; + } + + if (rv) { + rv->SetParams(pProfile, bInput, nIntent, nInterp, pHintManager); + } + + return rv; +} + + +/** +************************************************************************** +* Name: CIccXformMPE::Begin +* +* Purpose: +* This function will be called before the xform is applied. Derived objects +* should also call the base class function to initialize for Absolute Colorimetric +* Intent handling which is performed through the use of the CheckSrcAbs and +* CheckDstAbs functions. +************************************************************************** +*/ +icStatusCMM CIccXformMpe::Begin() +{ + icStatusCMM status; + status = CIccXform::Begin(); + + if (status != icCmmStatOk) + return status; + + if (!m_pTag) { + return icCmmStatInvalidLut; + } + + if (!m_pTag->Begin()) { + return icCmmStatInvalidProfile; + } + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccXformMpe::GetNewApply +* +* Purpose: +* This Factory function allocates data specific for the application of the xform. +* This allows multiple threads to simultaneously use the same xform. +************************************************************************** +*/ +CIccApplyXform *CIccXformMpe::GetNewApply(icStatusCMM &status) +{ + if (!m_pTag) + return NULL; + + CIccApplyXformMpe *rv= new CIccApplyXformMpe(this); + + if (!rv) { + status = icCmmStatAllocErr; + return NULL; + } + + rv->m_pApply = m_pTag->GetNewApply(); + if (!rv->m_pApply) { + status = icCmmStatAllocErr; + delete rv; + return NULL; + } + + status = icCmmStatOk; + return rv; +} + + +/** +************************************************************************** +* Name: CIccXformMPE::Apply +* +* Purpose: +* Does the actual application of the Xform. +* +* Args: +* pApply = ApplyXform object containging temporary storage used during Apply +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +void CIccXformMpe::Apply(CIccApplyXform* pApply, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const +{ + const CIccTagMultiProcessElement *pTag = m_pTag; + + if (!m_bInput) { //PCS comming in? + if (m_nIntent != icAbsoluteColorimetric) //B2D3 tags don't need abs conversion + SrcPixel = CheckSrcAbs(pApply, SrcPixel); + + //Since MPE tags use "real" values for PCS we need to convert from + //internal encoding used by IccProfLib + icFloatNumber temp[3]; + switch (GetSrcSpace()) { + case icSigXYZData: + memcpy(&temp[0], SrcPixel, 3*sizeof(icFloatNumber)); + icXyzFromPcs(temp); + SrcPixel = &temp[0]; + break; + + case icSigLabData: + memcpy(&temp[0], SrcPixel, 3*sizeof(icFloatNumber)); + icLabFromPcs(temp); + SrcPixel = &temp[0]; + break; + + default: + break; + } + } + + //Note: pApply should be a CIccApplyXformMpe type here + CIccApplyXformMpe *pApplyMpe = (CIccApplyXformMpe *)pApply; + + pTag->Apply(pApplyMpe->m_pApply, DstPixel, SrcPixel); + + if (m_bInput) { //PCS going out? + //Since MPE tags use "real" values for PCS we need to convert to + //internal encoding used by IccProfLib + switch(GetDstSpace()) { + case icSigXYZData: + icXyzToPcs(DstPixel); + break; + + case icSigLabData: + icLabToPcs(DstPixel); + break; + + default: + break; + } + + if (m_nIntent != icAbsoluteColorimetric) { //D2B3 tags don't need abs conversion + CheckDstAbs(DstPixel); + } + } +} + +/** +************************************************************************** +* Name: CIccApplyXformMpe::CIccApplyXformMpe +* +* Purpose: +* Constructor +************************************************************************** +*/ +CIccApplyXformMpe::CIccApplyXformMpe(CIccXformMpe *pXform) : CIccApplyXform(pXform) +{ +} + +/** +************************************************************************** +* Name: CIccApplyXformMpe::~CIccApplyXformMpe +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccApplyXformMpe::~CIccApplyXformMpe() +{ +} + + +/** +************************************************************************** +* Name: CIccApplyCmm::CIccApplyCmm +* +* Purpose: +* Constructor +* +* Args: +* pCmm = ptr to CMM to apply against +************************************************************************** +*/ +CIccApplyCmm::CIccApplyCmm(CIccCmm *pCmm) +{ + m_pCmm = pCmm; + m_pPCS = m_pCmm->GetPCS(); + + m_Xforms = new CIccApplyXformList; + m_Xforms->clear(); +} + +/** +************************************************************************** +* Name: CIccApplyCmm::~CIccApplyCmm +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccApplyCmm::~CIccApplyCmm() +{ + if (m_Xforms) { + CIccApplyXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + if (i->ptr) + delete i->ptr; + } + + delete m_Xforms; + } + + if (m_pPCS) + delete m_pPCS; +} + + +/** +************************************************************************** +* Name: CIccApplyCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccApplyCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + const CIccXform *pLastXform; + int j, n = (int)m_Xforms->size(); + bool bNoClip; + if (!n) + return icCmmStatBadXform; + m_pPCS->Reset(m_pCmm->m_nSrcSpace); + pSrc = SrcPixel; + pDst = Pixel; + + if (n>1) { + for (j=0, i=m_Xforms->begin(); jend(); i++, j++) { + + i->ptr->Apply(pDst, m_pPCS->Check(pSrc, i->ptr->GetXform())); + pSrc = pDst; + } + + pLastXform = i->ptr->GetXform(); + i->ptr->Apply(DstPixel, m_pPCS->Check(pSrc, pLastXform)); + bNoClip = pLastXform->NoClipPCS(); + } + else if (n==1) { + i = m_Xforms->begin(); + pLastXform = i->ptr->GetXform(); +// pri_debug("测试第一步4444"); + i->ptr->Apply(DstPixel, m_pPCS->Check(SrcPixel, pLastXform)); +// pri_debug("测试第一步3333"); + bNoClip = pLastXform->NoClipPCS(); +// pri_debug("测试第一步2222"); + } + else { + bNoClip = true; + } + + m_pPCS->CheckLast(DstPixel, m_pCmm->m_nDestSpace, bNoClip); + + return icCmmStatOk; +} + +/** +************************************************************************** +* Name: CIccApplyCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccApplyCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + icUInt32Number k; + + if (!n) + return icCmmStatBadXform; + + for (k=0; kReset(m_pCmm->m_nSrcSpace); + + pSrc = SrcPixel; + pDst = Pixel; + + if (n>1) { + for (j=0, i=m_Xforms->begin(); jend(); i++, j++) { + + i->ptr->Apply(pDst, m_pPCS->Check(pSrc, i->ptr->GetXform())); + pSrc = pDst; + } + + i->ptr->Apply(DstPixel, m_pPCS->Check(pSrc, i->ptr->GetXform())); + } + else if (n==1) { + i = m_Xforms->begin(); + i->ptr->Apply(DstPixel, m_pPCS->Check(SrcPixel, i->ptr->GetXform())); + } + + m_pPCS->CheckLast(DstPixel, m_pCmm->m_nDestSpace); + + DstPixel += m_pCmm->GetDestSamples(); + SrcPixel += m_pCmm->GetSourceSamples(); + } + + return icCmmStatOk; +} + +void CIccApplyCmm::AppendApplyXform(CIccApplyXform *pApplyXform) +{ + CIccApplyXformPtr ptr; + ptr.ptr = pApplyXform; + + m_Xforms->push_back(ptr); +} + +/** + ************************************************************************** + * Name: CIccCmm::CIccCmm + * + * Purpose: + * Constructor + * + * Args: + * nSrcSpace = signature of the source color space, + * nDestSpace = signature of the destination color space, + * bFirstInput = true if the first profile added is an input profile + ************************************************************************** + */ +CIccCmm::CIccCmm(icColorSpaceSignature nSrcSpace /*=icSigUnknownData*/, + icColorSpaceSignature nDestSpace /*=icSigUnknownData*/, + bool bFirstInput /*=true*/) +{ + m_bValid = false; + + m_bLastInput = !bFirstInput; + m_nSrcSpace = nSrcSpace; + m_nDestSpace = nDestSpace; + + m_nLastSpace = nSrcSpace; + m_nLastIntent = icUnknownIntent; + + m_Xforms = new CIccXformList; + m_Xforms->clear(); + + m_pApply = NULL; +} + +/** + ************************************************************************** + * Name: CIccCmm::~CIccCmm + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccCmm::~CIccCmm() +{ + if (m_Xforms) { + CIccXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + if (i->ptr) + delete i->ptr; + } + + delete m_Xforms; + } + + if (m_pApply) + delete m_pApply; +} + +/** + ************************************************************************** + * Name: CIccCmm::AddXform + * + * Purpose: + * Adds a profile at the end of the Xform list + * + * Args: + * szProfilePath = file name of the profile to be added, + * nIntent = rendering intent to be used with the profile, + * nInterp = type of interpolation to be used with the profile, + * nLutType = selection of which transform lut to use + * pHintManager = hints for creating the xform + * + * Return: + * icCmmStatOk, if the profile was added to the list succesfully + ************************************************************************** + */ +icStatusCMM CIccCmm::AddXform(const icChar *szProfilePath, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*icXformInterp*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + CIccProfile *pProfile = OpenIccProfile(szProfilePath); + + if (!pProfile) + return icCmmStatCantOpenProfile; + + icStatusCMM rv = AddXform(pProfile, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (rv != icCmmStatOk) + delete pProfile; + + return rv; +} + + +/** +************************************************************************** +* Name: CIccCmm::AddXform +* +* Purpose: +* Adds a profile at the end of the Xform list +* +* Args: +* pProfileMem = ptr to profile loaded into memory. Note: this memory +* needs to be available until after the Begin() function is called. +* nProfileLen = size in bytes of profile loaded into memory +* nIntent = rendering intent to be used with the profile, +* nInterp = type of interpolation to be used with the profile, +* nLutType = selection of which transform lut to use +* bUseMpeTags = flag to indicate the use MPE flags if available +* pHintManager = hints for creating the xform +* +* Return: +* icCmmStatOk, if the profile was added to the list succesfully +************************************************************************** +*/ +icStatusCMM CIccCmm::AddXform(icUInt8Number *pProfileMem, + icUInt32Number nProfileLen, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*icXformInterp*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + CIccMemIO *pFile = new CIccMemIO; + + if (!pFile || !pFile->Attach(pProfileMem, nProfileLen)) + return icCmmStatCantOpenProfile; + + CIccProfile *pProfile = new CIccProfile; + + if (!pProfile) + return icCmmStatCantOpenProfile; + + if (!pProfile->Attach(pFile)) { + delete pFile; + delete pProfile; + return icCmmStatCantOpenProfile; + } + + icStatusCMM rv = AddXform(pProfile, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (rv != icCmmStatOk) + delete pProfile; + + return rv; +} + + +/** + ************************************************************************** + * Name: CIccCmm::AddXform + * + * Purpose: + * Adds a profile at the end of the Xform list + * + * Args: + * pProfile = pointer to the CIccProfile object to be added, + * nIntent = rendering intent to be used with the profile, + * nInterp = type of interpolation to be used with the profile, + * nLutType = selection of which transform lut to use + * bUseMpeTags = flag to indicate the use MPE flags if available + * pHintManager = hints for creating the xform + * + * Return: + * icCmmStatOk, if the profile was added to the list succesfully + ************************************************************************** + */ +icStatusCMM CIccCmm::AddXform(CIccProfile *pProfile, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*=icInterpLinear*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + icColorSpaceSignature nSrcSpace, nDstSpace; + bool bInput = !m_bLastInput; + + if (!pProfile) + return icCmmStatInvalidProfile; + + switch (nLutType) { + case icXformLutColor: + { + //Check pProfile if nIntent and input can be found. + if (bInput) { + nSrcSpace = pProfile->m_Header.colorSpace; + nDstSpace = pProfile->m_Header.pcs; + } + else { + if (pProfile->m_Header.deviceClass == icSigLinkClass) { + return icCmmStatBadSpaceLink; + } + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = pProfile->m_Header.colorSpace; + if (pProfile->m_Header.deviceClass == icSigAbstractClass) { + bInput = true; + nIntent = icPerceptual; // Note: icPerceptualIntent = 0 + } + } + } + break; + + case icXformLutPreview: + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = pProfile->m_Header.pcs; + bInput = false; + break; + + case icXformLutGamut: + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = icSigGamutData; + bInput = true; + break; + + default: + return icCmmStatBadLutType; + } + + //Make sure colorspaces match with previous xforms + if (!m_Xforms->size()) { + if (m_nSrcSpace == icSigUnknownData) { + m_nLastSpace = nSrcSpace; + m_nSrcSpace = nSrcSpace; + } + else if (!IsCompatSpace(m_nSrcSpace, nSrcSpace)) { + return icCmmStatBadSpaceLink; + } + } + else if (!IsCompatSpace(m_nLastSpace, nSrcSpace)) { + return icCmmStatBadSpaceLink; + } + + if (nSrcSpace==icSigNamedData) + return icCmmStatBadSpaceLink; + + //Automatic creation of intent from header/last profile + if (nIntent==icUnknownIntent) { + if (bInput) { + nIntent = (icRenderingIntent)pProfile->m_Header.renderingIntent; + } + else { + nIntent = m_nLastIntent; + } + if (nIntent == icUnknownIntent) + nIntent = icPerceptual; + } + + CIccXformPtr Xform; + + Xform.ptr = CIccXform::Create(pProfile, bInput, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (!Xform.ptr) { + return icCmmStatBadXform; + } + + m_nLastSpace = nDstSpace; + m_nLastIntent = nIntent; + m_bLastInput = bInput; + + m_Xforms->push_back(Xform); + + return icCmmStatOk; +} + + +/** + ************************************************************************** + * Name: CIccCmm::AddXform + * + * Purpose: + * Adds a profile at the end of the Xform list + * + * Args: + * Profile = reference a CIccProfile object that will be copies and added, + * nIntent = rendering intent to be used with the profile, + * nInterp = type of interpolation to be used with the profile, + * nLutType = selection of which transform lut to use + * bUseMpeTags = flag to indicate the use MPE flags if available + * pHintManager = hints for creating the xform + * + * Return: + * icCmmStatOk, if the profile was added to the list succesfully + ************************************************************************** + */ +icStatusCMM CIccCmm::AddXform(CIccProfile &Profile, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*=icInterpLinear*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + CIccProfile *pProfile = new CIccProfile(Profile); + + if (!pProfile) + return icCmmStatAllocErr; + + icStatusCMM stat = AddXform(pProfile, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (stat != icCmmStatOk) + delete pProfile; + + return stat; +} + +/** +************************************************************************** +* Name: CIccCmm::GetNewApplyCmm +* +* Purpose: +* Does the initialization of the Xforms before Apply() is called. +* Must be called before Apply(). +* +************************************************************************** +*/ +icStatusCMM CIccCmm::Begin(bool bAllocApplyCmm/*=true*/) +{ + if (m_pApply) + return icCmmStatOk; + + if (m_nDestSpace==icSigUnknownData) { + m_nDestSpace = m_nLastSpace; + } + else if (!IsCompatSpace(m_nDestSpace, m_nLastSpace)) { + return icCmmStatBadSpaceLink; + } + + if (m_nSrcSpace==icSigNamedData || m_nDestSpace==icSigNamedData) { + return icCmmStatBadSpaceLink; + } + + icStatusCMM rv = icCmmStatOk; + CIccXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + rv = i->ptr->Begin(); + + if (rv!= icCmmStatOk) { + return rv; + } + } + + if (bAllocApplyCmm) { + m_pApply = GetNewApplyCmm(rv); + } + else + rv = icCmmStatOk; + + return rv; +} + + +/** + ************************************************************************** + * Name: CIccCmm::GetNewApplyCmm + * + * Purpose: + * Allocates an CIccApplyCmm object that does the initialization of the Xforms + * that provides an Apply() function. + * Multiple CIccApplyCmm objects can be allocated and used in separate threads. + * + ************************************************************************** + */ +CIccApplyCmm *CIccCmm::GetNewApplyCmm(icStatusCMM &status) +{ + CIccApplyCmm *pApply = new CIccApplyCmm(this); + + if (!pApply) { + status = icCmmStatAllocErr; + return NULL; + } + + CIccXformList::iterator i; + CIccApplyXform *pXform; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + pXform = i->ptr->GetNewApply(status); + if (!pXform || status != icCmmStatOk) { + delete pApply; + return NULL; + } + pApply->AppendApplyXform(pXform); + } + + m_bValid = true; + + status = icCmmStatOk; + + return pApply; +} + + +/** +************************************************************************** +* Name: CIccCmm::Apply +* +* Purpose: +* Uses the m_pApply object allocated during Begin to Apply the transformations +* associated with the CMM. +* +************************************************************************** +*/ +icStatusCMM CIccCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) +{ + return m_pApply->Apply(DstPixel, SrcPixel); +} + + +/** +************************************************************************** +* Name: CIccCmm::Apply +* +* Purpose: +* Uses the m_pApply object allocated during Begin to Apply the transformations +* associated with the CMM. +* +************************************************************************** +*/ +icStatusCMM CIccCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels) +{ + return m_pApply->Apply(DstPixel, SrcPixel, nPixels); +} + + +/** +************************************************************************** +* Name: CIccCmm::RemoveAllIO() +* +* Purpose: +* Remove any attachments to CIccIO objects associated with the profiles +* related to the transforms attached to the CMM. +* Must be called after Begin(). +* +* Return: +* icCmmStatOK - All IO objects removed +* icCmmStatBadXform - Begin() has not been performed. +************************************************************************** +*/ +icStatusCMM CIccCmm::RemoveAllIO() +{ + if (!Valid()) + return icCmmStatBadXform; + + CIccXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + i->ptr->RemoveIO(); + } + + return icCmmStatOk; +} + +/** + ************************************************************************* + ** Name: CIccCmm::IsInGamut + ** + ** Purpose: + ** Function to check if internal representation of gamut is in gamut. Note + ** since gamut table is 8 bit and a color is considered to be in out of gamut + ** if the value is not zero. Then we need to check where the 8 bit representation + ** of the internal value is not zero. + ** + ** Args: + ** pInternal = internal pixel representation of gamut value + ** + ** Return: + ** true if in gamut, false if out of gamut + **************************************************************************/ +bool CIccCmm::IsInGamut(icFloatNumber *pInternal) +{ + if (!((unsigned int)((*pInternal)*255.0))) + return true; + return false; +} + + +/** + ************************************************************************** + * Name: CIccCmm::ToInternalEncoding + * + * Purpose: + * Functions for converting to Internal representation of pixel colors. + * + * Args: + * nSpace = color space signature of the data, + * nEncode = icFloatColorEncoding type of the data, + * pInternal = converted data is stored here, + * pData = the data to be converted + * bClip = flag to clip to internal range + ************************************************************************** + */ +icStatusCMM CIccCmm::ToInternalEncoding(icColorSpaceSignature nSpace, icFloatColorEncoding nEncode, + icFloatNumber *pInternal, const icFloatNumber *pData, + bool bClip) +{ + int nSamples = icGetSpaceSamples(nSpace); + if (!nSamples) + return icCmmStatBadColorEncoding; + + icUInt16Number i; + icFloatNumber pInput[16]; + memcpy(pInput, pData, nSamples*sizeof(icFloatNumber)); + bool bCLRspace = icIsSpaceCLR(nSpace); + + switch(nSpace) { + + case icSigLabData: + { + switch(nEncode) { + case icEncodeValue: + { + icLabToPcs(pInput); + break; + } + case icEncodeFloat: + { + break; + } + case icEncode8Bit: + { + pInput[0] = icU8toF((icUInt8Number)pInput[0])*100.0f; + pInput[1] = icU8toAB((icUInt8Number)pInput[1]); + pInput[2] = icU8toAB((icUInt8Number)pInput[2]); + + icLabToPcs(pInput); + break; + } + case icEncode16Bit: + { + pInput[0] = icU16toF((icUInt16Number)pInput[0]); + pInput[1] = icU16toF((icUInt16Number)pInput[1]); + pInput[2] = icU16toF((icUInt16Number)pInput[2]); + break; + } + case icEncode16BitV2: + { + pInput[0] = icU16toF((icUInt16Number)pInput[0]); + pInput[1] = icU16toF((icUInt16Number)pInput[1]); + pInput[2] = icU16toF((icUInt16Number)pInput[2]); + + CIccPCS::Lab2ToLab4(pInput, pInput); + break; + } + default: + return icCmmStatBadColorEncoding; + break; + } + break; + } + + case icSigXYZData: + { + switch(nEncode) { + case icEncodeValue: + { + pInput[0] = (icFloatNumber)pInput[0]; + pInput[1] = (icFloatNumber)pInput[1]; + pInput[2] = (icFloatNumber)pInput[2]; + icXyzToPcs(pInput); + break; + } + case icEncodePercent: + { + pInput[0] = (icFloatNumber)(pInput[0] / 100.0); + pInput[1] = (icFloatNumber)(pInput[1] / 100.0); + pInput[2] = (icFloatNumber)(pInput[2] / 100.0); + icXyzToPcs(pInput); + break; + } + case icEncodeFloat: + { + icXyzToPcs(pInput); + break; + } + + case icEncode16Bit: + case icEncode16BitV2: + { + pInput[0] = icUSFtoD((icU1Fixed15Number)pInput[0]); + pInput[1] = icUSFtoD((icU1Fixed15Number)pInput[1]); + pInput[2] = icUSFtoD((icU1Fixed15Number)pInput[2]); + break; + } + + default: + return icCmmStatBadColorEncoding; + break; + } + break; + } + + case icSigNamedData: + return icCmmStatBadColorEncoding; + + default: + { + switch(nEncode) { + case icEncodeValue: + { + if (!bCLRspace || nSamples<3) { + return icCmmStatBadColorEncoding; + } + icLabToPcs(pInput); + break; + } + + case icEncodePercent: + { + if (bClip) { + for(i=0; i 1.0) pInput[i] = 1.0; + } + } + else { + for(i=0; i 1.0) pInput[i] = 1.0; + } + } + break; + } + + case icEncode8Bit: + { + for(i=0; i 1.0) pInput[i] = 1.0; + pInput[i] = (icFloatNumber)(pInput[i]*100.0); + } + } + else { + for(i=0; i 1.0) pInput[i] = 1.0; + } + } + break; + } + + case icEncode8Bit: + { + for(i=0; isize(); +} + + +/** +************************************************************************** +* Name: CIccCmm::GetFirstXformSource +* +* Purpose: +* Get source colorspace of first transform (similar to m_nSrcSpace with differences in dev colorimetric spaces) +* +* Return: +* colorspace +************************************************************************** +*/ +icColorSpaceSignature CIccCmm::GetFirstXformSource() +{ + if (!m_Xforms->size()) + return m_nSrcSpace; + + return m_Xforms->begin()->ptr->GetSrcSpace(); +} + +/** +************************************************************************** +* Name: CIccCmm::GetNumXforms +* +* Purpose: +* Get source colorspace of last transform (similar to m_nSrcSpace with differences in dev colorimetric spaces) +* +* Return: +* colorspace +************************************************************************** +*/ +icColorSpaceSignature CIccCmm::GetLastXformDest() +{ + if (!m_Xforms->size()) + return m_nDestSpace; + + return m_Xforms->rbegin()->ptr->GetDstSpace(); +} + +/** +************************************************************************** +* Name: CIccApplyCmm::CIccApplyCmm +* +* Purpose: +* Constructor +* +* Args: +* pCmm = ptr to CMM to apply against +************************************************************************** +*/ +CIccApplyNamedColorCmm::CIccApplyNamedColorCmm(CIccNamedColorCmm *pCmm) : CIccApplyCmm(pCmm) +{ +} + + +/** +************************************************************************** +* Name: CIccApplyCmm::CIccApplyCmm +* +* Purpose: +* Destructor +************************************************************************** +*/ +CIccApplyNamedColorCmm::~CIccApplyNamedColorCmm() +{ +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccApplyNamedColorCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + CIccApplyXform *pApply; + const CIccXform *pApplyXform; + CIccXformNamedColor *pXform; + + if (!n) + return icCmmStatBadXform; + + icChar NamedColor[256]; + icStatusCMM rv; + + m_pPCS->Reset(m_pCmm->GetSourceSpace()); + + pSrc = SrcPixel; + pDst = Pixel; + + if (n>1) { + for (j=0, i=m_Xforms->begin(); jend(); i++, j++) { + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + pXform->Apply(pApply, NamedColor, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyNamed2Pixel: + if (j==0) { + return icCmmStatIncorrectApply; + } + + rv = pXform->Apply(pApply, pDst, NamedColor); + + if (rv) { + return rv; + } + break; + + default: + break; + } + } + else { + pApplyXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pApplyXform)); + } + pSrc = pDst; + } + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + default: + return icCmmStatIncorrectApply; + break; + + case icApplyNamed2Pixel: + rv = pXform->Apply(pApply, DstPixel, NamedColor); + if (rv) { + return rv; + } + break; + + } + } + else { + pApplyXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pApplyXform)); + } + + } + else if (n==1) { + i = m_Xforms->begin(); + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + return icCmmStatIncorrectApply; + } + + pApplyXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pApplyXform)); + } + + m_pPCS->CheckLast(DstPixel, m_pCmm->GetDestSpace()); + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstPixel = Destination pixel where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccApplyNamedColorCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + CIccApplyXform *pApply; + const CIccXform *pApplyXform; + CIccXformNamedColor *pXform; + icUInt32Number k; + + if (!n) + return icCmmStatBadXform; + + icChar NamedColor[256]; + icStatusCMM rv; + + for (k=0; kReset(m_pCmm->GetSourceSpace()); + + pSrc = SrcPixel; + pDst = Pixel; + + if (n>1) { + for (j=0, i=m_Xforms->begin(); jend(); i++, j++) { + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + pXform->Apply(pApply, NamedColor, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyNamed2Pixel: + if (j==0) { + return icCmmStatIncorrectApply; + } + + rv = pXform->Apply(pApply, pDst, NamedColor); + + if (rv) { + return rv; + } + break; + + default: + break; + } + } + else { + pApplyXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pApplyXform)); + } + pSrc = pDst; + } + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + default: + return icCmmStatIncorrectApply; + break; + + case icApplyNamed2Pixel: + rv = pXform->Apply(pApply, DstPixel, NamedColor); + if (rv) { + return rv; + } + break; + + } + } + else { + pApplyXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pApplyXform)); + } + + } + else if (n==1) { + i = m_Xforms->begin(); + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + return icCmmStatIncorrectApply; + } + + pApplyXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pApplyXform)); + } + + m_pPCS->CheckLast(DstPixel, m_pCmm->GetDestSpace()); + + SrcPixel += m_pCmm->GetSourceSamples(); + DstPixel += m_pCmm->GetDestSamples(); + } + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstColorName = Destination string where the result is stored, +* SrcPixel = Source pixel which is to be applied. +************************************************************************** +*/ +icStatusCMM CIccApplyNamedColorCmm::Apply(icChar* DstColorName, const icFloatNumber *SrcPixel) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + CIccApplyXform *pApply; + const CIccXform *pApplyXform; + CIccXformNamedColor *pXform; + + if (!n) + return icCmmStatBadXform; + + icChar NamedColor[256]; + icStatusCMM rv; + + m_pPCS->Reset(m_pCmm->GetSourceSpace()); + + pSrc = SrcPixel; + pDst = Pixel; + + if (n>1) { + for (j=0, i=m_Xforms->begin(); jend(); i++, j++) { + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + pXform->Apply(pApply, NamedColor, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyNamed2Pixel: + if (j==0) { + return icCmmStatIncorrectApply; + } + rv = pXform->Apply(pApply, pDst, NamedColor); + if (rv) { + return rv; + } + break; + + default: + break; + } + } + else { + pApplyXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pApplyXform)); + } + pSrc = pDst; + } + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + + case icApplyPixel2Named: + pXform->Apply(pApply, DstColorName, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Pixel: + case icApplyNamed2Pixel: + default: + return icCmmStatIncorrectApply; + break; + } + } + else { + return icCmmStatIncorrectApply; + } + + } + else if (n==1) { + i = m_Xforms->begin(); + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()!=icXformTypeNamedColor) { + return icCmmStatIncorrectApply; + } + + pXform = (CIccXformNamedColor*)pApplyXform; + pXform->Apply(pApply, DstColorName, m_pPCS->Check(pSrc, pXform)); + } + + return icCmmStatOk; +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstPixel = Destination pixel where the result is stored, +* SrcColorName = Source color name which is to be searched. +************************************************************************** +*/ +icStatusCMM CIccApplyNamedColorCmm::Apply(icFloatNumber *DstPixel, const icChar *SrcColorName) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + CIccApplyXform *pApply; + const CIccXform *pApplyXform; + CIccXformNamedColor *pXform; + + if (!n) + return icCmmStatBadXform; + + icChar NamedColor[256]; + icStatusCMM rv; + + i=m_Xforms->begin(); + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()!=icXformTypeNamedColor) + return icCmmStatIncorrectApply; + + pXform = (CIccXformNamedColor*)pApplyXform; + m_pPCS->Reset(pXform->GetSrcSpace(), pXform->UseLegacyPCS()); + + pDst = Pixel; + + if (n>1) { + rv = pXform->Apply(pApply, pDst, SrcColorName); + if (rv) { + return rv; + } + + pSrc = pDst; + + for (j=0, i++; jend(); i++, j++) { + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + CIccXformNamedColor *pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + pXform->Apply(pApply, NamedColor, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyNamed2Pixel: + rv = pXform->Apply(pApply, pDst, NamedColor); + if (rv) { + return rv; + } + break; + + default: + break; + } + } + else { + pApplyXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pApplyXform)); + } + pSrc = pDst; + } + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Named: + default: + return icCmmStatIncorrectApply; + break; + + case icApplyNamed2Pixel: + rv = pXform->Apply(pApply, DstPixel, NamedColor); + if (rv) { + return rv; + } + break; + + } + } + else { + pApplyXform->Apply(pApply, DstPixel, m_pPCS->Check(pSrc, pApplyXform)); + } + + } + else if (n==1) { + rv = pXform->Apply(pApply, DstPixel, SrcColorName); + if (rv) { + return rv; + } + m_pPCS->Check(DstPixel, pXform); + } + + m_pPCS->CheckLast(DstPixel, m_pCmm->GetDestSpace()); + + return icCmmStatOk; +} + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstColorName = Destination string where the result is stored, +* SrcColorName = Source color name which is to be searched. +************************************************************************** +*/ +icStatusCMM CIccApplyNamedColorCmm::Apply(icChar *DstColorName, const icChar *SrcColorName) +{ + icFloatNumber Pixel[16], *pDst; + const icFloatNumber *pSrc; + CIccApplyXformList::iterator i; + int j, n = (int)m_Xforms->size(); + icChar NamedColor[256]; + icStatusCMM rv; + CIccApplyXform *pApply; + const CIccXform *pApplyXform; + CIccXformNamedColor *pXform; + + if (!n) + return icCmmStatBadXform; + + i=m_Xforms->begin(); + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()!=icXformTypeNamedColor) + return icCmmStatIncorrectApply; + + pXform = (CIccXformNamedColor*)pApplyXform; + + m_pPCS->Reset(pXform->GetSrcSpace(), pXform->UseLegacyPCS()); + + pDst = Pixel; + + if (n>1) { + rv = pXform->Apply(pApply, pDst, SrcColorName); + + if (rv) { + return rv; + } + + pSrc = pDst; + + for (j=0, i++; jend(); i++, j++) { + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + case icApplyPixel2Pixel: + pXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + break; + + + case icApplyPixel2Named: + pXform->Apply(pApply, NamedColor, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyNamed2Pixel: + rv = pXform->Apply(pApply, pDst, NamedColor); + if (rv) { + return rv; + } + break; + + default: + break; + } + } + else { + pApplyXform->Apply(pApply, pDst, m_pPCS->Check(pSrc, pXform)); + } + pSrc = pDst; + } + + pApply = i->ptr; + pApplyXform = pApply->GetXform(); + if (pApplyXform->GetXformType()==icXformTypeNamedColor) { + pXform = (CIccXformNamedColor*)pApplyXform; + switch(pXform->GetInterface()) { + case icApplyPixel2Named: + pXform->Apply(pApply, DstColorName, m_pPCS->Check(pSrc, pXform)); + break; + + case icApplyPixel2Pixel: + case icApplyNamed2Pixel: + default: + return icCmmStatIncorrectApply; + break; + } + } + else { + return icCmmStatIncorrectApply; + } + + } + else if (n==1) { + return icCmmStatIncorrectApply; + } + + return icCmmStatOk; +} + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::CIccNamedColorCmm + * + * Purpose: + * Constructor + * + * Args: + * nSrcSpace = signature of the source color space, + * nDestSpace = signature of the destination color space, + * bFirstInput = true if the first profile added is an input profile + ************************************************************************** + */ +CIccNamedColorCmm::CIccNamedColorCmm(icColorSpaceSignature nSrcSpace, icColorSpaceSignature nDestSpace, + bool bFirstInput) : CIccCmm(nSrcSpace, nDestSpace, bFirstInput) +{ + m_nApplyInterface = icApplyPixel2Pixel; +} + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::~CIccNamedColorCmm + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccNamedColorCmm::~CIccNamedColorCmm() +{ +} + + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::AddXform + * + * Purpose: + * Adds a profile at the end of the Xform list + * + * Args: + * szProfilePath = file name of the profile to be added, + * nIntent = rendering intent to be used with the profile, + * nInterp = type of interpolation to be used with the profile + * pHintManager = hints for creating the xform + * + * Return: + * icCmmStatOk, if the profile was added to the list succesfully + ************************************************************************** + */ +icStatusCMM CIccNamedColorCmm::AddXform(const icChar *szProfilePath, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*icXformInterp*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + CIccProfile *pProfile = OpenIccProfile(szProfilePath); + + if (!pProfile) + return icCmmStatCantOpenProfile; + + icStatusCMM rv = AddXform(pProfile, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (rv != icCmmStatOk) + delete pProfile; + + return rv; +} + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::AddXform + * + * Purpose: + * Adds a profile at the end of the Xform list + * + * Args: + * pProfile = pointer to the CIccProfile object to be added, + * nIntent = rendering intent to be used with the profile, + * nInterp = type of interpolation to be used with the profile + * nLutType = type of lut to use from the profile + * pHintManager = hints for creating the xform + * + * Return: + * icCmmStatOk, if the profile was added to the list succesfully + ************************************************************************** + */ +icStatusCMM CIccNamedColorCmm::AddXform(CIccProfile *pProfile, + icRenderingIntent nIntent /*=icUnknownIntent*/, + icXformInterp nInterp /*=icInterpLinear*/, + icXformLutType nLutType /*=icXformLutColor*/, + bool bUseMpeTags /*=true*/, + CIccCreateXformHintManager *pHintManager /*=NULL*/) +{ + icColorSpaceSignature nSrcSpace, nDstSpace; + CIccXformPtr Xform; + bool bInput = !m_bLastInput; + icStatusCMM rv; + + Xform.ptr = NULL; + switch (nLutType) { + //Automatically choose which one + case icXformLutColor: + case icXformLutNamedColor: + { + CIccTagNamedColor2 *pTag = (CIccTagNamedColor2*)pProfile->FindTag(icSigNamedColor2Tag); + + if (pTag && (pProfile->m_Header.deviceClass==icSigNamedColorClass || nLutType==icXformLutNamedColor)) { + if (bInput) { + nSrcSpace = icSigNamedData; + } + else { + nSrcSpace = pProfile->m_Header.pcs; + } + + if (!m_Xforms->size()) { + if (m_nSrcSpace==icSigUnknownData) { + m_nSrcSpace = nSrcSpace; + } + else { + nSrcSpace = m_nSrcSpace; + } + } + else { + if (m_nLastSpace==icSigUnknownData) { + m_nLastSpace = nSrcSpace; + } + else { + nSrcSpace = m_nLastSpace; + } + } + + if (nSrcSpace==icSigNamedData) { + nDstSpace = pProfile->m_Header.pcs; + bInput = true; + } + else { + nDstSpace = icSigNamedData; + bInput = false; + } + + Xform.ptr = CIccXform::Create(pProfile, bInput, nIntent, nInterp, icXformLutNamedColor, bUseMpeTags, pHintManager); + if (!Xform.ptr) { + return icCmmStatBadXform; + } + CIccXformNamedColor *pXform = (CIccXformNamedColor *)Xform.ptr; + rv = pXform->SetSrcSpace(nSrcSpace); + if (rv) + return rv; + + rv = pXform->SetDestSpace(nDstSpace); + if (rv) + return rv; + } + else { + //It isn't named color so make we will use color lut. + nLutType = icXformLutColor; + + //Check pProfile if nIntent and input can be found. + if (bInput) { + nSrcSpace = pProfile->m_Header.colorSpace; + nDstSpace = pProfile->m_Header.pcs; + } + else { + if (pProfile->m_Header.deviceClass == icSigLinkClass) { + return icCmmStatBadSpaceLink; + } + if (pProfile->m_Header.deviceClass == icSigAbstractClass) { + bInput = true; + nIntent = icPerceptual; // Note: icPerceptualIntent = 0 + } + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = pProfile->m_Header.colorSpace; + } + } + } + break; + + case icXformLutPreview: + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = pProfile->m_Header.pcs; + bInput = false; + break; + + case icXformLutGamut: + nSrcSpace = pProfile->m_Header.pcs; + nDstSpace = icSigGamutData; + bInput = true; + break; + + default: + return icCmmStatBadLutType; + } + + //Make sure color spaces match with previous xforms + if (!m_Xforms->size()) { + if (m_nSrcSpace == icSigUnknownData) { + m_nLastSpace = nSrcSpace; + m_nSrcSpace = nSrcSpace; + } + else if (!IsCompatSpace(m_nSrcSpace, nSrcSpace)) { + return icCmmStatBadSpaceLink; + } + } + else if (!IsCompatSpace(m_nLastSpace, nSrcSpace)) { + return icCmmStatBadSpaceLink; + } + + //Automatic creation of intent from header/last profile + if (nIntent==icUnknownIntent) { + if (bInput) { + nIntent = (icRenderingIntent)pProfile->m_Header.renderingIntent; + } + else { + nIntent = m_nLastIntent; + } + if (nIntent == icUnknownIntent) + nIntent = icPerceptual; + } + + if (!Xform.ptr) + Xform.ptr = CIccXform::Create(pProfile, bInput, nIntent, nInterp, nLutType, bUseMpeTags, pHintManager); + + if (!Xform.ptr) { + return icCmmStatBadXform; + } + + m_nLastSpace = nDstSpace; + m_nLastIntent = nIntent; + m_bLastInput = bInput; + + m_Xforms->push_back(Xform); + + return icCmmStatOk; +} + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::Begin + * + * Purpose: + * Does the initialization of the Xforms in the list before Apply() is called. + * Must be called before Apply(). + * + ************************************************************************** + */ + icStatusCMM CIccNamedColorCmm::Begin(bool bAllocNewApply/* =true */) +{ + if (m_nDestSpace==icSigUnknownData) { + m_nDestSpace = m_nLastSpace; + } + else if (!IsCompatSpace(m_nDestSpace, m_nLastSpace)) { + return icCmmStatBadSpaceLink; + } + + if (m_nSrcSpace != icSigNamedData) { + if (m_nDestSpace != icSigNamedData) { + m_nApplyInterface = icApplyPixel2Pixel; + } + else { + m_nApplyInterface = icApplyPixel2Named; + } + } + else { + if (m_nDestSpace != icSigNamedData) { + m_nApplyInterface = icApplyNamed2Pixel; + } + else { + m_nApplyInterface = icApplyNamed2Named; + } + } + + icStatusCMM rv; + CIccXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + rv = i->ptr->Begin(); + + if (rv!= icCmmStatOk) { + return rv; + } + } + + if (bAllocNewApply) { + m_pApply = GetNewApplyCmm(rv); + } + else + rv = icCmmStatOk; + + return rv; +} + + /** + ************************************************************************** + * Name: CIccNamedColorCmm::GetNewApply + * + * Purpose: + * Allocates a CIccApplyCmm object that allows one to call apply from + * multiple threads. + * + ************************************************************************** + */ + CIccApplyCmm *CIccNamedColorCmm::GetNewApply(icStatusCMM &status) + { + CIccApplyCmm *pApply = new CIccApplyNamedColorCmm(this); + + CIccXformList::iterator i; + + for (i=m_Xforms->begin(); i!=m_Xforms->end(); i++) { + CIccApplyXform *pXform = i->ptr->GetNewApply(status); + if (status != icCmmStatOk || !pXform) { + delete pApply; + return NULL; + } + pApply->AppendApplyXform(pXform); + } + + m_bValid = true; + + status = icCmmStatOk; + return pApply; +} + + + /** + ************************************************************************** + * Name: CIccApplyNamedColorCmm::Apply + * + * Purpose: + * Does the actual application of the Xforms in the list. + * + * Args: + * DstColorName = Destination string where the result is stored, + * SrcPoxel = Source pixel + ************************************************************************** + */ +icStatusCMM CIccNamedColorCmm::Apply(icChar* DstColorName, const icFloatNumber *SrcPixel) +{ + return ((CIccApplyNamedColorCmm*)m_pApply)->Apply(DstColorName, SrcPixel); +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DestPixel = Destination pixel where the result is stored, +* SrcColorName = Source color name which is to be searched. +************************************************************************** +*/ +icStatusCMM CIccNamedColorCmm::Apply(icFloatNumber *DstPixel, const icChar *SrcColorName) +{ + return ((CIccApplyNamedColorCmm*)m_pApply)->Apply(DstPixel, SrcColorName); +} + + +/** +************************************************************************** +* Name: CIccApplyNamedColorCmm::Apply +* +* Purpose: +* Does the actual application of the Xforms in the list. +* +* Args: +* DstColorName = Destination string where the result is stored, +* SrcColorName = Source color name which is to be searched. +************************************************************************** +*/ +icStatusCMM CIccNamedColorCmm::Apply(icChar* DstColorName, const icChar *SrcColorName) +{ + return ((CIccApplyNamedColorCmm*)m_pApply)->Apply(DstColorName, SrcColorName); +} + + +/** + ************************************************************************** + * Name: CIccNamedColorCmm::SetLastXformDest + * + * Purpose: + * Sets the destination Color space of the last Xform in the list + * + * Args: + * nDestSpace = signature of the color space to be set + ************************************************************************** + */ +icStatusCMM CIccNamedColorCmm::SetLastXformDest(icColorSpaceSignature nDestSpace) +{ + int n = (int)m_Xforms->size(); + CIccXformPtr *pLastXform; + + if (!n) + return icCmmStatBadXform; + + pLastXform = &m_Xforms->back(); + + if (pLastXform->ptr->GetXformType()==icXformTypeNamedColor) { + CIccXformNamedColor *pXform = (CIccXformNamedColor *)pLastXform->ptr; + if (pXform->GetSrcSpace() == icSigNamedData && + nDestSpace == icSigNamedData) { + return icCmmStatBadSpaceLink; + } + + if (nDestSpace != icSigNamedData && + pXform->GetDstSpace() == icSigNamedData) { + return icCmmStatBadSpaceLink; + } + + return pXform->SetDestSpace(nDestSpace); + } + + return icCmmStatBadXform; +} + + +/** +**************************************************************************** +* Name: CIccMruCmm::CIccMruCmm +* +* Purpose: private constructor - Use Attach to create CIccMruCmm objects +***************************************************************************** +*/ +CIccMruCmm::CIccMruCmm() +{ + m_pCmm = NULL; +} + + +/** +**************************************************************************** +* Name: CIccMruCmm::~CIccMruCmm +* +* Purpose: destructor +***************************************************************************** +*/ +CIccMruCmm::~CIccMruCmm() +{ + if (m_pCmm) + delete m_pCmm; +} + + +/** +**************************************************************************** +* Name: CIccMruCmm::Attach +* +* Purpose: Create a Cmm decorator object that implements a cache of most +* recently used pixel transformations. +* +* Args: +* pCmm - pointer to cmm object that we are attaching to. +* nCacheSize - number of most recently used transformations to cache +* +* Return: +* A CIccMruCmm object that represents a cached form of the pCmm passed in. +* The pCmm will be owned by the returned object. +* +* If this function fails the pCmm object will be deleted. +***************************************************************************** +*/ +CIccMruCmm* CIccMruCmm::Attach(CIccCmm *pCmm, icUInt8Number nCacheSize/* =4 */) +{ + if (!pCmm || !nCacheSize) + return NULL; + + if (!pCmm->Valid()) { + delete pCmm; + return NULL; + } + + CIccMruCmm *rv = new CIccMruCmm(); + + rv->m_pCmm = pCmm; + rv->m_nCacheSize = nCacheSize; + + rv->m_nSrcSpace = pCmm->GetSourceSpace(); + rv->m_nDestSpace = pCmm->GetDestSpace(); + rv->m_nLastSpace = pCmm->GetLastSpace(); + rv->m_nLastIntent = pCmm->GetLastIntent(); + + if (rv->Begin()!=icCmmStatOk) { + delete rv; + return NULL; + } + + return rv; +} + +CIccApplyCmm *CIccMruCmm::GetNewApplyCmm(icStatusCMM &status) +{ + CIccApplyMruCmm *rv = new CIccApplyMruCmm(this); + + if (!rv) { + status = icCmmStatAllocErr; + return NULL; + } + + if (!rv->Init(m_pCmm, m_nCacheSize)) { + delete rv; + status = icCmmStatBad; + return NULL; + } + + return rv; +} + + +CIccApplyMruCmm::CIccApplyMruCmm(CIccMruCmm *pCmm) : CIccApplyCmm(pCmm) +{ + m_cache = NULL; + + m_pixelData = NULL; +} + +/** +**************************************************************************** +* Name: CIccApplyMruCmm::~CIccApplyMruCmm +* +* Purpose: destructor +***************************************************************************** +*/ +CIccApplyMruCmm::~CIccApplyMruCmm() +{ + if (m_cache) + delete [] m_cache; + + if (m_pixelData) + free(m_pixelData); +} + +/** +**************************************************************************** +* Name: CIccApplyMruCmm::Init +* +* Purpose: Initialize the object and set up the cache +* +* Args: +* pCmm - pointer to cmm object that we are attaching to. +* nCacheSize - number of most recently used transformations to cache +* +* Return: +* true if successful +***************************************************************************** +*/ +bool CIccApplyMruCmm::Init(CIccCmm *pCachedCmm, icUInt16Number nCacheSize) +{ + m_pCachedCmm = pCachedCmm; + + m_nSrcSamples = m_pCmm->GetSourceSamples(); + m_nSrcSize = m_nSrcSamples * sizeof(icFloatNumber); + m_nDstSize = m_pCmm->GetDestSamples() * sizeof(icFloatNumber); + + m_nTotalSamples = m_nSrcSamples + m_pCmm->GetDestSamples(); + + m_nNumPixel = 0; + m_nCacheSize = nCacheSize; + + m_pFirst = NULL; + m_cache = new CIccMruPixel[nCacheSize]; + + if (!m_cache) + return false; + + m_pixelData = (icFloatNumber*)malloc(nCacheSize * m_nTotalSamples * sizeof(icFloatNumber)); + + if (!m_pixelData) + return false; + + return true; +} + +/** +**************************************************************************** +* Name: CIccMruCmm::Apply +* +* Purpose: Apply a transformation to a pixel. +* +* Args: +* DstPixel - Location to store pixel results +* SrcPixel - Location to get pixel values from +* +* Return: +* icCmmStatOk if successful +***************************************************************************** +*/ +icStatusCMM CIccApplyMruCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) +{ + CIccMruPixel *ptr, *prev=NULL, *last=NULL; + int i; + icFloatNumber *pixel; + + for (ptr = m_pFirst, i=0; ptr; ptr=ptr->pNext, i++) { + if (!memcmp(SrcPixel, ptr->pPixelData, m_nSrcSize)) { + memcpy(DstPixel, &ptr->pPixelData[m_nSrcSamples], m_nDstSize); + return icCmmStatOk; + } + prev = last; + last = ptr; + } + + //If we get here SrcPixel is not in the cache + if (ipPixelData = pixel; + + if (!last) { + m_pFirst = ptr; + } + else { + + last->pNext = ptr; + } + } + else { //Reuse oldest value and put it at the front of the list + prev->pNext = NULL; + last->pNext = m_pFirst; + + m_pFirst = last; + pixel = last->pPixelData; + } + icFloatNumber *dest = &pixel[m_nSrcSamples]; + + memcpy(pixel, SrcPixel, m_nSrcSize); + + m_pCachedCmm->Apply(dest, pixel); + + memcpy(DstPixel, dest, m_nDstSize); + + return icCmmStatOk; +} + +/** +**************************************************************************** +* Name: CIccMruCmm::Apply +* +* Purpose: Apply a transformation to a pixel. +* +* Args: +* DstPixel - Location to store pixel results +* SrcPixel - Location to get pixel values from +* nPixels - number of pixels to convert +* +* Return: +* icCmmStatOk if successful +***************************************************************************** +*/ +icStatusCMM CIccApplyMruCmm::Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels) +{ + CIccMruPixel *ptr, *prev=NULL, *last=NULL; + int i; + icFloatNumber *pixel, *dest; + icUInt32Number k; + + for (k=0; kpNext, i++) { + if (!memcmp(SrcPixel, ptr->pPixelData, m_nSrcSize)) { + memcpy(DstPixel, &ptr->pPixelData[m_nSrcSamples], m_nDstSize); + goto next_k; + } + prev = last; + last = ptr; + } + + //If we get here SrcPixel is not in the cache + if (ipPixelData = pixel; + + if (!last) { + m_pFirst = ptr; + } + else { + + last->pNext = ptr; + } + } + else { //Reuse oldest value and put it at the front of the list + prev->pNext = NULL; + last->pNext = m_pFirst; + + m_pFirst = last; + pixel = last->pPixelData; + } + dest = &pixel[m_nSrcSamples]; + + memcpy(pixel, SrcPixel, m_nSrcSize); + + m_pCachedCmm->Apply(dest, pixel); + + memcpy(DstPixel, dest, m_nDstSize); + +next_k: + k++; + } + + return icCmmStatOk; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccCmm.h b/library/src/main/cpp/icc/IccCmm.h new file mode 100644 index 00000000..4e450ca0 --- /dev/null +++ b/library/src/main/cpp/icc/IccCmm.h @@ -0,0 +1,1141 @@ +/** @file + File: IccCmm.h + + Contains: Header file for implementation of the CIccCmm class. + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// -Added support for Monochrome ICC profile apply by Rohit Patil 12-03-2008 +// -Integrate changes for PCS adjustment by George Pawle 12-09-2008 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCCMM_H) +#define _ICCCMM_H + +#include "IccProfile.h" +#include "IccTag.h" +#include "IccUtil.h" +#include +#include +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/// CMM return status values +typedef enum { + icCmmStatBad = -1, + icCmmStatOk = 0, + icCmmStatCantOpenProfile = 1, + icCmmStatBadSpaceLink = 2, + icCmmStatInvalidProfile = 3, + icCmmStatBadXform = 4, + icCmmStatInvalidLut = 5, + icCmmStatProfileMissingTag = 6, + icCmmStatColorNotFound = 7, + icCmmStatIncorrectApply = 8, + icCmmStatBadColorEncoding = 9, + icCmmStatAllocErr = 10, + icCmmStatBadLutType = 11, +} icStatusCMM; + +/// CMM Interpolation types +typedef enum { + icInterpLinear = 0, + icInterpTetrahedral = 1, +} icXformInterp; + +/// CMM Xform LUT types +typedef enum { + icXformLutColor = 0, + icXformLutNamedColor = 1, + icXformLutPreview = 2, + icXformLutGamut = 3, +} icXformLutType; + +#define icPerceptualRefBlackX 0.00336 +#define icPerceptualRefBlackY 0.0034731 +#define icPerceptualRefBlackZ 0.00287 + +#define icPerceptualRefWhiteX 0.9642 +#define icPerceptualRefWhiteY 1.0000 +#define icPerceptualRefWhiteZ 0.8249 + +// CMM Xform types +typedef enum { + icXformTypeMatrixTRC = 0, + icXformType3DLut = 1, + icXformType4DLut = 2, + icXformTypeNDLut = 3, + icXformTypeNamedColor = 4, //Creator uses icNamedColorXformHint + icXformTypeMpe = 5, + icXformTypeMonochrome = 6, + + icXformTypeUnknown = 0x7ffffff, +} icXformType; + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Interface for creation of a named xform hint +************************************************************************** +*/ +class ICCPROFLIB_API IIccCreateXformHint +{ +public: + virtual const char *GetHintType() const=0; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Manages the named xform hints +************************************************************************** +*/ +class ICCPROFLIB_API CIccCreateXformHintManager +{ +public: + CIccCreateXformHintManager() { m_pList = NULL; } + ~CIccCreateXformHintManager(); + + /// Adds and owns the passed named hint to it's list + bool AddHint(IIccCreateXformHint* pHint); + + /// Deletes the object referenced by the passed named hint pointer and removes it from the list + bool DeleteHint(IIccCreateXformHint* pHint); + + /// Finds and returns a pointer to the named hint + IIccCreateXformHint* GetHint(const char* hintName); + +private: + // private hint ptr class + class IIccCreateXformHintPtr { + public: + IIccCreateXformHint* ptr; + }; + typedef std::list IIccCreateXformHintList; + + // private members + IIccCreateXformHintList* m_pList; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Hint for creation of a named color xform +************************************************************************** +*/ +class ICCPROFLIB_API CIccCreateNamedColorXformHint : public IIccCreateXformHint +{ +public: + virtual const char *GetHintType() const {return "CIccCreateNamedColorXformHint";} + + icColorSpaceSignature csPcs; + icColorSpaceSignature csDevice; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Interface for calculating adjust PCS factors +************************************************************************** +*/ +class CIccXform; +class ICCPROFLIB_API IIccAdjustPCSXform +{ +public: + virtual ~IIccAdjustPCSXform() {} + virtual bool CalcFactors(const CIccProfile* pProfile, const CIccXform* pXfm, icFloatNumber* Scale, icFloatNumber* Offset) const=0; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: +* Hint for calculating adjust PCS factors +************************************************************************** +*/ +class ICCPROFLIB_API CIccCreateAdjustPCSXformHint : public IIccCreateXformHint +{ +public: + virtual const char *GetHintType() const {return "CIccCreateAdjustPCSXformHint";} + virtual const char *GetAdjustPCSType() const=0; + virtual IIccAdjustPCSXform *GetNewAdjustPCSXform() const=0; +}; + +//forward reference to CIccXform used by CIccApplyXform +class CIccApplyXform; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: + * This is the base CMM xform object. A general static creation function, + * base behavior, and data are defined. The Create() function will assign + * a profile to the class. The CIccProfile object will then be owned by the + * xform object and later deleted when the IccXform is deleted. + ************************************************************************** + */ +class ICCPROFLIB_API CIccXform +{ +public: + CIccXform(); + virtual ~CIccXform(); + + virtual icXformType GetXformType() const = 0; + + ///Note: The returned CIccXform will own the profile. + static CIccXform *Create(CIccProfile *pProfile, bool bInput=true, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); + + ///Note: Provide an interface to work profile references. The IccProfile is copied, and the copy's ownership + ///is turned over to the Returned CIccXform object. + static CIccXform *Create(CIccProfile &pProfile, bool bInput=true, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); + + virtual icStatusCMM Begin(); + + virtual CIccApplyXform *GetNewApply(icStatusCMM &status); + + virtual void Apply(CIccApplyXform *pXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const = 0; + + //Detach and remove CIccIO object associated with xform's profile. Must call after Begin() + virtual bool RemoveIO() { return m_pProfile->Detach(); } + + ///Returns the source color space of the transform + virtual icColorSpaceSignature GetSrcSpace() const; + + ///Returns the destination color space of the transform + virtual icColorSpaceSignature GetDstSpace() const; + + ///Checks if version 2 PCS is to be used + virtual bool UseLegacyPCS() const { return false; } + ///Checks if the profile is version 2 + virtual bool IsVersion2() const { return !m_pProfile || m_pProfile->m_Header.version < icVersionNumberV4; } + + ///Checks if the profile is to be used as input profile + bool IsInput() const { return m_bInput; } + + /// The following function is for Overridden create function + void SetParams(CIccProfile *pProfile, bool bInput, icRenderingIntent nIntent, icXformInterp nInterp, CIccCreateXformHintManager *pHintManager=NULL); + + /// Use these functions to extract the input/output curves from the xform + virtual LPIccCurve* ExtractInputCurves()=0; + virtual LPIccCurve* ExtractOutputCurves()=0; + + virtual bool NoClipPCS() const { return false; } + + /// Returns the profile pointer. Profile is still owned by the Xform. + const CIccProfile* GetProfile() const { return m_pProfile; } + + /// Returns the rendering intent being used by the Xform + icRenderingIntent GetIntent() const { return m_nIntent; } + +protected: + //Called by derived classes to initialize Base + + const icFloatNumber *CheckSrcAbs(CIccApplyXform *pApply, const icFloatNumber *Pixel) const; + void CheckDstAbs(icFloatNumber *Pixel) const; + void AdjustPCS(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual bool HasPerceptualHandling() { return true; } + + CIccProfile *m_pProfile; + bool m_bInput; + icRenderingIntent m_nIntent; + icXYZNumber m_MediaXYZ; + icXformInterp m_nInterp; + + // track PCS adjustments + IIccAdjustPCSXform* m_pAdjustPCS; + bool m_bAdjustPCS; + icFloatNumber m_PCSScale[3]; // scale and offset for PCS adjustment in XYZ + icFloatNumber m_PCSOffset[3]; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Pointer to the Cmm Xform object + ************************************************************************** + */ +class ICCPROFLIB_API CIccXformPtr { +public: + CIccXform *ptr; +}; + + +/** + ************************************************************************** + * Type: List Class + * + * Purpose: List of CIccXformPtr which is updated on addition of Xforms + ************************************************************************** + */ +typedef std::list CIccXformList; + + +/** +************************************************************************** +* Type: Class +* +* Purpose: The Apply Cmm Xform object (Allows xforms to have apply time data) +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyXform +{ + friend class CIccXform; +public: + virtual ~CIccApplyXform(); + virtual icXformType GetXformType() const { return icXformTypeUnknown; } + + void __inline Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) { m_pXform->Apply(this, DstPixel, SrcPixel); } + + const CIccXform *GetXform() { return m_pXform; } + +protected: + icFloatNumber m_AbsLab[3]; + + CIccApplyXform(CIccXform *pXform); + + const CIccXform *m_pXform; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: Pointer to the Apply Cmm Xform object +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyXformPtr { +public: + CIccApplyXform *ptr; +}; + + +/** +************************************************************************** +* Type: List Class +* +* Purpose: List of CIccApplyXformPtr which is updated on addition of Apply Xforms +************************************************************************** +*/ +typedef std::list CIccApplyXformList; + +/** +************************************************************************** +* Type: Class +* +* Purpose: This is the general Monochrome Xform (uses a grayTRCTag) +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccXformMonochrome : public CIccXform +{ +public: + CIccXformMonochrome(); + virtual ~CIccXformMonochrome(); + + virtual icXformType GetXformType() const { return icXformTypeMonochrome; } + + virtual icStatusCMM Begin(); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual LPIccCurve* ExtractInputCurves(); + virtual LPIccCurve* ExtractOutputCurves(); + +protected: + + virtual bool HasPerceptualHandling() { return false; } + + CIccCurve *m_Curve; + CIccCurve *GetCurve(icSignature sig) const; + CIccCurve *GetInvCurve(icSignature sig) const; + + bool m_bFreeCurve; + /// used only when applying the xform + LPIccCurve m_ApplyCurvePtr; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the general Matrix-TRC Xform + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccXformMatrixTRC : public CIccXform +{ +public: + CIccXformMatrixTRC(); + virtual ~CIccXformMatrixTRC(); + + virtual icXformType GetXformType() const { return icXformTypeMatrixTRC; } + + virtual icStatusCMM Begin(); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual LPIccCurve* ExtractInputCurves(); + virtual LPIccCurve* ExtractOutputCurves(); + +protected: + + virtual bool HasPerceptualHandling() { return false; } + + icFloatNumber m_e[9]; + CIccCurve *m_Curve[3]; + CIccCurve *GetCurve(icSignature sig) const; + CIccCurve *GetInvCurve(icSignature sig) const; + + CIccTagXYZ *GetColumn(icSignature sig) const; + bool m_bFreeCurve; + /// used only when applying the xform + const LPIccCurve* m_ApplyCurvePtr; +}; + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the general 3D-LUT Xform + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccXform3DLut : public CIccXform +{ +public: + CIccXform3DLut(CIccTag *pTag); + virtual ~CIccXform3DLut(); + + virtual icXformType GetXformType() const { return icXformType3DLut; } + + virtual icStatusCMM Begin(); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual bool UseLegacyPCS() const { return m_pTag->UseLegacyPCS(); } + + virtual LPIccCurve* ExtractInputCurves(); + virtual LPIccCurve* ExtractOutputCurves(); +protected: + + const CIccMBB *m_pTag; + + /// Pointers to data in m_pTag, used only for applying the xform + const LPIccCurve* m_ApplyCurvePtrA; + const LPIccCurve* m_ApplyCurvePtrB; + const LPIccCurve* m_ApplyCurvePtrM; + const CIccMatrix* m_ApplyMatrixPtr; +}; + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the general 4D-LUT Xform + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccXform4DLut : public CIccXform +{ +public: + CIccXform4DLut(CIccTag *pTag); + virtual ~CIccXform4DLut(); + + virtual icXformType GetXformType() const { return icXformType4DLut; } + + virtual icStatusCMM Begin(); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual bool UseLegacyPCS() const { return m_pTag->UseLegacyPCS(); } + + virtual LPIccCurve* ExtractInputCurves(); + virtual LPIccCurve* ExtractOutputCurves(); +protected: + const CIccMBB *m_pTag; + + /// Pointers to data in m_pTag, used only for applying the xform + const LPIccCurve* m_ApplyCurvePtrA; + const LPIccCurve* m_ApplyCurvePtrB; + const LPIccCurve* m_ApplyCurvePtrM; + const CIccMatrix* m_ApplyMatrixPtr; +}; + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the general ND-LUT Xform + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccXformNDLut : public CIccXform +{ +public: + CIccXformNDLut(CIccTag *pTag); + virtual ~CIccXformNDLut(); + + virtual icXformType GetXformType() const { return icXformTypeNDLut; } + + virtual icStatusCMM Begin(); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual bool UseLegacyPCS() const { return m_pTag->UseLegacyPCS(); } + + virtual LPIccCurve* ExtractInputCurves(); + virtual LPIccCurve* ExtractOutputCurves(); +protected: + const CIccMBB *m_pTag; + int m_nNumInput; + + /// Pointers to data in m_pTag, used only for applying the xform + const LPIccCurve* m_ApplyCurvePtrA; + const LPIccCurve* m_ApplyCurvePtrB; + const LPIccCurve* m_ApplyCurvePtrM; + const CIccMatrix* m_ApplyMatrixPtr; +}; + + + +/** + ************************************************************************** + * Type: Enum + * + * Purpose: Defines the interface to be used when applying Named Color + * Profiles. + * + ************************************************************************** + */ +typedef enum { + icApplyPixel2Pixel = 0, + icApplyNamed2Pixel = 1, + icApplyPixel2Named = 2, + icApplyNamed2Named = 3, +} icApplyInterface; + + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: This is the general Xform for Named Color Profiles. + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccXformNamedColor : public CIccXform +{ +public: + CIccXformNamedColor(CIccTag *pTag, icColorSpaceSignature csPCS, icColorSpaceSignature csDevice); + virtual ~CIccXformNamedColor(); + + virtual icXformType GetXformType() const { return icXformTypeNamedColor; } + + virtual icStatusCMM Begin(); + + ///Returns the type of interface that will be applied + icApplyInterface GetInterface() const {return m_nApplyInterface;} + + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const {} + + icStatusCMM Apply(CIccApplyXform *pApplyXform, icChar *DstColorName, const icFloatNumber *SrcPixel) const; + icStatusCMM Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icChar *SrcColorName) const; + + virtual bool UseLegacyPCS() const { return m_pTag->UseLegacyPCS(); } + + icStatusCMM SetSrcSpace(icColorSpaceSignature nSrcSpace); + icStatusCMM SetDestSpace(icColorSpaceSignature nDestSpace); + + ///Returns the source color space of the transform + icColorSpaceSignature GetSrcSpace() const { return m_nSrcSpace; } + ///Returns the destination color space of the transform + icColorSpaceSignature GetDstSpace() const { return m_nDestSpace; } + + ///Checks if the source space of the transform is PCS + bool IsSrcPCS() const {return m_nSrcSpace == m_pTag->GetPCS();} + ///Checks if the destination space of the transform is PCS + bool IsDestPCS() const {return m_nDestSpace == m_pTag->GetPCS();} + + + virtual LPIccCurve* ExtractInputCurves() {return NULL;} + virtual LPIccCurve* ExtractOutputCurves() {return NULL;} + +protected: + + virtual bool HasPerceptualHandling() { return false; } + + CIccTagNamedColor2 *m_pTag; + icApplyInterface m_nApplyInterface; + icColorSpaceSignature m_nSrcSpace; + icColorSpaceSignature m_nDestSpace; +}; + + +/** +************************************************************************** +* Type: Class +* +* Purpose: This is the general Xform for Multi Processing Elements. +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccXformMpe : public CIccXform +{ +public: + CIccXformMpe(CIccTag *pTag); + virtual ~CIccXformMpe(); + + virtual icXformType GetXformType() const { return icXformTypeMpe; } + + ///Note: The returned CIccXform will own the profile. + static CIccXform *Create(CIccProfile *pProfile, bool bInput=true, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, CIccCreateXformHintManager *pHintManager=NULL); + + virtual icStatusCMM Begin(); + + virtual CIccApplyXform *GetNewApply(icStatusCMM &status); + virtual void Apply(CIccApplyXform *pApplyXform, icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) const; + + virtual bool UseLegacyPCS() const { return false; } + virtual LPIccCurve* ExtractInputCurves() {return NULL;} + virtual LPIccCurve* ExtractOutputCurves() {return NULL;} + + virtual bool NoClipPCS() const { return true; } + +protected: + CIccTagMultiProcessElement *m_pTag; + bool m_bUsingAcs; +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: The Apply general MPE Xform object (Allows xforms to have apply time data) +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyXformMpe : public CIccApplyXform +{ + friend class CIccXformMpe; +public: + virtual ~CIccApplyXformMpe(); + virtual icXformType GetXformType() const { return icXformTypeMpe; } + +protected: + CIccApplyXformMpe(CIccXformMpe *pXform); + + CIccApplyTagMpe *m_pApply; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Independant PCS class to do PCS based calculations. + * This is a class for managing PCS colorspace transformations. There + * are two important categories V2 <-> V4, and Lab <-> XYZ. + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccPCS +{ +public: + CIccPCS(); + virtual ~CIccPCS() {} + + void Reset(icColorSpaceSignature StartSpace, bool bUseLegacyPCS = false); + + virtual const icFloatNumber *Check(const icFloatNumber *SrcPixel, const CIccXform *pXform); + void CheckLast(icFloatNumber *SrcPixel, icColorSpaceSignature Space, bool bNoClip=false); + + static void LabToXyz(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip=false); + static void XyzToLab(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip=false); + static void Lab2ToXyz(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip=false); + static void XyzToLab2(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoClip=false); + static icFloatNumber NegClip(icFloatNumber v); + static icFloatNumber UnitClip(icFloatNumber v); + + static void Lab2ToLab4(icFloatNumber *Dst, const icFloatNumber *Src, bool bNoclip=false); + static void Lab4ToLab2(icFloatNumber *Dst, const icFloatNumber *Src); +protected: + + bool m_bIsV2Lab; + icColorSpaceSignature m_Space; + + icFloatNumber m_Convert[3]; +}; + +/** + ************************************************************************** + Color data passed to/from the CMM is encoded as floating point numbers ranging from 0.0 to 1.0 + Often data is encoded using other ranges. The icFloatColorEncoding enum is used by the + ToInternalEncoding() and FromInternalEncoding() functions to convert to/from the internal + encoding. The valid encoding transforms for the following color space signatures are given + below. + + 'CMYK', 'RGB ', 'GRAY', 'CMY ', 'Luv ', 'YCbr', 'Yxy ', 'HSV ', 'HLS ', 'gamt' + icEncodePercent: 0.0 <= value <= 100.0 + icEncodeFloat: 0.0 <= value <= 1.0 + icEncode8Bit: 0.0 <= value <= 255 + icEncode16Bit: 0.0 <= value <= 65535 + icEncode16BitV2: 0.0 <= value <= 65535 + + 'XCLR' + icEncodeValue: (if X>=3) 0.0 <= L <= 100.0; -128.0 <= a,b <= 127.0 others 0.0 <= value <= 1.0 + icEncodePercent: 0.0 <= value <= 100.0 + icEncodeFloat: 0.0 <= value <= 1.0 + icEncode8Bit: 0.0 <= value <= 255 + icEncode16Bit: 0.0 <= value <= 65535 + icEncode16BitV2: 0.0 <= value <= 65535 + + 'Lab ' + icEncodeValue: 0.0 <= L <= 100.0; -128.0 <= a,b <= 127.0 + icEncodeFloat: 0.0 <= L,a,b <= 1.0 - ICC PCS encoding (See ICC Specification) + icEncode8BIt: ICC 8 bit Lab Encoding - See ICC Specification + icEncode16Bit: ICC 16 bit V4 Lab Encoding - See ICC Specification + icEncode16BitV2: ICC 16 bit V2 Lab Encoding - See ICC Specification + + 'XYZ ' + icEncodeValue: 0.0 <= X,Y,Z < 1.999969482421875 + icEncodePercent: 0.0 <= X,Y,Z < 199.9969482421875 + icEncodeFloat: 0.0 <= L,a,b <= 1.0 - ICC PCS encoding (See ICC Specification + icEncode16Bit: ICC 16 bit XYZ Encoding - (icU1Fixed15) See ICC Specification + icEncode16BitV2: ICC 16 bit XYZ Encoding - (icU1Fixed15) See ICC Specification + ************************************************************************** +*/ + +typedef enum +{ + icEncodeValue=0, + icEncodePercent, + icEncodeFloat, + icEncode8Bit, + icEncode16Bit, + icEncode16BitV2, + icEncodeUnknown, +} icFloatColorEncoding; + +//Forward Reference of CIccCmm for CIccCmmApply +class CIccCmm; + +/** +************************************************************************** +* Type: Class +* +* Purpose: Defines a class that provides and interface for applying pixel +* transformations through a CMM. Multiply CIccCmmApply objects can use +* a single CIccCmm Object. +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyCmm +{ + friend class CIccCmm; +public: + virtual ~CIccApplyCmm(); + + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel); + + //Make sure that when DstPixel==SrcPixel the sizeof DstPixel is less than size of SrcPixel + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels); + + void AppendApplyXform(CIccApplyXform *pApplyXform); + + CIccCmm *GetCmm() { return m_pCmm; } + +protected: + CIccApplyCmm(CIccCmm *pCmm); + + CIccApplyXformList *m_Xforms; + CIccCmm *m_pCmm; + + CIccPCS *m_pPCS; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Defines a class that allows one or more profiles to be applied + * in order that they are Added. + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccCmm +{ + friend class CIccApplyCmm; +public: + CIccCmm(icColorSpaceSignature nSrcSpace=icSigUnknownData, + icColorSpaceSignature nDestSpace=icSigUnknownData, + bool bFirstInput=true); + virtual ~CIccCmm(); + + virtual CIccPCS *GetPCS() { return new CIccPCS(); } + + ///Must make at least one call to some form of AddXform() before calling Begin() + virtual icStatusCMM AddXform(const icChar *szProfilePath, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); + virtual icStatusCMM AddXform(icUInt8Number *pProfileMem, icUInt32Number nProfileLen, + icRenderingIntent nIntent=icUnknownIntent, icXformInterp nInterp=icInterpLinear, + icXformLutType nLutType=icXformLutColor, bool bUseMpeTags=true, + CIccCreateXformHintManager *pHintManager=NULL); + virtual icStatusCMM AddXform(CIccProfile *pProfile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); //Note: profile will be owned by the CMM + virtual icStatusCMM AddXform(CIccProfile &Profile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); //Note the profile will be copied + + //The Begin function should be called before Apply or GetNewApplyCmm() + virtual icStatusCMM Begin(bool bAllocNewApply=true); + + //Get an additional Apply cmm object to apply pixels with. The Apply object should be deleted by the caller. + virtual CIccApplyCmm *GetNewApplyCmm(icStatusCMM &status); + + virtual CIccApplyCmm *GetApply() { return m_pApply; } + + //The following apply functions should only be called if using Begin(true); + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel); + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels); + + //Call to Detach and remove all pending IO objects attached to the profiles used by the CMM. Should be called only after Begin() + virtual icStatusCMM RemoveAllIO(); + + ///Returns the number of profiles/transforms added + virtual icUInt32Number GetNumXforms() const; + + ///Returns the source color space + icColorSpaceSignature GetSourceSpace() const { return m_nSrcSpace; } + ///Returns the destination color space + icColorSpaceSignature GetDestSpace() const { return m_nDestSpace; } + ///Returns the color space of the last profile added + icColorSpaceSignature GetLastSpace() const { return m_nLastSpace; } + ///Returns the rendering intent of the last profile added + icRenderingIntent GetLastIntent() const { return m_nLastIntent; } + + ///Returns the number of samples in the source color space + icUInt16Number GetSourceSamples() const {return (icUInt16Number)icGetSpaceSamples(m_nSrcSpace);} + ///Returns the number of samples in the destination color space + icUInt16Number GetDestSamples() const {return (icUInt16Number)icGetSpaceSamples(m_nDestSpace);} + + ///Checks if this is a valid CMM object + bool Valid() const { return m_bValid; } + + //Function to convert check if Internal representation of 'gamt' color is in gamut. + static bool IsInGamut(icFloatNumber *pData); + + ///Functions for converting to Internal representation of pixel colors + static icStatusCMM ToInternalEncoding(icColorSpaceSignature nSpace, icFloatColorEncoding nEncode, + icFloatNumber *pInternal, const icFloatNumber *pData, bool bClip=true); + static icStatusCMM ToInternalEncoding(icColorSpaceSignature nSpace, icFloatNumber *pInternal, + const icUInt8Number *pData); + static icStatusCMM ToInternalEncoding(icColorSpaceSignature nSpace, icFloatNumber *pInternal, + const icUInt16Number *pData); + icStatusCMM ToInternalEncoding(icFloatNumber *pInternal, const icUInt8Number *pData) {return ToInternalEncoding(m_nSrcSpace, pInternal, pData);} + icStatusCMM ToInternalEncoding(icFloatNumber *pInternal, const icUInt16Number *pData) {return ToInternalEncoding(m_nSrcSpace, pInternal, pData);} + + + ///Functions for converting from Internal representation of pixel colors + static icStatusCMM FromInternalEncoding(icColorSpaceSignature nSpace, icFloatColorEncoding nEncode, + icFloatNumber *pData, const icFloatNumber *pInternal, bool bClip=true); + static icStatusCMM FromInternalEncoding(icColorSpaceSignature nSpace, icUInt8Number *pData, + const icFloatNumber *pInternal); + static icStatusCMM FromInternalEncoding(icColorSpaceSignature nSpace, icUInt16Number *pData, + const icFloatNumber *pInternal); + icStatusCMM FromInternalEncoding(icUInt8Number *pData, icFloatNumber *pInternal) {return FromInternalEncoding(m_nDestSpace, pData, pInternal);} + icStatusCMM FromInternalEncoding(icUInt16Number *pData, icFloatNumber *pInternal) {return FromInternalEncoding(m_nDestSpace, pData, pInternal);} + + static const icChar *GetFloatColorEncoding(icFloatColorEncoding val); + static icFloatColorEncoding GetFloatColorEncoding(const icChar* val); + + virtual icColorSpaceSignature GetFirstXformSource(); + virtual icColorSpaceSignature GetLastXformDest(); + +protected: + + CIccApplyCmm *m_pApply; + + bool m_bValid; + + bool m_bLastInput; + icColorSpaceSignature m_nSrcSpace; + icColorSpaceSignature m_nDestSpace; + + icColorSpaceSignature m_nLastSpace; + icRenderingIntent m_nLastIntent; + + CIccXformList *m_Xforms; +}; + +//Forward Class for CIccApplyNamedColorCmm +class CIccNamedColorCmm; +/** +************************************************************************** +* Type: Class +* +* Purpose: Defines a class that provides and interface for applying pixel +* transformations through a Named Color CMM. Multiply CIccApplyNamedColorCmm +* objects can refer to a single CIccNamedColorCmm Object. +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyNamedColorCmm : public CIccApplyCmm +{ + friend class CIccNamedColorCmm; +public: + virtual ~CIccApplyNamedColorCmm(); + + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel); + + //Make sure that when DstPixel==SrcPixel the sizeof DstPixel is less than size of SrcPixel + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels); + + ///Define 4 apply interfaces that are used depending upon the source and destination xforms + virtual icStatusCMM Apply(icChar* DstColorName, const icFloatNumber *SrcPixel); + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icChar *SrcColorName); + virtual icStatusCMM Apply(icChar* DstColorName, const icChar *SrcColorName); + +protected: + CIccApplyNamedColorCmm(CIccNamedColorCmm *pCmm); +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: A Slower Named Color Profile compatible CMM + * + ************************************************************************** + */ +class ICCPROFLIB_API CIccNamedColorCmm : public CIccCmm +{ + friend class CIccApplyNamedColorCmm; +public: + ///nSrcSpace cannot be icSigUnknownData if first profile is named color + CIccNamedColorCmm(icColorSpaceSignature nSrcSpace=icSigUnknownData, + icColorSpaceSignature nDestSpace=icSigUnknownData, + bool bFirstInput=true); + virtual ~CIccNamedColorCmm(); + + ///Must make at least one call to some form of AddXform() before calling Begin() + virtual icStatusCMM AddXform(const icChar *szProfilePath, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); + virtual icStatusCMM AddXform(CIccProfile *pProfile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool buseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL); //Note: profile will be owned by the CMM + + ///Must be called before calling Apply() or GetNewApply() + //The Begin function should be called before Apply or GetNewApplyCmm() + virtual icStatusCMM Begin(bool bAllocNewApply=true); + + virtual CIccApplyCmm *GetNewApply(icStatusCMM &status); + + + //The following apply functions should only be called if using Begin(true); + icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel) { return CIccCmm::Apply(DstPixel, SrcPixel); } + icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels) { return CIccCmm::Apply(DstPixel, SrcPixel, nPixels); } + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icChar *SrcColorName); + virtual icStatusCMM Apply(icChar* DstColorName, const icFloatNumber *SrcPixel); + virtual icStatusCMM Apply(icChar* DstColorName, const icChar *SrcColorName); + + ///Returns the type of interface that will be applied + icApplyInterface GetInterface() const {return m_nApplyInterface;} + + icStatusCMM SetLastXformDest(icColorSpaceSignature nDestSpace); + +protected: + icApplyInterface m_nApplyInterface; +}; + + +class ICCPROFLIB_API CIccMruPixel +{ +public: + CIccMruPixel() { pPixelData = NULL; pNext = NULL; } + + icFloatNumber *pPixelData; + CIccMruPixel *pNext; +}; + +//Forward Class for CIccApplyNamedColorCmm +class CIccMruCmm; +/** +************************************************************************** +* Type: Class +* +* Purpose: Defines a class that provides and interface for applying pixel +* transformations through a CMM. Multiply CIccCmmApply objects can use +* a single CIccCmm Object. +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccApplyMruCmm : public CIccApplyCmm +{ + friend class CIccMruCmm; +public: + virtual ~CIccApplyMruCmm(); + + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel); + + //Make sure that when DstPixel==SrcPixel the sizeof DstPixel is greater than size of SrcPixel + virtual icStatusCMM Apply(icFloatNumber *DstPixel, const icFloatNumber *SrcPixel, icUInt32Number nPixels); + +protected: + CIccApplyMruCmm(CIccMruCmm *pCmm); + + bool Init(CIccCmm *pCachedCmm, icUInt16Number nCacheSize); + + CIccCmm *m_pCachedCmm; + + icUInt16Number m_nCacheSize; + + icFloatNumber *m_pixelData; + + CIccMruPixel *m_pFirst; + CIccMruPixel *m_cache; + + icUInt16Number m_nNumPixel; + + icUInt32Number m_nTotalSamples; + icUInt32Number m_nSrcSamples; + + icUInt32Number m_nSrcSize; + icUInt32Number m_nDstSize; + +}; + +/** +************************************************************************** +* Type: Class +* +* Purpose: A CMM decorator class that provides limited caching of results +* +************************************************************************** +*/ +class ICCPROFLIB_API CIccMruCmm : public CIccCmm +{ + friend class CIccApplyMruCmm; +private: + CIccMruCmm(); +public: + virtual ~CIccMruCmm(); + + //This is the function used to create a new CIccMruCmm. The pCmm must be valid and its Begin() already called. + static CIccMruCmm* Attach(CIccCmm *pCmm, icUInt8Number nCacheSize=6); //The returned object will own pCmm, and pCmm is deleted on failure. + + //override AddXform/Begin functions to return bad status. + virtual icStatusCMM AddXform(const icChar *szProfilePath, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL) { return icCmmStatBad; } + virtual icStatusCMM AddXform(icUInt8Number *pProfileMem, icUInt32Number nProfileLen, + icRenderingIntent nIntent=icUnknownIntent, icXformInterp nInterp=icInterpLinear, + icXformLutType nLutType=icXformLutColor, bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL) { return icCmmStatBad; } + virtual icStatusCMM AddXform(CIccProfile *pProfile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL) { return icCmmStatBad; } + virtual icStatusCMM AddXform(CIccProfile &Profile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, icXformLutType nLutType=icXformLutColor, + bool bUseMpeTags=true, CIccCreateXformHintManager *pHintManager=NULL) { return icCmmStatBad; } + + virtual CIccApplyCmm *GetNewApplyCmm(icStatusCMM &status); + + //Forward calls to attached CMM + virtual icStatusCMM RemoveAllIO() { return m_pCmm->RemoveAllIO(); } + virtual CIccPCS *GetPCS() { return m_pCmm->GetPCS(); } + virtual icUInt32Number GetNumXforms() const { return m_pCmm->GetNumXforms(); } + + virtual icColorSpaceSignature GetFirstXformSource() { return m_pCmm->GetFirstXformSource(); } + virtual icColorSpaceSignature GetLastXformDest() { return m_pCmm->GetLastXformDest(); } + +protected: + CIccCmm *m_pCmm; + icUInt16Number m_nCacheSize; + +}; + +#ifdef USESAMPLEICCNAMESPACE +}; //namespace sampleICC +#endif + +#endif // !defined(_ICCCMM_H) diff --git a/library/src/main/cpp/icc/IccConvertUTF.cpp b/library/src/main/cpp/icc/IccConvertUTF.cpp new file mode 100644 index 00000000..f796dafa --- /dev/null +++ b/library/src/main/cpp/icc/IccConvertUTF.cpp @@ -0,0 +1,541 @@ +/* +* Copyright 2001-2004 Unicode, Inc. +* +* Disclaimer +* +* This source code is provided as is by Unicode, Inc. No claims are +* made as to fitness for any particular purpose. No warranties of any +* kind are expressed or implied. The recipient agrees to determine +* applicability of information provided. If this file has been +* purchased on magnetic or optical media from Unicode, Inc., the +* sole remedy for any claim will be exchange of defective media +* within 90 days of receipt. +* +* Limitations on Rights to Redistribute This Code +* +* Unicode, Inc. hereby grants the right to freely use the information +* supplied in this file in the creation of products supporting the +* Unicode Standard, and to make copies of this file in any form +* for internal or external distribution as long as this notice +* remains attached. +*/ + +/* --------------------------------------------------------------------- + +Conversions between UTF32, UTF-16, and UTF-8. Source code file. +Author: Mark E. Davis, 1994. +Rev History: Rick McGowan, fixes & updates May 2001. +Sept 2001: fixed const & error conditions per +mods suggested by S. Parent & A. Lillich. +June 2002: Tim Dodd added detection and handling of incomplete +source sequences, enhanced error detection, added casts +to eliminate compiler warnings. +July 2003: slight mods to back out aggressive FFFE detection. +Jan 2004: updated switches in from-UTF8 conversions. +Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + +See the header file "icConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + + +#include "IccConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF +#define false 0 +#define true 1 + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF32toUTF16 (const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF16toUTF32 (const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG + if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); + } +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* +* Index into the table below with the first byte of a UTF-8 sequence to +* get the number of trailing bytes that are supposed to follow it. +* Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is +* left as-is for anyone who may want to do such conversion, which was +* allowed in earlier algorithms. +*/ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* +* Magic values subtracted from a buffer value during UTF8 conversion. +* This table contains as many values as there might be trailing bytes +* in a UTF-8 sequence. +*/ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, +0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* +* Once the bits are split out into bytes of UTF-8, this is a mask OR-ed +* into the first byte, depending on how many bytes follow. There are +* as many entries in this table as there are UTF-8 sequence types. +* (I.e., one byte sequence, two byte... etc.). Remember that sequencs +* for *legal* UTF-8 will be 4 or fewer bytes total. +*/ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. +* Constants have been gathered. Loops & conditionals have been removed as +* much as possible for efficiency, in favor of drop-through switches. +* (See "Note A" at the bottom of the file for equivalent code.) +* If your compiler supports it, the "isLegalUTF8" call can be turned +* into an inline function. +*/ + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF16toUTF8 (const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* +* Utility routine to tell whether a sequence of bytes is legal UTF-8. +* This must be called with the length pre-determined by the first byte. +* If not calling this from ConvertUTF8to*, then the length can be set by: +* length = trailingBytesForUTF8[*source]+1; +* and the sequence is illegal right away if there aren't that many bytes +* available. +* If presented with a length > 4, this returns false. The Unicode +* definition of UTF-8 goes up to 4-byte sequences. +*/ + +static Boolean isLegalUTF8(const UTF8 *source, int length) +{ + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* +* Exported function to return whether a UTF-8 sequence is legal or not. +* This is not used here; it's just exported. +*/ +Boolean icIsLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) +{ + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF8toUTF16 (const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF32toUTF8 (const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +icUtfConversionResult icConvertUTF8toUTF32 (const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, icUtfConversionFlags flags) +{ + icUtfConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + +Note A. +The fall-through switches in UTF-8 reading code save a +temp variable, some decrements & conditionals. The switches +are equivalent to the following loop: +{ +int tmpBytesToRead = extraBytesToRead+1; +do { +ch += *source++; +--tmpBytesToRead; +if (tmpBytesToRead) ch <<= 6; +} while (tmpBytesToRead > 0); +} +In UTF-8 writing code, the switches on "bytesToWrite" are +similarly unrolled loops. + +--------------------------------------------------------------------- */ diff --git a/library/src/main/cpp/icc/IccConvertUTF.h b/library/src/main/cpp/icc/IccConvertUTF.h new file mode 100644 index 00000000..fcb224b2 --- /dev/null +++ b/library/src/main/cpp/icc/IccConvertUTF.h @@ -0,0 +1,158 @@ +/* +* Copyright 2001-2004 Unicode, Inc. +* +* Disclaimer +* +* This source code is provided as is by Unicode, Inc. No claims are +* made as to fitness for any particular purpose. No warranties of any +* kind are expressed or implied. The recipient agrees to determine +* applicability of information provided. If this file has been +* purchased on magnetic or optical media from Unicode, Inc., the +* sole remedy for any claim will be exchange of defective media +* within 90 days of receipt. +* +* Limitations on Rights to Redistribute This Code +* +* Unicode, Inc. hereby grants the right to freely use the information +* supplied in this file in the creation of products supporting the +* Unicode Standard, and to make copies of this file in any form +* for internal or external distribution as long as this notice +* remains attached. +*/ + +/* --------------------------------------------------------------------- + +Conversions between UTF32, UTF-16, and UTF-8. Header file. + +Several funtions are included here, forming a complete set of +conversions between the three formats. UTF-7 is not included +here, but is handled in a separate source file. + +Each of these routines takes pointers to input buffers and output +buffers. The input buffers are const. + +Each routine converts the text between *sourceStart and sourceEnd, +putting the result into the buffer between *targetStart and +targetEnd. Note: the end pointers are *after* the last item: e.g. +*(sourceEnd - 1) is the last item. + +The return result indicates whether the conversion was successful, +and if not, whether the problem was in the source or target buffers. +(Only the first encountered problem is indicated.) + +After the conversion, *sourceStart and *targetStart are both +updated to point to the end of last text successfully converted in +the respective buffers. + +Input parameters: +sourceStart - pointer to a pointer to the source buffer. +The contents of this are modified on return so that +it points at the next thing to be converted. +targetStart - similarly, pointer to pointer to the target buffer. +sourceEnd, targetEnd - respectively pointers to the ends of the +two buffers, for overflow checking only. + +These conversion functions take an icUtfConversionFlags argument. When this +flag is set to strict, both irregular sequences and isolated surrogates +will cause an error. When the flag is set to lenient, both irregular +sequences and isolated surrogates are converted. + +Whether the flag is strict or lenient, all illegal sequences will cause +an error return. This includes sequences such as: , , +or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code +must check for illegal sequences. + +When the flag is set to lenient, characters over 0x10FFFF are converted +to the replacement character; otherwise (when the flag is set to strict) +they constitute an error. + +Output parameters: +The value "sourceIllegal" is returned from some routines if the input +sequence is malformed. When "sourceIllegal" is returned, the source +value will point to the illegal value that caused the problem. E.g., +in UTF-8 when a sequence is malformed, it points to the start of the +malformed sequence. + +Author: Mark E. Davis, 1994. +Rev History: Rick McGowan, fixes & updates May 2001. +Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- +July 2009 +- Modified names to avoid possible conflicts - Max Derhak +- Added IccProfLibConf.h include to use ICCPROFLIB_API with functions +- Changed typedef of UTF32 to use ICCUINT32 +------------------------------------------------------------------------ */ + +#include "IccProfLibConf.h" + +/* --------------------------------------------------------------------- +The following 4 definitions are compiler-specific. +The C standard does not guarantee that wchar_t has at least +16 bits, so wchar_t is no less portable than unsigned short! +All should be unsigned values to avoid sign extension during +bit mask & shift operations. +------------------------------------------------------------------------ */ + +typedef ICCUINT32 UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Boolean; /* 0 or 1 */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} icUtfConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} icUtfConversionFlags; + +/* This is for C++ and does no harm in C */ +#ifdef __cplusplus +extern "C" { +#endif + +icUtfConversionResult ICCPROFLIB_API icConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, icUtfConversionFlags flags); + +icUtfConversionResult ICCPROFLIB_API icConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, icUtfConversionFlags flags); + +icUtfConversionResult ICCPROFLIB_API icConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, icUtfConversionFlags flags); + +icUtfConversionResult ICCPROFLIB_API icConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, icUtfConversionFlags flags); + +icUtfConversionResult ICCPROFLIB_API icConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, icUtfConversionFlags flags); + +icUtfConversionResult ICCPROFLIB_API icConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, icUtfConversionFlags flags); + +Boolean ICCPROFLIB_API icIsLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------- */ diff --git a/library/src/main/cpp/icc/IccDefs.h b/library/src/main/cpp/icc/IccDefs.h new file mode 100644 index 00000000..8212906f --- /dev/null +++ b/library/src/main/cpp/icc/IccDefs.h @@ -0,0 +1,136 @@ +/** @file + File: IccDefs.h + + Contains: Access ICC profile definitions and structures including + Version 4 extensions + + Copyright: see ICC Software License + */ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +#include + +/* Header file guard bands */ +#ifndef _ICCDEFS_H +#define _ICCDEFS_H + +#pragma pack(4) + +//Get any platform specific prototypes +#include "IccProfLibConf.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +//Include the standard icProfileHeader definition file +#include "icProfileHeader.h" + +// Define signature for SampleICC's use +#define icSigSampleICC ((icSignature)0x53494343) /* 'SICC' */ + +//Definitions used for conversion of fixed floating point numbers +typedef icUInt16Number icU1Fixed15Number; +typedef icUInt16Number icU8Fixed8Number; + + +/** +* Additional convenience color space signatures to distinguish between device +* encoding and PCS encoding. +* +* Device encoding of these color spaces is left to the device to define. +*/ +#define icSigDevLabData ((icColorSpaceSignature) 0x644C6162) /* 'dLab' */ +#define icSigDevXYZData ((icColorSpaceSignature) 0x6458595A) /* 'dXYZ' */ + + +/** +* All floating point operations/variables in IccProfLib use the icFloatNumber data type. +* It was found that using float instead of double increased performance. Changing +* the definition to double will add greater precision at the cost of performance. +*/ +typedef float icFloatNumber; + +/** String formating macros need to match precision of icFloatNumber +*If precision is double change the "f" below to "lf" +*/ +#define ICFLOATSFX "f" +#define ICFLOATFMT "%f" + +/* For string operations */ +typedef char icChar; +#if defined(WIN32) || defined(WIN64) +typedef wchar_t icWChar; +#endif + +/* Validation Status values */ +typedef enum { + icValidateOK, /*Profile is valid and conforms to specification*/ + icValidateWarning, /*Profile conforms to specification with concerns*/ + icValidateNonCompliant, /*Profile does not conform to specification, but may still be useable*/ + icValidateCriticalError, /*Profile does not conform to specification and is not useable*/ +} icValidateStatus; + + +#pragma pack() + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif /* _ICCDEFS_H */ + + + diff --git a/library/src/main/cpp/icc/IccDemo.cpp b/library/src/main/cpp/icc/IccDemo.cpp new file mode 100644 index 00000000..d73b5152 --- /dev/null +++ b/library/src/main/cpp/icc/IccDemo.cpp @@ -0,0 +1,91 @@ +// +// Created by 钟元杰 on 2022/9/19. +// + +//#include "include/IccDemo.h" +#include "IccCmm.h" +#include "IccProfile.h" +#include + +CIccCmm cmm; +icFloatNumber Pixels[16]; + +icUInt8Number* ConvertJByteaArrayToChars(JNIEnv *env, jbyteArray bytearray) +{ + icUInt8Number *chars = NULL; + jbyte *bytes; + bytes = env->GetByteArrayElements(bytearray, 0); + int chars_len = env->GetArrayLength(bytearray); + chars = new icUInt8Number[chars_len + 1]; + memset(chars,0,chars_len + 1); + memcpy(chars, bytes, chars_len); + chars[chars_len] = 0; + + env->ReleaseByteArrayElements(bytearray, bytes, 0); + return chars; +} + +extern "C" +JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfile(JNIEnv *env, jobject thiz, jstring path) { + cmm.RemoveAllIO(); + if (cmm.GetNumXforms()!=0) { + return 1; + } + const char *nativeString = env->GetStringUTFChars(path, 0); + if (cmm.AddXform(nativeString, (icRenderingIntent)0)) { +// printf("Invalid Profile: %s\n", szSrcProfile); + return -1; + } + if (cmm.Begin() != icCmmStatOk) { + return false; + } + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfileByData(JNIEnv *env, jobject thiz, jbyteArray data) { + cmm.RemoveAllIO(); + icUInt8Number *pmsg = ConvertJByteaArrayToChars(env,data); + int chars_len = env->GetArrayLength(data); + CIccProfile* cIccProfile = OpenIccProfile(pmsg, chars_len); + if (cmm.AddXform(cIccProfile, (icRenderingIntent)0)) { + return -1; + } + if (cmm.Begin() != icCmmStatOk) { + return false; + } + return cmm.GetSourceSpace(); +} + +extern "C" +JNIEXPORT jfloat JNICALL Java_com_xsooy_icc_IccUtils_apply(JNIEnv *env, jobject thiz, jfloat pixel) { + Pixels[0] = (float) pixel; + cmm.Apply(Pixels, Pixels); + return Pixels[0]; +} + +extern "C" +JNIEXPORT void JNICALL Java_com_xsooy_icc_IccUtils_applyGray(JNIEnv *env, jobject thiz, jfloatArray array,jfloatArray outArray) { + jboolean isCopy = JNI_FALSE; + jfloat *parray = env->GetFloatArrayElements(array, &isCopy); + Pixels[0] = float (parray[0]); + + cmm.Apply(Pixels, Pixels); + + env->SetFloatArrayRegion(outArray,0,3,Pixels); +} + +extern "C" +JNIEXPORT void JNICALL Java_com_xsooy_icc_IccUtils_applyCmyk(JNIEnv *env, jobject thiz, jfloatArray array,jfloatArray outArray) { + jboolean isCopy = JNI_FALSE; + jfloat *parray = env->GetFloatArrayElements(array, &isCopy); + Pixels[0] = float (parray[0]); + Pixels[1] = float (parray[1]); + Pixels[2] = float (parray[2]); + Pixels[3] = float (parray[3]); + + //change data to 'lab' + cmm.Apply(Pixels, Pixels); + env->SetFloatArrayRegion(outArray,0,3,Pixels); +} + diff --git a/library/src/main/cpp/icc/IccEval.cpp b/library/src/main/cpp/icc/IccEval.cpp new file mode 100644 index 00000000..cf8b7afc --- /dev/null +++ b/library/src/main/cpp/icc/IccEval.cpp @@ -0,0 +1,208 @@ +/** @file +File: IccEval.cpp + +Contains: Implementation of the CIccProfile Evaluation utilites. + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2003-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#include +#include "IccEval.h" +#include "IccTag.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +static const icFloatNumber SMALLNUM = (icFloatNumber)0.0001; +static const icFloatNumber LESSTHANONE = (icFloatNumber)(1.0 - SMALLNUM); + +icStatusCMM CIccEvalCompare::EvaluateProfile(CIccProfile *pProfile, icUInt8Number nGran/* =0 */, + icRenderingIntent nIntent/* =icUnknownIntent */, icXformInterp nInterp/* =icInterpLinear */, + bool buseMpeTags/* =true */) +{ + if (!pProfile) + { + return icCmmStatCantOpenProfile; + } + + if (pProfile->m_Header.deviceClass!=icSigInputClass && + pProfile->m_Header.deviceClass!=icSigDisplayClass && + pProfile->m_Header.deviceClass!=icSigOutputClass && + pProfile->m_Header.deviceClass!=icSigColorSpaceClass) + { + return icCmmStatInvalidProfile; + } + + CIccCmm dev2Lab(icSigUnknownData, icSigLabData); + CIccCmm Lab2Dev2Lab(icSigLabData, icSigLabData, false); + + icStatusCMM result; + + result = dev2Lab.AddXform(*pProfile, nIntent, nInterp, icXformLutColor, buseMpeTags); + + if (result!=icCmmStatOk) { + return result; + } + + result = dev2Lab.Begin(); + if (result != icCmmStatOk) { + return result; + } + + result = Lab2Dev2Lab.AddXform(*pProfile, nIntent, nInterp, icXformLutColor, buseMpeTags); + if (result != icCmmStatOk) { + return result; + } + + result = Lab2Dev2Lab.AddXform(*pProfile, nIntent, nInterp, icXformLutColor, buseMpeTags); + if (result != icCmmStatOk) { + return result; + } + + result = Lab2Dev2Lab.Begin(); + if (result != icCmmStatOk) { + return result; + } + + icFloatNumber sPixel[15]; + icFloatNumber devPcs[15], roundPcs1[15], roundPcs2[15]; + + int ndim = icGetSpaceSamples(pProfile->m_Header.colorSpace); + int ndim1 = ndim+1; + + // determine granularity + if (!nGran) + { + CIccTagLutAtoB* pTag = (CIccTagLutAtoB*)pProfile->FindTag(icSigAToB0Tag+(nIntent==icAbsoluteColorimetric ? icRelativeColorimetric : nIntent)); + if (!pTag || ndim==3) + { + nGran = 33; + } + else { + CIccCLUT* pClut = pTag->GetCLUT(); + if (pClut) + nGran = pClut->GridPoints()+2; + else + nGran = 33; + } + } + + int i, j; + icFloatNumber stepsize = (icFloatNumber)(1.0/(icFloatNumber)(nGran-1)); + icFloatNumber* steps = new icFloatNumber[ndim1]; + icFloatNumber nstart = 0.0; + icFloatNumber nEnd = (icFloatNumber)(1.0+stepsize/2.0); + for(j=0; j=0; i--) { + if(steps[i]>nEnd) { + steps[i] = nstart; + steps[i-1] = (steps[i-1]+stepsize); + } + else break; + } + + dev2Lab.Apply(devPcs, sPixel); //Convert device value to pcs from input table + Lab2Dev2Lab.Apply(roundPcs1, devPcs); //First round trip gets color into output gamut + Lab2Dev2Lab.Apply(roundPcs2, roundPcs1); //Second round trip find reproducibility error + + icLabFromPcs(devPcs); + icLabFromPcs(roundPcs1); + icLabFromPcs(roundPcs2); + + Compare(sPixel, devPcs, roundPcs1, roundPcs2); + } + + delete [] steps; + + return icCmmStatOk; +} + +icStatusCMM CIccEvalCompare::EvaluateProfile(const icChar *szProfilePath, icUInt8Number nGrid/* =0 */, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, bool buseMpeTags/* =true */) +{ + CIccProfile *pProfile = ReadIccProfile(szProfilePath); + + if (!pProfile) + return icCmmStatCantOpenProfile; + + icStatusCMM result = EvaluateProfile(pProfile, nGrid, nIntent, nInterp, buseMpeTags); + + delete pProfile; + + return result; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccEval.h b/library/src/main/cpp/icc/IccEval.h new file mode 100644 index 00000000..722508ce --- /dev/null +++ b/library/src/main/cpp/icc/IccEval.h @@ -0,0 +1,99 @@ +/** @file +File: IccEval.h + +Contains: Header file for implementation of the CIccProfile Evaluation utilites. + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2003-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 9-25-2007 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCEVAL_H) +#define _ICCEVAL_H + +#include "IccCmm.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + + +class CIccEvalCompare { +public: + //Create prototype for Compare function that must be implemented by a derived class + virtual void Compare(icFloatNumber *pPixel, icFloatNumber *deviceLab, icFloatNumber *destLab1, icFloatNumber *destLab2)=0; + + icStatusCMM ICCPROFLIB_API EvaluateProfile(CIccProfile *pProfile, icUInt8Number nGran=0, + icRenderingIntent nIntent=icUnknownIntent, icXformInterp nInterp=icInterpLinear, + bool buseMpeTags=true); + + icStatusCMM ICCPROFLIB_API EvaluateProfile(const icChar *szProfilePath, icUInt8Number nGran=0, + icRenderingIntent nIntent=icUnknownIntent, icXformInterp nInterp=icInterpLinear, + bool buseMpeTags=true); +}; + +#ifdef USESAMPLEICCNAMESPACE +}; //namespace sampleICC +#endif + +#endif // !defined(_ICCCMM_H) diff --git a/library/src/main/cpp/icc/IccIO.cpp b/library/src/main/cpp/icc/IccIO.cpp new file mode 100644 index 00000000..1efa9111 --- /dev/null +++ b/library/src/main/cpp/icc/IccIO.cpp @@ -0,0 +1,712 @@ +/** @file + File: IccIO.cpp + + Contains: Implementation of the CIccIO class. + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#include "IccIO.h" +#include "IccUtil.h" +#include +#include +#include + +#ifndef __max +#define __max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef __min +#define __min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +////////////////////////////////////////////////////////////////////// +// Class CIccIO +////////////////////////////////////////////////////////////////////// + + +icInt32Number CIccIO::ReadLine(void *pBuf8, icInt32Number nNum/*=256*/) +{ + icInt32Number n=0; + icInt8Number c, *ptr=(icInt8Number*)pBuf8; + + while(n>1; + icSwab16Array(pBuf16, nNum); + + return nNum; +} + +icInt32Number CIccIO::Write16(void *pBuf16, icInt32Number nNum) +{ +#ifndef ICC_BYTE_ORDER_LITTLE_ENDIAN + return Write8(pBuf16, nNum<<1)>>1; +#else + icUInt16Number *ptr = (icUInt16Number*)pBuf16; + icUInt16Number tmp; + icInt32Number i; + + for (i=0; i>2; + icSwab32Array(pBuf32, nNum); + + return nNum; +} + + +icInt32Number CIccIO::Write32(void *pBuf32, icInt32Number nNum) +{ +#ifndef ICC_BYTE_ORDER_LITTLE_ENDIAN + return Write8(pBuf32, nNum<<2)>>2; +#else + icUInt32Number *ptr = (icUInt32Number*)pBuf32; + icUInt32Number tmp; + icInt32Number i; + + for (i=0; i>3; + icSwab64Array(pBuf64, nNum); + + return nNum; +} + + +icInt32Number CIccIO::Write64(void *pBuf64, icInt32Number nNum) +{ +#ifndef ICC_BYTE_ORDER_LITTLE_ENDIAN + return Write8(pBuf64, nNum<<3)>>3; +#else + icUInt64Number *ptr = (icUInt64Number*)pBuf64; + icUInt64Number tmp; + icInt32Number i; + + for (i=0; i>2)<<2; + if (Seek(nPos + nOffset, icSeekSet)<0) + return false; + return true; +} + + +////////////////////////////////////////////////////////////////////// +// Class CIccFileIO +////////////////////////////////////////////////////////////////////// + +CIccFileIO::CIccFileIO() : CIccIO() +{ + m_fFile = NULL; +} + +CIccFileIO::~CIccFileIO() +{ + Close(); +} + +bool CIccFileIO::Open(const icChar *szFilename, const icChar *szAttr) +{ +#if defined(WIN32) || defined(WIN64) + char myAttr[20]; + + if (!strchr(szAttr, 'b')) { + myAttr[0] = szAttr[0]; + myAttr[1] = 'b'; + strcpy(myAttr+2, szAttr+1); + szAttr = myAttr; + } +#endif + + if (m_fFile) + fclose(m_fFile); + + m_fFile = fopen(szFilename, szAttr); + + return m_fFile != NULL; +} + + +#if defined(WIN32) || defined(WIN64) +bool CIccFileIO::Open(const icWChar *szFilename, const icWChar *szAttr) +{ + icWChar myAttr[20]; + + if (!wcschr(szAttr, 'b')) { + myAttr[0] = szAttr[0]; + myAttr[1] = 'b'; + wcscpy(myAttr+2, szAttr+1); + szAttr = myAttr; + } + + if (m_fFile) + fclose(m_fFile); + + m_fFile = _wfopen(szFilename, szAttr); + + return m_fFile != NULL; +} +#endif + + +void CIccFileIO::Close() +{ + if (m_fFile) { + fclose(m_fFile); + m_fFile = NULL; + } +} + + +icInt32Number CIccFileIO::Read8(void *pBuf, icInt32Number nNum) +{ + if (!m_fFile) + return 0; + + return (icInt32Number)fread(pBuf, 1, nNum, m_fFile); +} + + +icInt32Number CIccFileIO::Write8(void *pBuf, icInt32Number nNum) +{ + if (!m_fFile) + return 0; + + return (icInt32Number)fwrite(pBuf, 1, nNum, m_fFile); +} + + +icInt32Number CIccFileIO::GetLength() +{ + if (!m_fFile) + return 0; + + fflush(m_fFile); + icInt32Number current = ftell(m_fFile), end; + fseek (m_fFile, 0, SEEK_END); + end = ftell(m_fFile); + fseek (m_fFile, current, SEEK_SET); + return end; +} + + +icInt32Number CIccFileIO::Seek(icInt32Number nOffset, icSeekVal pos) +{ + if (!m_fFile) + return -1; + + return !fseek(m_fFile, nOffset, pos) ? ftell(m_fFile) : -1; +} + + +icInt32Number CIccFileIO::Tell() +{ + if (!m_fFile) + return -1; + + return ftell(m_fFile); +} + + +////////////////////////////////////////////////////////////////////// +// Class CIccMemIO +////////////////////////////////////////////////////////////////////// + +CIccMemIO::CIccMemIO() : CIccIO() +{ + m_pData = NULL; + m_nSize = 0; + m_nAvail = 0; + m_nPos = 0; + + m_bFreeData = false; +} + +CIccMemIO::~CIccMemIO() +{ + Close(); +} + + +bool CIccMemIO::Alloc(icUInt32Number nSize, bool bWrite) +{ + if (m_pData) + Close(); + + icUInt8Number *pData = (icUInt8Number*)malloc(nSize); + + if (!pData) + return false; + + if (!Attach(pData, nSize, bWrite)) { + free(pData); + return false; + } + + m_bFreeData = true; + + return true; +} + + +bool CIccMemIO::Attach(icUInt8Number *pData, icUInt32Number nSize, bool bWrite) +{ + if (!pData) + return false; + + if (m_pData) + Close(); + + m_pData = pData; + m_nPos = 0; + + if (bWrite) { + m_nAvail = nSize; + m_nSize = 0; + } + else { + m_nAvail = m_nSize = nSize; + } + + return true; +} + + +void CIccMemIO::Close() +{ + if (m_pData) { + if (m_bFreeData) { + free(m_pData); + + m_bFreeData = false; + } + m_pData = NULL; + } +} + + +icInt32Number CIccMemIO::Read8(void *pBuf, icInt32Number nNum) +{ + if (!m_pData) + return 0; + + nNum = __min((icInt32Number)(m_nSize-m_nPos), nNum); + + memcpy(pBuf, m_pData+m_nPos, nNum); + m_nPos += nNum; + + return nNum; +} + + +icInt32Number CIccMemIO::Write8(void *pBuf, icInt32Number nNum) +{ + if (!m_pData) + return 0; + + nNum = __min((icInt32Number)(m_nAvail-m_nPos), nNum); + + memcpy(m_pData + m_nPos, pBuf, nNum); + + m_nPos += nNum; + if (m_nPos > m_nSize) + m_nSize = m_nPos; + + return nNum; +} + + +icInt32Number CIccMemIO::GetLength() +{ + if (!m_pData) + return 0; + + return m_nSize; +} + + +icInt32Number CIccMemIO::Seek(icInt32Number nOffset, icSeekVal pos) +{ + if (!m_pData) + return -1; + + icInt32Number nPos; + switch(pos) { + case icSeekSet: + nPos = nOffset; + break; + case icSeekCur: + nPos = (icInt32Number)m_nPos + nOffset; + break; + case icSeekEnd: + nPos = (icInt32Number)m_nSize + nOffset; + break; + default: + nPos = 0; + break; + } + + if (nPos < 0) + return -1; + + icUInt32Number uPos = (icUInt32Number)nPos; + + if (uPos > m_nSize && m_nSize != m_nAvail && uPos <=m_nAvail) { + memset(m_pData+m_nSize, 0, (icInt32Number)(uPos - m_nSize)); + m_nSize = uPos; + } + if (uPos > m_nSize) + return -1; + + m_nPos = uPos; + + return nPos; +} + + +icInt32Number CIccMemIO::Tell() +{ + if (!m_pData) + return -1; + + return (icInt32Number)m_nPos; +} + +/////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// Class CIccNullIO +////////////////////////////////////////////////////////////////////// + +CIccNullIO::CIccNullIO() : CIccIO() +{ + m_nSize = 0; + m_nPos = 0; +} + +CIccNullIO::~CIccNullIO() +{ + Close(); +} + + +void CIccNullIO::Open() +{ + m_nPos = 0; + m_nSize = 0; +} + + +void CIccNullIO::Close() +{ +} + + +icInt32Number CIccNullIO::Read8(void *pBuf, icInt32Number nNum) +{ + icInt32Number nLeft = m_nSize - m_nPos; + icInt32Number nRead = (nNum <= (icInt32Number)nLeft) ? nNum : nLeft; + + memset(pBuf, 0, nRead); + m_nPos += nRead; + + return nRead; +} + + +icInt32Number CIccNullIO::Write8(void *pBuf, icInt32Number nNum) +{ + m_nPos += nNum; + if (m_nPos > m_nSize) + m_nSize = m_nPos; + + return nNum; +} + + +icInt32Number CIccNullIO::GetLength() +{ + return m_nSize; +} + + +icInt32Number CIccNullIO::Seek(icInt32Number nOffset, icSeekVal pos) +{ + icInt32Number nPos; + switch(pos) { + case icSeekSet: + nPos = nOffset; + break; + case icSeekCur: + nPos = (icInt32Number)m_nPos + nOffset; + break; + case icSeekEnd: + nPos = (icInt32Number)m_nSize + nOffset; + break; + default: + nPos = 0; + break; + } + + if (nPos < 0) + return -1; + + m_nPos = (icUInt32Number)nPos; + + if (m_nPos>m_nSize) + m_nSize = m_nPos; + + return nPos; +} + + +icInt32Number CIccNullIO::Tell() +{ + return (icInt32Number)m_nPos; +} + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccIO.h b/library/src/main/cpp/icc/IccIO.h new file mode 100644 index 00000000..7ae8b616 --- /dev/null +++ b/library/src/main/cpp/icc/IccIO.h @@ -0,0 +1,243 @@ +/** @file + File: IccIO.h + + Contains: Implementation of the CIccIO class. + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCIO_H) +#define _ICCIO_H + +#include "IccDefs.h" +#include "stdio.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +///Seek types +typedef enum { + icSeekSet=0, //Seek to an absolute position + icSeekCur, //Seek to relative position + icSeekEnd, //Seek relative to the ending +} icSeekVal; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: + * This is the base object that handles the IO with an ICC profile. + ************************************************************************** + */ +class ICCPROFLIB_API CIccIO +{ +public: + + virtual ~CIccIO() {} + + virtual void Close() {} + + virtual icInt32Number Read8(void *pBuf8, icInt32Number nNum=1) { return 0; } + virtual icInt32Number Write8(void *pBuf8, icInt32Number nNum=1) { return 0; } + + icInt32Number ReadLine(void *pBuf8, icInt32Number nNum=256); + + icInt32Number Read16(void *pBuf16, icInt32Number nNum=1); + icInt32Number Write16(void *pBuf16, icInt32Number nNum=1); + + icInt32Number Read32(void *pBuf32, icInt32Number nNum=1); + icInt32Number Write32(void *pBuf32, icInt32Number nNum=1); + + icInt32Number Read64(void *pBuf64, icInt32Number nNum=1); + icInt32Number Write64(void *pBuf64, icInt32Number nNum=1); + + icInt32Number Read8Float(void *pBufFloat, icInt32Number nNum=1); + icInt32Number Write8Float(void *pBuf16, icInt32Number nNum=1); + + icInt32Number Read16Float(void *pBufFloat, icInt32Number nNum=1); + icInt32Number Write16Float(void *pBuf16, icInt32Number nNum=1); + + icInt32Number ReadFloat32Float(void *pBufFloat, icInt32Number nNum=1); + icInt32Number WriteFloat32Float(void *pBufFloat, icInt32Number nNum=1); + + virtual icInt32Number GetLength() {return 0;} + + virtual icInt32Number Seek(icInt32Number nOffset, icSeekVal pos) {return -1;} + virtual icInt32Number Tell() {return 0;} + + ///Write operation to make sure that filelength is evenly divisible by 4 + bool Align32(); + + ///Operation to make sure read position is evenly divisible by 4 + bool Sync32(icUInt32Number nOffset=0); +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Handles generic File IO + ************************************************************************** + */ +class ICCPROFLIB_API CIccFileIO : public CIccIO +{ +public: + CIccFileIO(); + virtual ~CIccFileIO(); + + bool Open(const icChar *szFilename, const icChar *szAttr); +#if defined(WIN32) || defined(WIN64) + bool Open(const icWChar *szFilename, const icWChar *szAttr); +#endif + virtual void Close(); + + virtual icInt32Number Read8(void *pBuf, icInt32Number nNum=1); + virtual icInt32Number Write8(void *pBuf, icInt32Number nNum=1); + + virtual icInt32Number GetLength(); + + virtual icInt32Number Seek(icInt32Number nOffset, icSeekVal pos); + virtual icInt32Number Tell(); + +protected: + FILE *m_fFile; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Handles generic memory IO + ************************************************************************** + */ +class ICCPROFLIB_API CIccMemIO : public CIccIO +{ +public: + CIccMemIO(); + virtual ~CIccMemIO(); + + bool Alloc(icUInt32Number nSize, bool bWrite = false); + + bool Attach(icUInt8Number *pData, icUInt32Number nSize, bool bWrite=false); + virtual void Close(); + + virtual icInt32Number Read8(void *pBuf, icInt32Number nNum=1); + virtual icInt32Number Write8(void *pBuf, icInt32Number nNum=1); + + virtual icInt32Number GetLength(); + + virtual icInt32Number Seek(icInt32Number nOffset, icSeekVal pos); + virtual icInt32Number Tell(); + + icUInt8Number *GetData() { return m_pData; } + +protected: + icUInt8Number *m_pData; + icUInt32Number m_nSize; + icUInt32Number m_nAvail; + icUInt32Number m_nPos; + + bool m_bFreeData; +}; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: Handles simulated File IO + ************************************************************************** + */ +class ICCPROFLIB_API CIccNullIO : public CIccIO +{ +public: + CIccNullIO(); + virtual ~CIccNullIO(); + + //Open resets the file to being zero size + void Open(); + virtual void Close(); + + + virtual icInt32Number Read8(void *pBuf, icInt32Number nNum=1); //Read zero's into buf + virtual icInt32Number Write8(void *pBuf, icInt32Number nNum=1); + + virtual icInt32Number GetLength(); + + virtual icInt32Number Seek(icInt32Number nOffset, icSeekVal pos); + virtual icInt32Number Tell(); + +protected: + icUInt32Number m_nSize; + icUInt32Number m_nPos; +}; + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif // !defined(_ICCIO_H) diff --git a/library/src/main/cpp/icc/IccMpeACS.cpp b/library/src/main/cpp/icc/IccMpeACS.cpp new file mode 100644 index 00000000..8e4354c3 --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeACS.cpp @@ -0,0 +1,491 @@ +/** @file + File: IccMpeACS.cpp + + Contains: Implementation of ACS Elements + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 1-30-2006 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include +#include +#include +#include +#include "IccMpeACS.h" +#include "IccIO.h" +#include +#include "IccUtil.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + + +/** +****************************************************************************** +* Name: CIccMpeAcs::CIccMpeACS +* +* Purpose: +* Base constructor (protected) +******************************************************************************/ +CIccMpeAcs::CIccMpeAcs() +{ + m_pData = NULL; + m_nDataSize = 0; + + m_nReserved = 0; +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::~CIccMpeAcs +* +* Purpose: +* Base destructor +******************************************************************************/ +CIccMpeAcs::~CIccMpeAcs() +{ + if (m_pData) + free(m_pData); +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Describe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccMpeAcs::Describe(std::string &sDescription) +{ + icChar sigBuf[30]; + + if (GetBAcsSig()) + sDescription += "ELEM_bACS\r\n"; + else + sDescription += "ELEM_eACS\r\n"; + + icGetSig(sigBuf, m_signature); + sDescription += " Signature = "; + sDescription += sigBuf; + sDescription += "\r\n"; + + if (m_pData) { + sDescription += "\r\nData Follows:\r\n"; + + icMemDump(sDescription, m_pData, m_nDataSize); + } +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Read +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccMpeAcs::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt16Number) + + sizeof(icUInt16Number) + + sizeof(icUInt32Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&m_nInputChannels)) + return false; + + if (!pIO->Read16(&m_nOutputChannels)) + return false; + + if (!pIO->Read32(&m_signature)) + return false; + + icUInt32Number dataSize = size - headerSize; + + if (!AllocData(dataSize)) + return false; + + if (dataSize) { + if (pIO->Read8(m_pData, dataSize)!=(icInt32Number)dataSize) + return false; + } + + return true; +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Write +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccMpeAcs::Write(CIccIO *pIO) +{ + icElemTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nOutputChannels)) + return false; + + if (!pIO->Write32(&m_signature)) + return false; + + if (m_pData && m_nDataSize) { + if (!pIO->Write8(m_pData, m_nDataSize)!=m_nDataSize) + return false; + } + + return true; +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Begin +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccMpeAcs::Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE) +{ + if (m_nInputChannels!=m_nOutputChannels) + return false; + + return true; +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Apply +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccMpeAcs::Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const +{ + memcpy(dstPixel, srcPixel, m_nInputChannels*sizeof(icFloatNumber)); +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::Validate +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +icValidateStatus CIccMpeAcs::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + icValidateStatus rv = CIccMultiProcessElement::Validate(sig, sReport, pMPE); + + return rv; +} + +/** +****************************************************************************** +* Name: CIccMpeAcs::AllocData +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccMpeAcs::AllocData(icUInt32Number size) +{ + if (m_pData) + free(m_pData); + + if (size) { + m_pData = (icUInt8Number*)malloc(size); + if (m_pData) + m_nDataSize = size; + } + else { + m_pData = NULL; + m_nDataSize = 0; + } + + return (size==0 || m_pData!=NULL); +} + + +/** +****************************************************************************** +* Name: CIccMpeBeginAcs::CIccMpeBeginAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeBAcs::CIccMpeBAcs(icUInt16Number nChannels/* =0 */, icAcsSignature sig /* = icSigUnknownAcs */) +{ + m_signature = sig; + + m_nInputChannels = nChannels; + m_nOutputChannels = nChannels; +} + +/** +****************************************************************************** +* Name: CIccMpeBeginAcs::CIccMpeBeginAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeBAcs::CIccMpeBAcs(const CIccMpeBAcs &elemAcs) +{ + + m_signature = elemAcs.m_signature; + m_nReserved = elemAcs.m_nReserved; + m_nInputChannels = elemAcs.m_nInputChannels; + m_nOutputChannels = elemAcs.m_nOutputChannels; + + m_pData = NULL; + m_nDataSize = 0; + + AllocData(elemAcs.m_nDataSize); + if (m_pData && elemAcs.m_nDataSize) { + memcpy(m_pData, elemAcs.m_pData, m_nDataSize); + } + + m_nReserved = 0; +} + +/** +****************************************************************************** +* Name: &CIccMpeBeginAcs::operator= +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeBAcs &CIccMpeBAcs::operator=(const CIccMpeBAcs &elemAcs) +{ + m_signature = elemAcs.m_signature; + m_nReserved = elemAcs.m_nReserved; + m_nInputChannels = elemAcs.m_nInputChannels; + m_nOutputChannels = elemAcs.m_nOutputChannels; + + AllocData(elemAcs.m_nDataSize); + if (m_pData && elemAcs.m_nDataSize) { + memcpy(m_pData, elemAcs.m_pData, m_nDataSize); + } + + return *this; +} + +/** +****************************************************************************** +* Name: CIccMpeBeginAcs::~CIccMpeBeginAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeBAcs::~CIccMpeBAcs() +{ +} + +/** +****************************************************************************** +* Name: CIccMpeEndAcs::CIccMpeEndAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeEAcs::CIccMpeEAcs(icUInt16Number nChannels/* =0 */, icAcsSignature sig /* = icSigUnknownAcs */) +{ + m_signature = sig; + + m_nInputChannels = nChannels; + m_nOutputChannels = nChannels; +} + +/** +****************************************************************************** +* Name: CIccMpeEndAcs::CIccMpeEndAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeEAcs::CIccMpeEAcs(const CIccMpeEAcs &elemAcs) +{ + m_signature = elemAcs.m_signature; + m_nReserved = elemAcs.m_nReserved; + + m_nInputChannels = elemAcs.m_nInputChannels; + m_nOutputChannels = elemAcs.m_nOutputChannels; + + AllocData(elemAcs.m_nDataSize); + if (m_pData && elemAcs.m_nDataSize) { + memcpy(m_pData, elemAcs.m_pData, m_nDataSize); + } +} + +/** +****************************************************************************** +* Name: &CIccMpeEndAcs::operator= +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeEAcs &CIccMpeEAcs::operator=(const CIccMpeEAcs &elemAcs) +{ + m_signature = elemAcs.m_signature; + m_nReserved = elemAcs.m_nReserved; + m_nInputChannels = elemAcs.m_nInputChannels; + m_nOutputChannels = elemAcs.m_nOutputChannels; + + AllocData(elemAcs.m_nDataSize); + if (m_pData && elemAcs.m_nDataSize) { + memcpy(m_pData, elemAcs.m_pData, m_nDataSize); + } + + return *this; +} + +/** +****************************************************************************** +* Name: CIccMpeEndAcs::~CIccMpeEndAcs +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMpeEAcs::~CIccMpeEAcs() +{ +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccMpeACS.h b/library/src/main/cpp/icc/IccMpeACS.h new file mode 100644 index 00000000..79ff5378 --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeACS.h @@ -0,0 +1,175 @@ +/** @file +File: IccMpeACS.h + +Contains: Header for implementation of CIccTagMultiProcessElement +ACS elements and supporting classes + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2005-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Jan 30, 2005 +// Initial CIccMpeent prototype development +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCMPEACS_H +#define _ICCMPEACS_H + +#include "IccTagMPE.h" + + +//CIccFloatTag support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** +**************************************************************************** +* Class: CIccMpeAcs +* +* Purpose: The alternate connection space base class element +***************************************************************************** +*/ +class CIccMpeAcs : public CIccMultiProcessElement +{ +public: + virtual ~CIccMpeAcs(); + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE); + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + + virtual bool IsAcs() { return true; } + + bool AllocData(icUInt32Number size); + icUInt8Number* GetData() { return m_pData; } + icUInt32Number GetDataSize() { return m_nDataSize; } + + virtual icAcsSignature GetAcsSig() { return m_signature; } + +protected: + CIccMpeAcs(); + icAcsSignature m_signature; + + icUInt32Number m_nDataSize; + icUInt8Number *m_pData; +}; + + + +/** +**************************************************************************** +* Class: CIccMpeBAcs +* +* Purpose: The bACS element +***************************************************************************** +*/ +class CIccMpeBAcs : public CIccMpeAcs +{ +public: + CIccMpeBAcs(icUInt16Number nChannels=0, icAcsSignature sig = 0); + CIccMpeBAcs(const CIccMpeBAcs &elemAcs); + CIccMpeBAcs &operator=(const CIccMpeBAcs &elemAcs); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeBAcs(*this);} + virtual ~CIccMpeBAcs(); + + virtual icElemTypeSignature GetType() const { return icSigBAcsElemType; } + virtual const icChar *GetClassName() const { return "CIccMpeBAcs"; } + + virtual icAcsSignature GetBAcsSig() { return m_signature; } + +}; + + +/** +**************************************************************************** +* Class: CIccMpeEndAcs +* +* Purpose: The eAcs element +***************************************************************************** +*/ +class CIccMpeEAcs : public CIccMpeAcs +{ +public: + CIccMpeEAcs(icUInt16Number nChannels=0, icAcsSignature sig = 0); + CIccMpeEAcs(const CIccMpeEAcs &elemAcs); + CIccMpeEAcs &operator=(const CIccMpeEAcs &elemAcs); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeEAcs(*this);} + virtual ~CIccMpeEAcs(); + + virtual icElemTypeSignature GetType() const { return icSigEAcsElemType; } + virtual const icChar *GetClassName() const { return "CIccMpeEAcs"; } + + virtual icAcsSignature GetEAcsSig() { return m_signature;} +}; + + +//CIccMPElements support +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //_ICCMPEACS_H diff --git a/library/src/main/cpp/icc/IccMpeBasic.cpp b/library/src/main/cpp/icc/IccMpeBasic.cpp new file mode 100644 index 00000000..b1ceba4f --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeBasic.cpp @@ -0,0 +1,2657 @@ +/** @file + File: IccMpeBasic.cpp + + Contains: Implementation of Basic Multi Processing Elements + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 1-30-2006 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include +#include +#include +#include +#include "IccMpeBasic.h" +#include "IccIO.h" +#include +#include "IccUtil.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::CIccFormulaCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccFormulaCurveSegment::CIccFormulaCurveSegment(icFloatNumber start, icFloatNumber end) +{ + m_nReserved = 0; + m_nReserved2 = 0; + m_startPoint = start; + m_endPoint = end; + + m_nFunctionType = 0; + m_nParameters = 0; + m_params = NULL; +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::CIccFormulaCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccFormulaCurveSegment::CIccFormulaCurveSegment(const CIccFormulaCurveSegment &seg) +{ + m_nReserved = seg.m_nReserved; + m_nReserved2 = seg.m_nReserved2; + m_startPoint = seg.m_startPoint; + m_endPoint = seg.m_endPoint; + + m_nFunctionType = seg.m_nFunctionType; + m_nParameters = seg.m_nParameters; + + if (seg.m_params) { + m_params = (icFloatNumber*)malloc(m_nParameters*sizeof(icFloatNumber)); + memcpy(m_params, seg.m_params, m_nParameters*sizeof(icFloatNumber)); + } + else + m_params = NULL; +} + +/** + ****************************************************************************** + * Name: &CIccFormulaCurveSegment::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccFormulaCurveSegment &CIccFormulaCurveSegment::operator=(const CIccFormulaCurveSegment &seg) +{ + if (m_params) + free(m_params); + + m_nReserved = seg.m_nReserved; + m_nReserved2 = seg.m_nReserved2; + m_startPoint = seg.m_startPoint; + m_endPoint = seg.m_endPoint; + + m_nFunctionType = seg.m_nFunctionType; + m_nParameters = seg.m_nParameters; + if (seg.m_params) { + m_params = (icFloatNumber*)malloc(m_nParameters*sizeof(icFloatNumber)); + memcpy(m_params, seg.m_params, m_nParameters*sizeof(icFloatNumber)); + } + else + m_params = NULL; + + return (*this); +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::~CIccFormulaCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccFormulaCurveSegment::~CIccFormulaCurveSegment() +{ + if (m_params) { + free(m_params); + } +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccFormulaCurveSegment::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sDescription += "Segment ["; + if (m_startPoint==icMinFloat32Number) + sDescription += "-Infinity, "; + else { + sprintf(buf, "%.8f, ", m_startPoint); + sDescription += buf; + } + if (m_endPoint==icMaxFloat32Number) + sDescription += "+Infinity"; + else { + sprintf(buf, "%.8f", m_endPoint); + sDescription += buf; + } + sprintf(buf, "]\r\nFunctionType: %04Xh\r\n", m_nFunctionType); + sDescription += buf; + + switch(m_nFunctionType) { + case 0x0000: + if (m_params[1]==0.0 && m_params[2]==0.0) + sprintf(buf, "Y = %.8f\r\n\r\n", m_params[3]); + else if (m_params[0]==1.0 && m_params[1]==1.0 && m_params[2]==0.0 && m_params[3]==0.0) + sprintf(buf, "Y = X\r\n\r\n"); + else if (m_params[0]==1.0 && m_params[2]==0.0) + sprintf(buf, "Y = %.8f * X + %.8f\r\n\r\n", + m_params[1], m_params[3]); + else + sprintf(buf, "Y = (%.8f * X + %.8f)^%.4f + %.8f\r\n\r\n", + m_params[1], m_params[2], m_params[0], m_params[3]); + sDescription += buf; + return; + + case 0x0001: + sprintf(buf, "Y = %.8f * log (%.8f * (X ^ %.8f) + %.8f) + %.8f\r\n\r\n", + m_params[1], m_params[2], m_params[0], m_params[3], m_params[4]); + sDescription += buf; + return; + + case 0x0002: + sprintf(buf, "Y = %.8f * (%.8f ^ (%.8f * X + %.8f)) + %.8f\r\n\r\n", + m_params[0], m_params[1], m_params[2], m_params[3], m_params[4]); + sDescription += buf; + return; + + default: + int i; + sprintf(buf, "Unknown Function with %d parameters:\r\n\r\n", m_nParameters); + sDescription += buf; + + for (i=0; i size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&m_nFunctionType)) + return false; + + if (!pIO->Read16(&m_nReserved2)) + return false; + + if (m_params) { + free(m_params); + } + + switch(m_nFunctionType) { + case 0x0000: + m_nParameters = 4; + break; + + case 0x0001: + case 0x0002: + m_nParameters = 5; + break; + + default: + return false; + } + + if (m_nParameters) { + + m_params = (icFloatNumber*)malloc(m_nParameters * sizeof(icFloatNumber)); + if (!m_params) + return false; + + if (pIO->ReadFloat32Float(m_params, m_nParameters)!= m_nParameters) { + return false; + } + } + else + m_params = NULL; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccFormulaCurveSegment::Write(CIccIO *pIO) +{ + icCurveSegSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nFunctionType)) + return false; + + if (!pIO->Write16(&m_nReserved2)) + return false; + + switch(m_nFunctionType) { + case 0x0000: + if (m_nParameters!=4) + return false; + break; + + case 0x0001: + case 0x0002: + if (m_nParameters!=5) + return false; + break; + } + + if (m_nParameters) { + if (pIO->WriteFloat32Float(m_params, m_nParameters)!=m_nParameters) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccFormulaCurveSegment::Begin(CIccCurveSegment *pPrevSeg = NULL) +{ + switch (m_nFunctionType) { + case 0x0000: + if (!m_params || m_nParameters<4) + return false; + + return true; + + case 0x0001: + if (!m_params || m_nParameters<5) + return false; + + return true; + + case 0x0002: + if (!m_params || m_nParameters<5) + return false; + + return true; + + default: + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icFloatNumber CIccFormulaCurveSegment::Apply(icFloatNumber v) const +{ + switch (m_nFunctionType) { + case 0x0000: + //Y = (a * X + b) ^ g + c : g a b c + return (pow(m_params[1] * v + m_params[2], m_params[0]) + m_params[3]); + + case 0x0001: + // Y = a * log (b * X^g + c) + d : g a b c d + return (m_params[1] * log10(m_params[2] * pow(v, m_params[0]) + m_params[3]) + m_params[4]); + + case 0x0002: + //Y = a * b^(c*X+d) + e : a b c d e + return (m_params[0] * pow(m_params[1], m_params[2] * v + m_params[3]) + m_params[4]); + } + + //Shouldn't get here! + return v; +} + +/** + ****************************************************************************** + * Name: CIccFormulaCurveSegment::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccFormulaCurveSegment::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + icValidateStatus rv = icValidateOK; + if (m_nReserved || m_nReserved2) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " formula curve has non zero reserved data.\r\n"; + rv = icValidateWarning; + } + + switch (m_nFunctionType) { + case 0x0000: + if (!m_params || m_nParameters<4) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " formula curve has Invalid formulaCurveSegment parameters.\r\n"; + rv = icValidateCriticalError; + } + else if (m_nParameters > 4) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " formula curve has too many formulaCurveSegment parameters.\r\n"; + rv = icValidateWarning; + } + break; + + case 0x0001: + if (!m_params || m_nParameters<5) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " formula curve has Invalid formulaCurveSegment parameters.\r\n"; + rv = icValidateCriticalError; + } + else if (m_nParameters > 5) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " formula curve has too many formulaCurveSegment parameters.\r\n"; + rv = icValidateWarning; + } + break; + + case 0x0002: + if (!m_params || m_nParameters<5) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " formula curve has Invalid formulaCurveSegment parameters.\r\n"; + rv = icValidateCriticalError; + } + else if (m_nParameters > 5) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " formula curve has too many formulaCurveSegment parameters.\r\n"; + rv = icValidateWarning; + } + break; + + default: + { + icChar buf[128]; + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sprintf(buf, " formula curve uses unknown formulaCurveSegment function type %d\r\n", m_nFunctionType); + sReport += buf; + rv = icValidateCriticalError; + } + } + + return rv; +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::CIccSampledCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSampledCurveSegment::CIccSampledCurveSegment(icFloatNumber start, icFloatNumber end) +{ + m_nReserved = 0; + m_startPoint = start; + m_endPoint = end; + m_nCount = 0; + m_pSamples = 0; +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::CIccSampledCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSampledCurveSegment::CIccSampledCurveSegment(const CIccSampledCurveSegment &curve) +{ + m_nReserved = curve.m_nReserved; + m_startPoint = curve.m_startPoint; + m_endPoint = curve.m_endPoint; + m_nCount = curve.m_nCount; + + if (m_nCount) { + m_pSamples = (icFloatNumber*)malloc(m_nCount * sizeof(icFloatNumber)); + if (m_pSamples) + memcpy(m_pSamples, curve.m_pSamples, m_nCount * sizeof(icFloatNumber)); + else + m_nCount = 0; + } + else{ + m_pSamples = NULL; + } +} + +/** + ****************************************************************************** + * Name: &CIccSampledCurveSegment::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSampledCurveSegment &CIccSampledCurveSegment::operator=(const CIccSampledCurveSegment &curve) +{ + if (m_pSamples) + free(m_pSamples); + + m_nReserved = curve.m_nReserved; + m_startPoint = curve.m_startPoint; + m_endPoint = curve.m_endPoint; + m_nCount = curve.m_nCount; + + if (m_nCount) { + m_pSamples = (icFloatNumber*)malloc(m_nCount * sizeof(icFloatNumber)); + if (m_pSamples) + memcpy(m_pSamples, curve.m_pSamples, m_nCount * sizeof(icFloatNumber)); + else + m_nCount = 0; + } + else { + m_pSamples = NULL; + } + return (*this); +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::~CIccSampledCurveSegment + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSampledCurveSegment::~CIccSampledCurveSegment() +{ + if (m_pSamples) + free(m_pSamples); +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::SetSize + * + * Purpose: + * Sets size of sampled lookup table. Previous data (if exists) is lost. + * + * Args: + * nCount = number of elements in lut (must be >= 2). Note: the m_pSample[0] is + * initialized from the the previous segment. It is not saved as part + * of Write(), or loaded as part of Read(). It will be initialized during + * the call to Begin(), The actual count of elements written to the file + * will be nCount-1 + * bZeroAlloc = flag to decide if memory should be set to zero. + * + * Return: + * true if allocation successful. + ******************************************************************************/ +bool CIccSampledCurveSegment::SetSize(icUInt32Number nCount, bool bZeroAlloc/*=true*/) +{ + if (!nCount) { + if (m_pSamples) + free(m_pSamples); + m_pSamples = NULL; + m_nCount = nCount; + return true; + } + + if (m_pSamples) { + free(m_pSamples); + } + + if (bZeroAlloc) + m_pSamples = (icFloatNumber*)calloc(nCount, sizeof(icFloatNumber)); + else + m_pSamples = (icFloatNumber*)malloc(nCount * sizeof(icFloatNumber)); + + if (m_pSamples) + m_nCount = nCount; + else + m_nCount = 0; + + return (m_pSamples != NULL); +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccSampledCurveSegment::Describe(std::string &sDescription) +{ + icChar buf[128]; + + if (m_nCount<2) { + sDescription += "Empty Segment ["; + if (m_startPoint==icMinFloat32Number) + sDescription += "-Infinity, "; + else { + sprintf(buf, "%.8f, ", m_startPoint); + sDescription += buf; + } + if (m_endPoint==icMaxFloat32Number) + sDescription += "+Infinity"; + else { + sprintf(buf, "%.8f", m_endPoint); + sDescription += buf; + } + + sprintf(buf, "]\r\n"); + sDescription += buf; + } + else { + sDescription += "Sampled Segment ["; + if (m_startPoint==icMinFloat32Number) + sDescription += "-Infinity, "; + else { + sprintf(buf, "%.8f, ", m_startPoint); + sDescription += buf; + } + if (m_endPoint==icMaxFloat32Number) + sDescription += "+Infinity"; + else { + sprintf(buf, "%.8f", m_endPoint); + sDescription += buf; + } + sprintf(buf, "]\r\n"); + sDescription += buf; + sDescription += "IN OUT\r\n"; + + icUInt32Number i; + + icFloatNumber range = m_endPoint - m_startPoint; + icFloatNumber last = (icFloatNumber)(m_nCount-1); + + for (i=1; i size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read32(&m_nCount)) + return false; + + //Reserve room for first point who's value comes from previous segment + m_nCount++; + + if (!SetSize(m_nCount, false)) + return false; + + if (m_nCount) { + if (pIO->ReadFloat32Float(m_pSamples+1, m_nCount-1)!=(icInt32Number)(m_nCount-1)) + return false; + } + + //Initialize first point with zero. Properly initialized during Begin() + m_pSamples[0] = 0; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSampledCurveSegment::Write(CIccIO *pIO) +{ + icCurveSegSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + icUInt32Number nCount; + + if (m_nCount) + nCount = m_nCount -1; + else + nCount = 0; + + if (!pIO->Write32(&nCount)) + return false; + + //First point in samples is ONLY for interpolation (not saved) + if (nCount) { + if (pIO->WriteFloat32Float(m_pSamples+1, nCount)!=(icInt32Number)nCount) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSampledCurveSegment::Begin(CIccCurveSegment *pPrevSeg = NULL) +{ + if (m_nCount<2) + return false; + + m_range = m_endPoint - m_startPoint; + m_last = (icFloatNumber)(m_nCount - 1); + + if (m_endPoint-m_startPoint == 0.0) + return false; + + if (!pPrevSeg) + return false; + + //Set up interpolation from Application of last segment + m_pSamples[0] = pPrevSeg->Apply(m_startPoint); + + return true; +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icFloatNumber CIccSampledCurveSegment::Apply(icFloatNumber v) const +{ + if (vm_endPoint) + v=m_endPoint; + + icFloatNumber pos = (v-m_startPoint)/m_range * m_last; + icUInt32Number index = (icUInt32Number) pos; + icFloatNumber remainder = pos - (icFloatNumber)index; + + if (remainder==0.0) + return m_pSamples[index]; + + return (icFloatNumber)((1.0-remainder)*m_pSamples[index] + remainder*m_pSamples[index+1]); +} + +/** + ****************************************************************************** + * Name: CIccSampledCurveSegment::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccSampledCurveSegment::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + icValidateStatus rv = icValidateOK; + if (m_nReserved) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " sampled curve has non zero reserved data.\r\n"; + rv = icValidateWarning; + } + + if (m_nCount<2) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " sampled curve has too few sample points.\r\n"; + rv = icValidateCriticalError; + } + else if (m_endPoint-m_startPoint == 0.0) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " sampled curve has a range of zero.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccCurveSegment::Create + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccCurveSegment* CIccCurveSegment::Create(icCurveSegSignature sig, icFloatNumber start, icFloatNumber end) +{ + switch(sig) { + case icSigFormulaCurveSeg: + return new CIccFormulaCurveSegment(start, end); + case icSigSampledCurveSeg: + return new CIccSampledCurveSegment(start, end); + default: + return NULL; + } + +} + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::CIccSegmentedCurve + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSegmentedCurve::CIccSegmentedCurve() +{ + m_list = new CIccCurveSegmentList(); + m_nReserved1 = 0; + m_nReserved2 = 0; + +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::CIccSegmentedCurve + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSegmentedCurve::CIccSegmentedCurve(const CIccSegmentedCurve &curve) +{ + CIccCurveSegmentList::iterator i; + + m_list = new CIccCurveSegmentList(); + + for (i=curve.m_list->begin(); i!=curve.m_list->end(); i++) { + m_list->push_back((*i)->NewCopy()); + } + m_nReserved1 = curve.m_nReserved1; + m_nReserved2 = curve.m_nReserved2; +} + + +/** + ****************************************************************************** + * Name: &CIccSegmentedCurve::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSegmentedCurve &CIccSegmentedCurve::operator=(const CIccSegmentedCurve &curve) +{ + Reset(); + + CIccCurveSegmentList::iterator i; + + for (i=curve.m_list->begin(); i!=curve.m_list->end(); i++) { + m_list->push_back((*i)->NewCopy()); + } + m_nReserved1 = curve.m_nReserved1; + m_nReserved2 = curve.m_nReserved2; + + return (*this); +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::~CIccSegmentedCurve + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccSegmentedCurve::~CIccSegmentedCurve() +{ + Reset(); + delete m_list; +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccSegmentedCurve::Describe(std::string &sDescription) +{ + CIccCurveSegmentList::iterator i; + + sDescription += "BEGIN_CURVE\r\n"; + for (i=m_list->begin(); i!=m_list->end(); i++) { + (*i)->Describe(sDescription); + } +} + + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSegmentedCurve::Read(icUInt32Number size, CIccIO *pIO) +{ + icElemTypeSignature sig; + + icUInt32Number startPos = pIO->Tell(); + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt16Number) + + sizeof(icUInt16Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved1)) + return false; + + icUInt16Number nSegments; + + if (!pIO->Read16(&nSegments)) + return false; + + if (!pIO->Read16(&m_nReserved2)) + return false; + + Reset(); + + icUInt32Number pos = pIO->Tell(); + icCurveSegSignature segSig; + CIccCurveSegment *pSeg; + + if (nSegments==1) { + if (!pIO->Read32(&segSig)) + return false; + pSeg = CIccCurveSegment::Create(segSig, icMinFloat32Number, icMaxFloat32Number); + if (!pSeg) + return false; + + pIO->Seek(pos, icSeekSet); + + if (!pSeg->Read(size-(pos-startPos), pIO)) { + delete pSeg; + return false; + } + + m_list->push_back(pSeg); + } + else if (nSegments) { + icFloatNumber *breakpoints=(icFloatNumber*)calloc(nSegments-1, sizeof(icFloatNumber)); + + if (!breakpoints) + return false; + + if (pIO->ReadFloat32Float(breakpoints, nSegments-1)!=nSegments-1) { + free(breakpoints); + return false; + } + + int i; + for (i=0; iTell(); + if (!pIO->Read32(&segSig)) { + free(breakpoints); + return false; + } + if (pIO->Seek(pos, icSeekSet)!=(icInt32Number)pos) + return false;; + + if (!i) + pSeg = CIccCurveSegment::Create(segSig, icMinFloat32Number, breakpoints[i]); + else if (i==nSegments-1) + pSeg = CIccCurveSegment::Create(segSig, breakpoints[i-1], icMaxFloat32Number); + else + pSeg = CIccCurveSegment::Create(segSig, breakpoints[i-1], breakpoints[i]); + + if (!pSeg) { + free(breakpoints); + return false; + } + + if (!pSeg->Read(size-(pos-startPos), pIO)) { + delete pSeg; + free(breakpoints); + return false; + } + + m_list->push_back(pSeg); + } + + free(breakpoints); + } + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSegmentedCurve::Write(CIccIO *pIO) +{ + icCurveElemSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved1)) + return false; + + icUInt16Number nSegments = (icUInt16Number)m_list->size(); + + if (!pIO->Write16(&nSegments)) + return false; + + if (!pIO->Write16(&m_nReserved2)) + return false; + + CIccCurveSegmentList::iterator i; + if (nSegments>1) { + icFloatNumber breakpoint; + + i=m_list->begin(); + for (i++; i!=m_list->end(); i++) { + breakpoint = (*i)->StartPoint(); + if (!pIO->WriteFloat32Float(&breakpoint)) + return false; + } + } + for (i=m_list->begin(); i!=m_list->end(); i++) { + if (!(*i)->Write(pIO)) + return false; + } + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Reset + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccSegmentedCurve::Reset() +{ + CIccCurveSegmentList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + delete (*i); + } + m_list->clear(); +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Insert + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSegmentedCurve::Insert(CIccCurveSegment *pCurveSegment) +{ + CIccCurveSegmentList::reverse_iterator last = m_list->rbegin(); + + if (last!=m_list->rend()) { + if (pCurveSegment->StartPoint() == (*last)->EndPoint()) { + m_list->push_back(pCurveSegment); + return true; + } + } + else { + m_list->push_back(pCurveSegment); + return true; + } + + return false; +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccSegmentedCurve::Begin() +{ + if (m_list->size()==0) + return false; + + CIccCurveSegmentList::iterator i; + CIccCurveSegment *pLast = NULL; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + if (!(*i)->Begin(pLast)) + return false; + pLast = *i; + } + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icFloatNumber CIccSegmentedCurve::Apply(icFloatNumber v) const +{ + CIccCurveSegmentList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + if (v <= (*i)->EndPoint()) + return (*i)->Apply(v); + } + return v; +} + +/** + ****************************************************************************** + * Name: CIccSegmentedCurve::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccSegmentedCurve::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + icValidateStatus rv = icValidateOK; + if (m_nReserved1 || m_nReserved2) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " Segmented curve has non zero reserved data.\r\n"; + rv = icValidateWarning; + } + + if (m_list->size()==0) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " Has Empty CurveSegment!\r\n"; + return icValidateCriticalError; + } + + CIccCurveSegmentList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + rv = icMaxStatus(rv, (*i)->Validate(sig, sReport, pMPE)); + } + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccCurveSetCurve::Create + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccCurveSetCurve* CIccCurveSetCurve::Create(icCurveElemSignature sig) +{ + switch (sig) { + case icSigSementedCurve: + return new CIccSegmentedCurve(); + default: + return NULL; + } +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::CIccMpeCurveSet + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeCurveSet::CIccMpeCurveSet(int nSize/*=0*/) +{ + m_nReserved = 0; + if (nSize) { + m_nInputChannels = m_nOutputChannels = nSize; + m_curve = (icCurveSetCurvePtr*)calloc(nSize, sizeof(icCurveSetCurvePtr)); + m_position = (icPositionNumber*)calloc(nSize, sizeof(icPositionNumber)); + } + else { + m_nInputChannels = m_nOutputChannels = 0; + m_curve = NULL; + m_position = NULL; + } +} + +typedef std::map icCurveMap; + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::CIccMpeCurveSet + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeCurveSet::CIccMpeCurveSet(const CIccMpeCurveSet &curveSet) +{ + m_nReserved = curveSet.m_nReserved; + + if (curveSet.m_nInputChannels) { + int i; + + m_nInputChannels = m_nOutputChannels = curveSet.m_nInputChannels; + m_curve = (icCurveSetCurvePtr*)calloc(m_nInputChannels, sizeof(icCurveSetCurvePtr)); + m_position = (icPositionNumber*)calloc(m_nInputChannels, sizeof(icPositionNumber)); + + icCurveMap map; + for (i=0; iNewCopy(); + map[ptr] = m_curve[i]; + } + else + m_curve[i] = map[ptr]; + } + } + } + else { + m_nInputChannels = m_nOutputChannels = 0; + m_curve = NULL; + } +} + +/** + ****************************************************************************** + * Name: &CIccMpeCurveSet::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeCurveSet &CIccMpeCurveSet::operator=(const CIccMpeCurveSet &curveSet) +{ + m_nReserved = m_nReserved; + + if (m_curve) { + delete [] m_curve; + } + + if (curveSet.m_nInputChannels) { + int i; + + m_nInputChannels = m_nOutputChannels = curveSet.m_nInputChannels; + m_curve = (icCurveSetCurvePtr*)calloc(m_nInputChannels, sizeof(icCurveSetCurvePtr)); + m_position = (icPositionNumber*)calloc(m_nInputChannels, sizeof(icPositionNumber)); + + icCurveMap map; + for (i=0; iNewCopy(); + map[ptr] = m_curve[i]; + } + else + m_curve[i] = map[ptr]; + } + } + } + else { + m_nInputChannels = m_nOutputChannels = 0; + m_curve = NULL; + } + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::~CIccMpeCurveSet + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeCurveSet::~CIccMpeCurveSet() +{ + SetSize(0); +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::SetSize + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeCurveSet::SetSize(int nNewSize) +{ + if (m_curve) { + icCurveMap map; + int i; + + for (i=0; im_nInputChannels) + return false; + + if (m_curve) { + int i; + + for (i=0; iDescribe(sDescription); + } + } + } +} + +typedef std::map icCurveOffsetMap; +typedef std::map icCurvePtrMap; + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCurveSet::Read(icUInt32Number size, CIccIO *pIO) +{ + icElemTypeSignature sig; + + icUInt32Number startPos = pIO->Tell(); + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt16Number) + + sizeof(icUInt16Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + icUInt16Number nInputChannels, nOutputChannels; + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&nInputChannels)) + return false; + + if (!pIO->Read16(&nOutputChannels)) + return false; + + if (nInputChannels != nOutputChannels) + return false; + + SetSize(nInputChannels); + + if (m_curve) { + int i; + + if (headerSize + m_nInputChannels*2*sizeof(icUInt32Number) > size) + return false; + + for (i=0; iRead32(&m_position[i].offset)) { + return false; + } + if (!pIO->Read32(&m_position[i].size)) { + return false; + } + } + + icCurveOffsetMap map; + icCurveElemSignature curveSig; + for (i=0; iSeek(pos, icSeekSet)!=(icInt32Number)pos) { + return false; + } + + if (!pIO->Read32(&curveSig)) { + return false; + } + m_curve[i] = CIccCurveSetCurve::Create(curveSig); + + if (!m_curve[i]) { + return false; + } + + if (pIO->Seek(pos, icSeekSet)!=(icInt32Number)pos) { + return false; + } + + if (!m_curve[i]->Read(m_position[i].size, pIO)) { + return false; + } + + map[m_position[i].offset] = m_curve[i]; + } + else { + m_curve[i] = map[m_position[i].offset]; + } + } + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCurveSet::Write(CIccIO *pIO) +{ + icElemTypeSignature sig = GetType(); + + if (!pIO) + return false; + + icUInt32Number elemStart = pIO->Tell(); + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (m_curve && m_nInputChannels) { + int i; + icCurvePtrMap map; + icUInt32Number start, end; + icUInt32Number zeros[2] = { 0, 0}; + icPositionNumber position; + + icUInt32Number startTable = pIO->Tell(); + + //First write empty position table + for (i=0; iWrite32(&zeros[0], 2)!=2) + return false; + } + + //Now Write curves + for (i=0; iTell(); + m_curve[i]->Write(pIO); + end = pIO->Tell(); + pIO->Sync32(); + position.offset = start - elemStart; + position.size = end - start; + map[m_curve[i]] = position; + } + m_position[i] = map[m_curve[i]]; + } + } + end = pIO->Tell(); + + //Back fill position table + pIO->Seek(startTable, icSeekSet); + for (i=0; iWrite32(&m_position[i].offset)) + return false; + if (!pIO->Write32(&m_position[i].size)) + return false; + } + + pIO->Seek(end, icSeekSet); + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCurveSet::Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE) +{ + if (!m_curve) + return false; + + int i; + for (i=0; iBegin()) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeCurveSet::Apply(CIccApplyMpe *pApply, icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) const +{ + int i; + for (i=0; iApply(*pSrcPixel++); + } +} + +/** + ****************************************************************************** + * Name: CIccMpeCurveSet::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccMpeCurveSet::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + icValidateStatus rv = CIccMultiProcessElement::Validate(sig, sReport, pMPE); + + bool empty=false; + if (m_curve) { + int i; + for (i=0; !empty && iValidate(sig, sReport, pMPE)); + } + } + } + else + empty = true; + + if (empty) { + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Element "; + sSigName = Info.GetSigName(GetType()); + sReport += sSigName; + sReport += " Has Empty Curve Element(s)!\r\n"; + return icValidateCriticalError; + } + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::CIccMpeMatrix + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeMatrix::CIccMpeMatrix() +{ + m_nReserved = 0; + m_nInputChannels = m_nOutputChannels = 0; + m_size = 0; + m_pMatrix = NULL; + m_pConstants = NULL; +} + + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::CIccMpeMatrix + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeMatrix::CIccMpeMatrix(const CIccMpeMatrix &matrix) +{ + m_nReserved = matrix.m_nReserved; + + m_nInputChannels = matrix.m_nInputChannels; + m_nOutputChannels = matrix.m_nOutputChannels; + + m_size = matrix.m_size; + if(matrix.m_pMatrix) { + int num = m_size * sizeof(icFloatNumber); + m_pMatrix = (icFloatNumber*)malloc(num); + memcpy(m_pMatrix, matrix.m_pMatrix, num); + } + else + m_pMatrix = NULL; + + if (matrix.m_pConstants) { + int num = m_nOutputChannels*sizeof(icFloatNumber); + m_pConstants = (icFloatNumber*)malloc(num); + memcpy(m_pConstants, matrix.m_pConstants, num); + } + else + m_pConstants = NULL; +} + +/** + ****************************************************************************** + * Name: &CIccMpeMatrix::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeMatrix &CIccMpeMatrix::operator=(const CIccMpeMatrix &matrix) +{ + m_nReserved = matrix.m_nReserved; + + m_nInputChannels = matrix.m_nInputChannels; + m_nOutputChannels = matrix.m_nOutputChannels; + + if (m_pMatrix) + free(m_pMatrix); + + m_size = matrix.m_size; + if (matrix.m_pMatrix) { + int num = m_size * sizeof(icFloatNumber); + m_pMatrix = (icFloatNumber*)malloc(num); + memcpy(m_pMatrix, matrix.m_pMatrix, num); + } + else + m_pMatrix = NULL; + + if (m_pConstants) + free(m_pConstants); + + if (matrix.m_pConstants) { + int num = m_nOutputChannels*sizeof(icFloatNumber); + m_pConstants = (icFloatNumber*)malloc(num); + memcpy(m_pConstants, matrix.m_pConstants, num); + } + else + m_pConstants = NULL; + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::~CIccMpeMatrix + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeMatrix::~CIccMpeMatrix() +{ + if (m_pMatrix) + free(m_pMatrix); + + if (m_pConstants) + free(m_pConstants); +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::SetSize + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeMatrix::SetSize(icUInt16Number nInputChannels, icUInt16Number nOutputChannels) +{ + if (m_pMatrix) + free(m_pMatrix); + + m_size = (icUInt32Number)nInputChannels * nOutputChannels; + + m_pMatrix = (icFloatNumber*)calloc(m_size, sizeof(icFloatNumber)); + m_pConstants = (icFloatNumber*)calloc(nOutputChannels, sizeof(icFloatNumber)); + + m_nInputChannels = nInputChannels; + m_nOutputChannels = nOutputChannels; +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeMatrix::Describe(std::string &sDescription) +{ + icChar buf[81]; + int i, j; + icFloatNumber *data = m_pMatrix; + + sprintf(buf, "BEGIN_ELEM_MATRIX %d %d\r\n", m_nInputChannels, m_nOutputChannels); + sDescription += buf; + + for (j=0; j size) + return false; + + if (!pIO) { + return false; + } + + icUInt16Number nInputChannels, nOutputChannels; + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&nInputChannels)) + return false; + + if (!pIO->Read16(&nOutputChannels)) + return false; + + SetSize(nInputChannels, nOutputChannels); + + if (!m_pMatrix) + return false; + + if (headerSize + m_size*sizeof(icFloat32Number) > size) + return false; + + //Read Matrix data + if (pIO->ReadFloat32Float(m_pMatrix, m_size)!=(icInt32Number)m_size) + return false; + + //Read Constant data + if (pIO->ReadFloat32Float(m_pConstants, m_nOutputChannels)!=m_nOutputChannels) + return false; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeMatrix::Write(CIccIO *pIO) +{ + icElemTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nOutputChannels)) + return false; + + if (m_pMatrix) { + if (pIO->WriteFloat32Float(m_pMatrix, m_size)!=(icInt32Number)m_size) + return false; + } + + //Write Constant data + if (m_pConstants) { + if (pIO->WriteFloat32Float(m_pConstants, m_nOutputChannels)!=m_nOutputChannels) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeMatrix::Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE) +{ + if (!m_pMatrix || !m_pConstants) + return false; + + if (m_nInputChannels==3 && m_nOutputChannels==3) + m_type = ic3x3Matrix; + else if (m_nInputChannels==3 && m_nOutputChannels==4) + m_type = ic3x4Matrix; + else if (m_nInputChannels==4 && m_nOutputChannels==3) + m_type = ic4x3Matrix; + else if (m_nInputChannels==4 && m_nOutputChannels==4) + m_type = ic4x4Matrix; + else + m_type = icOtherMatrix; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeMatrix::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeMatrix::Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const +{ + icFloatNumber *data = m_pMatrix; + switch (m_type) { + case ic3x3Matrix: + *dstPixel++ = data[ 0]*srcPixel[0] + data[ 1]*srcPixel[1] + data[ 2]*srcPixel[2] + m_pConstants[0]; + *dstPixel++ = data[ 3]*srcPixel[0] + data[ 4]*srcPixel[1] + data[ 5]*srcPixel[2] + m_pConstants[1]; + *dstPixel = data[ 6]*srcPixel[0] + data[ 7]*srcPixel[1] + data[ 8]*srcPixel[2] + m_pConstants[2]; + break; + + case ic3x4Matrix: + *dstPixel++ = data[ 0]*srcPixel[0] + data[ 1]*srcPixel[1] + data[ 2]*srcPixel[2] + m_pConstants[0]; + *dstPixel++ = data[ 3]*srcPixel[0] + data[ 4]*srcPixel[1] + data[ 5]*srcPixel[2] + m_pConstants[1]; + *dstPixel++ = data[ 6]*srcPixel[0] + data[ 7]*srcPixel[1] + data[ 8]*srcPixel[2] + m_pConstants[2]; + *dstPixel = data[ 9]*srcPixel[0] + data[10]*srcPixel[1] + data[11]*srcPixel[2] + m_pConstants[3]; + break; + + case ic4x3Matrix: + *dstPixel++ = data[ 0]*srcPixel[0] + data[ 1]*srcPixel[1] + data[ 2]*srcPixel[2] + data[ 3]*srcPixel[3] + m_pConstants[0]; + *dstPixel++ = data[ 4]*srcPixel[0] + data[ 5]*srcPixel[1] + data[ 6]*srcPixel[2] + data[ 7]*srcPixel[3] + m_pConstants[1]; + *dstPixel = data[ 8]*srcPixel[0] + data[ 9]*srcPixel[1] + data[10]*srcPixel[2] + data[11]*srcPixel[3] + m_pConstants[2]; + break; + + case ic4x4Matrix: + *dstPixel++ = data[ 0]*srcPixel[0] + data[ 1]*srcPixel[1] + data[ 2]*srcPixel[2] + data[ 3]*srcPixel[3] + m_pConstants[0]; + *dstPixel++ = data[ 4]*srcPixel[0] + data[ 5]*srcPixel[1] + data[ 6]*srcPixel[2] + data[ 7]*srcPixel[3] + m_pConstants[1]; + *dstPixel++ = data[ 8]*srcPixel[0] + data[ 9]*srcPixel[1] + data[10]*srcPixel[2] + data[11]*srcPixel[3] + m_pConstants[2]; + *dstPixel = data[12]*srcPixel[0] + data[13]*srcPixel[1] + data[14]*srcPixel[2] + data[15]*srcPixel[3] + m_pConstants[3]; + break; + + case icOtherMatrix: + default: + { + int i, j; + + for (j=0; jSetClipFunc(NoClip); + m_nInputChannels = pCLUT->GetInputDim(); + m_nOutputChannels = pCLUT->GetOutputChannels(); + } +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeCLUT::Describe(std::string &sDescription) +{ + if (m_pCLUT) { + m_pCLUT->DumpLut(sDescription, "ELEM_CLUT", icSigUnknownData, icSigUnknownData); + } +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCLUT::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt16Number) + + sizeof(icUInt16Number) + + 16 * sizeof(icUInt8Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&m_nInputChannels)) + return false; + + if (!pIO->Read16(&m_nOutputChannels)) + return false; + + icUInt8Number gridPoints[16]; + + if (pIO->Read8(gridPoints, 16)!=16) { + return false; + } + + m_pCLUT = new CIccCLUT((icUInt8Number)m_nInputChannels, (icUInt8Number)m_nOutputChannels, 4); + + if (!m_pCLUT) + return false; + + m_pCLUT->SetClipFunc(NoClip); + + m_pCLUT->Init(gridPoints); + + icFloatNumber *pData = m_pCLUT->GetData(0); + + if (!pData) + return false; + + icInt32Number nPoints = m_pCLUT->NumPoints()*m_nOutputChannels; + + if (pIO->ReadFloat32Float(pData,nPoints)!= nPoints) + return false; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCLUT::Write(CIccIO *pIO) +{ + icElemTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nOutputChannels)) + return false; + + if (m_pCLUT) { + icUInt8Number gridPoints[16]; + int i; + + for (i=0; i<16; i++) + gridPoints[i] = m_pCLUT->GridPoint(i); + + if (pIO->Write8(gridPoints, 16)!=16) + return false; + + icFloatNumber *pData = m_pCLUT->GetData(0); + icInt32Number nPoints = m_pCLUT->NumPoints()*m_nOutputChannels; + + if (pIO->WriteFloat32Float(pData, nPoints) != nPoints) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeCLUT::Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE) +{ + if (!m_pCLUT) + return false; + + m_pCLUT->Begin(); + + switch (m_nInputChannels) { + case 3: + if (nInterp==icElemInterpTetra) + m_interpType = ic3dInterpTetra; + else + m_interpType = ic3dInterp; + break; + case 4: + m_interpType = ic4dInterp; + break; + case 5: + m_interpType = ic5dInterp; + break; + case 6: + m_interpType = ic6dInterp; + break; + default: + m_interpType = icNdInterp; + break; + } + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeCLUT::Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const +{ + const CIccCLUT *pCLUT = m_pCLUT; + + switch(m_interpType) { + case ic3dInterpTetra: + pCLUT->Interp3dTetra(dstPixel, srcPixel); + break; + case ic3dInterp: + pCLUT->Interp3d(dstPixel, srcPixel); + break; + case ic4dInterp: + pCLUT->Interp4d(dstPixel, srcPixel); + break; + case ic5dInterp: + pCLUT->Interp5d(dstPixel, srcPixel); + break; + case ic6dInterp: + pCLUT->Interp6d(dstPixel, srcPixel); + break; + case icNdInterp: + pCLUT->InterpND(dstPixel, srcPixel); + break; + } +} + +/** + ****************************************************************************** + * Name: CIccMpeCLUT::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccMpeCLUT::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + icValidateStatus rv = CIccMultiProcessElement::Validate(sig, sReport, pMPE); + + if (!m_pCLUT) { + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Element "; + sSigName = Info.GetSigName(GetType()); + sReport += sSigName; + sReport += " Has No CLUT!\r\n"; + return icValidateCriticalError; + } + + return rv; + +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccMpeBasic.h b/library/src/main/cpp/icc/IccMpeBasic.h new file mode 100644 index 00000000..f146b909 --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeBasic.h @@ -0,0 +1,417 @@ +/** @file +File: IccMpeBasic.h + +Contains: Header for implementation of Basic CIccTagMPE elements + and supporting classes + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2005-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Jan 30, 2005 +// Initial CIccMpe prototype development +// +// -Nov 6, 2006 +// Prototype Merged into release +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCELEMBASIC_H +#define _ICCELEMBASIC_H + +#include "IccTagMPE.h" + + +//CIccFloatTag support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** +**************************************************************************** +* Class: CIccCurveSegment +* +* Purpose: +***************************************************************************** +*/ +class CIccCurveSegment +{ +public: + virtual ~CIccCurveSegment() {} + + static CIccCurveSegment* Create(icCurveSegSignature sig, icFloatNumber start, icFloatNumber end); + virtual CIccCurveSegment* NewCopy() const = 0; + + virtual icCurveSegSignature GetType() const = 0; + virtual const icChar *GetClassName() const = 0; + + virtual void Describe(std::string &sDescription)=0; + + virtual bool Read(icUInt32Number size, CIccIO *pIO)=0; + virtual bool Write(CIccIO *pIO)=0; + + virtual bool Begin(CIccCurveSegment *pPrevSeg) = 0; + virtual icFloatNumber Apply(icFloatNumber v) const =0; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const = 0; + + icFloatNumber StartPoint() { return m_startPoint; } + icFloatNumber EndPoint() { return m_endPoint;} + +protected: + icFloatNumber m_startPoint; + icFloatNumber m_endPoint; + icUInt32Number m_nReserved; +}; + + +/** +**************************************************************************** +* Class: CIccTagFormulaCurveSegment +* +* Purpose: The parametric curve segment +***************************************************************************** +*/ +class CIccFormulaCurveSegment : public CIccCurveSegment +{ +public: + CIccFormulaCurveSegment(icFloatNumber start, icFloatNumber end); + CIccFormulaCurveSegment(const CIccFormulaCurveSegment &seg); + CIccFormulaCurveSegment &operator=(const CIccFormulaCurveSegment &seg); + virtual CIccCurveSegment *NewCopy() const { return new CIccFormulaCurveSegment(*this);} + virtual ~CIccFormulaCurveSegment(); + + virtual icCurveSegSignature GetType() const { return icSigFormulaCurveSeg; } + virtual const icChar *GetClassName() const { return "CIccFormulaCurveSegment"; } + + virtual void Describe(std::string &sDescription); + + void SetFunction(icUInt16Number functionType, icUInt8Number num_parameters, icFloatNumber *parameters); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(CIccCurveSegment *pPrevSeg); + virtual icFloatNumber Apply(icFloatNumber v) const; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + +protected: + icUInt16Number m_nReserved2; + icUInt8Number m_nParameters; + icUInt16Number m_nFunctionType; + icFloatNumber *m_params; +}; + + +/** +**************************************************************************** +* Class: CIccSampledCurveSegment +* +* Purpose: The sampled curve segment +***************************************************************************** +*/ +class CIccSampledCurveSegment : public CIccCurveSegment +{ +public: + CIccSampledCurveSegment(icFloatNumber start, icFloatNumber end); + CIccSampledCurveSegment(const CIccSampledCurveSegment &ITPC); + CIccSampledCurveSegment &operator=(const CIccSampledCurveSegment &ParamCurveTag); + virtual CIccCurveSegment *NewCopy() const { return new CIccSampledCurveSegment(*this);} + virtual ~CIccSampledCurveSegment(); + + virtual icCurveSegSignature GetType() const { return icSigSampledCurveSeg; } + virtual const icChar *GetClassName() const { return "CIccSampledCurveSegment"; } + + virtual bool SetSize(icUInt32Number nSize, bool bZeroAlloc=true); //nSize must be >= 2 + virtual icUInt32Number GetSize() { return m_nCount; } + + virtual icFloatNumber *GetSamples() { return m_pSamples; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(CIccCurveSegment *pPrevSeg); + virtual icFloatNumber Apply(icFloatNumber v) const; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const ; + +protected: + icUInt32Number m_nCount; //number of samples used for interpolation + icFloatNumber *m_pSamples; //interpolation values - Note m_pSamples[0] is initialized from previous segment in Begin() + + icFloatNumber m_range; + icFloatNumber m_last; +}; + + +/** +**************************************************************************** +* Class: CIccCurveSetCurve +* +* Purpose: Base class for Curve Set Curves +***************************************************************************** +*/ +class CIccCurveSetCurve +{ +public: + virtual ~CIccCurveSetCurve() {} + + static CIccCurveSetCurve *Create(icCurveElemSignature sig); + virtual CIccCurveSetCurve *NewCopy() const = 0; + + virtual icCurveElemSignature GetType() const = 0; + virtual const icChar *GetClassName() const = 0; + + virtual void Describe(std::string &sDescription) = 0; + + virtual bool Read(icUInt32Number size, CIccIO *pIO) = 0; + virtual bool Write(CIccIO *pIO) = 0; + + virtual bool Begin() = 0; + virtual icFloatNumber Apply(icFloatNumber v) const = 0; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const = 0; + +protected: +}; + +typedef std::list CIccCurveSegmentList; + +/** +**************************************************************************** +* Class: CIccSegmentedCurve +* +* Purpose: The Curve Set Segmented Curve Type +***************************************************************************** +*/ +class CIccSegmentedCurve : public CIccCurveSetCurve +{ +public: + CIccSegmentedCurve(); + CIccSegmentedCurve(const CIccSegmentedCurve &ITPC); + CIccSegmentedCurve &operator=(const CIccSegmentedCurve &ParamCurveTag); + virtual CIccCurveSetCurve *NewCopy() const { return new CIccSegmentedCurve(*this);} + virtual ~CIccSegmentedCurve(); + + virtual icCurveElemSignature GetType() const { return icSigSementedCurve; } + virtual const icChar *GetClassName() const { return "CIccSegmentedCurve"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + void Reset(); + bool Insert(CIccCurveSegment *pCurveSegment); + + virtual bool Begin(); + virtual icFloatNumber Apply(icFloatNumber v) const; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + +protected: + CIccCurveSegmentList *m_list; + icUInt32Number m_nReserved1; + icUInt32Number m_nReserved2; +}; + +typedef CIccCurveSetCurve* icCurveSetCurvePtr; + +/** +**************************************************************************** +* Class: CIccMpeCurveSet +* +* Purpose: The curve set process element +***************************************************************************** +*/ +class CIccMpeCurveSet : public CIccMultiProcessElement +{ +public: + CIccMpeCurveSet(int nSize=0); + CIccMpeCurveSet(const CIccMpeCurveSet &curveSet); + CIccMpeCurveSet &operator=(const CIccMpeCurveSet &curveSet); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeCurveSet(*this);} + virtual ~CIccMpeCurveSet(); + + void SetSize(int nNewSize); + + bool SetCurve(int nIndex, icCurveSetCurvePtr newCurve); + + virtual icElemTypeSignature GetType() const { return icSigCurveSetElemType; } + virtual const icChar *GetClassName() const { return "CIccMpeCurveSet"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE); + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + +protected: + icCurveSetCurvePtr *m_curve; + + icPositionNumber *m_position; +}; + + +typedef enum { + ic3x3Matrix, + ic3x4Matrix, + ic4x3Matrix, + ic4x4Matrix, + icOtherMatrix +} icMatrixElemType; + +/** +**************************************************************************** +* Class: CIccMpeMatrix +* +* Purpose: The sampled float curve segment tag +***************************************************************************** +*/ +class CIccMpeMatrix : public CIccMultiProcessElement +{ +public: + CIccMpeMatrix(); + CIccMpeMatrix(const CIccMpeMatrix &ITPC); + CIccMpeMatrix &operator=(const CIccMpeMatrix &ParamCurveTag); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeMatrix(*this);} + virtual ~CIccMpeMatrix(); + + virtual icElemTypeSignature GetType() const { return icSigMatrixElemType; } + virtual const icChar *GetClassName() const { return "CIccMpeMatrix"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + void SetSize(icUInt16Number nInputChannels, icUInt16Number nOutputChannels); + + icFloatNumber *GetMatrix() {return m_pMatrix;} + icFloatNumber *GetConstants() {return m_pConstants;} + + virtual bool Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE); + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + +protected: + icFloatNumber *m_pMatrix; + icFloatNumber *m_pConstants; + icUInt32Number m_size; + icMatrixElemType m_type; +}; + +typedef enum { + ic3dInterpTetra, + ic3dInterp, + ic4dInterp, + ic5dInterp, + ic6dInterp, + icNdInterp, +} icCLUTElemType; + +/** +**************************************************************************** +* Class: CIccMpeCLUT +* +* Purpose: The sampled float curve segment tag +***************************************************************************** +*/ +class CIccMpeCLUT : public CIccMultiProcessElement +{ +public: + CIccMpeCLUT(); + CIccMpeCLUT(const CIccMpeCLUT &clut); + CIccMpeCLUT &operator=(const CIccMpeCLUT &clut); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeCLUT(*this);} + virtual ~CIccMpeCLUT(); + + virtual icElemTypeSignature GetType() const { return icSigCLutElemType; } + virtual const icChar *GetClassName() const { return "CIccMpeCLUT"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(icElemInterp nInterp, CIccTagMultiProcessElement *pMPE); + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *dstPixel, const icFloatNumber *srcPixel) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + + CIccCLUT *GetCLUT() { return m_pCLUT; } + void SetCLUT(CIccCLUT *pCLUT); + +protected: + CIccCLUT *m_pCLUT; + icCLUTElemType m_interpType; +}; + + +//CIccMPElements support +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //_ICCELEMBASIC_H diff --git a/library/src/main/cpp/icc/IccMpeFactory.cpp b/library/src/main/cpp/icc/IccMpeFactory.cpp new file mode 100644 index 00000000..fefdca5e --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeFactory.cpp @@ -0,0 +1,195 @@ +/** @file + File: IccMpeFactory.cpp + + Contains: Implementation of the CIccProcessElement class and creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Feb 4, 2006 +// Added CIccProcessElement Creation using factory support +// +////////////////////////////////////////////////////////////////////// + +#include "IccTagMPE.h" +#include "IccMpeBasic.h" +#include "IccMpeACS.h" +#include "IccMpeFactory.h" +#include "IccUtil.h" +#include "IccProfile.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +CIccMultiProcessElement* CIccBasicMpeFactory::CreateElement(icElemTypeSignature elemTypeSig) +{ + switch(elemTypeSig) { + case icSigCurveSetElemType: + return new CIccMpeCurveSet(); + + case icSigMatrixElemType: + return new CIccMpeMatrix(); + + case icSigCLutElemType: + return new CIccMpeCLUT(); + + case icSigBAcsElemType: + return new CIccMpeBAcs(); + + case icSigEAcsElemType: + return new CIccMpeEAcs(); + + default: + return new CIccMpeUnknown(); + } +} + +bool CIccBasicMpeFactory::GetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig) +{ + switch(elemTypeSig) { + case icSigCurveSetElemType: + elemName = "Curve Set Element"; + break; + + case icSigMatrixElemType: + elemName = "Matrix Element"; + break; + + case icSigCLutElemType: + elemName = "CLUT Element"; + break; + + default: + elemName = "Unknown Element Type"; + break; + } + + return true; +} + +std::auto_ptr CIccMpeCreator::theElementCreator; + +CIccMpeCreator::~CIccMpeCreator() +{ + IIccMpeFactory *pFactory = DoPopFactory(true); + + while (pFactory) { + delete pFactory; + pFactory = DoPopFactory(true); + } +} + +CIccMpeCreator* CIccMpeCreator::GetInstance() +{ + if (!theElementCreator.get()) { + theElementCreator = CIccMpeCreatorPtr(new CIccMpeCreator); + + theElementCreator->DoPushFactory(new CIccBasicMpeFactory); + } + + return theElementCreator.get(); +} + +CIccMultiProcessElement* CIccMpeCreator::DoCreateElement(icElemTypeSignature elemTypeSig) +{ + CIccMpeFactoryList::iterator i; + CIccMultiProcessElement *rv = NULL; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + rv = (*i)->CreateElement(elemTypeSig); + if (rv) + break; + } + return rv; +} + +bool CIccMpeCreator::DoGetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig) +{ + CIccMpeFactoryList::iterator i; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + if ((*i)->GetElementSigName(elemName, elemTypeSig)) + return true; + } + + return false; +} + +void CIccMpeCreator::DoPushFactory(IIccMpeFactory *pFactory) +{ + factoryStack.push_front(pFactory); +} + +IIccMpeFactory* CIccMpeCreator::DoPopFactory(bool bAll /*=false*/) +{ + if (factoryStack.size()>0) { + CIccMpeFactoryList::iterator i=factoryStack.begin(); + IIccMpeFactory* rv = (*i); + factoryStack.pop_front(); + return rv; + } + return NULL; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccMpeFactory.h b/library/src/main/cpp/icc/IccMpeFactory.h new file mode 100644 index 00000000..b5a413f7 --- /dev/null +++ b/library/src/main/cpp/icc/IccMpeFactory.h @@ -0,0 +1,296 @@ +/** @file + File: IccMpeFactory.h + + Contains: Header for implementation of CIccMpeFactory class and + creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2005-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Feb 4, 2006 +// A CIccMpeCreator singleton class has been added to provide general +// support for dynamically creating element classes using a element signature. +// Prototype and private element type support can be added to the system +// by pushing additional IIccMpeFactory based objects to the +// singleton CIccMpeCreator object. +// +// -Nov 6, 2006 +// Merged into release +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCMPEFACTORY_H +#define _ICCMPEFACTORY_H + +#include "IccDefs.h" +#include +#include + +//CIccProcessElement factory support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class CIccMultiProcessElement; + +/** + *********************************************************************** + * Class: IIccMpeFactory + * + * Purpose: + * IIccMpeFactory is a factory pattern interface for CIccProcessElement + * creation. + * This class is pure virtual. + *********************************************************************** + */ +class ICCPROFLIB_API IIccMpeFactory +{ +public: + virtual ~IIccMpeFactory() {} + + /** + * Function: CreateElement(elemTypeSig) + * Create a element of type elemTypeSig. + * + * Parameter(s): + * elemTypeSig = signature of the ICC element type for the element to + * be created + * + * Returns a new CIccProcessElement object of the given signature type. + * If the element factory doesn't support creation of elements of type + * elemTypeSig then it should return NULL. + */ + virtual CIccMultiProcessElement* CreateElement(icElemTypeSignature elemTypeSig)=0; + + /** + * Function: GetElementSigName(elemTypeSig) + * Get display name of elemTypeSig. + * + * Parameter(s): + * elemName = string to put element name into, + * elemTypeSig = signature of the ICC element type to get a name for + * + * Returns true if element type is recognized by the factory, false if + * the factory doesn't create elemTypeSig elements. + */ + virtual bool GetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig)=0; +}; + + +//A CIccMpeFactoryList is used by CIccMpeCreator to keep track of element +//creation factories +typedef std::list CIccMpeFactoryList; + + +/** + *********************************************************************** + * Class: CIccBasicMpeFactory + * + * Purpose: + * CIccBasicMpeFactory provides creation of CIccProcessElement's + * defined by the ICC profile specification. The CIccMpeCreator always + * creates a CIccBasicElemFactory. + *********************************************************************** + */ +class CIccBasicMpeFactory : public IIccMpeFactory +{ +public: + /** + * Function: CreateElement(elemTypeSig) + * Create a element of type elemTypeSig. + * + * Parameter(s): + * elemTypeSig = signature of the ICC element type for the element to be created + * + * Returns a new CIccProcessElement object of the given signature type. + * Unrecognized elemTypeSig's will be created as a CIccProcessElementUnknown object. + */ + virtual CIccMultiProcessElement* CreateElement(icElemTypeSignature elementSig); + + /** + * Function: GetElementSigName(elemTypeSig) + * Get display name of elemTypeSig. + * + * Parameter(s): + * elemName = string to put element name into, + * elemTypeSig = signature of the ICC element type to get a name for + * + * Returns true if element type is recognized by the factory, false if the + * factory doesn't create elemTypeSig elements. + */ + virtual bool GetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig); +}; + +class CIccMpeCreator; + +typedef std::auto_ptr CIccMpeCreatorPtr; + +/** + *********************************************************************** + * Class: CIccMpeCreator + * + * Purpose: + * CIccMpeCreator uses a singleton pattern to provide dynamically + * upgradeable CIccProcessElement derived object creation based on + * element signature. + *********************************************************************** + */ +class CIccMpeCreator +{ +public: + ~CIccMpeCreator(); + + /** + * Function: CreateElement(elemTypeSig) + * Create a element of type elemTypeSig. + * + * Parameter(s): + * elemTypeSig = signature of the ICC element type for the element to + * be created + * + * Returns a new CIccProcessElement object of the given signature type. + * Each factory in the factoryStack is used until a factory supports the + * signature type. + */ + static CIccMultiProcessElement* CreateElement(icElemTypeSignature elemTypeSig) + { return CIccMpeCreator::GetInstance()->DoCreateElement(elemTypeSig); } + + /** + * Function: GetElementSigName(elemTypeSig) + * Get display name of elemTypeSig. + * + * Parameter(s): + * elemName = string to put element name into + * elemTypeSig = signature of the ICC element type to get a name for + * + * Returns true if element type is recognized by any factory, false if all + * factories do not create elemTypeSig elements. If element type is not + * recognized by any factories a suitable display name will be placed in + * elemName. + */ + static bool GetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig) + { return CIccMpeCreator::GetInstance()->DoGetElementSigName(elemName, elemTypeSig); } + + /** + * Function: PushFactory(pFactory) + * Add an IIccMpeFactory to the stack of element factories tracked by + * the system. + * + * Parameter(s): + * pFactory = pointer to an IIccMpeFactory object to add to the + * system. The pFactory must be created with new, and will be owned + * CIccMpeCreator until popped off the stack using PopFactory(). + * Any factories not popped off will be taken care of properly on + * application shutdown. + * + */ + static void PushFactory(IIccMpeFactory *pFactory) + { CIccMpeCreator::GetInstance()->CIccMpeCreator::DoPushFactory(pFactory); } + + /** + * Function: PopFactory() + * Remove the top IIccMpeFactory from the stack of element factories + * tracked by the system. + * + * Parameter(s): + * None + * + * Returns the top IIccMpeFactory from the stack of element factories + * tracked by the system. The returned element factory is no longer + * owned by the system and needs to be deleted to avoid memory leaks. + * + * Note: The initial CIccBasicElemFactory cannot be popped off the stack. + */ + static IIccMpeFactory* PopFactory() + { return CIccMpeCreator::GetInstance()->DoPopFactory(); } + +private: + /**Only GetInstance() can create the singleton*/ + CIccMpeCreator() { } + + /** + * Function: GetInstance() + * Private static function to access singleton CiccElementCreator Object. + * + * Parameter(s): + * None + * + * Returns the singleton CIccMpeCreator object. It will allocate + * a new one and push a single CIccSpecElement Factory object onto the + * factory stack if the singleton has not been intialized. + */ + static CIccMpeCreator* GetInstance(); + + CIccMultiProcessElement* DoCreateElement(icElemTypeSignature elemTypeSig); + bool DoGetElementSigName(std::string &elemName, icElemTypeSignature elemTypeSig); + void DoPushFactory(IIccMpeFactory *pFactory); + IIccMpeFactory* DoPopFactory(bool bAll=false); + + static CIccMpeCreatorPtr theElementCreator; + + CIccMpeFactoryList factoryStack; +}; + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif //_ICCMPEFACTORY_H diff --git a/library/src/main/cpp/icc/IccPrmg.cpp b/library/src/main/cpp/icc/IccPrmg.cpp new file mode 100644 index 00000000..fba1ebec --- /dev/null +++ b/library/src/main/cpp/icc/IccPrmg.cpp @@ -0,0 +1,298 @@ +/** @file +File: IccPRMG.cpp + +Contains: Implementation of CIccPRMG class + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2005-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +#include "IccPrmg.h" +#include "IccUtil.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/********************************************************************** + * The following table is from the PRMG specification. + * + * The first dimension corresponds to hue values going from 0 to 360 + * degrees in ten degree intervals (with 360 duplicating 0 for + * interpolation purposes. + * + * The second dimension corresponds to increasing lightness values. The + * first entry is for 3.5 L*, succeeding entries are for L* values + * increasing by 5 L* from 5 to 100. L* values below 3.5 or above 100 + * are considered to be out of gamut. + */ +static icFloatNumber icPRMG_Chroma[37][21] = { + {0, 11, 26, 39, 52, 64, 74, 83, 91, 92, 91, 87, 82, 75, 67, 57, 47, 37, 25, 13, 0}, + {0, 10, 24, 38, 50, 62, 73, 82, 90, 92, 91, 87, 82, 75, 67, 58, 48, 37, 26, 13, 0}, + {0, 10, 23, 37, 50, 62, 73, 84, 93, 94, 94, 90, 85, 78, 70, 60, 50, 39, 27, 14, 0}, + {0, 9, 22, 35, 48, 61, 74, 86, 98, 100, 101, 96, 90, 83, 75, 65, 54, 42, 30, 15, 0}, + {0, 8, 21, 34, 47, 60, 73, 83, 93, 97, 101, 99, 97, 90, 83, 73, 61, 47, 34, 17, 0}, + {0, 8, 20, 32, 43, 55, 66, 77, 88, 95, 99, 101, 100, 98, 92, 85, 72, 56, 40, 20, 0}, + {0, 7, 17, 27, 37, 47, 57, 67, 76, 84, 91, 96, 100, 102, 103, 98, 90, 72, 51, 26, 0}, + {0, 6, 16, 25, 34, 43, 52, 60, 68, 76, 83, 90, 96, 100, 104, 107, 109, 100, 74, 37, 0}, + {0, 6, 15, 23, 32, 40, 48, 57, 64, 71, 78, 85, 91, 97, 103, 107, 110, 113, 110, 70, 0}, + {0, 6, 14, 22, 30, 39, 47, 55, 62, 68, 75, 82, 88, 95, 101, 106, 112, 117, 120, 123, 0}, + {0, 6, 14, 22, 30, 38, 46, 54, 61, 68, 74, 81, 88, 94, 100, 106, 109, 112, 112, 92, 0}, + {0, 6, 14, 22, 31, 39, 47, 55, 63, 69, 76, 83, 89, 96, 100, 103, 106, 107, 102, 75, 0}, + {0, 6, 15, 24, 32, 41, 49, 58, 66, 73, 80, 87, 93, 98, 101, 102, 99, 91, 73, 50, 0}, + {0, 6, 16, 25, 35, 44, 54, 63, 72, 80, 87, 93, 97, 101, 99, 94, 86, 73, 56, 34, 0}, + {0, 7, 18, 28, 38, 48, 57, 67, 77, 86, 95, 98, 101, 97, 93, 85, 75, 61, 44, 26, 0}, + {0, 7, 19, 30, 40, 51, 62, 72, 83, 92, 97, 99, 96, 91, 85, 76, 66, 52, 37, 22, 0}, + {0, 7, 20, 32, 44, 56, 68, 80, 92, 96, 99, 97, 92, 87, 79, 70, 59, 46, 33, 19, 0}, + {0, 8, 20, 32, 43, 53, 64, 75, 85, 91, 96, 93, 89, 82, 75, 65, 55, 42, 30, 17, 0}, + {0, 8, 20, 31, 41, 52, 62, 72, 81, 87, 92, 90, 86, 79, 71, 61, 52, 40, 28, 15, 0}, + {0, 8, 20, 30, 40, 50, 60, 68, 76, 82, 87, 85, 82, 76, 69, 60, 50, 39, 27, 14, 0}, + {0, 8, 20, 30, 38, 47, 56, 63, 70, 76, 82, 81, 77, 72, 66, 58, 49, 38, 27, 14, 0}, + {0, 8, 20, 29, 37, 46, 53, 60, 66, 73, 79, 80, 75, 70, 64, 57, 49, 38, 27, 14, 0}, + {0, 8, 20, 29, 37, 45, 52, 59, 65, 71, 76, 75, 72, 68, 63, 56, 48, 38, 27, 14, 0}, + {0, 9, 20, 29, 38, 46, 53, 59, 65, 70, 75, 73, 71, 66, 61, 54, 46, 36, 26, 13, 0}, + {0, 10, 22, 31, 40, 48, 55, 61, 67, 71, 74, 70, 66, 61, 56, 49, 41, 32, 23, 12, 0}, + {0, 11, 24, 34, 43, 51, 59, 65, 70, 73, 71, 68, 63, 58, 52, 45, 38, 30, 21, 11, 0}, + {0, 14, 27, 38, 48, 57, 64, 69, 73, 73, 70, 66, 61, 56, 50, 43, 35, 28, 20, 10, 0}, + {0, 17, 32, 45, 55, 65, 70, 75, 75, 73, 70, 66, 61, 55, 49, 42, 34, 27, 19, 10, 0}, + {0, 21, 42, 55, 68, 75, 81, 80, 79, 76, 72, 67, 61, 55, 49, 41, 34, 26, 18, 9, 0}, + {0, 26, 52, 68, 83, 86, 89, 87, 84, 80, 75, 69, 63, 57, 50, 42, 35, 27, 18, 10, 0}, + {0, 25, 69, 82, 95, 94, 93, 91, 88, 85, 79, 73, 66, 59, 52, 44, 36, 28, 19, 10, 0}, + {0, 21, 51, 74, 91, 97, 100, 98, 95, 90, 84, 77, 70, 63, 55, 47, 39, 30, 20, 10, 0}, + {0, 18, 41, 62, 79, 91, 102, 101, 98, 95, 89, 83, 76, 68, 60, 51, 42, 32, 22, 11, 0}, + {0, 16, 35, 53, 71, 82, 91, 100, 104, 102, 98, 91, 84, 76, 67, 57, 47, 36, 24, 12, 0}, + {0, 14, 31, 46, 61, 73, 83, 92, 101, 103, 99, 95, 89, 80, 71, 61, 50, 38, 26, 13, 0}, + {0, 12, 28, 42, 55, 68, 77, 86, 94, 96, 93, 90, 85, 77, 68, 58, 48, 37, 25, 13, 0}, + {0, 11, 26, 39, 52, 64, 74, 83, 91, 92, 91, 87, 82, 75, 67, 57, 47, 37, 25, 13, 0}, +}; + +CIccPRMG::CIccPRMG() +{ + m_nTotal = m_nDE1 = m_nDE2 = m_nDE3 = m_nDE5 = m_nDE10 = 0; + + m_bPrmgImplied = false; +} + +icFloatNumber CIccPRMG::GetChroma(icFloatNumber L, icFloatNumber h) +{ + if (L<3.5 || L>100.0) + return -1; + + int nHIndex, nLIndex; + icFloatNumber dHFraction, dLFraction; + + while (h<0.0) + h+=360.0; + + while (h>=360.0) + h-=360.0; + + nHIndex = (int)(h/10.0); + dHFraction = (icFloatNumber)((h - nHIndex*10.0)/10.0); + + if (L<5) { + nLIndex = 0; + dLFraction = (icFloatNumber)((L-3.5) / (5.0-3.5)); + } + else if (L==100.0) { + nLIndex = 19; + dLFraction = 1.0; + } + else { + nLIndex = (int)((L-5.0)/5.0) + 1; + dLFraction = (icFloatNumber)((L-nLIndex*5.0)/5.0); + } + + icFloatNumber dInvLFraction = (icFloatNumber)(1.0 - dLFraction); + + icFloatNumber ch1 = icPRMG_Chroma[nHIndex][nLIndex]*dInvLFraction + icPRMG_Chroma[nHIndex][nLIndex+1]*dLFraction; + icFloatNumber ch2 = icPRMG_Chroma[nHIndex+1][nLIndex]*dInvLFraction + icPRMG_Chroma[nHIndex+1][nLIndex+1]*dLFraction; + + return (icFloatNumber)(ch1*(1.0-dHFraction) + ch2 * 1.0*dHFraction); +} + +bool CIccPRMG::InGamut(icFloatNumber L, icFloatNumber c, icFloatNumber h) +{ + icFloatNumber dChroma = GetChroma(L, h); + + if (dChroma<0.0 || c>dChroma) + return false; + + return true; +} + +bool CIccPRMG::InGamut(icFloatNumber *Lab) +{ + icFloatNumber Lch[3]; + + icLab2Lch(Lch, Lab); + return InGamut(Lch[0], Lch[1], Lch[2]); +} + +icStatusCMM CIccPRMG::EvaluateProfile(CIccProfile *pProfile, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, bool buseMpeTags/* =true */) +{ + if (!pProfile) + { + return icCmmStatCantOpenProfile; + } + + if (pProfile->m_Header.deviceClass!=icSigInputClass && + pProfile->m_Header.deviceClass!=icSigDisplayClass && + pProfile->m_Header.deviceClass!=icSigOutputClass && + pProfile->m_Header.deviceClass!=icSigColorSpaceClass) + { + return icCmmStatInvalidProfile; + } + + m_bPrmgImplied = false; + if (nIntent==icPerceptual || nIntent==icSaturation) { + icTagSignature rigSig = (icTagSignature)(icSigPerceptualRenderingIntentGamutTag + ((icUInt32Number)nIntent)%4); + CIccTag *pSigTag = pProfile->FindTag(rigSig); + + if (pSigTag && pSigTag->GetType()==icSigSignatureType) { + CIccTagSignature *pSig = (CIccTagSignature*)pSigTag; + + if (pSig->GetValue()==icSigPerceptualReferenceMediumGamut) + m_bPrmgImplied = true; + } + } + + CIccCmm Lab2Dev2Lab(icSigLabData, icSigLabData, false); + + icStatusCMM result = Lab2Dev2Lab.AddXform(*pProfile, nIntent, nInterp, icXformLutColor, buseMpeTags); + if (result != icCmmStatOk) { + return result; + } + + result = Lab2Dev2Lab.AddXform(*pProfile, nIntent, nInterp, icXformLutColor, buseMpeTags); + if (result != icCmmStatOk) { + return result; + } + + result = Lab2Dev2Lab.Begin(); + if (result != icCmmStatOk) { + return result; + } + icFloatNumber pcs[3], Lab1[3], Lab2[3], dE; + + m_nTotal = m_nDE1 = m_nDE2 = m_nDE3 = m_nDE5 = m_nDE10 = 0; + + for (pcs[0]=0.0; pcs[0]<=1.0; pcs[0] += (icFloatNumber)0.01) { + for (pcs[1]=0.0; pcs[1]<=1.0; pcs[1] += (icFloatNumber)0.01) { + for (pcs[2]=0.0; pcs[2]<=1.0; pcs[2] += (icFloatNumber)0.01) { + memcpy(Lab1, pcs, 3*sizeof(icFloatNumber)); + icLabFromPcs(Lab1); + if (InGamut(Lab1)) { + Lab2Dev2Lab.Apply(Lab2, pcs); + icLabFromPcs(Lab2); + + dE = icDeltaE(Lab1, Lab2); + m_nTotal++; + + if (dE<=1.0) { + m_nDE1++; + m_nDE2++; + m_nDE3++; + m_nDE5++; + m_nDE10++; + } + else if (dE<=2.0) { + m_nDE2++; + m_nDE3++; + m_nDE5++; + m_nDE10++; + } + else if (dE<=3.0) { + m_nDE3++; + m_nDE5++; + m_nDE10++; + } + else if (dE<=5.0) { + m_nDE5++; + m_nDE10++; + } + else if (dE<=10.0) { + m_nDE10++; + } + } + } + } + } + + return icCmmStatOk; +} + +icStatusCMM CIccPRMG::EvaluateProfile(const icChar *szProfilePath, icRenderingIntent nIntent/* =icUnknownIntent */, + icXformInterp nInterp/* =icInterpLinear */, bool buseMpeTags/* =true */) +{ + CIccProfile *pProfile = ReadIccProfile(szProfilePath); + + if (!pProfile) + return icCmmStatCantOpenProfile; + + icStatusCMM result = EvaluateProfile(pProfile, nIntent, nInterp, buseMpeTags); + + delete pProfile; + + return result; +} + + +#ifdef USESAMPLEICCNAMESPACE +} +#endif diff --git a/library/src/main/cpp/icc/IccPrmg.h b/library/src/main/cpp/icc/IccPrmg.h new file mode 100644 index 00000000..23338d1e --- /dev/null +++ b/library/src/main/cpp/icc/IccPrmg.h @@ -0,0 +1,105 @@ +/** @file +File: IccPrmg.h + +Contains: Header for implementation of CIccPrmg class + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2007-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Oct 27, 2007 +// Initial implementation of class CIccPRMG +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCPRMG_H +#define _ICCPRMG_H + +#include "IccCmm.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class CIccPRMG +{ +public: + CIccPRMG(); + + icFloatNumber GetChroma(icFloatNumber L, icFloatNumber h); + + bool InGamut(icFloatNumber *Lab); + bool InGamut(icFloatNumber L, icFloatNumber c, icFloatNumber h); + + icStatusCMM EvaluateProfile(CIccProfile *pProfile, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, bool buseMpeTags=true); + icStatusCMM EvaluateProfile(const icChar *szProfilePath, icRenderingIntent nIntent=icUnknownIntent, + icXformInterp nInterp=icInterpLinear, bool buseMpeTags=true); + + icUInt32Number m_nDE1, m_nDE2, m_nDE3, m_nDE5, m_nDE10, m_nTotal; + + bool m_bPrmgImplied; +}; + +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif \ No newline at end of file diff --git a/library/src/main/cpp/icc/IccProfLib.dsp b/library/src/main/cpp/icc/IccProfLib.dsp new file mode 100644 index 00000000..ba54a5a9 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib.dsp @@ -0,0 +1,270 @@ +# Microsoft Developer Studio Project File - Name="IccProfLib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=IccProfLib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "IccProfLib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "IccProfLib.mak" CFG="IccProfLib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "IccProfLib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "IccProfLib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "IccProfLib" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "IccProfLib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "IccProfLib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FD /GZ /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "IccProfLib - Win32 Release" +# Name "IccProfLib - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\IccApplyBPC.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccCmm.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccConvertUTF.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccEval.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccIO.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeACS.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeBasic.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccPrmg.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccProfile.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagBasic.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagDict.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagLut.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagMPE.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagProfSeqId.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccUtil.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccXformFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\md5.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\IccApplyBPC.h +# End Source File +# Begin Source File + +SOURCE=.\IccCmm.h +# End Source File +# Begin Source File + +SOURCE=.\IccConvertUTF.h +# End Source File +# Begin Source File + +SOURCE=.\IccDefs.h +# End Source File +# Begin Source File + +SOURCE=.\IccEval.h +# End Source File +# Begin Source File + +SOURCE=.\IccIO.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeACS.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeBasic.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeFactory.h +# End Source File +# Begin Source File + +SOURCE=.\IccPrmg.h +# End Source File +# Begin Source File + +SOURCE=.\IccProfile.h +# End Source File +# Begin Source File + +SOURCE=.\IccProfLibConf.h +# End Source File +# Begin Source File + +SOURCE=.\IccTag.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagBasic.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagDict.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagFactory.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagLut.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagMPE.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagProfSeqId.h +# End Source File +# Begin Source File + +SOURCE=.\IccUtil.h +# End Source File +# Begin Source File + +SOURCE=.\IccXformFactory.h +# End Source File +# Begin Source File + +SOURCE=.\icProfileHeader.h +# End Source File +# Begin Source File + +SOURCE=.\MainPage.h +# End Source File +# Begin Source File + +SOURCE=.\md5.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\Readme.txt +# End Source File +# End Target +# End Project diff --git a/library/src/main/cpp/icc/IccProfLib.vcproj b/library/src/main/cpp/icc/IccProfLib.vcproj new file mode 100644 index 00000000..53038ea5 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib.vcproj @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfLibConf.h b/library/src/main/cpp/icc/IccProfLibConf.h new file mode 100644 index 00000000..b390cabe --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLibConf.h @@ -0,0 +1,173 @@ +/** @file + File: IccProfLibConf.h + + Contains: Platform Specific Configuration + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +/* Header file guard bands */ +#ifndef ICCCONFIG_h +#define ICCCONFIG_h + +//Define the following to use namespace +//#define USESAMPLEICCNAMESPACE + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +//PC, visual C++ +#if defined(_MSC_VER) && !defined(__MWERKS__) && (defined(_M_IX86) || defined(_M_X64) || defined(__amd64__)) + + //Define how 64 bit integers are represented + #define ICCUINT64 unsigned __int64 + #define ICCINT64 __int64 + #define ICUINT64TYPE unsigned __int64 + #define ICINT64TYPE __int64 + + #define ICCUINT32 unsigned long + #define ICCINT32 long + #define ICUINT32TYPE unsigned long + #define ICINT32TYPE long + + #define USE_WINDOWS_MB_SUPPORT + #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + //#include //For Multibyte Translation Support + + #define ICC_BYTE_ORDER_LITTLE_ENDIAN + + #if defined(ICCPROFLIBDLL_EXPORTS) + #define ICCPROFLIB_API _declspec(dllexport) + #define ICCPROFLIB_EXTERN + #elif defined(ICCPROFLIBDLL_IMPORTS) + #define ICCPROFLIB_API _declspec(dllimport) + #define ICCPROFLIB_EXTERN extern + #else //just a regular lib + #define ICCPROFLIB_API + #define ICCPROFLIB_EXTERN + #endif + + //Since msvc doesn't support cbrtf use pow instead + #define ICC_CBRTF(v) pow((double)(v), 1.0/3.0) + + #if (_MSC_VER < 1300) + #define ICC_UNSUPPORTED_TAG_DICT + #endif + +#else // non-PC, perhaps Mac, Linux, or Solaris + + #define ICCUINT64 unsigned long long + #define ICCINT64 long long + #define ICUINT64TYPE unsigned long long + #define ICINT64TYPE long long + + #include + + //Make sure that 32 bit values are set correctly + #define ICCUINT32 uint32_t + #define ICCINT32 int32_t + #define ICUINT32TYPE uint32_t + #define ICINT32TYPE int32_t + + #if defined(__APPLE__) + #if defined(__LITTLE_ENDIAN__) + #define ICC_BYTE_ORDER_LITTLE_ENDIAN + #else + #define ICC_BYTE_ORDER_BIG_ENDIAN + #endif + + #else // Sun Solaris or Linux + #if defined(__sun__) + #define ICC_BYTE_ORDER_BIG_ENDIAN + #else + #define ICC_BYTE_ORDER_LITTLE_ENDIAN + #endif + #endif + + #define ICCPROFLIB_API + #define ICCPROFLIB_EXTERN + #define stricmp strcasecmp + + //Define ICC_CBRTF as a call to cbrtf (replace with pow if system doesn't support cbrtf) + #define ICC_CBRTF(v) cbrtf(v) + +// #define ICC_WCHAR_32BIT + + #define ICC_ENUM_CONVENIENCE + +#endif + +// remove comment below if you want LAB to XYZ conversions to not clip negative XYZ values +#define SAMPLEICC_NOCLIPLABTOXYZ + +#ifdef SAMPLEICCCMM_EXPORTS +#define MAKE_A_DLL +#endif + +#ifdef MAKE_A_DLL +#define SAMPLEICCEXPORT __declspec( dllexport) +#else +#define SAMPLEICCEXPORT __declspec( dllimport) +#endif + +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //ICCCOFIG_h diff --git a/library/src/main/cpp/icc/IccProfLibVer.h b/library/src/main/cpp/icc/IccProfLibVer.h new file mode 100644 index 00000000..f7f4e113 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLibVer.h @@ -0,0 +1,3 @@ +#ifndef ICCPROFLIBVER +#define ICCPROFLIBVER "1.6.11" +#endif diff --git a/library/src/main/cpp/icc/IccProfLib_CRTDLL.dsp b/library/src/main/cpp/icc/IccProfLib_CRTDLL.dsp new file mode 100644 index 00000000..27a29710 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_CRTDLL.dsp @@ -0,0 +1,258 @@ +# Microsoft Developer Studio Project File - Name="IccProfLib_CRTDLL" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=IccProfLib_CRTDLL - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "IccProfLib_CRTDLL.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "IccProfLib_CRTDLL.mak" CFG="IccProfLib_CRTDLL - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "IccProfLib_CRTDLL - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "IccProfLib_CRTDLL - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "IccProfLib_CRTDLL" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "IccProfLib_CRTDLL - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release_CRTDLL" +# PROP BASE Intermediate_Dir "Release_CRTDLL" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release_CRTDLL" +# PROP Intermediate_Dir "Release_CRTDLL" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "IccProfLib_CRTDLL - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_CRTDLL" +# PROP BASE Intermediate_Dir "Debug_CRTDLL" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_CRTDLL" +# PROP Intermediate_Dir "Debug_CRTDLL" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FD /GZ /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "IccProfLib_CRTDLL - Win32 Release" +# Name "IccProfLib_CRTDLL - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\IccApplyBPC.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccCmm.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccConvertUTF.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccEval.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccIO.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeACS.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeBasic.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccMpeFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccPrmg.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccProfile.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagBasic.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagBasic.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagDict.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagLut.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagLut.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagMPE.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccTagProfSeqId.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccUtil.cpp +# End Source File +# Begin Source File + +SOURCE=.\IccXformFactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\md5.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\IccApplyBPC.h +# End Source File +# Begin Source File + +SOURCE=.\IccCmm.h +# End Source File +# Begin Source File + +SOURCE=.\IccConvertUTF.h +# End Source File +# Begin Source File + +SOURCE=.\IccDefs.h +# End Source File +# Begin Source File + +SOURCE=.\IccEval.h +# End Source File +# Begin Source File + +SOURCE=.\IccIO.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeACS.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeBasic.h +# End Source File +# Begin Source File + +SOURCE=.\IccMpeFactory.h +# End Source File +# Begin Source File + +SOURCE=.\IccPrmg.h +# End Source File +# Begin Source File + +SOURCE=.\IccProfile.h +# End Source File +# Begin Source File + +SOURCE=.\IccProfLib_CRTDLLConf.h +# End Source File +# Begin Source File + +SOURCE=.\IccTag.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagDict.h +# End Source File +# Begin Source File + +SOURCE=.\IccTagProfSeqId.h +# End Source File +# Begin Source File + +SOURCE=.\IccUtil.h +# End Source File +# Begin Source File + +SOURCE=.\IccXformFactory.h +# End Source File +# Begin Source File + +SOURCE=.\MainPage.h +# End Source File +# Begin Source File + +SOURCE=.\md5.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\Readme.txt +# End Source File +# End Target +# End Project diff --git a/library/src/main/cpp/icc/IccProfLib_CRTDLL.vcproj b/library/src/main/cpp/icc/IccProfLib_CRTDLL.vcproj new file mode 100644 index 00000000..17c04d2f --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_CRTDLL.vcproj @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfLib_CRTDLL_v7.vcproj b/library/src/main/cpp/icc/IccProfLib_CRTDLL_v7.vcproj new file mode 100644 index 00000000..355029ae --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_CRTDLL_v7.vcproj @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfLib_CRTDLL_v8.vcproj b/library/src/main/cpp/icc/IccProfLib_CRTDLL_v8.vcproj new file mode 100644 index 00000000..41484103 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_CRTDLL_v8.vcproj @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfLib_v7.vcproj b/library/src/main/cpp/icc/IccProfLib_v7.vcproj new file mode 100644 index 00000000..f0f6e293 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_v7.vcproj @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfLib_v8.vcproj b/library/src/main/cpp/icc/IccProfLib_v8.vcproj new file mode 100644 index 00000000..ab5bddf8 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfLib_v8.vcproj @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/cpp/icc/IccProfile.cpp b/library/src/main/cpp/icc/IccProfile.cpp new file mode 100644 index 00000000..bf038c47 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfile.cpp @@ -0,0 +1,2458 @@ +/** @file + File: IccProfile.cpp + + Contains: Implementation of the CIccProfile class. + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) + #pragma warning( disable: 4786) //disable warning in +#endif +#include +#include +#include "IccProfile.h" +#include "IccTag.h" +#include "IccIO.h" +#include "IccUtil.h" +#include "md5.h" + + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +/** + ************************************************************************** + * Name: CIccProfile::CIccProfile + * + * Purpose: + * Constructor + ************************************************************************** + */ +CIccProfile::CIccProfile() +{ + m_pAttachIO = NULL; + memset(&m_Header, 0, sizeof(m_Header)); + m_Tags = new(TagEntryList); + m_TagVals = new(TagPtrList); +} + +/** + ************************************************************************** + * Name: CIccProfile::CIccProfile + * + * Purpose: + * Copy Constructor. The copy constructor makes the copy of the + * CIccProfile object in it's present state. It DOES NOT make a + * copy of the m_pAttachIO member variable. Any operation with the + * IO object should be done before making a copy. + * + * Args: + * Profile = CIccProfile object which is to be copied. + ************************************************************************** + */ +CIccProfile::CIccProfile(const CIccProfile &Profile) +{ + m_pAttachIO = NULL; + memset(&m_Header, 0, sizeof(m_Header)); + m_Tags = new(TagEntryList); + m_TagVals = new(TagPtrList); + memcpy(&m_Header, &Profile.m_Header, sizeof(m_Header)); + + if (!Profile.m_TagVals->empty()) { + TagPtrList::const_iterator i; + IccTagPtr tagptr; + for (i=Profile.m_TagVals->begin(); i!=Profile.m_TagVals->end(); i++) { + tagptr.ptr = i->ptr->NewCopy(); + m_TagVals->push_back(tagptr); + } + } + + if (!Profile.m_Tags->empty()) { + TagEntryList::const_iterator i; + IccTagEntry entry; + for (i=Profile.m_Tags->begin(); i!=Profile.m_Tags->end(); i++) { + TagPtrList::const_iterator j, k; + + //Make sure that tag entry values point to shared tags in m_TagVals + for (j=Profile.m_TagVals->begin(), k=m_TagVals->begin(); j!=Profile.m_TagVals->end() && k!=m_TagVals->end(); j++, k++) { + if (i->pTag == j->ptr) { + //k should point to the the corresponding copied tag + entry.pTag = k->ptr; + break; + } + } + + if (j==Profile.m_TagVals->end()) { //Did we not find the tag? + entry.pTag = NULL; + } + + memcpy(&entry.TagInfo, &i->TagInfo, sizeof(icTag)); + m_Tags->push_back(entry); + } + } + + m_pAttachIO = NULL; +} + +/** + ************************************************************************** + * Name: CIccProfile::operator= + * + * Purpose: + * Copy Operator. The copy operator makes the copy of the + * CIccProfile object in it's present state. It DOES NOT make a + * copy of the m_pAttachIO member variable. Any operation with the + * IO object should be done before making a copy. + * + * Args: + * Profile = CIccProfile object which is to be copied. + ************************************************************************** + */ +CIccProfile &CIccProfile::operator=(const CIccProfile &Profile) +{ + if (&Profile == this) + return *this; + + Cleanup(); + + memcpy(&m_Header, &Profile.m_Header, sizeof(m_Header)); + + if (!Profile.m_TagVals->empty()) { + TagPtrList::const_iterator i; + IccTagPtr tagptr; + for (i=Profile.m_TagVals->begin(); i!=Profile.m_TagVals->end(); i++) { + tagptr.ptr = i->ptr->NewCopy(); + m_TagVals->push_back(tagptr); + } + } + + if (!Profile.m_Tags->empty()) { + TagEntryList::const_iterator i; + IccTagEntry entry; + for (i=Profile.m_Tags->begin(); i!=Profile.m_Tags->end(); i++) { + TagPtrList::const_iterator j, k; + + //Make sure that tag entry values point to shared tags in m_TagVals + for (j=Profile.m_TagVals->begin(), k=m_TagVals->begin(); j!=Profile.m_TagVals->end() && k!=m_TagVals->end(); j++, k++) { + if (i->pTag == j->ptr) { + //k should point to the the corresponding copied tag + entry.pTag = k->ptr; + break; + } + } + + if (j==Profile.m_TagVals->end()) { //Did we not find the tag? + entry.pTag = NULL; + } + + memcpy(&entry.TagInfo, &i->TagInfo, sizeof(icTag)); + m_Tags->push_back(entry); + } + } + + m_pAttachIO = NULL; + + return *this; +} + +/** + ************************************************************************** + * Name: CIccProfile::CIccProfile + * + * Purpose: + * Destructor + ************************************************************************** + */ +CIccProfile::~CIccProfile() +{ + Cleanup(); + + delete m_Tags; + delete m_TagVals; +} + +/** + *************************************************************************** + * Name: CIccProfile::Cleanup + * + * Purpose: Detach from a pending IO object + *************************************************************************** + */ +void CIccProfile::Cleanup() +{ + if (m_pAttachIO) { + delete m_pAttachIO; + m_pAttachIO = NULL; + } + + TagPtrList::iterator i; + + for (i=m_TagVals->begin(); i!=m_TagVals->end(); i++) { + if (i->ptr) + delete i->ptr; + } + m_Tags->clear(); + m_TagVals->clear(); + memset(&m_Header, 0, sizeof(m_Header)); +} + +/** + **************************************************************************** + * Name: CIccProfile::GetTag + * + * Purpose: Get a tag entry with a given signature + * + * Args: + * sig - signature id to find in tag directory + * + * Return: + * Pointer to desired tag directory entry, or NULL if not found. + ***************************************************************************** + */ +IccTagEntry* CIccProfile::GetTag(icSignature sig) const +{ + TagEntryList::const_iterator i; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (i->TagInfo.sig==(icTagSignature)sig) + return (IccTagEntry*)&(i->TagInfo); + } + + return NULL; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::AreTagsUnique + * + * Purpose: For each tag it checks to see if any other tags have the same + * signature. + * + * + * Return: + * true if all tags have unique signatures, or false if there are duplicate + * tag signatures. + ******************************************************************************* + */ +bool CIccProfile::AreTagsUnique() const +{ + TagEntryList::const_iterator i, j; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + j=i; + for (j++; j!= m_Tags->end(); j++) { + if (i->TagInfo.sig == j->TagInfo.sig) + return false; + } + } + + return true; +} + + +/** +****************************************************************************** +* Name: CIccProfile::GetTag +* +* Purpose: Finds the first tag entry that points to the indicated tag object +* +* Args: +* pTag - pointer to tag object desired to be found +* +* Return: +* pointer to first tag directory entry that points to the desired tag object, +* or NULL if tag object is not pointed to by any tag directory entries. +******************************************************************************* +*/ +IccTagEntry* CIccProfile::GetTag(CIccTag *pTag) const +{ + TagEntryList::const_iterator i; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (i->pTag==pTag) + return (IccTagEntry*)&(i->TagInfo); + } + + return NULL; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::FindTag + * + * Purpose: Finds the tag object associated with the directory entry with the + * given signature. If the profile object is attached to an IO object then + * the tag may need to be loaded first. + * + * Args: + * sig - tag signature to find in profile + * + * Return: + * The desired tag object, or NULL if unable to find in the directory or load + * tag object. + ******************************************************************************* + */ +CIccTag* CIccProfile::FindTag(icSignature sig) +{ + IccTagEntry *pEntry = GetTag(sig); + + if (pEntry) { + if (!pEntry->pTag && m_pAttachIO) + LoadTag(pEntry, m_pAttachIO); + return pEntry->pTag; + } + + return NULL; +} + +/** +****************************************************************************** +* Name: CIccProfile::GetTagIO +* +* Purpose: Finds the tag directory entry with the given signature and returns +* a CIccIO object that can be used to read the tag data stored in the profile. +* This only works if the profile is still connected to the file IO object. +* +* Args: +* sig - tag signature to find in profile +* +* Return: +* A CIccIO object that can be used to read the tag data from the file. +* Note: the caller is responsible for deleting the returned CIccIO object. +******************************************************************************* +*/ +CIccMemIO* CIccProfile::GetTagIO(icSignature sig) +{ + IccTagEntry *pEntry = GetTag(sig); + + if (pEntry && m_pAttachIO) { + CIccMemIO *pIO = new CIccMemIO; + + if (!pIO) + return NULL; + + if (!pIO->Alloc(pEntry->TagInfo.size)) { + delete pIO; + return NULL; + } + + m_pAttachIO->Seek(pEntry->TagInfo.offset, icSeekSet); + m_pAttachIO->Read8(pIO->GetData(), pIO->GetLength()); + return pIO; + } + + return NULL; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::AttachTag + * + * Purpose: Assign a tag object to a directory entry in the profile. This + * will assume ownership of the tag object. + * + * Args: + * sig - signature of tag 'name' to use to assign tag object with, + * pTag - pointer to tag object to attach to profile. + * + * Return: + * true = tag assigned to profile, + * false - tag not assigned to profile (tag already exists). + ******************************************************************************* + */ +bool CIccProfile::AttachTag(icSignature sig, CIccTag *pTag) +{ + IccTagEntry *pEntry = GetTag(sig); + + if (pEntry) { + if (pEntry->pTag == pTag) + return true; + + return false; + } + + IccTagEntry Entry; + Entry.TagInfo.sig = (icTagSignature)sig; + Entry.TagInfo.offset = 0; + Entry.TagInfo.size = 0; + Entry.pTag = pTag; + + m_Tags->push_back(Entry); + + TagPtrList::iterator i; + + for (i=m_TagVals->begin(); i!=m_TagVals->end(); i++) + if (i->ptr == pTag) + break; + + if (i==m_TagVals->end()) { + IccTagPtr TagPtr; + TagPtr.ptr = pTag; + m_TagVals->push_back(TagPtr); + } + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::DeleteTag + * + * Purpose: Delete tag directory entry with given signature. If no other tag + * directory entries use the tag object, the tag object will also be deleted. + * + * Args: + * sig - signature of tag directory entry to remove + * + * Return: + * true - desired tag directory entry was found and deleted, + * false - desired tag directory entry was not found + ******************************************************************************* + */ +bool CIccProfile::DeleteTag(icSignature sig) +{ + TagEntryList::iterator i; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (i->TagInfo.sig==(icTagSignature)sig) + break; + } + if (i!=m_Tags->end()) { + CIccTag *pTag = i->pTag; + m_Tags->erase(i); + + if (!GetTag(pTag)) { + DetachTag(pTag); + delete pTag; + } + return true; + } + + return false; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::Attach + * + * Purpose: This allows for deferred IO with a profile. The profile header and + * tag directory will be read, but tag data will not be read. The IO object + * will remain attached to the profile for the purpose of reading data in as + * needed. + * + * Args: + * pIO - pointer to IO object to begin reading profile file with. + * + * Return: + * true - the IO object (file) is an ICC profile, and the CIccProfile object + * is now attached to the object, + * false - the IO object (file) is not an ICC profile. + ******************************************************************************* + */ +bool CIccProfile::Attach(CIccIO *pIO) +{ + if (m_Tags->size()) + Cleanup(); + + if (!ReadBasic(pIO)) { + Cleanup(); + return false; + } + + m_pAttachIO = pIO; + + return true; +} + +/** +****************************************************************************** +* Name: CIccProfile::Detach +* +* Purpose: Discontinues the use of defferred IO with a profile. This can be done +* once all the information needed for performing a transform has been extracted +* from the profile. +* +* Args: +* true - If an IO object was attached to the profile +* false - if no IO object was attached to the profile +******************************************************************************* +*/ +bool CIccProfile::Detach() +{ + if (m_pAttachIO) { + delete m_pAttachIO; + + m_pAttachIO = NULL; + return true; + } + + return false; +} + +/** +****************************************************************************** +* Name: CIccProfile::ReadTags +* +* Purpose: This will read the all the tags from the IO object into the +* CIccProfile object. The IO object must have been attached before +* calling this function. +* +* Return: +* true - CIccProfile object now contains all tag data, +* false - No IO object attached or tags cannot be read. +******************************************************************************* +*/ +bool CIccProfile::ReadTags(CIccProfile* pProfile) +{ + CIccIO *pIO = m_pAttachIO; + + if (pProfile && pProfile->m_pAttachIO) { + pIO = pProfile->m_pAttachIO; + } + + if (!pIO) { + return false; + } + + TagEntryList::iterator i; + icUInt32Number pos = pIO->Tell(); + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (!LoadTag((IccTagEntry*)&(i->TagInfo), pIO)) { + pIO->Seek(pos, icSeekSet); + return false; + } + } + + pIO->Seek(pos, icSeekSet); + + return true; +} + +/** + ****************************************************************************** + * Name: CIccProfile::Read + * + * Purpose: This will read the entire ICC profile from the IO object into the + * CIccProfile object + * + * Args: + * pIO - pointer to IO object to read ICC profile from + * + * Return: + * true - the IO object (file) is an ICC profile, and the CIccProfile object + * now contains all its data, + * false - the IO object (file) is not an ICC profile. + ******************************************************************************* + */ +bool CIccProfile::Read(CIccIO *pIO) +{ + if (m_Tags->size()) + Cleanup(); + + if (!ReadBasic(pIO)) { + Cleanup(); + return false; + } + + TagEntryList::iterator i; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (!LoadTag((IccTagEntry*)&(i->TagInfo), pIO)) { + Cleanup(); + return false; + } + } + + return true; +} + +/** +****************************************************************************** +* Name: CIccProfile::ReadValidate +* +* Purpose: This will read the entire ICC profile from the IO object into the +* CIccProfile object +* +* Args: +* pIO - pointer to IO object to read ICC profile from +* sReport - string to put validation report info into. String should be initialized +* before calling +* +* Return: +* icValidateOK if file can be read, bad status otherwise. +******************************************************************************* +*/ +icValidateStatus CIccProfile::ReadValidate(CIccIO *pIO, std::string &sReport) +{ + icValidateStatus rv = icValidateOK; + + if (m_Tags->size()) + Cleanup(); + + if (!ReadBasic(pIO)) { + sReport += icValidateCriticalErrorMsg; + sReport += " - Unable to read profile!**\r\n\tProfile has invalid structure!\r\n"; + Cleanup(); + + return icValidateCriticalError; + } + + // Check profile header + if (!CheckFileSize(pIO)) { + sReport += icValidateNonCompliantMsg; + sReport += "Bad Header File Size\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + CIccInfo Info; + icProfileID profileID; + + // Check profile ID + if (Info.IsProfileIDCalculated(&m_Header.profileID)) { + CalcProfileID(pIO, &profileID); + if (strncmp((char*)profileID.ID8, (char*)m_Header.profileID.ID8, 16) != 0) { + sReport += icValidateNonCompliantMsg; + sReport += "Bad Profile ID\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + + TagEntryList::iterator i; + + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + if (!LoadTag((IccTagEntry*)&(i->TagInfo), pIO)) { + sReport += icValidateCriticalErrorMsg; + sReport += " - "; + sReport += Info.GetTagSigName(i->TagInfo.sig); + sReport += " - Tag has invalid structure!\r\n"; + + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + if (rv==icValidateCriticalError) + Cleanup(); + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::Write + * + * Purpose: Write the data associated with the CIccProfile object to an IO + * IO object. + * + * Args: + * pIO - pointer to IO object to write data to + * + * Return: + * true - success, false - failure + ******************************************************************************* + */ +bool CIccProfile::Write(CIccIO *pIO, icProfileIDSaveMethod nWriteId) +{ + //Write Header + pIO->Seek(0, icSeekSet); + + pIO->Write32(&m_Header.size); + pIO->Write32(&m_Header.cmmId); + pIO->Write32(&m_Header.version); + pIO->Write32(&m_Header.deviceClass); + pIO->Write32(&m_Header.colorSpace); + pIO->Write32(&m_Header.pcs); + pIO->Write16(&m_Header.date.year); + pIO->Write16(&m_Header.date.month); + pIO->Write16(&m_Header.date.day); + pIO->Write16(&m_Header.date.hours); + pIO->Write16(&m_Header.date.minutes); + pIO->Write16(&m_Header.date.seconds); + pIO->Write32(&m_Header.magic); + pIO->Write32(&m_Header.platform); + pIO->Write32(&m_Header.flags); + pIO->Write32(&m_Header.manufacturer); + pIO->Write32(&m_Header.model); + pIO->Write64(&m_Header.attributes); + pIO->Write32(&m_Header.renderingIntent); + pIO->Write32(&m_Header.illuminant.X); + pIO->Write32(&m_Header.illuminant.Y); + pIO->Write32(&m_Header.illuminant.Z); + pIO->Write32(&m_Header.creator); + pIO->Write8(&m_Header.profileID, sizeof(m_Header.profileID)); + pIO->Write8(&m_Header.reserved[0], sizeof(m_Header.reserved)); + + TagEntryList::iterator i, j; + icUInt32Number count; + + for (count=0, i=m_Tags->begin(); i!= m_Tags->end(); i++) { + if (i->pTag) + count++; + } + + pIO->Write32(&count); + + icUInt32Number dirpos = pIO->GetLength(); + + //Write Unintialized TagDir + for (i=m_Tags->begin(); i!= m_Tags->end(); i++) { + if (i->pTag) { + i->TagInfo.offset = 0; + i->TagInfo.size = 0; + + pIO->Write32(&i->TagInfo.sig); + pIO->Write32(&i->TagInfo.offset); + pIO->Write32(&i->TagInfo.size); + } + } + + //Write Tags + for (i=m_Tags->begin(); i!= m_Tags->end(); i++) { + if (i->pTag) { + for (j=m_Tags->begin(); j!=i; j++) { + if (i->pTag == j->pTag) + break; + } + + if (i==j) { + i->TagInfo.offset = pIO->GetLength(); + i->pTag->Write(pIO); + i->TagInfo.size = pIO->GetLength() - i->TagInfo.offset; + + pIO->Align32(); + } + else { + i->TagInfo.offset = j->TagInfo.offset; + i->TagInfo.size = j->TagInfo.size; + } + } + } + + pIO->Seek(dirpos, icSeekSet); + + //Write TagDir with offsets and sizes + for (i=m_Tags->begin(); i!= m_Tags->end(); i++) { + if (i->pTag) { + pIO->Write32(&i->TagInfo.sig); + pIO->Write32(&i->TagInfo.offset); + pIO->Write32(&i->TagInfo.size); + } + } + + //Update header with size + m_Header.size = pIO->GetLength(); + pIO->Seek(0, icSeekSet); + pIO->Write32(&m_Header.size); + + bool bWriteId; + + switch (nWriteId) { + case icVersionBasedID: + default: + bWriteId = (m_Header.version>=icVersionNumberV4); + break; + case icAlwaysWriteID: + bWriteId = true; + break; + case icNeverWriteID: + bWriteId = false; + } + + //Write the profile ID if version 4 profile + if(bWriteId) { + CalcProfileID(pIO, &m_Header.profileID); + pIO->Seek(84, icSeekSet); + pIO->Write8(&m_Header.profileID, sizeof(m_Header.profileID)); + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccProfile::InitHeader + * + * Purpose: Initializes the data to be written in the profile header. + * + ******************************************************************************* + */ +void CIccProfile::InitHeader() +{ + m_Header.size = 0; + m_Header.cmmId = icSigSampleICC; + m_Header.version=icVersionNumberV4; + m_Header.deviceClass = (icProfileClassSignature)0; + m_Header.colorSpace = (icColorSpaceSignature)0; + m_Header.pcs = icSigLabData; + + struct tm *newtime; + time_t long_time; + + time( &long_time ); /* Get time as long integer. */ + newtime = gmtime( &long_time ); + + m_Header.date.year = newtime->tm_year+1900; + m_Header.date.month = newtime->tm_mon+1; + m_Header.date.day = newtime->tm_mday; + m_Header.date.hours = newtime->tm_hour; + m_Header.date.minutes = newtime->tm_min; + m_Header.date.seconds = newtime->tm_sec; + + m_Header.magic = icMagicNumber; + m_Header.platform = (icPlatformSignature)0; + m_Header.flags = 0; + m_Header.manufacturer=0; + m_Header.model=0; + m_Header.attributes=0; + m_Header.renderingIntent=icPerceptual; + m_Header.illuminant.X = icDtoF((icFloatNumber)0.9642); + m_Header.illuminant.Y = icDtoF((icFloatNumber)1.0000); + m_Header.illuminant.Z = icDtoF((icFloatNumber)0.8249); + m_Header.creator = icSigSampleICC; + + memset(&m_Header.profileID, 0, sizeof(m_Header.profileID)); + memset(&m_Header.reserved[0], 0, sizeof(m_Header.reserved)); +} + + +/** + ***************************************************************************** + * Name: CIccProfile::ReadBasic + * + * Purpose: Read in ICC header and tag directory entries. + * + * Args: + * pIO - pointer to IO object to read data with + * + * Return: + * true - valid ICC header and tag directory, false - failure + ****************************************************************************** + */ +bool CIccProfile::ReadBasic(CIccIO *pIO) +{ + //Read Header + if (pIO->Seek(0, icSeekSet)<0 || + !pIO->Read32(&m_Header.size) || + !pIO->Read32(&m_Header.cmmId) || + !pIO->Read32(&m_Header.version) || + !pIO->Read32(&m_Header.deviceClass) || + !pIO->Read32(&m_Header.colorSpace) || + !pIO->Read32(&m_Header.pcs) || + !pIO->Read16(&m_Header.date.year) || + !pIO->Read16(&m_Header.date.month) || + !pIO->Read16(&m_Header.date.day) || + !pIO->Read16(&m_Header.date.hours) || + !pIO->Read16(&m_Header.date.minutes) || + !pIO->Read16(&m_Header.date.seconds) || + !pIO->Read32(&m_Header.magic) || + !pIO->Read32(&m_Header.platform) || + !pIO->Read32(&m_Header.flags) || + !pIO->Read32(&m_Header.manufacturer) || + !pIO->Read32(&m_Header.model) || + !pIO->Read64(&m_Header.attributes) || + !pIO->Read32(&m_Header.renderingIntent) || + !pIO->Read32(&m_Header.illuminant.X) || + !pIO->Read32(&m_Header.illuminant.Y) || + !pIO->Read32(&m_Header.illuminant.Z) || + !pIO->Read32(&m_Header.creator) || + pIO->Read8(&m_Header.profileID, sizeof(m_Header.profileID))!=sizeof(m_Header.profileID) || + pIO->Read8(&m_Header.reserved[0], sizeof(m_Header.reserved))!=sizeof(m_Header.reserved)) { + return false; + } + + if (m_Header.magic != icMagicNumber) + return false; + + icUInt32Number count, i; + IccTagEntry TagEntry; + + TagEntry.pTag = NULL; + + if (!pIO->Read32(&count)) + return false; + + //Read TagDir + for (i=0; iRead32(&TagEntry.TagInfo.sig) || + !pIO->Read32(&TagEntry.TagInfo.offset) || + !pIO->Read32(&TagEntry.TagInfo.size)) { + return false; + } + m_Tags->push_back(TagEntry); + } + + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::LoadTag + * + * Purpose: This will load from the indicated IO object and associate a tag + * object to a tag directory entry. Nothing happens if tag directory entry + * is associated with a tag object. + * + * Args: + * pTagEntry - pointer to tag directory entry, + * pIO - pointer to IO object to read tag object data from + * + * Return: + * true - tag directory object associated with tag directory entry, + * false - failure + ******************************************************************************* + */ +bool CIccProfile::LoadTag(IccTagEntry *pTagEntry, CIccIO *pIO) +{ + if (!pTagEntry) + return false; + + if (pTagEntry->pTag) + return true; + + if (pTagEntry->TagInfo.offsetTagInfo.size) { + return false; + } + + icTagTypeSignature sigType; + + //First we need to get the tag type to create the right kind of tag + if (pIO->Seek(pTagEntry->TagInfo.offset, icSeekSet)!=(icInt32Number)pTagEntry->TagInfo.offset) + return false; + + if (!pIO->Read32(&sigType)) + return false; + + CIccTag *pTag = CIccTag::Create(sigType); + + if (!pTag) + return false; + + //Now seek back to where the tag starts so the created tag object can read + //in its data. + //First we need to get the tag type to create the right kind of tag + if (pIO->Seek(pTagEntry->TagInfo.offset, icSeekSet)!=(icInt32Number)pTagEntry->TagInfo.offset) { + delete pTag; + return false; + } + + if (!pTag->Read(pTagEntry->TagInfo.size, pIO)) { + delete pTag; + return false; + } + + switch(pTagEntry->TagInfo.sig) { + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + if (pTag->IsMBBType()) + ((CIccMBB*)pTag)->SetColorSpaces(m_Header.colorSpace, m_Header.pcs); + break; + + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + if (pTag->IsMBBType()) + ((CIccMBB*)pTag)->SetColorSpaces(m_Header.pcs, m_Header.colorSpace); + break; + + case icSigGamutTag: + if (pTag->IsMBBType()) + ((CIccMBB*)pTag)->SetColorSpaces(m_Header.pcs, icSigGamutData); + break; + + case icSigNamedColor2Tag: + ((CIccTagNamedColor2*)pTag)->SetColorSpaces(m_Header.pcs, m_Header.colorSpace); + + default: + break; + } + + pTagEntry->pTag = pTag; + + IccTagPtr TagPtr; + + TagPtr.ptr = pTag; + + m_TagVals->push_back(TagPtr); + + TagEntryList::iterator i; + + for (i=m_Tags->begin(); i!= m_Tags->end(); i++) { + if (i->TagInfo.offset == pTagEntry->TagInfo.offset && + i->pTag != pTag) + i->pTag = pTag; + } + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccProfile::DetachTag + * + * Purpose: Remove association of a tag object from all tag directory entries. + * Associated tag directory entries will be removed from the tag directory. + * The tag object is NOT deleted from memory, but is considered to be + * no longer associated with the CIccProfile object. The caller assumes + * ownership of the tag object. + * + * Args: + * pTag - pointer to tag object unassociate with the profile object + * + * Return: + * true - tag object found and unassociated with profile object, + * false - tag object not found + ******************************************************************************* + */ +bool CIccProfile::DetachTag(CIccTag *pTag) +{ + if (!pTag) + return false; + + TagPtrList::iterator i; + + for (i=m_TagVals->begin(); i!=m_TagVals->end(); i++) { + if (i->ptr == pTag) + break; + } + + if (i==m_TagVals->end()) + return false; + + m_TagVals->erase(i); + + TagEntryList::iterator j; + for (j=m_Tags->begin(); j!=m_Tags->end();) { + if (j->pTag == pTag) { + j=m_Tags->erase(j); + } + else + j++; + } + return true; +} + + +/** +**************************************************************************** +* Name: CIccProfile::CheckHeader +* +* Purpose: Validates profile header. +* +* Return: +* icValidateOK if valid, or other error status. +***************************************************************************** +*/ +icValidateStatus CIccProfile::CheckHeader(std::string &sReport) const +{ + icValidateStatus rv = icValidateOK; + + icChar buf[128]; + CIccInfo Info; + + switch(m_Header.deviceClass) { + case icSigInputClass: + case icSigDisplayClass: + case icSigOutputClass: + case icSigLinkClass: + case icSigColorSpaceClass: + case icSigAbstractClass: + case icSigNamedColorClass: + break; + + default: + sReport += icValidateCriticalErrorMsg; + sprintf(buf, " - %s: Unknown profile class!\r\n", Info.GetProfileClassSigName(m_Header.deviceClass)); + sReport += buf; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + if (!Info.IsValidSpace(m_Header.colorSpace)) { + sReport += icValidateCriticalErrorMsg; + sprintf(buf, " - %s: Unknown color space!\r\n", Info.GetColorSpaceSigName(m_Header.colorSpace)); + sReport += buf; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + if (m_Header.deviceClass==icSigLinkClass) { + if (!Info.IsValidSpace(m_Header.pcs)) { + sReport += icValidateCriticalErrorMsg; + sprintf(buf, " - %s: Unknown pcs color space!\r\n", Info.GetColorSpaceSigName(m_Header.pcs)); + sReport += buf; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + else { + if (m_Header.pcs!=icSigXYZData && m_Header.pcs!=icSigLabData) { + sReport += icValidateCriticalErrorMsg; + sprintf(buf, " - %s: Invalid pcs color space!\r\n", Info.GetColorSpaceSigName(m_Header.pcs)); + sReport += buf; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + rv = icMaxStatus(rv, Info.CheckData(sReport, m_Header.date)); + + switch(m_Header.platform) { + case icSigMacintosh: + case icSigMicrosoft: + case icSigSolaris: + case icSigSGI: + case icSigTaligent: + case icSigUnkownPlatform: + break; + + default: + sReport += icValidateWarningMsg; + sprintf(buf, " - %s: Unknown platform signature.\r\n", Info.GetPlatformSigName(m_Header.platform)); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + + switch((icCmmSignature)m_Header.cmmId) { + //Account for registered CMM's as well: + case icSigAdobe: + case icSigApple: + case icSigColorGear: + case icSigColorGearLite: + case icSigFujiFilm: + case icSigHarlequinRIP: + case icSigArgyllCMS: + case icSigLogoSync: + case icSigHeidelberg: + case icSigLittleCMS: + case icSigKodak: + case icSigKonicaMinolta: + case icSigMutoh: + case icSigSampleICC: + case icSigTheImagingFactory: + break; + + default: + sReport += icValidateWarningMsg; + sprintf(buf, " - %s: Unregisterd CMM signature.\r\n", Info.GetCmmSigName((icCmmSignature)m_Header.cmmId)); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + switch(m_Header.renderingIntent) { + case icPerceptual: + case icRelativeColorimetric: + case icSaturation: + case icAbsoluteColorimetric: + break; + + default: + sReport += icValidateCriticalErrorMsg; + sprintf(buf, " - %s: Unknown rendering intent!\r\n", Info.GetRenderingIntentName((icRenderingIntent)m_Header.renderingIntent)); + sReport += buf; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + rv = icMaxStatus(rv, Info.CheckData(sReport, m_Header.illuminant)); + icFloatNumber X = icFtoD(m_Header.illuminant.X); + icFloatNumber Y = icFtoD(m_Header.illuminant.Y); + icFloatNumber Z = icFtoD(m_Header.illuminant.Z); + if (X<0.9640 || X>0.9644 || Y!=1.0 || Z<0.8247 || Z>0.8251) { + sReport += icValidateNonCompliantMsg; + sReport += " - Non D50 Illuminant XYZ values.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + int sum=0, num = sizeof(m_Header.reserved) / sizeof(m_Header.reserved[0]); + for (int i=0; ibegin(); i!=m_Tags->end(); i++) { + tagsig = i->TagInfo.sig; + typesig = i->pTag->GetType(); + sprintf(buf, "%s", Info.GetSigName(tagsig)); + if (!IsTypeValid(tagsig, typesig)) { + sReport += icValidateNonCompliantMsg; + sReport += buf; + sprintf(buf," - %s: Invalid tag type (Might be critical!).\r\n", Info.GetTagTypeSigName(typesig)); + sReport += buf; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + + return rv; +} + + +/** +**************************************************************************** +* Name: CIccProfile::IsTypeValid +* +* Purpose: Check if tags have allowed tag types. +* +* Return: +* true if valid, else false. +***************************************************************************** +*/ +bool CIccProfile::IsTypeValid(icTagSignature tagSig, icTagTypeSignature typeSig) const +{ + switch(tagSig) { + // A to B tags + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + { + switch(typeSig) { + case icSigLut8Type: + case icSigLut16Type: + return true; + + case icSigLutAtoBType: + if (m_Header.version >= 0x04000000L) + return true; + else + return false; + + default: + return false; + } + } + + // B to A tags + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + case icSigGamutTag: + case icSigPreview0Tag: + case icSigPreview1Tag: + case icSigPreview2Tag: + { + switch(typeSig) { + case icSigLut8Type: + case icSigLut16Type: + return true; + + case icSigLutBtoAType: + if (m_Header.version >= 0x04000000L) + return true; + else + return false; + + default: + return false; + } + } + + // Matrix column tags - XYZ types + case icSigBlueMatrixColumnTag: + case icSigGreenMatrixColumnTag: + case icSigRedMatrixColumnTag: + case icSigLuminanceTag: + case icSigMediaWhitePointTag: + case icSigMediaBlackPointTag: + { + if (typeSig!=icSigXYZType) { + return false; + } + else return true; + } + + // TRC tags + case icSigBlueTRCTag: + case icSigGreenTRCTag: + case icSigRedTRCTag: + case icSigGrayTRCTag: + { + switch(typeSig) { + case icSigCurveType: + case icSigParametricCurveType: + return true; + + default: + return false; + } + } + + case icSigCalibrationDateTimeTag: + { + if (typeSig!=icSigDateTimeType) + return false; + else return true; + } + + case icSigCharTargetTag: + { + if (typeSig!=icSigTextType) + return false; + else + return true; + } + + case icSigChromaticAdaptationTag: + { + if (typeSig!=icSigS15Fixed16ArrayType) + return false; + else return true; + } + + case icSigChromaticityTag: + { + if (typeSig!=icSigChromaticityType) + return false; + else return true; + } + + case icSigColorantOrderTag: + { + if (typeSig!=icSigColorantOrderType) + return false; + else return true; + } + + case icSigColorantTableTag: + case icSigColorantTableOutTag: + { + if (typeSig!=icSigColorantTableType) + return false; + else return true; + } + + // Multi-localized Unicode type tags + case icSigCopyrightTag: + { + if (m_Header.version>=0x04000000L) { + if (typeSig!=icSigMultiLocalizedUnicodeType) + return false; + else return true; + } + else { + if (typeSig!=icSigTextType) + return false; + else return true; + } + } + + case icSigViewingCondDescTag: + case icSigDeviceMfgDescTag: + case icSigDeviceModelDescTag: + case icSigProfileDescriptionTag: + { + if (m_Header.version>=0x04000000L) { + if (typeSig!=icSigMultiLocalizedUnicodeType) + return false; + else return true; + } + else { + if (typeSig!=icSigTextDescriptionType) + return false; + else return true; + } + } + + case icSigMeasurementTag: + { + if (typeSig!=icSigMeasurementType) + return false; + else return true; + } + + case icSigNamedColor2Tag: + { + if (typeSig!=icSigNamedColor2Type) + return false; + else return true; + } + + case icSigOutputResponseTag: + { + if (typeSig!=icSigResponseCurveSet16Type) + return false; + else return true; + } + + case icSigProfileSequenceDescTag: + { + if (typeSig!=icSigProfileSequenceDescType) + return false; + else return true; + } + + case icSigTechnologyTag: + case icSigPerceptualRenderingIntentGamutTag: + case icSigSaturationRenderingIntentGamutTag: + { + if (typeSig!=icSigSignatureType) + return false; + else return true; + } + + case icSigViewingConditionsTag: + { + if (typeSig!=icSigViewingConditionsType) + return false; + else return true; + } + + //The Private Tag case + default: + { + return true; + } + } +} + + +/** + **************************************************************************** + * Name: CIccProfile::CheckRequiredTags + * + * Purpose: Check if the Profile has the required tags + * for the specified Profile/Device class. + * + * Return: + * icValidateOK if valid, or other error status. + ***************************************************************************** + */ +icValidateStatus CIccProfile::CheckRequiredTags(std::string &sReport) const +{ + if (m_Tags->size() <= 0) { + sReport += icValidateCriticalErrorMsg; + sReport += "No tags present.\r\n"; + return icValidateCriticalError; + } + + icValidateStatus rv = icValidateOK; + + if (!GetTag(icSigProfileDescriptionTag) || + !GetTag(icSigCopyrightTag)) { + sReport += icValidateNonCompliantMsg; + sReport += "Required tags missing.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + icProfileClassSignature sig = m_Header.deviceClass; + + if (sig != icSigLinkClass) { + if (!GetTag(icSigMediaWhitePointTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Media white point tag missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + switch(sig) { + case icSigInputClass: + if (m_Header.colorSpace == icSigGrayData) { + if (!GetTag(icSigGrayTRCTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Gray TRC tag missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + else { + if (!GetTag(icSigAToB0Tag)) { + if (!GetTag(icSigRedMatrixColumnTag) || !GetTag(icSigGreenMatrixColumnTag) || + !GetTag(icSigBlueMatrixColumnTag) || !GetTag(icSigRedTRCTag) || + !GetTag(icSigGreenTRCTag) || !GetTag(icSigBlueTRCTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + break; + + case icSigDisplayClass: + if (m_Header.colorSpace == icSigGrayData) { + if (!GetTag(icSigGrayTRCTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Gray TRC tag missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + else { + if (!GetTag(icSigAToB0Tag) || !GetTag(icSigBToA0Tag)) { + if (!GetTag(icSigRedMatrixColumnTag) || !GetTag(icSigGreenMatrixColumnTag) || + !GetTag(icSigBlueMatrixColumnTag) || !GetTag(icSigRedTRCTag) || + !GetTag(icSigGreenTRCTag) || !GetTag(icSigBlueTRCTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + break; + + case icSigOutputClass: + if (m_Header.colorSpace == icSigGrayData) { + if (!GetTag(icSigGrayTRCTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Gray TRC tag missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + else { + if (!GetTag(icSigAToB0Tag) || !GetTag(icSigBToA0Tag) || + !GetTag(icSigAToB1Tag) || !GetTag(icSigBToA1Tag) || + !GetTag(icSigAToB2Tag) || !GetTag(icSigBToA2Tag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + if (!GetTag(icSigGamutTag)) { + sReport += icValidateNonCompliantMsg; + sReport += "Gamut tag missing.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + if (m_Header.version >= 0x04000000L) { + switch (m_Header.colorSpace) { + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + case icSig16colorData: + if (!GetTag(icSigColorantTableTag)) { + sReport += icValidateNonCompliantMsg; + sReport += "xCLR output profile is missing colorantTableTag\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + default: + break; + } + } + } + break; + + case icSigLinkClass: + if (!GetTag(icSigAToB0Tag) || !GetTag(icSigProfileSequenceDescTag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + if (icIsSpaceCLR(m_Header.colorSpace)) { + if (!GetTag(icSigColorantTableTag)) { + sReport += icValidateNonCompliantMsg; + sReport += "Required tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + + if (icIsSpaceCLR(m_Header.pcs)) { + if (!GetTag(icSigColorantTableOutTag)) { + sReport += icValidateNonCompliantMsg; + sReport += "Required tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + break; + + case icSigColorSpaceClass: + if (!GetTag(icSigAToB0Tag) || !GetTag(icSigBToA0Tag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + + case icSigAbstractClass: + if (!GetTag(icSigAToB0Tag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + + case icSigNamedColorClass: + if (!GetTag(icSigNamedColor2Tag)) { + sReport += icValidateCriticalErrorMsg; + sReport += "Critical tag(s) missing.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + break; + + default: + sReport += icValidateCriticalErrorMsg; + sReport += "Unknown Profile Class.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + break; + } + + if (!CheckTagExclusion(sReport)) { + rv = icMaxStatus(rv, icValidateWarning); + } + + return rv; +} + +/** + ***************************************************************************** + * Name: CIccProfile::CheckFileSize() + * + * Purpose: Check if the Profile file size matches with the size mentioned + * in the header and is evenly divisible by four. + * + * Args: + * + * Return: + * true - size matches, + * false - size mismatches + ****************************************************************************** + */ +bool CIccProfile::CheckFileSize(CIccIO *pIO) const +{ + icUInt32Number FileSize; + icUInt32Number curPos = pIO->Tell(); + + if (!pIO->Seek(0, icSeekEnd)) + return false; + + FileSize = pIO->Tell(); + + if (!FileSize) + return false; + + if (!pIO->Seek(curPos, icSeekSet)) + return false; + + if (FileSize != m_Header.size) + return false; + + if ((m_Header.version>=icVersionNumberV4_2) && ((FileSize%4 != 0) || (m_Header.size%4 != 0))) + return false; + + + return true; +} + + +/** + **************************************************************************** + * Name: CIccProfile::Validate + * + * Purpose: Check the data integrity of the profile, and conformance to + * the ICC specification + * + * Args: + * sReport = String to put report into + * + * Return: + * icValidateOK if profile is valid, warning/error level otherwise + ***************************************************************************** + */ +icValidateStatus CIccProfile::Validate(std::string &sReport) const +{ + icValidateStatus rv = icValidateOK; + + //Check header + rv = icMaxStatus(rv, CheckHeader(sReport)); + + // Check for duplicate tags + if (!AreTagsUnique()) { + sReport += icValidateWarning; + sReport += " - There are duplicate tags.\r\n"; + rv =icMaxStatus(rv, icValidateWarning); + } + + // Check Required Tags which includes exclusion tests + rv = icMaxStatus(rv, CheckRequiredTags(sReport)); + + // Per Tag tests + rv = icMaxStatus(rv, CheckTagTypes(sReport)); + TagEntryList::iterator i; + for (i=m_Tags->begin(); i!=m_Tags->end(); i++) { + rv = icMaxStatus(rv, i->pTag->Validate(i->TagInfo.sig, sReport, this)); + } + + return rv; +} + +/** + **************************************************************************** + * Name: CIccProfile::GetSpaceSamples + * + * Purpose: Get the number of device channels from the color space + * of data. + * + * Return: Number of device channels. + * + ***************************************************************************** + */ +icUInt16Number CIccProfile::GetSpaceSamples() const +{ + switch(m_Header.colorSpace) { + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigRgbData: + case icSigHsvData: + case icSigHlsData: + case icSigCmyData: + case icSig3colorData: + return 3; + + case icSigCmykData: + case icSig4colorData: + return 4; + + case icSig5colorData: + return 5; + + case icSig6colorData: + return 6; + + case icSig7colorData: + return 7; + + case icSig8colorData: + return 8; + + case icSig9colorData: + return 9; + + case icSig10colorData: + return 10; + + case icSig11colorData: + return 11; + + case icSig12colorData: + return 12; + + case icSig13colorData: + return 13; + + case icSig14colorData: + return 14; + + case icSig15colorData: + return 15; + + default: + return 0; + } + +} + +////////////////////////////////////////////////////////////////////// +// Function Definitions +////////////////////////////////////////////////////////////////////// + +/** + ***************************************************************************** + * Name: ReadIccProfile + * + * Purpose: Read an ICC profile file. + * + * Args: + * szFilename - zero terminated string with filename of ICC profile to read + * + * Return: + * Pointer to icc profile object, or NULL on failure + ****************************************************************************** + */ +CIccProfile* ReadIccProfile(const icChar *szFilename) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, "rb")) { + delete pFileIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Read(pFileIO)) { + delete pIcc; + delete pFileIO; + return NULL; + } + delete pFileIO; + + return pIcc; +} + + +#if defined(WIN32) || defined(WIN64) +/** +***************************************************************************** +* Name: ReadIccProfile +* +* Purpose: Read an ICC profile file. +* +* Args: +* szFilename - zero terminated string with filename of ICC profile to read +* +* Return: +* Pointer to icc profile object, or NULL on failure +****************************************************************************** +*/ +CIccProfile* ReadIccProfile(const icWChar *szFilename) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, L"rb")) { + delete pFileIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Read(pFileIO)) { + delete pIcc; + delete pFileIO; + return NULL; + } + delete pFileIO; + + return pIcc; +} + +#endif + +/** +***************************************************************************** +* Name: ReadIccProfile +* +* Purpose: Read an ICC profile file. +* +* Args: +* pMem = pointer to memory containing profile data +* nSize = size of memory related to profile +* +* Return: +* Pointer to icc profile object, or NULL on failure +****************************************************************************** +*/ +CIccProfile* ReadIccProfile(const icUInt8Number *pMem, icUInt32Number nSize) +{ + CIccMemIO *pMemIO = new CIccMemIO(); + + if (!pMemIO->Attach((icUInt8Number*)pMem, nSize)) { + delete pMemIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Read(pMemIO)) { + delete pIcc; + delete pMemIO; + return NULL; + } + delete pMemIO; + + return pIcc; +} + + +/** + ****************************************************************************** + * Name: OpenIccProfile + * + * Purpose: Open an ICC profile file. This will only read the profile header + * and tag directory. Loading of actual tags will be deferred until the + * tags are actually referenced by FindTag(). + * + * Args: + * szFilename - zero terminated string with filename of ICC profile to read + * + * Return: + * Pointer to icc profile object, or NULL on failure + ******************************************************************************* + */ +CIccProfile* OpenIccProfile(const icChar *szFilename) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, "rb")) { + delete pFileIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Attach(pFileIO)) { + delete pIcc; + delete pFileIO; + return NULL; + } + + return pIcc; +} + +#if defined(WIN32) || defined(WIN64) +/** +****************************************************************************** +* Name: OpenIccProfile +* +* Purpose: Open an ICC profile file. This will only read the profile header +* and tag directory. Loading of actual tags will be deferred until the +* tags are actually referenced by FindTag(). +* +* Args: +* szFilename - zero terminated string with filename of ICC profile to read +* +* Return: +* Pointer to icc profile object, or NULL on failure +******************************************************************************* +*/ +CIccProfile* OpenIccProfile(const icWChar *szFilename) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, L"rb")) { + delete pFileIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Attach(pFileIO)) { + delete pIcc; + delete pFileIO; + return NULL; + } + + return pIcc; +} +#endif + +/** +****************************************************************************** +* Name: OpenIccProfile +* +* Purpose: Open an ICC profile file. This will only read the profile header +* and tag directory. Loading of actual tags will be deferred until the +* tags are actually referenced by FindTag(). +* +* Args: +* pMem = pointer to memory containing profile data +* nSize = size of memory related to profile +* +* Return: +* Pointer to icc profile object, or NULL on failure +******************************************************************************* +*/ +CIccProfile* OpenIccProfile(const icUInt8Number *pMem, icUInt32Number nSize) +{ + CIccMemIO *pMemIO = new CIccMemIO; + + if (!pMemIO->Attach((icUInt8Number*)pMem, nSize)) { + delete pMemIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc->Attach(pMemIO)) { + delete pIcc; + delete pMemIO; + return NULL; + } + + return pIcc; +} + +/** +****************************************************************************** +* Name: ValidateIccProfile +* +* Purpose: Open an ICC profile file. This will only read the profile header +* and tag directory. Loading of actual tags will be deferred until the +* tags are actually referenced by FindTag(). +* +* Args: +* pIO - Handle to IO access object (Not ValidateIccProfile assumes ownership of this object) +* sReport - std::string to put report into +* nStatus - return status value +* +* Return: +* Pointer to icc profile object, or NULL on failure +******************************************************************************* +*/ +CIccProfile* ValidateIccProfile(CIccIO *pIO, std::string &sReport, icValidateStatus &nStatus) +{ + if (!pIO) { + sReport = icValidateCriticalErrorMsg; + sReport += " - "; + sReport += "- Invalid I/O Handle\r\n"; + delete pIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc) { + delete pIO; + return NULL; + } + + nStatus = pIcc->ReadValidate(pIO, sReport); + + if (nStatus>=icValidateCriticalError) { + delete pIcc; + delete pIO; + return NULL; + } + + delete pIO; + + nStatus = pIcc->Validate(sReport); + + return pIcc; +} + +#if defined(WIN32) || defined(WIN64) +/** +****************************************************************************** +* Name: ValidateIccProfile +* +* Purpose: Open an ICC profile file. This will only read the profile header +* and tag directory. Loading of actual tags will be deferred until the +* tags are actually referenced by FindTag(). +* +* Args: +* szFilename - zero terminated string with filename of ICC profile to read +* sReport - std::string to put report into +* nStatus - return status value +* +* Return: +* Pointer to icc profile object, or NULL on failure +******************************************************************************* +*/ +CIccProfile* ValidateIccProfile(const icWChar *szFilename, std::string &sReport, icValidateStatus &nStatus) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, L"rb")) { + delete pFileIO; + return NULL; + } + + return ValidateIccProfile(pFileIO, sReport, nStatus); +} +#endif + + +/** +****************************************************************************** +* Name: ValidateIccProfile +* +* Purpose: Open an ICC profile file. This will only read the profile header +* and tag directory. Loading of actual tags will be deferred until the +* tags are actually referenced by FindTag(). +* +* Args: +* szFilename - zero terminated string with filename of ICC profile to read +* sReport - std::string to put report into +* nStatus - return status value +* +* Return: +* Pointer to icc profile object, or NULL on failure +******************************************************************************* +*/ +CIccProfile* ValidateIccProfile(const icChar *szFilename, std::string &sReport, icValidateStatus &nStatus) +{ + CIccFileIO *pFileIO = new CIccFileIO; + + if (!pFileIO->Open(szFilename, "rb")) { + sReport = icValidateCriticalErrorMsg; + sReport += " - "; + sReport += szFilename; + sReport += "- Invalid Filename\r\n"; + delete pFileIO; + return NULL; + } + + CIccProfile *pIcc = new CIccProfile; + + if (!pIcc) { + delete pFileIO; + return NULL; + } + + nStatus = pIcc->ReadValidate(pFileIO, sReport); + + if (nStatus>=icValidateCriticalError) { + delete pIcc; + delete pFileIO; + return NULL; + } + + delete pFileIO; + + nStatus = pIcc->Validate(sReport); + + return pIcc; +} + + + +/** + ****************************************************************************** + * Name: SaveIccProfile + * + * Purpose: Save an ICC profile file. + * + * Args: + * szFilename - zero terminated string with filename of ICC profile to create + * + * Return: + * true = success, false = failure + ******************************************************************************* + */ +bool SaveIccProfile(const icChar *szFilename, CIccProfile *pIcc, icProfileIDSaveMethod nWriteId) +{ + CIccFileIO FileIO; + + if (!pIcc) + return false; + + if (!FileIO.Open(szFilename, "w+b")) { + return false; + } + + if (!pIcc->Write(&FileIO, nWriteId)) { + return false; + } + + return true; +} + +#if defined(WIN32) || defined(WIN64) +/** +****************************************************************************** +* Name: SaveIccProfile +* +* Purpose: Save an ICC profile file. +* +* Args: +* szFilename - zero terminated string with filename of ICC profile to create +* +* Return: +* true = success, false = failure +******************************************************************************* +*/ +bool SaveIccProfile(const icWChar *szFilename, CIccProfile *pIcc, icProfileIDSaveMethod nWriteId) +{ + CIccFileIO FileIO; + + if (!pIcc) + return false; + + if (!FileIO.Open(szFilename, L"w+b")) { + return false; + } + + if (!pIcc->Write(&FileIO, nWriteId)) { + return false; + } + + return true; +} +#endif + +/** + **************************************************************************** + * Name: CalcProfileID + * + * Purpose: Calculate the Profile ID using MD5 Fingerprinting method. + * + * Args: + * pIO = The CIccIO object, + * pProfileID = array where the profileID will be stored + * + **************************************************************************** + */ +void CalcProfileID(CIccIO *pIO, icProfileID *pProfileID) +{ + icUInt32Number len, num, nBlock, pos; + MD5_CTX context; + icUInt8Number buffer[1024]; + + //remember where we are + pos = pIO->Tell(); + + //Get length and set up to read entire file + len = pIO->GetLength(); + pIO->Seek(0, icSeekSet); + + //read file updating checksum as we go + icMD5Init(&context); + nBlock = 0; + while(len) { + num = pIO->Read8(&buffer[0],1024); + if (!nBlock) { // Zero out 3 header contents in Profile ID calculation + memset(buffer+44, 0, 4); //Profile flags + memset(buffer+64, 0, 4); //Rendering Intent + memset(buffer+84, 0, 16); //Profile Id + } + icMD5Update(&context,buffer,num); + nBlock++; + len -=num; + } + icMD5Final(&pProfileID->ID8[0],&context); + + //go back where we were + pIO->Seek(pos, icSeekSet); +} + +/** + **************************************************************************** + * Name: CalcProfileID + * + * Purpose: Calculate the Profile ID using MD5 Fingerprinting method. + * + * Args: + * szFileName = name of the file whose profile ID has to be calculated, + * pProfileID = array where the profileID will be stored + ***************************************************************************** + */ +bool CalcProfileID(const icChar *szFilename, icProfileID *pProfileID) +{ + CIccFileIO FileIO; + + if (!FileIO.Open(szFilename, "rb")) { + memset(pProfileID, 0, sizeof(icProfileID)); + return false; + } + + CalcProfileID(&FileIO, pProfileID); + return true; +} + +#if defined(WIN32) || defined(WIN64) +/** +**************************************************************************** +* Name: CalcProfileID +* +* Purpose: Calculate the Profile ID using MD5 Fingerprinting method. +* +* Args: +* szFileName = name of the file whose profile ID has to be calculated, +* pProfileID = array where the profileID will be stored +***************************************************************************** +*/ +bool CalcProfileID(const icWChar *szFilename, icProfileID *pProfileID) +{ + CIccFileIO FileIO; + + if (!FileIO.Open(szFilename, L"rb")) { + memset(pProfileID, 0, sizeof(icProfileID)); + return false; + } + + CalcProfileID(&FileIO, pProfileID); + return true; +} +#endif + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccProfile.h b/library/src/main/cpp/icc/IccProfile.h new file mode 100644 index 00000000..41d52e81 --- /dev/null +++ b/library/src/main/cpp/icc/IccProfile.h @@ -0,0 +1,225 @@ +/** @file + File: IccProfile.h + + Contains: Header for implementation of the CIccProfile class. + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCPROFILE_H) +#define _ICCPROFILE_H + +#include "IccDefs.h" +#include +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class ICCPROFLIB_API CIccTag; +class ICCPROFLIB_API CIccIO; +class ICCPROFLIB_API CIccMemIO; + +/** + ************************************************************************** + * Type: Structure + * + * Purpose: + * This structure stores all the information of an individual tag + * including the header information (tag signature, offset and size). + ************************************************************************** + */ +struct IccTagEntry +{ + + icTag TagInfo; + CIccTag* pTag; +}; + +/** + ************************************************************************** + * Type: List + * + * Purpose: List of all the tag entries. + * + ************************************************************************** + */ +typedef std::list TagEntryList; + +/** + ************************************************************************** + * Type: Structure + * + * Purpose: Contains pointer to a tag. + ************************************************************************** + */ +typedef struct {CIccTag *ptr;} IccTagPtr; + +/** + ************************************************************************** + * Type: List + * + * Purpose: List of pointers to the tags. + * + ************************************************************************** + */ +typedef std::list TagPtrList; + +typedef enum { + icVersionBasedID, + icAlwaysWriteID, + icNeverWriteID, +}icProfileIDSaveMethod; + +/** + ************************************************************************** + * Type: Class + * + * Purpose: + * This is the base class for an ICC profile. All the operations + * on a profile is done using this object. + ************************************************************************** + */ +class ICCPROFLIB_API CIccProfile +{ +public: + CIccProfile(); + CIccProfile(const CIccProfile &Profile); + CIccProfile &operator=(const CIccProfile &Profile); + virtual ~CIccProfile(); + + icHeader m_Header; + + TagEntryList *m_Tags; + + CIccTag* FindTag(icSignature sig); + bool AttachTag(icSignature sig, CIccTag *pTag); + bool DeleteTag(icSignature sig); + CIccMemIO* GetTagIO(icSignature sig); //caller should delete returned result + bool ReadTags(CIccProfile* pProfile); // will read in all the tags using the IO of the passed profile + + bool Attach(CIccIO *pIO); + bool Detach(); + bool Read(CIccIO *pIO); + icValidateStatus ReadValidate(CIccIO *pIO, std::string &sReport); + bool Write(CIccIO *pIO, icProfileIDSaveMethod nWriteId=icVersionBasedID); + + void InitHeader(); + icValidateStatus Validate(std::string &sReport) const; + + icUInt16Number GetSpaceSamples() const; + + bool AreTagsUnique() const; + bool IsTagPresent(icSignature sig) const { return (GetTag(sig)!=NULL); } + +protected: + + void Cleanup(); + IccTagEntry* GetTag(icSignature sig) const; + IccTagEntry* GetTag(CIccTag *pTag) const; + bool ReadBasic(CIccIO *pIO); + bool LoadTag(IccTagEntry *pTagEntry, CIccIO *pIO); + bool DetachTag(CIccTag *pTag); + + // Profile Validation functions + icValidateStatus CheckRequiredTags(std::string &sReport) const; + bool CheckTagExclusion(std::string &sReport) const; + icValidateStatus CheckHeader(std::string &sReport) const; + icValidateStatus CheckTagTypes(std::string &sReport) const; + bool IsTypeValid(icTagSignature tagSig, icTagTypeSignature typeSig) const; + bool CheckFileSize(CIccIO *pIO) const; + + CIccIO *m_pAttachIO; + + TagPtrList *m_TagVals; +}; + +CIccProfile ICCPROFLIB_API *ReadIccProfile(const icChar *szFilename); +CIccProfile ICCPROFLIB_API *ReadIccProfile(const icUInt8Number *pMem, icUInt32Number nSize); +CIccProfile ICCPROFLIB_API *OpenIccProfile(const icChar *szFilename); +CIccProfile ICCPROFLIB_API *OpenIccProfile(const icUInt8Number *pMem, icUInt32Number nSize); //pMem must be available for entire life of returned CIccProfile Object + +CIccProfile ICCPROFLIB_API *ValidateIccProfile(CIccIO *pIO, std::string &sReport, icValidateStatus &nStatus); +CIccProfile ICCPROFLIB_API *ValidateIccProfile(const icChar *szFilename, std::string &sReport, icValidateStatus &nStatus); + +bool ICCPROFLIB_API SaveIccProfile(const icChar *szFilename, CIccProfile *pIcc, icProfileIDSaveMethod nWriteId=icVersionBasedID); + +void ICCPROFLIB_API CalcProfileID(CIccIO *pIO, icProfileID *profileID); +bool ICCPROFLIB_API CalcProfileID(const icChar *szFilename, icProfileID *profileID); + +#if defined(WIN32) || defined(WIN64) +CIccProfile ICCPROFLIB_API *ReadIccProfile(const icWChar *szFilename); +CIccProfile ICCPROFLIB_API *OpenIccProfile(const icWChar *szFilename); +CIccProfile ICCPROFLIB_API *ValidateIccProfile(const icWChar *szFilename, std::string &sReport, icValidateStatus &nStatus); +bool ICCPROFLIB_API SaveIccProfile(const icWChar *szFilename, CIccProfile *pIcc, icProfileIDSaveMethod nWriteId=icVersionBasedID); +bool ICCPROFLIB_API CalcProfileID(const icWChar *szFilename, icProfileID *profileID); +#endif + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif // !defined(_ICCPROFILE_H) diff --git a/library/src/main/cpp/icc/IccTag.h b/library/src/main/cpp/icc/IccTag.h new file mode 100644 index 00000000..0d003032 --- /dev/null +++ b/library/src/main/cpp/icc/IccTag.h @@ -0,0 +1,94 @@ +/** @file + File: IccTag.h + + Contains: Header for implementation of CIccTag class and + creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2005-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Separated tags to separate files and created this single header +// file to declare them all +// +// -Oct 30, 2005 +// A CIccTagCreator singleton class has been added to provide general +// support for dynamically creating tag classes using a tag signature. +// Prototype and private tag type support can be added to the system +// by pushing additional IIccTagFactory based objects to the +// singleton CIccTagCreator object. +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCTAG_H +#define _ICCTAG_H + +#include "IccDefs.h" +#include "IccIO.h" +#include +#include + +#include "IccTagBasic.h" +#include "IccTagLut.h" +#include "IccTagMPE.h" +#include "IccTagProfSeqId.h" +#include "IccTagDict.h" + +#endif //_ICCTAG_H diff --git a/library/src/main/cpp/icc/IccTagBasic.cpp b/library/src/main/cpp/icc/IccTagBasic.cpp new file mode 100644 index 00000000..f930c869 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagBasic.cpp @@ -0,0 +1,6959 @@ +/** @file + File: IccTagBasic.cpp + + Contains: Implementation of the CIccTag class and basic inherited classes + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) + #pragma warning( disable: 4786) //disable warning in + #include +#endif +#include +#include +#include +#include +#include "IccTag.h" +#include "IccUtil.h" +#include "IccProfile.h" +#include "IccTagFactory.h" +#include "IccConvertUTF.h" + +#ifndef __min +#include +using std::min; +#define __min min +#endif + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + + +/** + **************************************************************************** + * Name: CIccTag::CIccTag + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTag::CIccTag() +{ + m_nReserved = 0; +} + +/** + **************************************************************************** + * Name: CIccTag::CIccTag + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTag::~CIccTag() +{ + +} + +/** + **************************************************************************** + * Name: CIccTag::Create + * + * Purpose: This is a static tag creator based upon tag signature type + * + * Args: + * sig = tag type signature + * + * Return: Pointer to Allocated tag + ***************************************************************************** + */ +CIccTag* CIccTag::Create(icTagTypeSignature sig) +{ + return CIccTagCreator::CreateTag(sig); +} + + +/** + ****************************************************************************** + * Name: CIccTag::Validate + * + * Purpose: Check tag data validity. In base class we only look at the + * tag's reserved data value + * + * Args: + * sig = signature of tag being validated, + * sReport = String to add report information to + * + * Return: + * icValidateStatusOK if valid, or other error status. + ****************************************************************************** + */ +icValidateStatus CIccTag::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = icValidateOK; + + if (m_nReserved != 0) { + CIccInfo Info; + sReport += icValidateNonCompliantMsg; + sReport += Info.GetSigName(sig); + sReport += " - Reserved Value must be zero.\r\n"; + + rv = icValidateNonCompliant; + } + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccTagUnknown::CIccTagUnknown + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagUnknown::CIccTagUnknown() +{ + m_nType = icSigUnknownType; + m_pData = NULL; +} + +/** + **************************************************************************** + * Name: CIccTagUnknown::CIccTagUnknown + * + * Purpose: Copy Constructor + * + * Args: + * ITU = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccTagUnknown::CIccTagUnknown(const CIccTagUnknown &ITU) +{ + m_nSize = ITU.m_nSize; + m_nType = ITU.m_nType; + + m_pData = new icUInt8Number[m_nSize]; + memcpy(m_pData, ITU.m_pData, sizeof(icUInt8Number)*m_nSize); +} + +/** + **************************************************************************** + * Name: CIccTagUnknown::operator= + * + * Purpose: Copy Operator + * + * Args: + * UnknownTag = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccTagUnknown &CIccTagUnknown::operator=(const CIccTagUnknown &UnknownTag) +{ + if (&UnknownTag == this) + return *this; + + m_nSize = UnknownTag.m_nSize; + m_nType = UnknownTag.m_nType; + + if (m_pData) + delete [] m_pData; + m_pData = new icUInt8Number[m_nSize]; + memcpy(m_pData, UnknownTag.m_pData, sizeof(icUInt8Number)*m_nSize); + + return *this; +} + +/** + **************************************************************************** + * Name: CIccTagUnknown::~CIccTagUnknown + * + * Purpose: Destructor + ***************************************************************************** + */ +CIccTagUnknown::~CIccTagUnknown() +{ + if (m_pData) + delete [] m_pData; +} + + +/** + **************************************************************************** + * Name: CIccTagUnknown::Read + * + * Purpose: Read in an unknown tag type into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagUnknown::Read(icUInt32Number size, CIccIO *pIO) +{ + if (m_pData) { + delete [] m_pData; + m_pData = NULL; + } + + if (sizeRead32(&m_nType)) + return false; + + m_nSize = size - sizeof(icTagTypeSignature); + + if (m_nSize) { + + m_pData = new icUInt8Number[m_nSize]; + + if (pIO->Read8(m_pData, m_nSize) != (icInt32Number)m_nSize) { + return false; + } + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagUnknown::Write + * + * Purpose: Write an unknown tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagUnknown::Write(CIccIO *pIO) +{ + if (!pIO) + return false; + + if (!pIO->Write32(&m_nType)) + return false; + + if (m_nSize && m_pData) { + if (pIO->Write8(m_pData, m_nSize) != (icInt32Number)m_nSize) + return false; + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagUnknown::Describe + * + * Purpose: Dump data associated with unknown tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagUnknown::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sDescription = "Unknown Tag Type of "; + sprintf(buf, "%u Bytes.", m_nSize-4); + sDescription += buf; + + sDescription += "\r\n\r\nData Follows:\r\n"; + + icMemDump(sDescription, m_pData+4, m_nSize-4); +} + +/** + **************************************************************************** + * Name: CIccTagText::CIccTagText + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagText::CIccTagText() +{ + m_szText = (icChar*)malloc(1); + m_szText[0] = '\0'; + m_nBufSize = 1; +} + +/** + **************************************************************************** + * Name: CIccTagText::CIccTagText + * + * Purpose: Copy Constructor + * + * Args: + * ITT = The CIccTagText object to be copied + ***************************************************************************** + */ +CIccTagText::CIccTagText(const CIccTagText &ITT) +{ + m_szText = (icChar*)malloc(1); + m_szText[0] = '\0'; + m_nBufSize = 1; + SetText(ITT.m_szText); +} + +/** + **************************************************************************** + * Name: CIccTagText::operator= + * + * Purpose: Copy Operator + * + * Args: + * TextTag = The CIccTagText object to be copied + ***************************************************************************** + */ +CIccTagText &CIccTagText::operator=(const CIccTagText &TextTag) +{ + if (&TextTag == this) + return *this; + + m_szText = (icChar*)malloc(1); + m_szText[0] = '\0'; + m_nBufSize = 1; + SetText(TextTag.m_szText); + + return *this; +} + +/** + **************************************************************************** + * Name: CIccTagText::~CIccTagText + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagText::~CIccTagText() +{ + free(m_szText); +} + +/** + **************************************************************************** + * Name: CIccTagText::Read + * + * Purpose: Read in a text type tag into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagText::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeRead32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nSize = size - sizeof(icTagTypeSignature) - sizeof(icUInt32Number); + + icChar *pBuf = GetBuffer(nSize); + + if (nSize) { + if (pIO->Read8(pBuf, nSize) != (icInt32Number)nSize) { + return false; + } + } + + Release(); + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagText::Write + * + * Purpose: Write a text type tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagText::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!m_szText) + return false; + + icUInt32Number nSize = (icUInt32Number)strlen(m_szText)+1; + + if (pIO->Write8(m_szText, nSize) != (icInt32Number)nSize) + return false; + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagText::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagText::Describe(std::string &sDescription) +{ + sDescription += "\""; + if (m_szText && *m_szText) + sDescription += m_szText; + + sDescription += "\"\r\n"; +} + + +/** + **************************************************************************** + * Name: CIccTagText::SetText + * + * Purpose: Allows text data associated with the tag to be set. + * + * Args: + * szText - zero terminated string to put in tag + ***************************************************************************** + */ +void CIccTagText::SetText(const icChar *szText) +{ + if (!szText) + SetText(""); + + icUInt32Number len=(icUInt32Number)strlen(szText) + 1; + icChar *szBuf = GetBuffer(len); + + strcpy(szBuf, szText); + Release(); +} + +/** + **************************************************************************** + * Name: *CIccTagText::operator= + * + * Purpose: Define assignment operator to associate text with tag. + * + * Args: + * szText - zero terminated string to put in the tag + * + * Return: A pointer to the string assigned to the tag. + ***************************************************************************** + */ +const icChar *CIccTagText::operator=(const icChar *szText) +{ + SetText(szText); + return m_szText; +} + +/** + ****************************************************************************** + * Name: CIccTagText::GetBuffer + * + * Purpose: This function allocates room and returns pointer to data buffer to + * put string into + * + * Args: + * nSize = Requested size of data buffer. + * + * Return: The character buffer array + ******************************************************************************* + */ +icChar *CIccTagText::GetBuffer(icUInt32Number nSize) +{ + if (m_nBufSize < nSize) { + m_szText = (icChar*)realloc(m_szText, nSize+1); + + m_szText[nSize] = '\0'; + + m_nBufSize = nSize; + } + + return m_szText; +} + +/** + **************************************************************************** + * Name: CIccTagText::Release + * + * Purpose: This will resize the buffer to fit the zero terminated string in + * the buffer. + ***************************************************************************** + */ +void CIccTagText::Release() +{ + icUInt32Number nSize = (icUInt32Number)strlen(m_szText)+1; + + if (nSize < m_nBufSize-1) { + m_szText=(icChar*)realloc(m_szText, nSize+1); + m_nBufSize = nSize+1; + } +} + + +/** +****************************************************************************** +* Name: CIccTagText::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagText::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (m_nBufSize) { + switch(sig) { + case icSigCopyrightTag: + break; + case icSigCharTargetTag: + if (m_nBufSize<7) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Tag must have at least seven text characters.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + break; + default: + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Unknown Tag.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + int i; + for (i=0; m_szText[i] && i<(int)m_nBufSize; i++) { + if (m_szText[i]&0x80) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Text do not contain 7bit data.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + } + else { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Empty Tag.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + + + return rv; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::CIccTagTextDescription + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagTextDescription::CIccTagTextDescription() +{ + m_szText = (icChar*)malloc(1); + m_szText[0] = '\0'; + m_nASCIISize = 1; + + m_uzUnicodeText = (icUInt16Number*)malloc(sizeof(icUInt16Number)); + m_uzUnicodeText[0] = 0; + m_nUnicodeSize = 1; + m_nUnicodeLanguageCode = 0; + + m_nScriptSize = 0; + m_nScriptCode = 0; + memset(m_szScriptText, 0, sizeof(m_szScriptText)); + + m_bInvalidScript = false; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::CIccTagTextDescription + * + * Purpose: Copy Constructor + * + * Args: + * ITTD = The CIccTagTextDescription object to be copied + ***************************************************************************** + */ +CIccTagTextDescription::CIccTagTextDescription(const CIccTagTextDescription &ITTD) +{ + m_nASCIISize = ITTD.m_nASCIISize; + m_nUnicodeSize = ITTD.m_nUnicodeSize; + m_nUnicodeLanguageCode = ITTD.m_nUnicodeLanguageCode; + m_nScriptSize = ITTD.m_nScriptSize; + m_nScriptCode = ITTD.m_nScriptCode; + + if (m_nASCIISize) { + m_szText = (icChar*)malloc(m_nASCIISize * sizeof(icChar)); + memcpy(m_szText, ITTD.m_szText, m_nASCIISize*sizeof(icChar)); + } + else { + m_nASCIISize = 1; + m_szText = (icChar*)calloc(m_nASCIISize, sizeof(icChar)); + m_szText[0] = '\0'; + } + + if (m_nUnicodeSize) { + m_uzUnicodeText = (icUInt16Number*)malloc((m_nUnicodeSize) * sizeof(icUInt16Number)); + memcpy(m_uzUnicodeText, ITTD.m_uzUnicodeText, m_nUnicodeSize*sizeof(icUInt16Number)); + } + else { + m_nUnicodeSize = 1; + m_uzUnicodeText = (icUInt16Number*)calloc(m_nUnicodeSize, sizeof(icUInt16Number)); + m_uzUnicodeText[0] = 0; + } + + memcpy(m_szScriptText, ITTD.m_szScriptText, sizeof(m_szScriptText)); + + m_bInvalidScript = ITTD.m_bInvalidScript; +} + + +/** + **************************************************************************** + * Name: CIccTagTextDescription::operator= + * + * Purpose: Copy Operator + * + * Args: + * TextDescTag = The CIccTagTextDescription object to be copied + ***************************************************************************** + */ +CIccTagTextDescription &CIccTagTextDescription::operator=(const CIccTagTextDescription& TextDescTag) +{ + if (&TextDescTag == this) + return *this; + + m_nASCIISize = TextDescTag.m_nASCIISize; + m_nUnicodeSize = TextDescTag.m_nUnicodeSize; + m_nUnicodeLanguageCode = TextDescTag.m_nUnicodeLanguageCode; + m_nScriptSize = TextDescTag.m_nScriptSize; + m_nScriptCode = TextDescTag.m_nScriptCode; + + if (m_szText) + free(m_szText); + if (m_nASCIISize) { + m_szText = (icChar*)calloc(m_nASCIISize, sizeof(icChar)); + memcpy(m_szText, TextDescTag.m_szText, m_nASCIISize*sizeof(icChar)); + } + else { + m_nASCIISize = 1; + m_szText = (icChar*)calloc(m_nASCIISize, sizeof(icChar)); + m_szText[0] = '\0'; + } + + if (m_uzUnicodeText) + free(m_uzUnicodeText); + if (m_nUnicodeSize) { + m_uzUnicodeText = (icUInt16Number*)calloc(m_nUnicodeSize, sizeof(icUInt16Number)); + memcpy(m_uzUnicodeText, TextDescTag.m_uzUnicodeText, m_nUnicodeSize*sizeof(icUInt16Number)); + } + else { + m_nUnicodeSize = 1; + m_uzUnicodeText = (icUInt16Number*)calloc(m_nUnicodeSize, sizeof(icUInt16Number)); + m_uzUnicodeText[0] = 0; + } + + memcpy(m_szScriptText, TextDescTag.m_szScriptText, sizeof(m_szScriptText)); + + m_bInvalidScript = TextDescTag.m_bInvalidScript; + + return *this; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::~CIccTagTextDescription + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagTextDescription::~CIccTagTextDescription() +{ + free(m_szText); + free(m_uzUnicodeText); +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagTextDescription::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nEnd; + + nEnd = pIO->Tell() + size; + + if (size<3*sizeof(icUInt32Number) || !pIO) { + m_szText[0] = '\0'; + return false; + } + + icUInt32Number nSize; + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read32(&nSize)) + return false; + + if (3*sizeof(icUInt32Number) + nSize > size) + return false; + + icChar *pBuf = GetBuffer(nSize); + + if (nSize) { + if (pIO->Read8(pBuf, nSize) != (icInt32Number)nSize) { + return false; + } + } + else + m_szText[0] = '\0'; + + Release(); + + if (pIO->Tell() + 2 * sizeof(icUInt32Number) > nEnd) + return false; + + if (!pIO->Read32(&m_nUnicodeLanguageCode) || + !pIO->Read32(&nSize)) + return false; + + icUInt16Number *pBuf16 = GetUnicodeBuffer(nSize); + + if (nSize) { + if (pIO->Read16(pBuf16, nSize) != (icInt32Number)nSize) { + return false; + } + } + else + pBuf16[0] = 0; + + ReleaseUnicode(); + + if (pIO->Tell()+3 > (icInt32Number)nEnd) + return false; + + if (!pIO->Read16(&m_nScriptCode) || + !pIO->Read8(&m_nScriptSize)) + return false; + + if (pIO->Tell() + m_nScriptSize> (icInt32Number)nEnd || + m_nScriptSize > sizeof(m_szScriptText)) + return false; + + int nScriptLen = pIO->Read8(m_szScriptText, 67); + + if (!nScriptLen) + return false; + + if (nScriptLen<67) { + memset(&m_szScriptText[0], 0, 67-nScriptLen); + m_bInvalidScript = true; + } + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagTextDescription::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt32Number zero = 0; + + if (!pIO) + return false; + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write32(&m_nASCIISize)) + return false; + + if (m_nASCIISize) { + if (pIO->Write8(m_szText, m_nASCIISize) != (icInt32Number)m_nASCIISize) + return false; + } + + if (!pIO->Write32(&m_nUnicodeLanguageCode)) + return false; + + if (m_nUnicodeSize > 1) { + if (!pIO->Write32(&m_nUnicodeSize) || + pIO->Write16(m_uzUnicodeText, m_nUnicodeSize) != (icInt32Number)m_nUnicodeSize) + return false; + } + else { + if (!pIO->Write32(&zero)) + return false; + } + + if (!pIO->Write16(&m_nScriptCode) || + !pIO->Write8(&m_nScriptSize) || + pIO->Write8(m_szScriptText, 67)!= 67) + return false; + + m_bInvalidScript = false; + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagTextDescription::Describe(std::string &sDescription) +{ + sDescription += "\""; + if (m_szText && *m_szText) + sDescription += m_szText; + + sDescription += "\"\r\n"; +} + + +/** + **************************************************************************** + * Name: CIccTagTextDescription::SetText + * + * Purpose: Allows text data associated with the tag to be set. + * + * Args: + * szText - zero terminated string to put in tag + ***************************************************************************** + */ +void CIccTagTextDescription::SetText(const icChar *szText) +{ + m_bInvalidScript = false; + + if (!szText) + SetText(""); + + icUInt32Number len=(icUInt32Number)strlen(szText) + 1; + icChar *szBuf = GetBuffer(len); + + strcpy(szBuf, szText); + Release(); +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::operator= + * + * Purpose: Define assignment operator to associate text with tag. + * + * Args: + * szText - zero terminated string to put in the tag + * + * Return: A pointer to the string assigned to the tag. + ***************************************************************************** + */ +const icChar *CIccTagTextDescription::operator=(const icChar *szText) +{ + SetText(szText); + return m_szText; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::GetBuffer + * + * Purpose: This function allocates room and returns pointer to data buffer to + * put string into + * + * Args: + * nSize = Requested size of data buffer. + * + * Return: + ***************************************************************************** + */ +icChar *CIccTagTextDescription::GetBuffer(icUInt32Number nSize) +{ + if (m_nASCIISize < nSize) { + m_szText = (icChar*)realloc(m_szText, nSize+1); + + m_szText[nSize] = '\0'; + + m_nASCIISize = nSize; + } + + return m_szText; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::Release + * + * Purpose: This will resize the buffer to fit the zero terminated string in + * the buffer. + ***************************************************************************** + */ +void CIccTagTextDescription::Release() +{ + icUInt32Number nSize = (icUInt32Number)strlen(m_szText); + + if (nSize < m_nASCIISize-1) { + m_szText=(icChar*)realloc(m_szText, nSize+1); + m_nASCIISize = nSize+1; + } +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::GetUnicodeBuffer + * + * Purpose: This function allocates room and returns pointer to data buffer to + * put string into + * + * Args: + * nSize = Requested size of data buffer. + * + * Return: + ***************************************************************************** + */ +icUInt16Number *CIccTagTextDescription::GetUnicodeBuffer(icUInt32Number nSize) +{ + if (m_nUnicodeSize < nSize) { + m_uzUnicodeText = (icUInt16Number*)realloc(m_uzUnicodeText, (nSize+1)*sizeof(icUInt16Number)); + + m_uzUnicodeText[nSize] = 0; + + m_nUnicodeSize = nSize; + } + + return m_uzUnicodeText; +} + +/** + **************************************************************************** + * Name: CIccTagTextDescription::ReleaseUnicode + * + * Purpose: This will resize the buffer to fit the zero terminated string in + * the buffer. + ***************************************************************************** + */ +void CIccTagTextDescription::ReleaseUnicode() +{ + int i; + for (i=0; m_uzUnicodeText[i]; i++); + + icUInt32Number nSize = i+1; + + if (nSize < m_nUnicodeSize-1) { + m_uzUnicodeText=(icUInt16Number*)realloc(m_uzUnicodeText, (nSize+1)*sizeof(icUInt16Number)); + m_nUnicodeSize = nSize+1; + } +} + + +/** +****************************************************************************** +* Name: CIccTagTextDescription::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagTextDescription::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (m_nScriptSize>67) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - ScriptCode count must not be greater than 67.\r\n"; + + rv =icMaxStatus(rv, icValidateNonCompliant); + } + + if (m_bInvalidScript) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - ScriptCode must contain 67 bytes.\r\n"; + + rv =icMaxStatus(rv, icValidateNonCompliant); + } + + return rv; +} + +/** + **************************************************************************** + * Name: CIccTagSignature::CIccTagSignature + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagSignature::CIccTagSignature() +{ + m_nSig = 0x3f3f3f3f; //'????'; +} + + + +/** + **************************************************************************** + * Name: CIccTagSignature::CIccTagSignature + * + * Purpose: Copy Constructor + * + * Args: + * ITS = The CIccTagSignature object to be copied + ***************************************************************************** + */ +CIccTagSignature::CIccTagSignature(const CIccTagSignature &ITS) +{ + m_nSig = ITS.m_nSig; +} + + + +/** + **************************************************************************** + * Name: CIccTagSignature::operator= + * + * Purpose: Copy Operator + * + * Args: + * SignatureTag = The CIccTagSignature object to be copied + ***************************************************************************** + */ +CIccTagSignature &CIccTagSignature::operator=(const CIccTagSignature &SignatureTag) +{ + if (&SignatureTag == this) + return *this; + + m_nSig = SignatureTag.m_nSig; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagSignature::~CIccTagSignature + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagSignature::~CIccTagSignature() +{ +} + +/** + **************************************************************************** + * Name: CIccTagSignature::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagSignature::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + 2*sizeof(icUInt32Number) > size) + return false; + + if (!pIO) { + m_nSig = 0x3f3f3f3f; //'????'; + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read32(&m_nSig)) + return false; + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagSignature::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagSignature::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nSig)) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagSignature::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagSignature::Describe(std::string &sDescription) +{ + CIccInfo Fmt; + + sDescription += Fmt.GetSigName(m_nSig); + sDescription += "\r\n"; +} + + +/** +****************************************************************************** +* Name: CIccTagSignature::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagSignature::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + char buf[128]; + + if (sig==icSigTechnologyTag) { + switch(m_nSig) { + case icSigFilmScanner: + case icSigDigitalCamera: + case icSigReflectiveScanner: + case icSigInkJetPrinter: + case icSigThermalWaxPrinter: + case icSigElectrophotographicPrinter: + case icSigElectrostaticPrinter: + case icSigDyeSublimationPrinter: + case icSigPhotographicPaperPrinter: + case icSigFilmWriter: + case icSigVideoMonitor: + case icSigVideoCamera: + case icSigProjectionTelevision: + case icSigCRTDisplay: + case icSigPMDisplay: + case icSigAMDisplay: + case icSigPhotoCD: + case icSigPhotoImageSetter: + case icSigGravure: + case icSigOffsetLithography: + case icSigSilkscreen: + case icSigFlexography: + case icSigMotionPictureFilmScanner: + case icSigMotionPictureFilmRecorder: + case icSigDigitalMotionPictureCamera: + case icSigDigitalCinemaProjector: + break; + + default: + { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sprintf(buf, " - %s: Unknown Technology.\r\n", Info.GetSigName(m_nSig)); + sReport += buf; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + } + else if (sig==icSigPerceptualRenderingIntentGamutTag || + sig==icSigSaturationRenderingIntentGamutTag) { + switch(m_nSig) { + case icSigPerceptualReferenceMediumGamut: + break; + + default: + { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sprintf(buf, " - %s: Unknown Reference Medium Gamut.\r\n", Info.GetSigName(m_nSig)); + sReport += buf; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + } + else if (sig==icSigColorimetricIntentImageStateTag) { + switch(m_nSig) { + case icSigSceneColorimetryEstimates: + case icSigSceneAppearanceEstimates: + case icSigFocalPlaneColorimetryEstimates: + case icSigReflectionHardcopyOriginalColorimetry: + case icSigReflectionPrintOutputColorimetry: + break; + + default: + { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sprintf(buf, " - %s: Unknown Colorimetric Intent Image State.\r\n", Info.GetSigName(m_nSig)); + sReport += buf; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + } + + + return rv; +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::CIccTagNamedColor2 + * + * Purpose: Constructor + * + * Args: + * nSize = number of named color entries, + * nDeviceCoords = number of device channels + ***************************************************************************** + */ +CIccTagNamedColor2::CIccTagNamedColor2(int nSize/*=1*/, int nDeviceCoords/*=0*/) +{ + m_nSize = nSize; + m_nVendorFlags = 0; + m_nDeviceCoords = nDeviceCoords; + if (m_nSize <1) + m_nSize = 1; + if (m_nDeviceCoords<0) + m_nDeviceCoords = nDeviceCoords = 0; + + if (nDeviceCoords>0) + nDeviceCoords--; + + m_szPrefix[0] = '\0'; + m_szSufix[0] = '\0'; + m_csPCS = icSigUnknownData; + m_csDevice = icSigUnknownData; + + m_nColorEntrySize = 32/*rootName*/ + (3/*PCS*/ + 1/*iAny*/ + nDeviceCoords)*sizeof(icFloatNumber); + + m_NamedColor = (SIccNamedColorEntry*)calloc(nSize, m_nColorEntrySize); + + m_NamedLab = NULL; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::CIccTagNamedColor2 + * + * Purpose: Copy Constructor + * + * Args: + * ITNC = The CIccTagNamedColor2 object to be copied + ***************************************************************************** + */ +CIccTagNamedColor2::CIccTagNamedColor2(const CIccTagNamedColor2 &ITNC) +{ + m_nColorEntrySize = ITNC.m_nColorEntrySize; + m_nVendorFlags = ITNC.m_nVendorFlags; + m_nDeviceCoords = ITNC.m_nDeviceCoords; + m_nSize = ITNC.m_nSize; + + m_csPCS = ITNC.m_csPCS; + m_csDevice = ITNC.m_csDevice; + + memcpy(m_szPrefix, ITNC.m_szPrefix, sizeof(m_szPrefix)); + memcpy(m_szSufix, ITNC.m_szSufix, sizeof(m_szSufix)); + + m_NamedColor = (SIccNamedColorEntry*)calloc(m_nSize, m_nColorEntrySize); + memcpy(m_NamedColor, ITNC.m_NamedColor, m_nColorEntrySize*m_nSize); + + m_NamedLab = NULL; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::operator= + * + * Purpose: Copy Operator + * + * Args: + * NamedColor2Tag = The CIccTagNamedColor2 object to be copied + ***************************************************************************** + */ +CIccTagNamedColor2 &CIccTagNamedColor2::operator=(const CIccTagNamedColor2 &NamedColor2Tag) +{ + if (&NamedColor2Tag == this) + return *this; + + m_nColorEntrySize = NamedColor2Tag.m_nColorEntrySize; + m_nVendorFlags = NamedColor2Tag.m_nVendorFlags; + m_nDeviceCoords = NamedColor2Tag.m_nDeviceCoords; + m_nSize = NamedColor2Tag.m_nSize; + + m_csPCS = NamedColor2Tag.m_csPCS; + m_csDevice = NamedColor2Tag.m_csDevice; + + memcpy(m_szPrefix, NamedColor2Tag.m_szPrefix, sizeof(m_szPrefix)); + memcpy(m_szSufix, NamedColor2Tag.m_szSufix, sizeof(m_szSufix)); + + if (m_NamedColor) + free(m_NamedColor); + m_NamedColor = (SIccNamedColorEntry*)calloc(m_nSize, m_nColorEntrySize); + memcpy(m_NamedColor, NamedColor2Tag.m_NamedColor, m_nColorEntrySize*m_nSize); + + m_NamedLab = NULL; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::~CIccTagNamedColor2 + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagNamedColor2::~CIccTagNamedColor2() +{ + if (m_NamedColor) + free(m_NamedColor); + + if (m_NamedLab) + delete [] m_NamedLab; +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::SetSize + * + * Purpose: Sets the size of the named color array. + * + * Args: + * nSize - number of named color entries, + * nDeviceCoords - number of device channels + ***************************************************************************** + */ +void CIccTagNamedColor2::SetSize(icUInt32Number nSize, icInt32Number nDeviceCoords/*=-1*/) +{ + if (nSize <1) + nSize = 1; + if (nDeviceCoords<0) + nDeviceCoords = m_nDeviceCoords; + + icInt32Number nNewCoords=nDeviceCoords; + + if (nDeviceCoords>0) + nDeviceCoords--; + + icUInt32Number nColorEntrySize = 32/*rootName*/ + (3/*PCS*/ + 1/*iAny*/ + nDeviceCoords)*sizeof(icFloatNumber); + + SIccNamedColorEntry* pNamedColor = (SIccNamedColorEntry*)calloc(nSize, nColorEntrySize); + + icUInt32Number i, nCopy = __min(nSize, m_nSize); + icUInt32Number j, nCoords = __min(nNewCoords, (icInt32Number)m_nDeviceCoords); + + for (i=0; irootName, pFrom->rootName); + for (j=0; j<3; j++) + pTo->pcsCoords[j] = pFrom->pcsCoords[j]; + + for (j=0; jdeviceCoords[j] = pFrom->deviceCoords[j]; + } + } + free(m_NamedColor); + + m_nColorEntrySize = nColorEntrySize; + + m_NamedColor = pNamedColor; + m_nSize = nSize; + m_nDeviceCoords = nNewCoords; + + ResetPCSCache(); +} + + +/** +**************************************************************************** +* Name: CIccTagNamedColor2::SetPrefix +* +* Purpose: Set contents of suffix member field +* +* Args: +* szPrefix - string to set prefix to +***************************************************************************** +*/ +void CIccTagNamedColor2::SetPrefix(const icChar *szPrefix) +{ + strncpy(m_szPrefix, szPrefix, sizeof(m_szPrefix)); + m_szPrefix[sizeof(m_szPrefix)-1]='\0'; +} + + +/** +**************************************************************************** +* Name: CIccTagNamedColor2::SetSufix +* +* Purpose: Set contents of suffix member field +* +* Args: +* szPrefix - string to set prefix to +***************************************************************************** +*/ +void CIccTagNamedColor2::SetSufix(const icChar *szSufix) +{ + strncpy(m_szSufix, szSufix, sizeof(m_szSufix)); + m_szSufix[sizeof(m_szSufix)-1]='\0'; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagNamedColor2::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nNum, nCoords; + + icUInt32Number nTagHdrSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + //m_nReserved=0 + sizeof(icUInt32Number) + //VendorFlags + sizeof(icUInt32Number) + //Num Colors + sizeof(icUInt32Number) + //Num Device Coords + sizeof(m_szPrefix) + + sizeof(m_szSufix); + if (nTagHdrSize > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read32(&m_nVendorFlags) || + !pIO->Read32(&nNum) || + !pIO->Read32(&nCoords) || + pIO->Read8(m_szPrefix, sizeof(m_szPrefix))!=sizeof(m_szPrefix) || + pIO->Read8(m_szSufix, sizeof(m_szSufix))!=sizeof(m_szSufix)) { + return false; + } + + size -= nTagHdrSize; + + icUInt32Number nCount = size / (32+(3+nCoords)*sizeof(icUInt16Number)); + + if (nCount < nNum) + return false; + + SetSize(nNum, nCoords); + + icUInt32Number i; + SIccNamedColorEntry *pNamedColor=m_NamedColor; + + for (i=0; iRead8(&pNamedColor->rootName, sizeof(pNamedColor->rootName))!=sizeof(pNamedColor->rootName) || + pIO->Read16Float(&pNamedColor->pcsCoords, 3)!=3) + return false; + if (nCoords) { + if (pIO->Read16Float(&pNamedColor->deviceCoords, nCoords)!=(icInt32Number)nCoords) + return false; + } + pNamedColor = (SIccNamedColorEntry*)((icChar*)pNamedColor + m_nColorEntrySize); + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagNamedColor2::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nVendorFlags)) + return false; + + if (!pIO->Write32(&m_nSize)) + return false; + + if (!pIO->Write32(&m_nDeviceCoords)) + return false; + + if (!pIO->Write8(m_szPrefix, sizeof(m_szPrefix))) + return false; + + if (!pIO->Write8(m_szSufix, sizeof(m_szSufix))) + return false; + + icUInt32Number i; + SIccNamedColorEntry *pNamedColor=m_NamedColor; + + for (i=0; iWrite8(&pNamedColor->rootName, sizeof(pNamedColor->rootName))!=sizeof(pNamedColor->rootName) || + pIO->Write16Float(&pNamedColor->pcsCoords, 3)!=3) + return false; + if (m_nDeviceCoords) { + if (pIO->Write16Float(&pNamedColor->deviceCoords, m_nDeviceCoords) != (icInt32Number)m_nDeviceCoords) + return false; + } + pNamedColor = (SIccNamedColorEntry*)((icChar*)pNamedColor + m_nColorEntrySize); + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagNamedColor2::Describe(std::string &sDescription) +{ + icChar buf[128], szColorVal[40], szColor[40]; + + icUInt32Number i, j; + SIccNamedColorEntry *pNamedColor=m_NamedColor; + + sDescription.reserve(sDescription.size() + m_nSize*79); + + sprintf(buf, "BEGIN_NAMED_COLORS flags=%08x %u %u\r\n", m_nVendorFlags, m_nSize, m_nDeviceCoords); + sDescription += buf; + + sprintf(buf, "Prefix=\"%s\"\r\n", m_szPrefix); + sDescription += buf; + + sprintf(buf, "Sufix= \"%s\"\r\n", m_szSufix); + sDescription += buf; + + for (i=0; irootName); + sDescription += buf; + + icFloatNumber pcsCoord[3]; + for (j=0; j<3; j++) + pcsCoord[j] = pNamedColor->pcsCoords[j]; + + if (m_csPCS==icSigLabData) { + for (j=0; j<3; j++) + pcsCoord[j] = (icFloatNumber)(pcsCoord[j] * 65535.0 / 65280.0); + } + + for (j=0; j<3; j++) { + icColorIndexName(szColor, m_csPCS, j, 3, "P"); + icColorValue(szColorVal, pcsCoord[j], m_csPCS, j); + sprintf(buf, " %s=%s", szColor, szColorVal); + sDescription += buf; + } + if (m_nDeviceCoords) { + sDescription += " :"; + for (j=0; jdeviceCoords[j], m_csDevice, j); + sprintf(buf, " %s=%s", szColor, szColorVal); + sDescription += buf; + } + } + sDescription += "\r\n"; + + pNamedColor = (SIccNamedColorEntry*)((icChar*)pNamedColor + m_nColorEntrySize); + } +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::SetColorSpaces + * + * Purpose: Set the device and PCS color space of the tag + * + * Args: + * csPCS = PCS color space signature, + * csDevice = Device color space signature + * + ***************************************************************************** + */ +void CIccTagNamedColor2::SetColorSpaces(icColorSpaceSignature csPCS, icColorSpaceSignature csDevice) +{ + m_csPCS = csPCS; + m_csDevice = csDevice; +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::FindRootColor + * + * Purpose: Find the root color name + * + * Args: + * szRootColor = string containing the root color name to be found + * + * Return: Index of the named color array where the root color name was found, + * if the color was not found -1 is returned + ***************************************************************************** + */ +icInt32Number CIccTagNamedColor2::FindRootColor(const icChar *szRootColor) const +{ + for (icUInt32Number i=0; i (icInt32Number)m_nSize-1) + return false; + + sColorName += m_szPrefix; + sColorName += m_NamedColor[index].rootName; + sColorName += m_szSufix; + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::UnitClip + * + * Purpose: Clip number so that its between 0-1 + * + * Args: + * v = number to be clipped + * + * Return: Clipped number + * + ***************************************************************************** + */ +icFloatNumber CIccTagNamedColor2::UnitClip(icFloatNumber v) const +{ + if (v<0) + v = 0; + if (v>1.0) + v = 1.0; + + return v; +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::NegClip + * + * Purpose: Negative numbers are clipped to zero + * + * Args: + * v = number to be clipped + * + * Return: Clipped number + * + ***************************************************************************** + */ +icFloatNumber CIccTagNamedColor2::NegClip(icFloatNumber v) const +{ + if (v<0) + v=0; + + return v; +} + + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::Lab2ToLab4 + * + * Purpose: Convert version 2 Lab to version 4 Lab + * + * Args: + * Dst = array to store version 4 Lab coordinates, + * Src = array containing version 2 Lab coordinates + * + ***************************************************************************** + */ +void CIccTagNamedColor2::Lab2ToLab4(icFloatNumber *Dst, const icFloatNumber *Src) const +{ + Dst[0] = UnitClip((icFloatNumber)(Src[0] * 65535.0 / 65280.0)); + Dst[1] = UnitClip((icFloatNumber)(Src[1] * 65535.0 / 65280.0)); + Dst[2] = UnitClip((icFloatNumber)(Src[2] * 65535.0 / 65280.0)); +} + +/** + **************************************************************************** + * Name: CIccTagNamedColor2::Lab4ToLab2 + * + * Purpose: Convert version 4 Lab to version 2 Lab + * + * Args: + * Dst = array to store version 2 Lab coordinates, + * Src = array containing version 4 Lab coordinates + * + ***************************************************************************** + */ +void CIccTagNamedColor2::Lab4ToLab2(icFloatNumber *Dst, const icFloatNumber *Src) const +{ + Dst[0] = (icFloatNumber)(Src[0] * 65280.0 / 65535.0); + Dst[1] = (icFloatNumber)(Src[1] * 65280.0 / 65535.0); + Dst[2] = (icFloatNumber)(Src[2] * 65280.0 / 65535.0); +} + + +/** +****************************************************************************** +* Name: CIccTagNamedColor2::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagNamedColor2::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!m_nSize) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Empty tag!\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (m_nDeviceCoords) { + if (pProfile) { + icUInt32Number nCoords = icGetSpaceSamples(pProfile->m_Header.colorSpace); + if (m_nDeviceCoords != nCoords) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of device co-ordinates.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + else { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::CIccTagXYZ + * + * Purpose: Constructor + * + * Args: + * nSize = number of XYZ entries + * + ***************************************************************************** + */ +CIccTagXYZ::CIccTagXYZ(int nSize/*=1*/) +{ + m_nSize = nSize; + if (m_nSize <1) + m_nSize = 1; + m_XYZ = (icXYZNumber*)calloc(nSize, sizeof(icXYZNumber)); +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::CIccTagXYZ + * + * Purpose: Copy Constructor + * + * Args: + * ITXYZ = The CIccTagXYZ object to be copied + ***************************************************************************** + */ +CIccTagXYZ::CIccTagXYZ(const CIccTagXYZ &ITXYZ) +{ + m_nSize = ITXYZ.m_nSize; + + m_XYZ = (icXYZNumber*)calloc(m_nSize, sizeof(icXYZNumber)); + memcpy(m_XYZ, ITXYZ.m_XYZ, sizeof(icXYZNumber)*m_nSize); +} + + + +/** + **************************************************************************** + * Name: CIccTagXYZ::operator= + * + * Purpose: Copy Operator + * + * Args: + * XYZTag = The CIccTagXYZ object to be copied + ***************************************************************************** + */ +CIccTagXYZ &CIccTagXYZ::operator=(const CIccTagXYZ &XYZTag) +{ + if (&XYZTag == this) + return *this; + + m_nSize = XYZTag.m_nSize; + + if (m_XYZ) + free(m_XYZ); + m_XYZ = (icXYZNumber*)calloc(m_nSize, sizeof(icXYZNumber)); + memcpy(m_XYZ, XYZTag.m_XYZ, sizeof(icXYZNumber)*m_nSize); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::~CIccTagXYZ + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagXYZ::~CIccTagXYZ() +{ + if (m_XYZ) + free(m_XYZ); +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagXYZ::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icXYZNumber) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nNum=((size-2*sizeof(icUInt32Number)) / sizeof(icXYZNumber)); + icUInt32Number nNum32 = nNum*sizeof(icXYZNumber)/sizeof(icUInt32Number); + + SetSize(nNum); + + if (pIO->Read32(m_XYZ, nNum32) != (icInt32Number)nNum32 ) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagXYZ::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + icUInt32Number nNum32 = m_nSize * sizeof(icXYZNumber)/sizeof(icUInt32Number); + + if ( + pIO->Write32(m_XYZ, nNum32) != (icInt32Number)nNum32) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagXYZ::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagXYZ::Describe(std::string &sDescription) +{ + icChar buf[128]; + + if (m_nSize == 1 ) { + sprintf(buf, "X=%.4lf, Y=%.4lf, Z=%.4lf\r\n", icFtoD(m_XYZ[0].X), icFtoD(m_XYZ[0].Y), icFtoD(m_XYZ[0].Z)); + sDescription += buf; + } + else { + icUInt32Number i; + sDescription.reserve(sDescription.size() + m_nSize*79); + + for (i=0; i size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&nChannels) || + !pIO->Read16(&m_nColorantType)) + return false; + + icUInt32Number nNum = (size-3*sizeof(icUInt32Number)) / sizeof(icChromaticityNumber); + icUInt32Number nNum32 = nNum*sizeof(icChromaticityNumber)/sizeof(icU16Fixed16Number); + + if (nNum < nChannels) + return false; + + SetSize((icUInt16Number)nNum); + + if (pIO->Read32(&m_xy[0], nNum32) != (icInt32Number)nNum32 ) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagChromaticity::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagChromaticity::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nChannels)) + return false; + + if (!pIO->Write16(&m_nColorantType)) + return false; + + icUInt32Number nNum32 = m_nChannels*sizeof(icChromaticityNumber)/sizeof(icU16Fixed16Number); + + if (pIO->Write32(&m_xy[0], nNum32) != (icInt32Number)nNum32) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagChromaticity::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagChromaticity::Describe(std::string &sDescription) +{ + icChar buf[128]; + CIccInfo Fmt; + + icUInt32Number i; + //sDescription.reserve(sDescription.size() + m_nChannels*79); + sprintf(buf, "Number of Channels : %u\r\n", m_nChannels); + sDescription += buf; + + sprintf(buf, "Colorant Encoding : %s\r\n", Fmt.GetColorantEncoding((icColorantEncoding)m_nColorantType)); + sDescription += buf; + + for (i=0; i m_nChannels) { + memset(&m_xy[m_nChannels], 0, (nSize - m_nChannels)*sizeof(icChromaticityNumber)); + } + + m_nChannels = nSize; +} + + +/** +****************************************************************************** +* Name: CIccTagChromaticity::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagChromaticity::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (m_nColorantType) { + + if (m_nChannels!=3) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of device channels must be three.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + switch(m_nColorantType) { + case icColorantITU: + { + if ( (m_xy[0].x != icDtoUF((icFloatNumber)0.640)) || (m_xy[0].y != icDtoUF((icFloatNumber)0.330)) || + (m_xy[1].x != icDtoUF((icFloatNumber)0.300)) || (m_xy[1].y != icDtoUF((icFloatNumber)0.600)) || + (m_xy[2].x != icDtoUF((icFloatNumber)0.150)) || (m_xy[2].y != icDtoUF((icFloatNumber)0.060)) ) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Chromaticity data does not match specification.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + break; + } + + case icColorantSMPTE: + { + if ( (m_xy[0].x != icDtoUF((icFloatNumber)0.630)) || (m_xy[0].y != icDtoUF((icFloatNumber)0.340)) || + (m_xy[1].x != icDtoUF((icFloatNumber)0.310)) || (m_xy[1].y != icDtoUF((icFloatNumber)0.595)) || + (m_xy[2].x != icDtoUF((icFloatNumber)0.155)) || (m_xy[2].y != icDtoUF((icFloatNumber)0.070)) ) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Chromaticity data does not match specification.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + break; + } + + case icColorantEBU: + { + if ( (m_xy[0].x != icDtoUF((icFloatNumber)0.64)) || (m_xy[0].y != icDtoUF((icFloatNumber)0.33)) || + (m_xy[1].x != icDtoUF((icFloatNumber)0.29)) || (m_xy[1].y != icDtoUF((icFloatNumber)0.60)) || + (m_xy[2].x != icDtoUF((icFloatNumber)0.15)) || (m_xy[2].y != icDtoUF((icFloatNumber)0.06)) ) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Chromaticity data does not match specification.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + break; + } + + case icColorantP22: + { + if ( (m_xy[0].x != icDtoUF((icFloatNumber)0.625)) || (m_xy[0].y != icDtoUF((icFloatNumber)0.340)) || + (m_xy[1].x != icDtoUF((icFloatNumber)0.280)) || (m_xy[1].y != icDtoUF((icFloatNumber)0.605)) || + (m_xy[2].x != icDtoUF((icFloatNumber)0.155)) || (m_xy[2].y != icDtoUF((icFloatNumber)0.070)) ) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Chromaticity data does not match specification.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + break; + } + + default: + { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Invalid colorant type encoding.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + } + + return rv; +} + + +/** + **************************************************************************** + * Name: ::CIccTagFixedNum + * + * Purpose: ConstrCIccTagFixedNumuctor + * + * Args: + * nSize = number of data entries + * + ***************************************************************************** + */ +template +CIccTagFixedNum::CIccTagFixedNum(int nSize/*=1*/) +{ + m_nSize = nSize; + if (m_nSize <1) + m_nSize = 1; + m_Num = (T*)calloc(nSize, sizeof(T)); +} + + +/** + **************************************************************************** + * Name: CIccTagFixedNum::CIccTagFixedNum + * + * Purpose: Copy Constructor + * + * Args: + * ITFN = The CIccTagFixedNum object to be copied + ***************************************************************************** + */ +template +CIccTagFixedNum::CIccTagFixedNum(const CIccTagFixedNum &ITFN) +{ + m_nSize = ITFN.m_nSize; + m_Num = (T*)calloc(m_nSize, sizeof(T)); + memcpy(m_Num, ITFN.m_Num, sizeof(T) * m_nSize); +} + + +/** + **************************************************************************** + * Name: CIccTagFixedNum::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITFN = The CIccTagFixedNum object to be copied + ***************************************************************************** + */ +template +CIccTagFixedNum &CIccTagFixedNum::operator=(const CIccTagFixedNum &ITFN) +{ + if (&ITFN == this) + return *this; + + m_nSize = ITFN.m_nSize; + + if (m_Num) + free(m_Num); + m_Num = (T*)calloc(m_nSize, sizeof(T)); + memcpy(m_Num, ITFN.m_Num, sizeof(T) * m_nSize); + + return *this; +} + + + +/** + **************************************************************************** + * Name: CIccTagFixedNum::~CIccTagFixedNum + * + * Purpose: Destructor + * + ***************************************************************************** + */ +template +CIccTagFixedNum::~CIccTagFixedNum() +{ + if (m_Num) + free(m_Num); +} + +/** + **************************************************************************** + * Name: CIccTagFixedNum::GetClassName + * + * Purpose: Returns the tag type class name + * + ***************************************************************************** + */ +template +const icChar* CIccTagFixedNum::GetClassName() const +{ + if (Tsig==icSigS15Fixed16ArrayType) + return "CIccTagS15Fixed16"; + else + return "CIccTagU16Fixed16"; +} + + +/** + **************************************************************************** + * Name: CIccTagFixedNum::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +template +bool CIccTagFixedNum::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(T) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nSize=((size-2*sizeof(icUInt32Number)) / sizeof(T)); + + SetSize(nSize); + + if (pIO->Read32(m_Num, nSize) != (icInt32Number)nSize ) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagFixedNum::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +template +bool CIccTagFixedNum::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (pIO->Write32(m_Num, m_nSize) != (icInt32Number)m_nSize) + return false; + + return true; +} + +/** + **************************************************************************** + * Name: CIccTagFixedNum::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +template +void CIccTagFixedNum::Describe(std::string &sDescription) +{ + icChar buf[128]; + + if (m_nSize == 1 ) { + if (Tsig==icSigS15Fixed16ArrayType) + sprintf(buf, "Value = %.4lf\r\n", icFtoD(m_Num[0])); + else + sprintf(buf, "Value = %.4lf\r\n", icUFtoD(m_Num[0])); + sDescription += buf; + } + else { + icUInt32Number i; + + if (Tsig==icSigS15Fixed16ArrayType && m_nSize==9) { + sDescription += "Matrix Form:\r\n"; + icMatrixDump(sDescription, (icS15Fixed16Number*)m_Num); + + sDescription += "\r\nArrayForm:\r\n"; + } + sDescription.reserve(sDescription.size() + m_nSize*79); + + for (i=0; i +void CIccTagFixedNum::SetSize(icUInt32Number nSize, bool bZeroNew/*=true*/) +{ + if (nSize==m_nSize) + return; + + m_Num = (T*)realloc(m_Num, nSize*sizeof(T)); + if (bZeroNew && m_nSize < nSize) { + memset(&m_Num[m_nSize], 0, (nSize-m_nSize)*sizeof(T)); + } + m_nSize = nSize; +} + + +//Make sure typedef classes get built +template class CIccTagFixedNum; +template class CIccTagFixedNum; + + +/** + **************************************************************************** + * Name: CIccTagNum::CIccTagNum + * + * Purpose: Constructor + * + * Args: + * nSize = number of data entries + ***************************************************************************** + */ +template +CIccTagNum::CIccTagNum(int nSize/*=1*/) +{ + m_nSize = nSize; + if (m_nSize <1) + m_nSize = 1; + m_Num = (T*)calloc(nSize, sizeof(T)); +} + + +/** + **************************************************************************** + * Name: CIccTagNum::CIccTagNum + * + * Purpose: Copy Constructor + * + * Args: + * ITNum = The CIccTagNum object to be copied + ***************************************************************************** + */ +template +CIccTagNum::CIccTagNum(const CIccTagNum &ITNum) +{ + m_nSize = ITNum.m_nSize; + + m_Num = (T*)calloc(m_nSize, sizeof(T)); + memcpy(m_Num, ITNum.m_Num, sizeof(T)*m_nSize); +} + + +/** + **************************************************************************** + * Name: CIccTagNum::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITNum = The CIccTagNum object to be copied + ***************************************************************************** + */ +template +CIccTagNum &CIccTagNum::operator=(const CIccTagNum &ITNum) +{ + if (&ITNum == this) + return *this; + + m_nSize = ITNum.m_nSize; + + m_Num = (T*)calloc(m_nSize, sizeof(T)); + memcpy(m_Num, ITNum.m_Num, sizeof(T)*m_nSize); + + return *this; +} + + + +/** + **************************************************************************** + * Name: CIccTagNum::~CIccTagNum + * + * Purpose: Destructor + * + ***************************************************************************** + */ +template +CIccTagNum::~CIccTagNum() +{ + if (m_Num) + free(m_Num); +} + +/** + **************************************************************************** + * Name: CIccTagNum::GetClassName + * + * Purpose: Returns the tag type class name + * + ***************************************************************************** + */ +template +const icChar *CIccTagNum::GetClassName() const +{ + if (sizeof(T)==sizeof(icUInt8Number)) + return "CIccTagUInt8"; + else if (sizeof(T)==sizeof(icUInt16Number)) + return "CIccTagUInt16"; + else if (sizeof(T)==sizeof(icUInt32Number)) + return "CIccTagUInt32"; + else if (sizeof(T)==sizeof(icUInt64Number)) + return "CIccTagUInt64"; + else + return "CIccTagNum<>"; +} + + +/** + **************************************************************************** + * Name: CIccTagNum::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +template +bool CIccTagNum::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(T) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nSize=((size-2*sizeof(icUInt32Number)) / sizeof(T)); + + SetSize(nSize); + + if (sizeof(T)==sizeof(icUInt8Number)) { + if (pIO->Read8(m_Num, nSize) != (icInt32Number)nSize ) + return false; + } + else if (sizeof(T)==sizeof(icUInt16Number)) { + if (pIO->Read16(m_Num, nSize) != (icInt32Number)nSize ) + return false; + } + else if (sizeof(T)==sizeof(icUInt32Number)) { + if (pIO->Read32(m_Num, nSize) != (icInt32Number)nSize ) + return false; + } + else if (sizeof(T)==sizeof(icUInt64Number)) { + if (pIO->Read64(m_Num, nSize) != (icInt32Number)nSize ) + return false; + } + else + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagNum::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +template +bool CIccTagNum::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (sizeof(T)==sizeof(icUInt8Number)) { + if (pIO->Write32(m_Num, m_nSize) != (icInt32Number)m_nSize) + return false; + } + else if (sizeof(T)==sizeof(icUInt16Number)) { + if (pIO->Write32(m_Num, m_nSize) != (icInt32Number)m_nSize) + return false; + } + else if (sizeof(T)==sizeof(icUInt32Number)) { + if (pIO->Write32(m_Num, m_nSize) != (icInt32Number)m_nSize) + return false; + } + else if (sizeof(T)==sizeof(icUInt64Number)) { + if (pIO->Write32(m_Num, m_nSize) != (icInt32Number)m_nSize) + return false; + } + else + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagNum::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +template +void CIccTagNum::Describe(std::string &sDescription) +{ + icChar buf[128]; + + if (m_nSize == 1 ) { + switch (sizeof(T)) { + case 1: + sprintf(buf, "Value = %u (0x02%x)\r\n", m_Num[0], m_Num[0]); + break; + case 2: + sprintf(buf, "Value = %u (0x04%x)\r\n", m_Num[0], m_Num[0]); + break; + case 4: + sprintf(buf, "Value = %u (0x08%x)\r\n", m_Num[0], m_Num[0]); + break; + case 8: + sprintf(buf, "Value = %u (0x016%x)\r\n", m_Num[0], m_Num[0]); + break; + default: + sprintf(buf, "Value = %u (0x%x)\r\n", m_Num[0], m_Num[0]); + break; + } + sDescription += buf; + } + else { + icUInt32Number i; + sDescription.reserve(sDescription.size() + m_nSize*79); + + for (i=0; i +void CIccTagNum::Describe(std::string &sDescription) +{ + icChar buf[128]; + + if (m_nSize == 1 ) { + sprintf(buf, "Value = %llu (0x016%llx)\r\n", m_Num[0], m_Num[0]); + sDescription += buf; + } + else { + icUInt32Number i; + sDescription.reserve(sDescription.size() + m_nSize*79); + + for (i=0; i +void CIccTagNum::SetSize(icUInt32Number nSize, bool bZeroNew/*=true*/) +{ + if (nSize==m_nSize) + return; + + m_Num = (T*)realloc(m_Num, nSize*sizeof(T)); + if (bZeroNew && m_nSize < nSize) { + memset(&m_Num[m_nSize], 0, (nSize-m_nSize)*sizeof(T)); + } + m_nSize = nSize; +} + +//Make sure typedef classes get built +template class CIccTagNum; +template class CIccTagNum; +template class CIccTagNum; +template class CIccTagNum; + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::CIccTagMeasurement + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagMeasurement::CIccTagMeasurement() +{ + memset(&m_Data, 0, sizeof(m_Data)); +} + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::CIccTagMeasurement + * + * Purpose: Copy Constructor + * + * Args: + * ITM = The CIccTagMeasurement object to be copied + ***************************************************************************** + */ +CIccTagMeasurement::CIccTagMeasurement(const CIccTagMeasurement &ITM) +{ + memcpy(&m_Data, &ITM.m_Data, sizeof(m_Data)); +} + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::operator= + * + * Purpose: Copy Operator + * + * Args: + * MeasTag = The CIccTagMeasurement object to be copied + ***************************************************************************** + */ +CIccTagMeasurement &CIccTagMeasurement::operator=(const CIccTagMeasurement &MeasTag) +{ + if (&MeasTag == this) + return *this; + + memcpy(&m_Data, &MeasTag.m_Data, sizeof(m_Data)); + + return *this; +} + + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::~CIccTagMeasurement + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagMeasurement::~CIccTagMeasurement() +{ +} + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagMeasurement::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(m_Data) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nSize=sizeof(m_Data)/sizeof(icUInt32Number); + + if (pIO->Read32(&m_Data,nSize) != (icInt32Number)nSize) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagMeasurement::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + icUInt32Number nSize=sizeof(m_Data)/sizeof(icUInt32Number); + + if (pIO->Write32(&m_Data,nSize) != (icInt32Number)nSize) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagMeasurement::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagMeasurement::Describe(std::string &sDescription) +{ + CIccInfo Fmt; + icChar buf[128]; + + sDescription += Fmt.GetStandardObserverName(m_Data.stdObserver); sDescription += "\r\n"; + sprintf(buf, "Backing measurement: X=%.4lf, Y=%.4lf, Z=%.4lf\r\n", + icFtoD(m_Data.backing.X), + icFtoD(m_Data.backing.Y), + icFtoD(m_Data.backing.Z)); + sDescription += buf; + sDescription += Fmt.GetMeasurementGeometryName(m_Data.geometry); sDescription += "\r\n"; + sDescription += Fmt.GetMeasurementFlareName(m_Data.flare); sDescription += "\r\n"; + sDescription += Fmt.GetIlluminantName(m_Data.illuminant); sDescription += "\r\n"; +} + + +/** +****************************************************************************** +* Name: CIccTagMeasurement::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagMeasurement::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + switch(m_Data.stdObserver) { + case icStdObsUnknown: + case icStdObs1931TwoDegrees: + case icStdObs1964TenDegrees: + break; + + default: + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Invalid standard observer encoding.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + switch(m_Data.geometry) { + case icGeometryUnknown: + case icGeometry045or450: + case icGeometry0dord0: + break; + + default: + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Invalid measurement geometry encoding.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + switch(m_Data.illuminant) { + case icIlluminantUnknown: + case icIlluminantD50: + case icIlluminantD65: + case icIlluminantD93: + case icIlluminantF2: + case icIlluminantD55: + case icIlluminantA: + case icIlluminantEquiPowerE: + case icIlluminantF8: + break; + + default: + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Invalid standard illuminant encoding.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::CIccLocalizedUnicode + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccLocalizedUnicode::CIccLocalizedUnicode() +{ + m_pBuf = (icUInt16Number*)malloc(1*sizeof(icUInt16Number)); + *m_pBuf = 0; + m_nLength = 0; +} + + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::CIccLocalizedUnicode + * + * Purpose: Copy Constructor + * + * Args: + * ILU = The CIccLocalizedUnicode object to be copied + ***************************************************************************** + */ +CIccLocalizedUnicode::CIccLocalizedUnicode(const CIccLocalizedUnicode& ILU) +{ + m_nLength = ILU.GetLength(); + m_pBuf = (icUInt16Number*)malloc((m_nLength+1) * sizeof(icUInt16Number)); + if (m_nLength) + memcpy(m_pBuf, ILU.GetBuf(), m_nLength*sizeof(icUInt16Number)); + m_pBuf[m_nLength] = 0; + m_nLanguageCode = ILU.m_nLanguageCode; + m_nCountryCode = ILU.m_nCountryCode; +} + + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::operator= + * + * Purpose: Copy Operator + * + * Args: + * UnicodeText = The CIccLocalizedUnicode object to be copied + ***************************************************************************** + */ +CIccLocalizedUnicode &CIccLocalizedUnicode::operator=(const CIccLocalizedUnicode &UnicodeText) +{ + if (&UnicodeText == this) + return *this; + + SetSize(UnicodeText.GetLength()); + memcpy(m_pBuf, UnicodeText.GetBuf(), m_nLength*sizeof(icUInt16Number)); + m_nLanguageCode = UnicodeText.m_nLanguageCode; + m_nCountryCode = UnicodeText.m_nCountryCode; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::~CIccLocalizedUnicode + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccLocalizedUnicode::~CIccLocalizedUnicode() +{ + if (m_pBuf) + free(m_pBuf); +} + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::GetAnsiSize + * + * Purpose: Returns the size of the ANSI data buffer + * + ***************************************************************************** + */ +icUInt32Number CIccLocalizedUnicode::GetAnsiSize() +{ + icUInt32Number len; +#ifdef USE_WINDOWS_MB_SUPPORT + len = WideCharToMultiByte(CP_ACP, 0x00000400, (LPCWSTR)m_pBuf, m_nLength, NULL, 0, NULL, NULL); +#else + len = m_nLength; +#endif + + return len; +} + +/** + **************************************************************************** + * Name: CIccLocalizedUnicode::GetAnsi + * + * Purpose: Extracts the ANSI data buffer + * + * Args: + * szBuf = pointer where the returned string buffer is to be stored + * nBufSize = size of the buffer to be extracted + * + * Return: + * Pointer to the ANSI data string + ***************************************************************************** + */ +const icChar *CIccLocalizedUnicode::GetAnsi(icChar *szBuf, icUInt32Number nBufSize) +{ + if (!szBuf) + return NULL; + + if (!m_nLength) { + *szBuf='\0'; + } + else { +#ifdef USE_WINDOWS_MB_SUPPORT + int len = WideCharToMultiByte(CP_ACP, 0x00000400, (LPCWSTR)m_pBuf, m_nLength, szBuf, nBufSize, NULL, NULL); + szBuf[len]='\0'; +#else + icUInt32Number i; + + for (i=0; iclear(); + *m_Strings = *MultiLocalizedTag.m_Strings; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagMultiLocalizedUnicode::~CIccTagMultiLocalizedUnicode + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagMultiLocalizedUnicode::~CIccTagMultiLocalizedUnicode() +{ + delete m_Strings; +} + + +/** + **************************************************************************** + * Name: CIccTagMultiLocalizedUnicode::Read + * + * Purpose: Read in the tag contents into a data block + * + * Since MultiLocalizedUnicode tags can be embedded in other tags + * this function ensures that the current read pointer will be set to the + * position just after the last name record. + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagMultiLocalizedUnicode::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nNumRec, nRecSize; + icLanguageCode nLanguageCode; + icCountryCode nRegionCode; + icUInt32Number nLength, nOffset, nNumChar; + + if (!m_Strings->empty()) + m_Strings->clear(); + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number)*3 > size) + return false; + + if (!pIO) { + return false; + } + + icUInt32Number nTagPos = pIO->Tell(); + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read32(&nNumRec) || + !pIO->Read32(&nRecSize)) + return false; + + + if (nRecSize!=12) { //Recognized version name records are 12 bytes each + return false; + } + + icUInt32Number i; + CIccLocalizedUnicode Unicode; + icUInt32Number nLastPos = 0; + + for (i=0; i size) + return false; + + pIO->Seek(nTagPos+4*sizeof(icUInt32Number) + i*12, icSeekSet); + + if (!pIO->Read16(&nLanguageCode) || + !pIO->Read16(&nRegionCode) || + !pIO->Read32(&nLength) || + !pIO->Read32(&nOffset)) + return false; + + if (nOffset+nLength > size) + return false; + + //Find out position of the end of last named record + if (nOffset+nLength > nLastPos) + nLastPos = nOffset + nLength; + + nNumChar = nLength / sizeof(icUInt16Number); + + Unicode.SetSize(nNumChar); + Unicode.m_nLanguageCode = nLanguageCode; + Unicode.m_nCountryCode = nRegionCode; + + pIO->Seek(nTagPos+nOffset, icSeekSet); + + if (pIO->Read16(Unicode.GetBuf(), nNumChar) != (icInt32Number)nNumChar) + return false; + + m_Strings->push_back(Unicode); + } + + //Now seek past the last named record + if (nLastPos > 0) + pIO->Seek(nTagPos+nLastPos, icSeekSet); + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagMultiLocalizedUnicode::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagMultiLocalizedUnicode::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt32Number nNumRec=(icUInt32Number)m_Strings->size(), nRecSize=12; + icUInt32Number nLength; + + if (!pIO) { + return false; + } + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write32(&nNumRec) || + !pIO->Write32(&nRecSize)) + return false; + + + icUInt32Number nPos = 4*sizeof(icUInt32Number) + nNumRec*12; + + CIccMultiLocalizedUnicode::iterator i; + + for (i=m_Strings->begin(); i!=m_Strings->end(); i++) { + nLength = i->GetLength() * sizeof(icUInt16Number); + + if (!pIO->Write16(&i->m_nLanguageCode) || + !pIO->Write16(&i->m_nCountryCode) || + !pIO->Write32(&nLength) || + !pIO->Write32(&nPos)) + return false; + nPos += nLength; + } + + for (i=m_Strings->begin(); i!=m_Strings->end(); i++) { + nLength = i->GetLength(); + + if (nLength) { + if (pIO->Write16(i->GetBuf(), nLength) != (icInt32Number)nLength) + return false; + } + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagMultiLocalizedUnicode::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagMultiLocalizedUnicode::Describe(std::string &sDescription) +{ + icChar *szBuf = (icChar*)malloc(128); + int nSize = 127, nAnsiSize; + CIccMultiLocalizedUnicode::iterator i; + + for (i=m_Strings->begin(); i!=m_Strings->end(); i++) { + if (i!=m_Strings->begin()) + sDescription += "\r\n"; + + sprintf(szBuf, "Language = '%c%c', Region = '%c%c'\r\n", + i->m_nLanguageCode>>8, i->m_nLanguageCode, + i->m_nCountryCode>>8, i->m_nCountryCode); + + sDescription += szBuf; + + nAnsiSize = i->GetAnsiSize(); + + if (nAnsiSize>nSize) { + szBuf = (icChar*)realloc(szBuf, nAnsiSize+1); + nSize = nAnsiSize; + } + i->GetAnsi(szBuf, nSize); + sDescription += "\""; + sDescription += szBuf; + sDescription += "\"\r\n"; + } +} + + +/** +****************************************************************************** +* Name: CIccTagMultiLocalizedUnicode::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagMultiLocalizedUnicode::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!m_Strings->size()) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Empty tag!\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + + // TODO: Look at the ISO-639 and ISO-3166 documents for valid + // Language and Region codes +/* + CIccMultiLocalizedUnicode::iterator i; + for (i=m_Strings->begin(); i!=m_Strings->end(); i++) { + switch(i->m_nLanguageCode) { + case : + break; + default: + } + + switch(i->m_nRegionCode) { + case : + break; + default: + } + } +*/ + + return rv; +} + +/** +**************************************************************************** +* Name: sampleICC::CIccTagMultiLocalizedUnicode::Find +* +* Purpose: +* +* Args: +* nLanguageCode +* nRegionCode +* +* Return: +* Pointer to CIccLocalizedUnicode object associated with the nLanguageCode +* and nRegionCode or NULL if not found +***************************************************************************** +*/ +CIccLocalizedUnicode *CIccTagMultiLocalizedUnicode::Find(icLanguageCode nLanguageCode /* = icLanguageCodeEnglish */, + icCountryCode nRegionCode /* = icCountryCodeUSA */) +{ + CIccMultiLocalizedUnicode::iterator i; + + for (i=m_Strings->begin(); i!=m_Strings->end(); i++) { + if (i->m_nLanguageCode == nLanguageCode && + i->m_nCountryCode == nRegionCode) { + return &(*i); + } + } + + return NULL; +} + +/** +**************************************************************************** +* Name: sampleICC::CIccTagMultiLocalizedUnicode::SetText +* +* Purpose: +* +* Args: +* sszUnicodeText +* nLanguageCode +* RegionCode +***************************************************************************** +*/ +void CIccTagMultiLocalizedUnicode::SetText(const icChar *szText, + icLanguageCode nLanguageCode /* = icLanguageCodeEnglish */, + icCountryCode nRegionCode /* = icCountryCodeUSA */) +{ + CIccLocalizedUnicode *pText = Find(nLanguageCode, nRegionCode); + + if (!pText) { + CIccLocalizedUnicode newText; + newText.SetText(szText, nLanguageCode, nRegionCode); + m_Strings->push_back(newText); + } + else { + pText->SetText(szText, nLanguageCode, nRegionCode); + } +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccTagMultiLocalizedUnicode::SetText +* +* Purpose: +* +* Args: +* sszUnicodeText +* nLanguageCode +* RegionCode +***************************************************************************** +*/ +void CIccTagMultiLocalizedUnicode::SetText(const icUInt16Number *sszUnicode16Text, + icLanguageCode nLanguageCode /* = icLanguageCodeEnglish */, + icCountryCode nRegionCode /* = icCountryCodeUSA */) +{ + CIccLocalizedUnicode *pText = Find(nLanguageCode, nRegionCode); + + if (!pText) { + CIccLocalizedUnicode newText; + newText.SetText(sszUnicode16Text, nLanguageCode, nRegionCode); + m_Strings->push_back(newText); + } + else { + pText->SetText(sszUnicode16Text, nLanguageCode, nRegionCode); + } +} + +/** +**************************************************************************** +* Name: sampleICC::CIccTagMultiLocalizedUnicode::SetText +* +* Purpose: +* +* Args: +* sszUnicodeText +* nLanguageCode +* RegionCode +***************************************************************************** +*/ +void CIccTagMultiLocalizedUnicode::SetText(const icUInt32Number *sszUnicode32Text, + icLanguageCode nLanguageCode /* = icLanguageCodeEnglish */, + icCountryCode nRegionCode /* = icCountryCodeUSA */) +{ + CIccLocalizedUnicode *pText = Find(nLanguageCode, nRegionCode); + + if (!pText) { + CIccLocalizedUnicode newText; + newText.SetText(sszUnicode32Text, nLanguageCode, nRegionCode); + m_Strings->push_back(newText); + } + else { + pText->SetText(sszUnicode32Text, nLanguageCode, nRegionCode); + } +} + +// +// MD: Moved Curve and LUT tags to IccTagLut.cpp (4-30-05) +// + + +/** + **************************************************************************** + * Name: CIccTagData::CIccTagData + * + * Purpose: Constructor + * + * Args: + * nSize = number of data entries + * + ***************************************************************************** + */ +CIccTagData::CIccTagData(int nSize/*=1*/) +{ + m_nSize = nSize; + if (m_nSize <1) + m_nSize = 1; + m_pData = (icUInt8Number*)calloc(nSize, sizeof(icUInt8Number)); +} + + +/** + **************************************************************************** + * Name: CIccTagData::CIccTagData + * + * Purpose: Copy Constructor + * + * Args: + * ITD = The CIccTagData object to be copied + ***************************************************************************** + */ +CIccTagData::CIccTagData(const CIccTagData &ITD) +{ + m_nDataFlag = ITD.m_nDataFlag; + m_nSize = ITD.m_nSize; + + m_pData = (icUInt8Number*)calloc(m_nSize, sizeof(icUInt8Number)); + memcpy(m_pData, ITD.m_pData, sizeof(icUInt8Number)*m_nSize); +} + + +/** + **************************************************************************** + * Name: CIccTagData::operator= + * + * Purpose: Copy Operator + * + * Args: + * DataTag = The CIccTagData object to be copied + ***************************************************************************** + */ +CIccTagData &CIccTagData::operator=(const CIccTagData &DataTag) +{ + if (&DataTag == this) + return *this; + + m_nDataFlag = DataTag.m_nDataFlag; + m_nSize = DataTag.m_nSize; + + if (m_pData) + free(m_pData); + m_pData = (icUInt8Number*)calloc(m_nSize, sizeof(icUInt8Number)); + memcpy(m_pData, DataTag.m_pData, sizeof(icUInt8Number)*m_nSize); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagData::~CIccTagData + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagData::~CIccTagData() +{ + if (m_pData) + free(m_pData); +} + + +/** + **************************************************************************** + * Name: CIccTagData::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagData::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number) + + sizeof(icUInt8Number) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read32(&m_nDataFlag)) + return false; + + icUInt32Number nNum = size-3*sizeof(icUInt32Number); + + SetSize(nNum); + + if (pIO->Read8(m_pData, nNum) != (icInt32Number)nNum) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagData::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagData::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nDataFlag)) + return false; + + if (pIO->Write8(m_pData, m_nSize) != (icInt32Number)m_nSize) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagData::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagData::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sDescription = "\r\nData:\r\n"; + + if (IsTypeAscii()) { + sprintf(buf, "%s\r\n", (icChar*)m_pData); + sDescription += buf; + } + else + for (int i = 0; i<(int)m_nSize; i++) { + sprintf(buf, "%d\r\n", m_pData[i]); + sDescription += buf; + } + +} + +/** + **************************************************************************** + * Name: CIccTagData::SetSize + * + * Purpose: Sets the size of the data array. + * + * Args: + * nSize - number of data entries, + * bZeroNew - flag to zero newly formed values + ***************************************************************************** + */ +void CIccTagData::SetSize(icUInt32Number nSize, bool bZeroNew/*=true*/) +{ + if (m_nSize == nSize) + return; + + m_pData = (icUInt8Number*)realloc(m_pData, nSize*sizeof(icUInt8Number)); + if (bZeroNew && nSize > m_nSize) { + memset(&m_pData[m_nSize], 0, (nSize-m_nSize)*sizeof(icUInt8Number)); + } + m_nSize = nSize; +} + + +/** +****************************************************************************** +* Name: CIccTagData::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagData::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + switch(m_nDataFlag) { + case 0x00000000: + case 0x00000001: + break; + default: + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Invalid data flag encoding.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + return rv; +} + +/** + **************************************************************************** + * Name: CIccTagDateTime::CIccTagDateTime + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagDateTime::CIccTagDateTime() +{ + memset(&m_DateTime, 0, sizeof(m_DateTime)); +} + + +/** + **************************************************************************** + * Name: CIccTagDateTime::CIccTagDateTime + * + * Purpose: Copy Constructor + * + * Args: + * ITDT = The CIccTagDateTime object to be copied + ***************************************************************************** + */ +CIccTagDateTime::CIccTagDateTime(const CIccTagDateTime &ITDT) +{ + memcpy(&m_DateTime, &ITDT.m_DateTime, sizeof(m_DateTime)); +} + + +/** + **************************************************************************** + * Name: CIccTagDateTime::operator= + * + * Purpose: Copy Operator + * + * Args: + * DateTimeTag = The CIccTagDateTime object to be copied + ***************************************************************************** + */ +CIccTagDateTime &CIccTagDateTime::operator=(const CIccTagDateTime &DateTimeTag) +{ + if (&DateTimeTag == this) + return *this; + + memcpy(&m_DateTime, &DateTimeTag.m_DateTime, sizeof(m_DateTime)); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagDateTime::~CIccTagDateTime + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagDateTime::~CIccTagDateTime() +{ +} + + + +/** + **************************************************************************** + * Name: CIccTagDateTime::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagDateTime::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icDateTimeNumber) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + + icUInt32Number nsize = (size-2*sizeof(icUInt32Number))/sizeof(icUInt16Number); + + if (pIO->Read16(&m_DateTime,nsize) != (icInt32Number)nsize) + return false; + + return true; +} + + + +/** + **************************************************************************** + * Name: CIccTagDateTime::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagDateTime::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (pIO->Write16(&m_DateTime,6) != 6) + return false; + + return true; +} + + + +/** + **************************************************************************** + * Name: CIccTagDateTime::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagDateTime::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sDescription = "Date = "; + sprintf(buf, "%u-%u-%u\r\n", m_DateTime.month, m_DateTime.day, m_DateTime.year); + sDescription += buf; + + sDescription += "Time = "; + sprintf(buf, "%u:%u:%u\r\n", m_DateTime.hours, m_DateTime.minutes, m_DateTime.seconds); + sDescription += buf; +} + + +/** +****************************************************************************** +* Name: CIccTagDateTime::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagDateTime::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + CIccInfo Info; + + rv = icMaxStatus(rv, Info.CheckData(sReport, m_DateTime)); + + return rv; +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::CIccTagColorantOrder + * + * Purpose: Constructor + * + * Args: + * nSize = number of channels + * + ***************************************************************************** + */ +CIccTagColorantOrder::CIccTagColorantOrder(int nsize/*=1*/) +{ + m_nCount = nsize; + if (m_nCount <1) + m_nCount = 1; + m_pData = (icUInt8Number*)calloc(nsize, sizeof(icUInt8Number)); +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::CIccTagColorantOrder + * + * Purpose: Copy Constructor + * + * Args: + * ITCO = The CIccTagColorantOrder object to be copied + ***************************************************************************** + */ +CIccTagColorantOrder::CIccTagColorantOrder(const CIccTagColorantOrder &ITCO) +{ + m_nCount = ITCO.m_nCount; + + m_pData = (icUInt8Number*)calloc(m_nCount, sizeof(icUInt8Number)); + memcpy(m_pData, ITCO.m_pData, sizeof(icUInt8Number)*m_nCount); +} + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::operator= + * + * Purpose: Copy Operator + * + * Args: + * ColorantOrderTag = The CIccTagColorantOrder object to be copied + ***************************************************************************** + */ +CIccTagColorantOrder &CIccTagColorantOrder::operator=(const CIccTagColorantOrder &ColorantOrderTag) +{ + if (&ColorantOrderTag == this) + return *this; + + m_nCount = ColorantOrderTag.m_nCount; + + if (m_pData) + free(m_pData); + m_pData = (icUInt8Number*)calloc(m_nCount, sizeof(icUInt8Number)); + memcpy(m_pData, ColorantOrderTag.m_pData, sizeof(icUInt8Number)*m_nCount); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::~CIccTagColorantOrder + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagColorantOrder::~CIccTagColorantOrder() +{ + if (m_pData) + free(m_pData); +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagColorantOrder::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nCount; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read32(&nCount)) + return false; + + icUInt32Number nNum = (size - 3*sizeof(icUInt32Number))/sizeof(icUInt8Number); + + if (nNum < nCount) + return false; + + SetSize((icUInt16Number)nCount); + + if (pIO->Read8(&m_pData[0],nNum) != (icInt32Number)nNum) + return false; + + return true; +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagColorantOrder::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nCount)) + return false; + + if (pIO->Write8(&m_pData[0], m_nCount) != (icInt32Number)m_nCount) + return false; + + return true; +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagColorantOrder::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sprintf(buf, "Colorant Count : %u\r\n", m_nCount); + sDescription += buf; + sDescription += "Order of Colorants:\r\n"; + + for (int i=0; i<(int)m_nCount; i++) { + sprintf(buf, "%u\r\n", m_pData[i]); + sDescription += buf; + } +} + + +/** + **************************************************************************** + * Name: CIccTagColorantOrder::SetSize + * + * Purpose: Sets the size of the data array. + * + * Args: + * nSize - number of channels, + * bZeroNew - flag to zero newly formed values + ***************************************************************************** + */ +void CIccTagColorantOrder::SetSize(icUInt16Number nSize, bool bZeroNew/*=true*/) +{ + if (m_nCount == nSize) + return; + + m_pData = (icUInt8Number*)realloc(m_pData, nSize*sizeof(icUInt8Number)); + if (bZeroNew && nSize > m_nCount) { + memset(&m_pData[m_nCount], 0, (nSize - m_nCount)*sizeof(icUInt8Number)); + } + + m_nCount = nSize; +} + + +/** +****************************************************************************** +* Name: CIccTagColorantOrder::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagColorantOrder::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + return rv; + } + + if (sig==icSigColorantTableTag) { + if (m_nCount != icGetSpaceSamples(pProfile->m_Header.colorSpace)) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of colorants.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + else if (sig==icSigColorantTableOutTag) { + if (m_nCount != icGetSpaceSamples(pProfile->m_Header.pcs)) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of colorants.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + else { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Unknown number of required colorants.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + + return rv; +} + + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::CIccTagColorantTable + * + * Purpose: Constructor + * + * Args: + * nSize = number of entries + * + ***************************************************************************** + */ +CIccTagColorantTable::CIccTagColorantTable(int nSize/*=1*/) +{ + m_nCount = nSize; + if (m_nCount<1) + m_nCount = 1; + + m_pData = (icColorantTableEntry*)calloc(nSize, sizeof(icColorantTableEntry)); +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::CIccTagColorantTable + * + * Purpose: Copy Constructor + * + * Args: + * ITCT = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccTagColorantTable::CIccTagColorantTable(const CIccTagColorantTable &ITCT) +{ + m_PCS = ITCT.m_PCS; + m_nCount = ITCT.m_nCount; + + m_pData = (icColorantTableEntry*)calloc(m_nCount, sizeof(icColorantTableEntry)); + memcpy(m_pData, ITCT.m_pData, m_nCount * sizeof(icColorantTableEntry)); +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::operator= + * + * Purpose: Copy Operator + * + * Args: + * ColorantTableTag = The CIccTagColorantTable object to be copied + ***************************************************************************** + */ +CIccTagColorantTable &CIccTagColorantTable::operator=(const CIccTagColorantTable &ColorantTableTag) +{ + if (&ColorantTableTag == this) + return *this; + + m_PCS = ColorantTableTag.m_PCS; + m_nCount = ColorantTableTag.m_nCount; + + if (m_pData) + free(m_pData); + m_pData = (icColorantTableEntry*)calloc(m_nCount, sizeof(icColorantTableEntry)); + memcpy(m_pData, ColorantTableTag.m_pData, m_nCount * sizeof(icColorantTableEntry)); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::~CIccTagColorantTable + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagColorantTable::~CIccTagColorantTable() +{ + if (m_pData) + free(m_pData); +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagColorantTable::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nCount; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number) + + sizeof(icColorantTableEntry) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read32(&nCount)) + return false; + + icUInt32Number nNum = (size - 3*sizeof(icUInt32Number))/sizeof(icColorantTableEntry); + icUInt32Number nNum8 = sizeof(m_pData->name); + icUInt32Number nNum16 = sizeof(m_pData->data)/sizeof(icUInt16Number); + + if (nNum < nCount) + return false; + + SetSize((icUInt16Number)nCount); + + for (icUInt32Number i=0; iRead8(&m_pData[i].name[0], nNum8) != (icInt32Number)nNum8) + return false; + + if (pIO->Read16(&m_pData[i].data[0], nNum16) != (icInt32Number)nNum16) + return false; + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagColorantTable::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nCount)) + return false; + + icUInt32Number nNum8 = sizeof(m_pData->name); + icUInt32Number nNum16 = sizeof(m_pData->data)/sizeof(icUInt16Number); + + for (icUInt32Number i=0; iWrite8(&m_pData[i].name[0],nNum8) != (icInt32Number)nNum8) + return false; + + if (pIO->Write16(&m_pData[i].data[0],nNum16) != (icInt32Number)nNum16) + return false; + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagColorantTable::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagColorantTable::Describe(std::string &sDescription) +{ + icChar buf[128]; + + icUInt32Number i, nLen, nMaxLen=0; + icFloatNumber Lab[3]; + + sprintf(buf, "BEGIN_COLORANTS %u\r\n", m_nCount); + sDescription += buf; + + for (i=0; inMaxLen) + nMaxLen =nLen; + } + sDescription += "# NAME "; + + if (m_PCS == icSigXYZData) { + sprintf(buf, "XYZ_X XYZ_Y XYZ_Z\r\n"); + sDescription += buf; + } + else { + sprintf(buf, "Lab_L Lab_a Lab_b\r\n"); + sDescription += buf; + } + for (i=0; i m_nCount) { + memset(&m_pData[m_nCount], 0, (nSize-m_nCount)*sizeof(icColorantTableEntry)); + } + m_nCount = nSize; +} + + +/** +****************************************************************************** +* Name: CIccTagColorantTable::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagColorantTable::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + return rv; + } + + + if (sig==icSigColorantTableOutTag) { + if (pProfile->m_Header.deviceClass!=icSigLinkClass) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Use of this tag is allowed only in DeviceLink Profiles.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + if (m_nCount != icGetSpaceSamples(pProfile->m_Header.pcs)) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of colorants.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + else { + if (m_nCount != icGetSpaceSamples(pProfile->m_Header.colorSpace)) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of colorants.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::CIccTagViewingConditions + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagViewingConditions::CIccTagViewingConditions() +{ + m_XYZIllum.X = 0; + m_XYZIllum.Y = 0; + m_XYZIllum.Z = 0; + + m_XYZSurround.X = 0; + m_XYZSurround.Y = 0; + m_XYZSurround.Z = 0; + + m_illumType = icIlluminantUnknown; +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::CIccTagViewingConditions + * + * Purpose: Copy Constructor + * + * Args: + * ITVC = The CIccTagViewingConditions object to be copied + ***************************************************************************** + */ +CIccTagViewingConditions::CIccTagViewingConditions(const CIccTagViewingConditions &ITVC) +{ + m_illumType = ITVC.m_illumType; + + memcpy(&m_XYZIllum, &ITVC.m_XYZIllum, sizeof(icXYZNumber)); + memcpy(&m_XYZSurround, &ITVC.m_XYZSurround, sizeof(icXYZNumber)); +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::operator= + * + * Purpose: Copy Operator + * + * Args: + * ViewCondTag = The CIccTagViewingConditions object to be copied + ***************************************************************************** + */ +CIccTagViewingConditions &CIccTagViewingConditions::operator=(const CIccTagViewingConditions &ViewCondTag) +{ + if (&ViewCondTag == this) + return *this; + + m_illumType = ViewCondTag.m_illumType; + + memcpy(&m_XYZIllum, &ViewCondTag.m_XYZIllum, sizeof(icXYZNumber)); + memcpy(&m_XYZSurround, &ViewCondTag.m_XYZSurround, sizeof(icXYZNumber)); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::~CIccTagViewingConditions + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagViewingConditions::~CIccTagViewingConditions() +{ +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagViewingConditions::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + 2*sizeof(icUInt32Number) + + 2*sizeof(icXYZNumber) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (pIO->Read32(&m_XYZIllum.X, 3) != 3) + return false; + + if (pIO->Read32(&m_XYZSurround.X, 3) != 3) + return false; + + if (!pIO->Read32(&m_illumType)) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagViewingConditions::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (pIO->Write32(&m_XYZIllum.X, 3) !=3) + return false; + + if (pIO->Write32(&m_XYZSurround.X, 3) !=3) + return false; + + if (!pIO->Write32(&m_illumType)) + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagViewingConditions::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagViewingConditions::Describe(std::string &sDescription) +{ + icChar buf[128]; + CIccInfo Fmt; + + sprintf(buf, "Illuminant Tristimulus values: X = %.4lf, Y = %.4lf, Z = %.4lf\r\n", + icFtoD(m_XYZIllum.X), + icFtoD(m_XYZIllum.Y), + icFtoD(m_XYZIllum.Z)); + sDescription += buf; + + sprintf(buf, "Surround Tristimulus values: X = %.4lf, Y = %.4lf, Z = %.4lf\r\n", + icFtoD(m_XYZSurround.X), + icFtoD(m_XYZSurround.Y), + icFtoD(m_XYZSurround.Z)); + sDescription += buf; + + sDescription += "Illuminant Type: "; + + sDescription += Fmt.GetIlluminantName(m_illumType); + sDescription += "\r\n"; + +} + + +/** +****************************************************************************** +* Name: CIccTagViewingConditions::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagViewingConditions::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + rv = icMaxStatus(rv, Info.CheckData(sReport, m_XYZIllum)); + rv = icMaxStatus(rv, Info.CheckData(sReport, m_XYZSurround)); + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::CIccProfileDescText + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccProfileDescText::CIccProfileDescText() +{ + m_pTag = NULL; + m_bNeedsPading = false; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::CIccProfileDescText + * + * Purpose: Copy Constructor + * + * Args: + * IPDC = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccProfileDescText::CIccProfileDescText(const CIccProfileDescText &IPDC) +{ + if (IPDC.m_pTag) { + m_pTag = IPDC.m_pTag->NewCopy(); + m_bNeedsPading = IPDC.m_bNeedsPading; + } + else { + m_pTag = NULL; + m_bNeedsPading = false; + } +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::operator= + * + * Purpose: Copy Operator + * + * Args: + * ProfDescText = The CIccProfileDescText object to be copied + ***************************************************************************** + */ +CIccProfileDescText &CIccProfileDescText::operator=(const CIccProfileDescText &ProfDescText) +{ + if (&ProfDescText == this) + return *this; + + if (m_pTag) + delete m_pTag; + + if (ProfDescText.m_pTag) { + m_pTag = ProfDescText.m_pTag->NewCopy(); + m_bNeedsPading = ProfDescText.m_bNeedsPading; + } + else { + m_pTag = NULL; + m_bNeedsPading = false; + } + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::~CIccProfileDescText + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccProfileDescText::~CIccProfileDescText() +{ + if (m_pTag) + delete m_pTag; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::SetType + * + * Purpose: Sets the type of the profile description text. Could be either + * a MultiLocalizedUnicodeType or a TextDescriptionType. + * + * Args: + * nType = the tag type signature + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccProfileDescText::SetType(icTagTypeSignature nType) +{ + if (m_pTag) { + if (m_pTag->GetType() == nType) + return true; + + delete m_pTag; + } + + if (nType == icSigMultiLocalizedUnicodeType || + nType == icSigTextDescriptionType) + m_pTag = CIccTag::Create(nType); + else + m_pTag = NULL; + + return(m_pTag != NULL); +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::SetType + * + * Purpose: Gets the type of the profile description text. Could be either + * a MultiLocalizedUnicodeType or a TextDescriptionType. + * + ***************************************************************************** + */ +icTagTypeSignature CIccProfileDescText::GetType() const +{ + if (m_pTag) + return m_pTag->GetType(); + + return icSigUnknownType; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccProfileDescText::Describe(std::string &sDescription) +{ + if (m_pTag) + m_pTag->Describe(sDescription); +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccProfileDescText::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nPos; + + //Check for description tag type signature + nPos = pIO->Tell(); + + if ((nPos&0x03) != 0) + m_bNeedsPading = true; + + if (!pIO->Read32(&sig)) + return false; + pIO->Seek(nPos, icSeekSet); + + if (sig==icSigTextDescriptionType) + m_bNeedsPading = false; + + if (!SetType(sig)) { + //We couldn't find it, but we may be looking in the wrong place + //Re-Syncronize on a 4 byte boundary + pIO->Sync32(); + + nPos = pIO->Tell(); + if (!pIO->Read32(&sig)) + return false; + pIO->Seek(nPos, icSeekSet); + + if (!SetType(sig)) { + return false; + } + } + + if (m_pTag) { + return m_pTag->Read(size, pIO); + } + + return false; +} + + +/** + **************************************************************************** + * Name: CIccProfileDescText::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccProfileDescText::Write(CIccIO *pIO) +{ + if (!m_pTag) + return false; + + if (m_pTag->Write(pIO)) { + if (m_pTag->GetType() != icSigTextDescriptionType) + return pIO->Align32(); + else + return true; + } + + return false; +} + + + +/** + **************************************************************************** + * Name: CIccProfileDescStruct::CIccProfileDescStruct + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccProfileDescStruct::CIccProfileDescStruct() +{ +} + + +/** + **************************************************************************** + * Name: CIccProfileDescStruct::CIccProfileDescStruct + * + * Purpose: Copy Constructor + * + * Args: + * IPDS = The CIccProfileDescStruct object to be copied + ***************************************************************************** + */ +CIccProfileDescStruct::CIccProfileDescStruct(const CIccProfileDescStruct &IPDS) +{ + m_deviceMfg = IPDS.m_deviceMfg; + m_deviceModel = IPDS.m_deviceModel; + m_attributes = IPDS.m_attributes; + m_technology = IPDS.m_technology; + m_deviceMfgDesc = IPDS.m_deviceMfgDesc; + m_deviceModelDesc = IPDS.m_deviceModelDesc; +} + + + +/** + **************************************************************************** + * Name: CIccProfileDescStruct::operator= + * + * Purpose: Copy Operator + * + * Args: + * ProfDescStruct = The CIccProfileDescStruct object to be copied + ***************************************************************************** + */ +CIccProfileDescStruct &CIccProfileDescStruct::operator=(const CIccProfileDescStruct &ProfDescStruct) +{ + if (&ProfDescStruct == this) + return *this; + + m_deviceMfg = ProfDescStruct.m_deviceMfg; + m_deviceModel = ProfDescStruct.m_deviceModel; + m_attributes = ProfDescStruct.m_attributes; + m_technology = ProfDescStruct.m_technology; + m_deviceMfgDesc = ProfDescStruct.m_deviceMfgDesc; + m_deviceModelDesc = ProfDescStruct.m_deviceModelDesc; + + return *this; +} + + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::CIccTagProfileSeqDesc + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagProfileSeqDesc::CIccTagProfileSeqDesc() +{ + m_Descriptions = new(CIccProfileSeqDesc); +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::CIccTagProfileSeqDesc + * + * Purpose: Copy Constructor + * + * Args: + * ITPSD = The CIccTagProfileSeqDesc object to be copied + ***************************************************************************** + */ +CIccTagProfileSeqDesc::CIccTagProfileSeqDesc(const CIccTagProfileSeqDesc &ITPSD) +{ + m_Descriptions = new(CIccProfileSeqDesc); + *m_Descriptions = *ITPSD.m_Descriptions; +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::operator= + * + * Purpose: Copy Operator + * + * Args: + * ProfSeqDescTag = The CIccTagProfileSeqDesc object to be copied + ***************************************************************************** + */ +CIccTagProfileSeqDesc &CIccTagProfileSeqDesc::operator=(const CIccTagProfileSeqDesc &ProfSeqDescTag) +{ + if (&ProfSeqDescTag == this) + return *this; + + *m_Descriptions = *ProfSeqDescTag.m_Descriptions; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::~CIccTagProfileSeqDesc + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagProfileSeqDesc::~CIccTagProfileSeqDesc() +{ + delete m_Descriptions; +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagProfileSeqDesc::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nCount, nEnd; + + nEnd = pIO->Tell() + size; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number)*2 > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read32(&nCount)) + return false; + + if (!nCount) + return true; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number)*2 + + sizeof(CIccProfileDescStruct) > size) + return false; + + icUInt32Number i, nPos; + CIccProfileDescStruct ProfileDescStruct; + + for (i=0; iRead32(&ProfileDescStruct.m_deviceMfg) || + !pIO->Read32(&ProfileDescStruct.m_deviceModel) || + !pIO->Read64(&ProfileDescStruct.m_attributes) || + !pIO->Read32(&ProfileDescStruct.m_technology)) + return false; + + nPos = pIO->Tell(); + + if (!ProfileDescStruct.m_deviceMfgDesc.Read(nEnd - nPos, pIO)) + return false; + + nPos = pIO->Tell(); + if (!ProfileDescStruct.m_deviceModelDesc.Read(nEnd - nPos, pIO)) + return false; + + m_Descriptions->push_back(ProfileDescStruct); + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagProfileSeqDesc::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt32Number nCount=(icUInt32Number)m_Descriptions->size(); + + if (!pIO) { + return false; + } + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write32(&nCount)) + return false; + + CIccProfileSeqDesc::iterator i; + + for (i=m_Descriptions->begin(); i!=m_Descriptions->end(); i++) { + + if (!pIO->Write32(&i->m_deviceMfg) || + !pIO->Write32(&i->m_deviceModel) || + !pIO->Write64(&i->m_attributes) || + !pIO->Write32(&i->m_technology)) + return false; + + if (!i->m_deviceMfgDesc.Write(pIO) || + !i->m_deviceModelDesc.Write(pIO)) + return false; + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagProfileSeqDesc::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagProfileSeqDesc::Describe(std::string &sDescription) +{ + CIccProfileSeqDesc::iterator i; + icChar buf[128], buf2[28]; + icUInt32Number count=0; + + sprintf(buf, "Number of Profile Description Structures: %u\r\n", (icUInt32Number)m_Descriptions->size()); + sDescription += buf; + + for (i=m_Descriptions->begin(); i!=m_Descriptions->end(); i++, count++) { + sDescription += "\r\n"; + + sprintf(buf, "Profile Description Structure Number [%u] follows:\r\n", count+1); + sDescription += buf; + + sprintf(buf, "Device Manufacturer Signature: %s\r\n", icGetSig(buf2, i->m_deviceMfg, false)); + sDescription += buf; + + sprintf(buf, "Device Model Signature: %s\r\n", icGetSig(buf2, i->m_deviceModel, false)); + sDescription += buf; + + sprintf(buf, "Device Attributes: %08x%08x\r\n", (icUInt32Number)(i->m_attributes >> 32), (icUInt32Number)(i->m_attributes)); + sDescription += buf; + + sprintf(buf, "Device Technology Signature: %s\r\n", icGetSig(buf2, i->m_technology, false)); + sDescription += buf; + + sprintf(buf, "Description of device manufacturer: \r\n"); + sDescription += buf; + i->m_deviceMfgDesc.Describe(sDescription); + + sprintf(buf, "Description of device model: \r\n"); + sDescription += buf; + i->m_deviceModelDesc.Describe(sDescription); + } +} + + +/** +****************************************************************************** +* Name: CIccTagProfileSeqDesc::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagProfileSeqDesc::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + char buf[128]; + std::string sSigName = Info.GetSigName(sig); + + CIccProfileSeqDesc::iterator i; + for (i=m_Descriptions->begin(); i!=m_Descriptions->end(); i++) { + switch(i->m_technology) { + case 0x00000000: //Technology not defined + case icSigFilmScanner: + case icSigDigitalCamera: + case icSigReflectiveScanner: + case icSigInkJetPrinter: + case icSigThermalWaxPrinter: + case icSigElectrophotographicPrinter: + case icSigElectrostaticPrinter: + case icSigDyeSublimationPrinter: + case icSigPhotographicPaperPrinter: + case icSigFilmWriter: + case icSigVideoMonitor: + case icSigVideoCamera: + case icSigProjectionTelevision: + case icSigCRTDisplay: + case icSigPMDisplay: + case icSigAMDisplay: + case icSigPhotoCD: + case icSigPhotoImageSetter: + case icSigGravure: + case icSigOffsetLithography: + case icSigSilkscreen: + case icSigFlexography: + break; + + default: + { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sprintf(buf, " - %s: Unknown Technology.\r\n", Info.GetSigName(i->m_technology)); + sReport += buf; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + } + + if (i->m_deviceMfgDesc.m_bNeedsPading) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + + sReport += " Contains non-aligned deviceMfgDesc text tag information\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + if (i->m_deviceModelDesc.m_bNeedsPading) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + + sReport += " Contains non-aligned deviceModelDesc text tag information\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + rv = icMaxStatus(rv, i->m_deviceMfgDesc.GetTag()->Validate(sig, sReport, pProfile)); + rv = icMaxStatus(rv, i->m_deviceModelDesc.GetTag()->Validate(sig, sReport, pProfile)); + } + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccResponseCurveStruct::CIccResponseCurveStruct + * + * Purpose: Constructor + * + * Args: + * nChannels = number of channels + * + ***************************************************************************** + */ +CIccResponseCurveStruct::CIccResponseCurveStruct(icUInt16Number nChannels/*=0*/) +{ + m_nChannels = nChannels; + m_maxColorantXYZ = (icXYZNumber*)calloc(nChannels, sizeof(icXYZNumber)); + m_Response16ListArray = new CIccResponse16List[nChannels]; +} + + +/** + **************************************************************************** + * Name: CIccResponseCurveStruct::CIccResponseCurveStruct + * + * Purpose: Constructor + * + * Args: + * sig = measurement unit signature indicating the type of measurement data, + * nChannels = number of channels + ***************************************************************************** + */ +CIccResponseCurveStruct::CIccResponseCurveStruct(icMeasurementUnitSig sig,icUInt16Number nChannels/*=0*/) +{ + m_nChannels = nChannels; + m_measurementUnitSig = sig; + m_maxColorantXYZ = (icXYZNumber*)calloc(nChannels, sizeof(icXYZNumber)); + m_Response16ListArray = new CIccResponse16List[nChannels]; +} + +/** + **************************************************************************** + * Name: CIccResponseCurveStruct::CIccResponseCurveStruct + * + * Purpose: Copy Constructor + * + * Args: + * IRCS = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccResponseCurveStruct::CIccResponseCurveStruct(const CIccResponseCurveStruct &IRCS) +{ + m_nChannels = IRCS.m_nChannels; + m_measurementUnitSig = IRCS.m_measurementUnitSig; + + m_maxColorantXYZ = (icXYZNumber*)calloc(m_nChannels, sizeof(icXYZNumber)); + memcpy(m_maxColorantXYZ, IRCS.m_maxColorantXYZ, m_nChannels*sizeof(icXYZNumber)); + + m_Response16ListArray = new CIccResponse16List[m_nChannels]; + for (icUInt32Number i=0; i size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&m_measurementUnitSig)) + return false; + + icUInt32Number* nMeasurements = new icUInt32Number[m_nChannels]; + + if (pIO->Read32(&nMeasurements[0],m_nChannels) != m_nChannels) + return false; + + icUInt32Number nNum32 = m_nChannels*sizeof(icXYZNumber)/sizeof(icS15Fixed16Number); + if (pIO->Read32(&m_maxColorantXYZ[0], nNum32) != (icInt32Number)nNum32) + return false; + + icResponse16Number nResponse16; + CIccResponse16List nResponseList; + + for (int i = 0; iRead16(&nResponse16.deviceCode) || + !pIO->Read16(&nResponse16.reserved) || + !pIO->Read32(&nResponse16.measurementValue)) + return false; + nResponseList.push_back(nResponse16); + } + m_Response16ListArray[i] = nResponseList; + } + + delete [] nMeasurements; + return true; +} + + +/** + **************************************************************************** + * Name: CIccResponseCurveStruct::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccResponseCurveStruct::Write(CIccIO *pIO) +{ + if (!m_nChannels) + return false; + + icMeasurementUnitSig sig = GetMeasurementType(); + + if (!pIO) { + return false; + } + + if (!pIO->Write32(&sig)) + return false; + + if (m_nChannels) { + + icUInt32Number* nMeasurements = new icUInt32Number[m_nChannels]; + for (int i=0; iWrite32(&nMeasurements[0],m_nChannels) != m_nChannels) + return false; + delete [] nMeasurements; + + icUInt32Number nNum32 = m_nChannels*sizeof(icXYZNumber)/sizeof(icS15Fixed16Number); + if (pIO->Write32(&m_maxColorantXYZ[0], nNum32) != (icInt32Number)nNum32) + return false; + } + else + return false; + + CIccResponse16List nResponseList; + CIccResponse16List::iterator j; + + for (int i = 0; iWrite16(&j->deviceCode) || + !pIO->Write16(&j->reserved) || + !pIO->Write32(&j->measurementValue)) + return false; + } + nResponseList.clear(); + } + + return true; +} + + +/** + **************************************************************************** + * Name: CIccResponseCurveStruct::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccResponseCurveStruct::Describe(std::string &sDescription) +{ + icChar buf[128]; + CIccInfo Fmt; + CIccResponse16List nResponseList; + CIccResponse16List::iterator j; + + sDescription += "Measurement Unit: "; + sDescription += Fmt.GetMeasurementUnit((icSignature)GetMeasurementType()); sDescription += "\r\n"; + + + for (int i=0; ideviceCode, icFtoD(j->measurementValue)); + sDescription += buf; + } + } +} + + +/** +****************************************************************************** +* Name: CIccResponseCurveStruct::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccResponseCurveStruct::Validate(std::string &sReport) const +{ + icValidateStatus rv = icValidateOK; + + CIccInfo Info; + std::string sSigName = Info.GetSigName(m_measurementUnitSig); + switch(m_measurementUnitSig) { + case icSigStatusA: + case icSigStatusE: + case icSigStatusI: + case icSigStatusT: + case icSigStatusM: + case icSigDN: + case icSigDNP: + case icSigDNN: + case icSigDNNP: + break; + + default: + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Unknown measurement unit signature.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + if (!m_nChannels) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Incorrect number of channels.\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + return rv; + } + for (int i=0; iinited = false; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::CIccTagResponseCurveSet16 + * + * Purpose: Copy Constructor + * + * Args: + * ITRCS = The CIccTagResponseCurveSet16 object to be copied + ***************************************************************************** + */ +CIccTagResponseCurveSet16::CIccTagResponseCurveSet16(const CIccTagResponseCurveSet16 &ITRCS) +{ + m_nChannels = ITRCS.m_nChannels; + m_ResponseCurves = new(CIccResponseCurveSet); + *m_ResponseCurves = *ITRCS.m_ResponseCurves; + m_Curve = new(CIccResponseCurveSetIter); + *m_Curve = *ITRCS.m_Curve; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::operator= + * + * Purpose: Copy Operator + * + * Args: + * RespCurveSet16Tag = The CIccTagResponseCurveSet16 object to be copied + ***************************************************************************** + */ +CIccTagResponseCurveSet16 &CIccTagResponseCurveSet16::operator=(const CIccTagResponseCurveSet16 &RespCurveSet16Tag) +{ + if (&RespCurveSet16Tag == this) + return *this; + + if (!m_ResponseCurves->empty()) + m_ResponseCurves->clear(); + + m_nChannels = RespCurveSet16Tag.m_nChannels; + *m_ResponseCurves = *RespCurveSet16Tag.m_ResponseCurves; + *m_Curve = *RespCurveSet16Tag.m_Curve; + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::~CIccTagResponseCurveSet16 + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagResponseCurveSet16::~CIccTagResponseCurveSet16() +{ + if (!m_ResponseCurves->empty()) + m_ResponseCurves->clear(); + + delete m_ResponseCurves; + delete m_Curve; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagResponseCurveSet16::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number)*4 + + sizeof(CIccResponseCurveStruct) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved)) + return false; + + icUInt16Number nCountMeasmntTypes; + + if (!pIO->Read16(&m_nChannels) || + !pIO->Read16(&nCountMeasmntTypes)) + return false; + + + icUInt32Number* nOffset = new icUInt32Number[nCountMeasmntTypes]; + + if (pIO->Read32(&nOffset[0], nCountMeasmntTypes) != nCountMeasmntTypes) + return false; + + delete [] nOffset; + + CIccResponseCurveStruct entry; + + for (icUInt16Number i=0; ipush_back(entry); + } + m_Curve->inited = false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagResponseCurveSet16::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt16Number nCountMeasmntTypes = (icUInt16Number)m_ResponseCurves->size(); + + if (!pIO) { + return false; + } + + icUInt32Number startPos = pIO->GetLength(); + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved)) + return false; + + + if (!pIO->Write16(&m_nChannels) || + !pIO->Write16(&nCountMeasmntTypes)) + return false; + + icUInt32Number offsetPos = pIO->GetLength(); + icUInt32Number* nOffset = new icUInt32Number[nCountMeasmntTypes]; + + + int j; + for (j=0; jWrite32(&nOffset[j])) + return false; + } + + CIccResponseCurveSet::iterator i; + + for (i=m_ResponseCurves->begin(), j=0; i!=m_ResponseCurves->end(); i++, j++) { + nOffset[j] = pIO->GetLength() - startPos; + if (!i->Write(pIO)) + return false; + } + + icUInt32Number curPOs = pIO->GetLength(); + + pIO->Seek(offsetPos,icSeekSet); + + for (j=0; jWrite32(&nOffset[j])) + return false; + } + + pIO->Seek(curPOs,icSeekSet); + delete [] nOffset; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::Describe + * + * Purpose: Dump data associated with the tag to a string + * + * Args: + * sDescription - string to concatenate tag dump to + ***************************************************************************** + */ +void CIccTagResponseCurveSet16::Describe(std::string &sDescription) +{ + CIccResponseCurveSet::iterator i; + icChar buf[128]; + + sprintf(buf, "Number of Channels: %u\r\n", m_nChannels); + sDescription += buf; + + sprintf(buf, "Number of Measurement Types used: %u\r\n", (icUInt32Number)m_ResponseCurves->size()); + sDescription += buf; + + int count = 0; + for (i=m_ResponseCurves->begin(); i!=m_ResponseCurves->end(); i++, count++) { + sDescription += "\r\n"; + + sprintf(buf, "Response Curve for measurement type [%u] follows:\r\n", count+1); + sDescription += buf; + + i->Describe(sDescription); + } +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::SetNumChannels + * + * Purpose: Sets the number of channels. This will delete any prior Response + * curves from the set. + * + * Args: + * nChannels = number of channels + ***************************************************************************** + */ +void CIccTagResponseCurveSet16::SetNumChannels(icUInt16Number nChannels) +{ + m_nChannels = nChannels; + + if (!m_ResponseCurves->empty()) + m_ResponseCurves->clear(); +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::NewResponseCurves + * + * Purpose: Creates and adds a new set of response curves to the list. + * SetNumChannels() must be called before calling this function. + * + * Args: + * sig = measurement unit signature + ***************************************************************************** + */ +CIccResponseCurveStruct *CIccTagResponseCurveSet16::NewResponseCurves(icMeasurementUnitSig sig) +{ + if (!m_nChannels) + return NULL; + + CIccResponseCurveStruct *pResponseCurveStruct; + pResponseCurveStruct = GetResponseCurves(sig); + + if (pResponseCurveStruct) + return pResponseCurveStruct; + + CIccResponseCurveStruct entry; + entry = CIccResponseCurveStruct(sig, m_nChannels); + m_ResponseCurves->push_back(entry); + m_Curve->inited = false; + + return GetResponseCurves(sig); +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::GetResponseCurves + * + * Purpose: Returns pointer to the requested set of response curves + * + * Args: + * sig = measurement unit signature of the response curve set desired + ***************************************************************************** + */ +CIccResponseCurveStruct *CIccTagResponseCurveSet16::GetResponseCurves(icMeasurementUnitSig sig) +{ + if (!m_nChannels) + return NULL; + + CIccResponseCurveSet::iterator i; + + for (i=m_ResponseCurves->begin(); i!=m_ResponseCurves->end(); i++) { + if (i->GetMeasurementType() == sig) + return (i->GetThis()); + } + + return NULL; +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::GetFirstCurves + * + * Purpose: Returns pointer to the first set of response curves in the list. + * + ***************************************************************************** + */ +CIccResponseCurveStruct *CIccTagResponseCurveSet16::GetFirstCurves() +{ + if (!m_Curve) + return NULL; + + m_Curve->item = m_ResponseCurves->begin(); + if (m_Curve->item == m_ResponseCurves->end()) { + m_Curve->inited = false; + return NULL; + } + m_Curve->inited = true; + return m_Curve->item->GetThis(); +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::GetNextCurves + * + * Purpose: Serves as an iterator for the list containing response curves. + * GetFirstCurves() must be called before calling this function. + * + ***************************************************************************** + */ +CIccResponseCurveStruct *CIccTagResponseCurveSet16::GetNextCurves() +{ + if (!m_Curve || !m_Curve->inited) + return NULL; + + m_Curve->item++; + if (m_Curve->item==m_ResponseCurves->end()) { + m_Curve->inited = false; + return NULL; + } + return m_Curve->item->GetThis(); +} + + +/** + **************************************************************************** + * Name: CIccTagResponseCurveSet16::GetNumResponseCurveTypes + * + * Purpose: Get the number of response curve types. + * + ***************************************************************************** + */ +icUInt16Number CIccTagResponseCurveSet16::GetNumResponseCurveTypes() const +{ + return(icUInt16Number) m_ResponseCurves->size(); +} + + +/** +****************************************************************************** +* Name: CIccTagResponseCurveSet16::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagResponseCurveSet16::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + return rv; + } + + if (m_nChannels!=icGetSpaceSamples(pProfile->m_Header.colorSpace)) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Incorrect number of channels.\r\n"; + } + + if (!GetNumResponseCurveTypes()) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Empty Tag!.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + else { + CIccResponseCurveSet::iterator i; + for (i=m_ResponseCurves->begin(); i!=m_ResponseCurves->end(); i++) { + rv = icMaxStatus(rv, i->Validate(sReport)); + } + } + + return rv; +} + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccTagBasic.h b/library/src/main/cpp/icc/IccTagBasic.h new file mode 100644 index 00000000..ebc62172 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagBasic.h @@ -0,0 +1,1187 @@ +/** @file + File: IccTagBasic.h + + Contains: Header for implementation of the CIccTag class + and inherited classes + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCTAGBASIC_H) +#define _ICCTAGBASIC_H + +#include +#include +#include "IccDefs.h" +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class CIccIO; + +class ICCPROFLIB_API CIccProfile; + +class IIccExtensionTag +{ +public: + virtual const char *GetExtClassName() const=0; + virtual const char *GetExtDerivedClassName() const=0; +}; + +/** + *********************************************************************** + * Class: CIccTag + * + * Purpose: + * CIccTag is the base class that all Icc Tags are derived + * from. It defines basic tag functionality, and provides + * a static function that acts as an object construction + * factory. + *********************************************************************** + */ +class ICCPROFLIB_API CIccTag +{ +public: + CIccTag(); + + /** + * Function: NewCopy(sDescription) + * Each derived tag will implement it's own NewCopy() function. + * + * Parameter(s): + * none + * + * Returns a new CIccTag object that is a copy of this object. + */ + virtual CIccTag* NewCopy() const {return new CIccTag;} + + virtual ~CIccTag(); + + /** + * Function: GetType() + * + * Purpose: Get Tag Type. + * Each derived tag will implement it's own GetType() function. + */ + virtual icTagTypeSignature GetType() const { return icMaxEnumType; } + virtual bool IsArrayType() { return false; } + virtual bool IsMBBType() { return false; } + + virtual const icChar *GetClassName() const { return "CIccTag"; } + + static CIccTag* Create(icTagTypeSignature sig); + + virtual IIccExtensionTag* GetExtension() {return NULL;} + + /** + * Function: IsSupported(size, pIO) - Check if tag fully + * supported for apply purposes. By Default inherited + * classes are supported. Unknown tag types are not + * supported. + * + * Returns true if tag type is supported. + */ + virtual bool IsSupported() { return true; } + + /** + * Function: Read(size, pIO) - Read tag from file. + * Each derived tag will implement it's own Read() function. + * + * Parameter(s): + * size - number of bytes in tag including the type signature. + * pIO - IO object used to read in tag. The IO object should + * already be initialized to point to the begining of + * the tag. + * + * Returns true if Read is successful. + */ + virtual bool Read(icUInt32Number size, CIccIO *pIO) { return false; } + + /** + * Function: Write(pIO) + * Each derived tag will implement it's own Write() function. + * + * Parameter(s): + * pIO - IO object used to write a tag. The IO object should + * already be initialized to point to the begining of + * the tag. + * + * Returns true if Write is successful. + */ + virtual bool Write(CIccIO *pIO) { return false; } + + /** + * Function: Describe(sDescription) + * Each derived tag will implement it's own Describe() function. + * + * Parameter(s): + * sDescription - A string to put the tag's description into. + */ + virtual void Describe(std::string &sDescription) { sDescription.empty(); } + + /** + ****************************************************************************** + * Function: Validate + * Each derived tag will implement it's own IsValid() function + * + * Parameter(s): + * sig - signature of tag being validated, + * sDescription - A string to put tag validation report. + */ + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + //All tags start with a reserved value. Allocate a place to put it. + icUInt32Number m_nReserved; +}; + + +/** +**************************************************************************** +* Class: IccTagUnknown +* +* Purpose: The general purpose I don't know tag. +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagUnknown : public CIccTag +{ +public: + CIccTagUnknown(); + CIccTagUnknown(const CIccTagUnknown &ITU); + CIccTagUnknown &operator=(const CIccTagUnknown &UnknownTag); + virtual CIccTag* NewCopy() const {return new CIccTagUnknown(*this);} + virtual ~CIccTagUnknown(); + + virtual bool IsSupported() { return false; } + + virtual icTagTypeSignature GetType() const { return m_nType; } + virtual const icChar *GetClassName() const { return "CIccTagUnknown"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Describe(std::string &sDescription); + + +protected: + icTagTypeSignature m_nType; + icUInt8Number *m_pData; + icUInt32Number m_nSize; +}; + + +/** +**************************************************************************** +* Class: CIccTagText() +* +* Purpose: The textType ICC tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagText : public CIccTag +{ +public: + CIccTagText(); + CIccTagText(const CIccTagText &ITT); + CIccTagText &operator=(const CIccTagText &TextTag); + virtual CIccTag* NewCopy() const {return new CIccTagText(*this);} + virtual ~CIccTagText(); + + virtual icTagTypeSignature GetType() const { return icSigTextType; } + virtual const icChar *GetClassName() const { return "CIccTagText"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Describe(std::string &sDescription); + + const icChar *GetText() const { return m_szText; } + void SetText(const icChar *szText); + const icChar *operator=(const icChar *szText); + + icChar *GetBuffer(icUInt32Number nSize); + void Release(); + icUInt32Number Capacity() const { return m_nBufSize; } + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icChar *m_szText; + icUInt32Number m_nBufSize; +}; + +/** +**************************************************************************** +* Class: CIccTagTextDescription() +* +* Purpose: The textType ICC tag +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagTextDescription : public CIccTag +{ +public: + CIccTagTextDescription(); + CIccTagTextDescription(const CIccTagTextDescription &ITTD); + CIccTagTextDescription &operator=(const CIccTagTextDescription &TextDescTag); + virtual CIccTag* NewCopy() const {return new CIccTagTextDescription(*this);} + virtual ~CIccTagTextDescription(); + + virtual icTagTypeSignature GetType() const { return icSigTextDescriptionType; } + virtual const icChar *GetClassName() const { return "CIccTagTextDescription"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Describe(std::string &sDescription); + + const icChar *GetText() const { return m_szText; } + void SetText(const icChar *szText); + const icChar *operator=(const icChar *szText); + + icChar *GetBuffer(icUInt32Number nSize); + void Release(); + icUInt32Number Capacity() const { return m_nASCIISize; } + + icUInt16Number *GetUnicodeBuffer(icUInt32Number nSize); + void ReleaseUnicode(); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + +protected: + icChar *m_szText; + icUInt32Number m_nASCIISize; + + icUInt16Number *m_uzUnicodeText; + icUInt32Number m_nUnicodeSize; + icUInt32Number m_nUnicodeLanguageCode; + + icUInt8Number m_szScriptText[67]; + icUInt8Number m_nScriptSize; + icUInt16Number m_nScriptCode; + + bool m_bInvalidScript; +}; + + +/** +**************************************************************************** +* Class: CIccTagSignature +* +* Purpose: The signatureType tag +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagSignature : public CIccTag +{ +public: + CIccTagSignature(); + CIccTagSignature(const CIccTagSignature &ITS); + CIccTagSignature &operator=(const CIccTagSignature &SignatureTag); + virtual CIccTag* NewCopy() const {return new CIccTagSignature(*this);} + virtual ~CIccTagSignature(); + + virtual icTagTypeSignature GetType() const { return icSigSignatureType; } + virtual const icChar *GetClassName() const { return "CIccTagSignature"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icUInt32Number GetValue() const { return m_nSig; } + void SetValue(icUInt32Number sig) { m_nSig = sig; } + icUInt32Number operator=(icUInt32Number sig) { SetValue(sig); return m_nSig; } + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt32Number m_nSig; +}; + +typedef struct +{ + icChar rootName[32]; + icFloatNumber pcsCoords[3]; + icFloatNumber deviceCoords[icAny]; +} SIccNamedColorEntry; + +typedef struct { + icFloatNumber lab[3]; +} SIccNamedLabEntry; + +/** +**************************************************************************** +* Class: CIccTagNamedColor2 +* +* Purpose: the NamedColor2 tag - an array of Named Colors +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagNamedColor2 : public CIccTag +{ +public: + CIccTagNamedColor2(int nSize=1, int nDeviceCoords=0); + CIccTagNamedColor2(const CIccTagNamedColor2 &ITNC); + CIccTagNamedColor2 &operator=(const CIccTagNamedColor2 &NamedColor2Tag); + virtual CIccTag* NewCopy() const {return new CIccTagNamedColor2(*this);} + virtual ~CIccTagNamedColor2(); + + virtual icTagTypeSignature GetType() const { return icSigNamedColor2Type; } + virtual const icChar *GetClassName() const { return "CIccTagNamedColor2"; } + + virtual bool UseLegacyPCS() const { return true; } //Treat Lab Encoding differently? + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + const icChar *GetPrefix() const { return m_szPrefix; } + void SetPrefix(const icChar *szPrefix); + + const icChar *GetSufix() const { return m_szSufix; } + void SetSufix(const icChar *szSufix); + + icUInt32Number GetVendorFlags() const { return m_nVendorFlags; } + void SetVendorFlags(icUInt32Number nVendorFlags) {m_nVendorFlags = nVendorFlags;} + + //The following Find functions return the zero based index of the color + //or -1 to indicate that the color was not found. + icInt32Number FindColor(const icChar *szColor) const; + icInt32Number FindRootColor(const icChar *szRootColor) const; + icInt32Number FindDeviceColor(icFloatNumber *pDevColor) const; + icInt32Number FindPCSColor(icFloatNumber *pPCS, icFloatNumber dMinDE=1000.0); + + bool InitFindCachedPCSColor(); + //FindPCSColor returns the zero based index of the color or -1 to indicate that the color was not found. + //InitFindPCSColor must be called before FindPCSColor + icInt32Number FindCachedPCSColor(icFloatNumber *pPCS, icFloatNumber dMinDE=1000.0) const; + + ///Call ResetPCSCache() if entry values change between calls to FindPCSColor() + void ResetPCSCache(); + + bool GetColorName(std::string &sColorName, icInt32Number index) const; + SIccNamedColorEntry &operator[](icUInt32Number index) const {return *(SIccNamedColorEntry*)((icUInt8Number*)m_NamedColor + index * m_nColorEntrySize);} + SIccNamedColorEntry *GetEntry(icUInt32Number index) const {return (SIccNamedColorEntry*)((icUInt8Number*)m_NamedColor + index * m_nColorEntrySize);} + + icUInt32Number GetSize() const { return m_nSize; } + icUInt32Number GetDeviceCoords() const {return m_nDeviceCoords;} + void SetSize(icUInt32Number nSize, icInt32Number nDeviceCoords=-1); + + virtual void SetColorSpaces(icColorSpaceSignature csPCS, icColorSpaceSignature csDevice); + icColorSpaceSignature GetPCS() const {return m_csPCS;} + icColorSpaceSignature GetDeviceSpace() const {return m_csDevice;} + + icFloatNumber NegClip(icFloatNumber v) const; + icFloatNumber UnitClip(icFloatNumber v) const; + + void Lab2ToLab4(icFloatNumber *Dst, const icFloatNumber *Src) const; + void Lab4ToLab2(icFloatNumber *Dst, const icFloatNumber *Src) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icChar m_szPrefix[32]; + icChar m_szSufix[32]; + + SIccNamedColorEntry *m_NamedColor; + SIccNamedLabEntry *m_NamedLab; ///For quick response of repeated FindPCSColor + icUInt32Number m_nColorEntrySize; + + icUInt32Number m_nVendorFlags; + icUInt32Number m_nDeviceCoords; + icUInt32Number m_nSize; + + icColorSpaceSignature m_csPCS; + icColorSpaceSignature m_csDevice; +}; + +/** +**************************************************************************** +* Class: CIccTagXYZ +* +* Purpose: the XYZType tag - an array of XYZ values +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagXYZ : public CIccTag +{ +public: + CIccTagXYZ(int nSize=1); + CIccTagXYZ(const CIccTagXYZ &ITXYZ); + CIccTagXYZ &operator=(const CIccTagXYZ &XYZTag); + virtual CIccTag* NewCopy() const {return new CIccTagXYZ(*this);} + virtual ~CIccTagXYZ(); + + virtual bool IsArrayType() { return m_nSize > 1; } + + virtual icTagTypeSignature GetType() const { return icSigXYZType; } + virtual const icChar *GetClassName() const { return "CIccTagXYZ"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icXYZNumber &operator[](icUInt32Number index) const {return m_XYZ[index];} + icXYZNumber *GetXYZ(icUInt32Number index) {return &m_XYZ[index];} + icUInt32Number GetSize() const { return m_nSize; } + void SetSize(icUInt32Number nSize, bool bZeroNew=true); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icXYZNumber *m_XYZ; + icUInt32Number m_nSize; +}; + +/** +**************************************************************************** +* Class: CIccTagChromaticity +* +* Purpose: the chromaticity tag - xy chromaticity values for each channel +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagChromaticity : public CIccTag +{ +public: + CIccTagChromaticity(int nSize=3); + CIccTagChromaticity(const CIccTagChromaticity &ITCh); + CIccTagChromaticity &operator=(const CIccTagChromaticity &ChromTag); + virtual CIccTag* NewCopy() const {return new CIccTagChromaticity(*this);} + virtual ~CIccTagChromaticity(); + + virtual bool IsArrayType() { return m_nChannels > 1; } + + virtual icTagTypeSignature GetType() const { return icSigChromaticityType; } + virtual const icChar *GetClassName() const { return "CIccTagChromaticity"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icChromaticityNumber &operator[](icUInt32Number index) {return m_xy[index];} + icChromaticityNumber *Getxy(icUInt32Number index) {return &m_xy[index];} + icUInt32Number GetSize() const { return m_nChannels; } + void SetSize(icUInt16Number nSize, bool bZeroNew=true); + + icUInt16Number m_nColorantType; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt16Number m_nChannels; + icChromaticityNumber *m_xy; +}; + +/** +**************************************************************************** +* Class: CIccTagFixedNum +* +* Purpose: A template class for arrays of Fixed point numbers +* +* Derived Tags: CIccTagS15Fixed16 and CIccTagU16Fixed16 +***************************************************************************** +*/ +template +class ICCPROFLIB_API CIccTagFixedNum : public CIccTag +{ +public: + CIccTagFixedNum(int nSize=1); + CIccTagFixedNum(const CIccTagFixedNum &ITFN); + CIccTagFixedNum &operator=(const CIccTagFixedNum &FixedNumTag); + virtual CIccTag* NewCopy() const { return new CIccTagFixedNum(*this); } + virtual ~CIccTagFixedNum(); + + virtual bool IsArrayType() { return m_nSize > 1; } + + virtual icTagTypeSignature GetType() const { return Tsig; } + virtual const icChar *GetClassName() const; + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + T &operator[](icUInt32Number index) {return m_Num[index];} + + /// Returns the size of the data array + icUInt32Number GetSize() const { return m_nSize; } + void SetSize(icUInt32Number nSize, bool bZeroNew=true); + +protected: + T *m_Num; + icUInt32Number m_nSize; +}; + +/** +**************************************************************************** +* Class: CIccTagS15Fixed16 +* +* Purpose: s15Fixed16type tag derived from CIccTagFixedNum +***************************************************************************** +*/ +typedef CIccTagFixedNum CIccTagS15Fixed16; + +/** +**************************************************************************** +* Classe: CIccTagU16Fixed16 +* +* Purpose: u16Fixed16type tag derived from CIccTagFixedNum +***************************************************************************** +*/ +typedef CIccTagFixedNum CIccTagU16Fixed16; + +/** +**************************************************************************** +* Class: CIccTagNum +* +* Purpose: A template class for arrays of integers +* +* Derived Tags: CIccTagUInt8, CIccTagUInt16, CIccTagUInt32 and CIccTagUInt64 +***************************************************************************** +*/ +template +class ICCPROFLIB_API CIccTagNum : public CIccTag +{ +public: + CIccTagNum(int nSize=1); + CIccTagNum(const CIccTagNum &ITNum); + CIccTagNum &operator=(const CIccTagNum &NumTag); + virtual CIccTag* NewCopy() const { return new CIccTagNum(*this); } + virtual ~CIccTagNum(); + + virtual bool IsArrayType() { return m_nSize > 1; } + + virtual icTagTypeSignature GetType() const { return Tsig; } + virtual const icChar *GetClassName() const; + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + T &operator[](icUInt32Number index) {return m_Num[index];} + + /// Returns the size of the data array + icUInt32Number GetSize() const { return m_nSize; } + void SetSize(icUInt32Number nSize, bool bZeroNew=true); + +protected: + T *m_Num; + icUInt32Number m_nSize; +}; + + +/** +**************************************************************************** +* Class: CIccTagUInt8 +* +* Purpose: icUInt8Number type tag derived from CIccTagNum +***************************************************************************** +*/ +typedef CIccTagNum CIccTagUInt8; + +/** +**************************************************************************** +* Class: CIccTagUInt16 +* +* Purpose: icUInt16Number type tag derived from CIccTagNum +***************************************************************************** +*/ +typedef CIccTagNum CIccTagUInt16; + +/** +**************************************************************************** +* Class: CIccTagUInt32 +* +* Purpose: icUInt32Number type tag derived from CIccTagNum +***************************************************************************** +*/ +typedef CIccTagNum CIccTagUInt32; + +/** +**************************************************************************** +* Class: CIccTagUInt64 +* +* Purpose: icUInt64Number type tag derived from CIccTagNum +***************************************************************************** +*/ +typedef CIccTagNum CIccTagUInt64; + + + +/** +**************************************************************************** +* Class: CIccTagMeasurement +* +* Purpose: The measurmentType tag +**************************************************************************** +*/ +class ICCPROFLIB_API CIccTagMeasurement : public CIccTag +{ +public: + CIccTagMeasurement(); + CIccTagMeasurement(const CIccTagMeasurement &ITM); + CIccTagMeasurement &operator=(const CIccTagMeasurement &MeasTag); + virtual CIccTag* NewCopy() const {return new CIccTagMeasurement(*this);} + virtual ~CIccTagMeasurement(); + + virtual icTagTypeSignature GetType() const { return icSigMeasurementType; } + virtual const icChar *GetClassName() const { return "CIccTagMeasurement"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + + icMeasurement m_Data; +}; + +/** +**************************************************************************** +* Data Class: CIccLocalizedUnicode +* +* Purpose: Implementation of a unicode string with language and region +* identifiers. +***************************************************************************** +*/ +class ICCPROFLIB_API CIccLocalizedUnicode +{ +public: //member functions + CIccLocalizedUnicode(); + CIccLocalizedUnicode(const CIccLocalizedUnicode& ILU); + CIccLocalizedUnicode &operator=(const CIccLocalizedUnicode &UnicodeText); + virtual ~CIccLocalizedUnicode(); + + icUInt32Number GetLength() const { return m_nLength; } + icUInt16Number *GetBuf() const { return m_pBuf; } + + icUInt32Number GetAnsiSize(); + const icChar *GetAnsi(icChar *szBuf, icUInt32Number nBufSize); + + void SetSize(icUInt32Number); + + void SetText(const icChar *szText, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + void SetText(const icUInt16Number *sszUnicode16Text, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + void SetText(const icUInt32Number *sszUnicode32Text, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + + const icChar *operator=(const icChar *szText) { SetText(szText); return szText; } + const icUInt16Number *operator=(const icUInt16Number *sszText) { SetText(sszText); return sszText; } + const icUInt32Number *operator=(const icUInt32Number *sszText) { SetText(sszText); return sszText; } + + //Data + icLanguageCode m_nLanguageCode; + icCountryCode m_nCountryCode; + +protected: + icUInt32Number m_nLength; + + icUInt16Number *m_pBuf; + +}; + +/** +**************************************************************************** +* List Class: CIccMultiLocalizedUnicode +* +* Purpose: List of CIccLocalizedUnicode objects +***************************************************************************** +*/ +typedef std::list CIccMultiLocalizedUnicode; + + +/** +**************************************************************************** +* Class: CIccTagMultiLocalizedUnicode +* +* Purpose: The MultiLocalizedUnicode tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagMultiLocalizedUnicode : public CIccTag +{ +public: + CIccTagMultiLocalizedUnicode(); + CIccTagMultiLocalizedUnicode(const CIccTagMultiLocalizedUnicode& ITMLU); + CIccTagMultiLocalizedUnicode &operator=(const CIccTagMultiLocalizedUnicode &MultiLocalizedTag); + virtual CIccTag* NewCopy() const {return new CIccTagMultiLocalizedUnicode(*this);} + virtual ~CIccTagMultiLocalizedUnicode(); + + virtual icTagTypeSignature GetType() const { return icSigMultiLocalizedUnicodeType; } + virtual const icChar *GetClassName() const { return "CIcciSigMultiLocalizedUnicode"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + CIccLocalizedUnicode *Find(icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + + void SetText(const icChar *szText, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + void SetText(const icUInt16Number *sszUnicode16Text, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + void SetText(const icUInt32Number *sszUnicode32Text, + icLanguageCode nLanguageCode = icLanguageCodeEnglish, + icCountryCode nRegionCode = icCountryCodeUSA); + + + CIccMultiLocalizedUnicode *m_Strings; +}; + + +// +// MD: Moved Curve & LUT Tags to IccTagLut.h (4-30-05) +// + +/** +**************************************************************************** +* Class: CIccTagData +* +* Purpose: The data type tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagData : public CIccTag +{ +public: + CIccTagData(int nSize=1); + CIccTagData(const CIccTagData &ITD); + CIccTagData &operator=(const CIccTagData &DataTag); + virtual CIccTag* NewCopy() const {return new CIccTagData(*this);} + virtual ~CIccTagData(); + + virtual icTagTypeSignature GetType() const { return icSigDataType; } + virtual const icChar *GetClassName() const { return "CIccTagData"; } + + virtual bool IsArrayType() { return m_nSize > 1; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icUInt32Number GetSize() const { return m_nSize; } + void SetSize(icUInt32Number nSize, bool bZeroNew=true); + icUInt8Number &operator[] (icUInt32Number index) { return m_pData[index]; } + icUInt8Number *GetData(icUInt32Number index) { return &m_pData[index];} + + void SetTypeAscii(bool bIsAscii=true) { m_nDataFlag = bIsAscii ? icAsciiData : icBinaryData; } + bool IsTypeAscii() { return m_nDataFlag == icAsciiData; } + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt32Number m_nDataFlag; + icUInt8Number *m_pData; + icUInt32Number m_nSize; +}; + +/** +**************************************************************************** +* Class: CIccTagDateTime +* +* Purpose: The date time tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagDateTime : public CIccTag +{ +public: + CIccTagDateTime(); + CIccTagDateTime(const CIccTagDateTime &ITDT); + CIccTagDateTime &operator=(const CIccTagDateTime &DateTimeTag); + virtual CIccTag* NewCopy() const {return new CIccTagDateTime(*this);} + virtual ~CIccTagDateTime(); + + virtual icTagTypeSignature GetType() const { return icSigDateTimeType; } + virtual const icChar *GetClassName() const { return "CIccTagDateTime"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Describe(std::string &sDescription); + + void SetDateTime(icDateTimeNumber nDateTime) { m_DateTime = nDateTime;} + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icDateTimeNumber m_DateTime; +}; + +/** +**************************************************************************** +* Class: CIccTagColorantOrder +* +* Purpose: Colorant Order Tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagColorantOrder : public CIccTag +{ +public: + CIccTagColorantOrder(int nsize=1); + CIccTagColorantOrder(const CIccTagColorantOrder &ITCO); + CIccTagColorantOrder &operator=(const CIccTagColorantOrder &ColorantOrderTag); + virtual CIccTag* NewCopy() const {return new CIccTagColorantOrder(*this);} + virtual ~CIccTagColorantOrder(); + + virtual icTagTypeSignature GetType() const { return icSigColorantOrderType; } + virtual const icChar *GetClassName() const { return "CIccTagColorantOrder"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Describe(std::string &sDescription); + icUInt8Number& operator[](int index) { return m_pData[index]; } + icUInt8Number *GetData(int index) { return &m_pData[index]; } + void SetSize(icUInt16Number nsize, bool bZeronew=true); + icUInt32Number GetSize() const {return m_nCount;} + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt32Number m_nCount; + icUInt8Number *m_pData; +}; + +/** +**************************************************************************** +* Class: CIccTagColorantTable +* +* Purpose: Colorant Table Tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagColorantTable : public CIccTag +{ +public: + CIccTagColorantTable(int nsize=1); + CIccTagColorantTable(const CIccTagColorantTable &ITCT); + CIccTagColorantTable &operator=(const CIccTagColorantTable &ColorantTableTag); + virtual CIccTag* NewCopy() const {return new CIccTagColorantTable(*this);} + virtual ~CIccTagColorantTable(); + + virtual icTagTypeSignature GetType() const { return icSigColorantTableType; } + virtual const icChar *GetClassName() const { return "CIccTagColorantTable"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icColorantTableEntry &operator[](icUInt32Number index) {return m_pData[index];} + icColorantTableEntry *GetEntry(icUInt32Number index) {return &m_pData[index];} + icUInt32Number GetSize() const { return m_nCount; } + void SetSize(icUInt16Number nSize, bool bZeroNew=true); + + void SetPCS(icColorSpaceSignature sig) {m_PCS = sig;} + icColorSpaceSignature GetPCS() const {return m_PCS;}; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt32Number m_nCount; + icColorantTableEntry *m_pData; + icColorSpaceSignature m_PCS; +}; + +/** +**************************************************************************** +* Class: CIccTagViewingConditions +* +* Purpose: Viewing conditions tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagViewingConditions : public CIccTag +{ +public: + CIccTagViewingConditions(); + CIccTagViewingConditions(const CIccTagViewingConditions &ITVC); + CIccTagViewingConditions &operator=(const CIccTagViewingConditions &ViewCondTag); + virtual CIccTag* NewCopy() const {return new CIccTagViewingConditions(*this);} + virtual ~CIccTagViewingConditions(); + + virtual icTagTypeSignature GetType() const { return icSigViewingConditionsType; } + virtual const icChar *GetClassName() const { return "CIccTagViewingConditions"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + icXYZNumber m_XYZIllum; + icXYZNumber m_XYZSurround; + icIlluminant m_illumType; +}; + +/** +**************************************************************************** +* Data Class: CIccProfileDescText +* +* Purpose: Private text class for CIccProfileDescStruct. +* Text can be either a CIccTagTextDescription or a CIccTagMultiLocalizedUnicode +* so this class provides a single interface to both. +***************************************************************************** +*/ +class ICCPROFLIB_API CIccProfileDescText +{ +public: + CIccProfileDescText(); + CIccProfileDescText(const CIccProfileDescText& IPDC); + CIccProfileDescText &operator=(const CIccProfileDescText &ProfDescText); + virtual ~CIccProfileDescText(); + + bool SetType(icTagTypeSignature nType); + virtual icTagTypeSignature GetType() const; + + CIccTag* GetTag() const { return m_pTag; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + bool m_bNeedsPading; + +protected: + CIccTag *m_pTag; //either a CIccTagTextDescription or a CIccTagMultiLocalizedUnicode +}; + + + +/** +**************************************************************************** +* Data Class: CIccProfileDescStruct +* +* Purpose: Storage class for profile description structure +***************************************************************************** +*/ +class ICCPROFLIB_API CIccProfileDescStruct +{ +public: + CIccProfileDescStruct(); + CIccProfileDescStruct(const CIccProfileDescStruct& IPDS); + CIccProfileDescStruct &operator=(const CIccProfileDescStruct& ProfDescStruct); + + + icSignature m_deviceMfg; /* Device Manufacturer */ + icSignature m_deviceModel; /* Device Model */ + icUInt64Number m_attributes; /* Device attributes */ + icTechnologySignature m_technology; /* Technology signature */ + CIccProfileDescText m_deviceMfgDesc; + CIccProfileDescText m_deviceModelDesc; +}; + +/** +**************************************************************************** +* List Class: CIccProfileSeqDesc +* +* Purpose: List of profile description structures +***************************************************************************** +*/ +typedef std::list CIccProfileSeqDesc; + + +/** +**************************************************************************** +* Class: CIccTagProfileSeqDesc +* +* Purpose: Profile Sequence description tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagProfileSeqDesc : public CIccTag +{ +public: + CIccTagProfileSeqDesc(); + CIccTagProfileSeqDesc(const CIccTagProfileSeqDesc &ITPSD); + CIccTagProfileSeqDesc &operator=(const CIccTagProfileSeqDesc &ProfSeqDescTag); + virtual CIccTag* NewCopy() const {return new CIccTagProfileSeqDesc(*this);} + virtual ~CIccTagProfileSeqDesc(); + + virtual icTagTypeSignature GetType() const { return icSigProfileSequenceDescType; } + virtual const icChar *GetClassName() const { return "CIccTagProfileSeqDesc"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + CIccProfileSeqDesc *m_Descriptions; +}; + + +/** +**************************************************************************** +* List Class: CIccResponse16List +* +* Purpose: List of response16numbers +***************************************************************************** +*/ +typedef std::list CIccResponse16List; + +/** +**************************************************************************** +* Data Class: CIccResponseCurveStruct +* +* Purpose: The base class for response curve structure +***************************************************************************** +*/ +class ICCPROFLIB_API CIccResponseCurveStruct +{ + friend class ICCPROFLIB_API CIccTagResponseCurveSet16; +public: //member functions + CIccResponseCurveStruct(icMeasurementUnitSig sig, icUInt16Number nChannels=0); + CIccResponseCurveStruct(icUInt16Number nChannels=0); + + CIccResponseCurveStruct(const CIccResponseCurveStruct& IRCS); + CIccResponseCurveStruct &operator=(const CIccResponseCurveStruct& RespCurveStruct); + virtual ~CIccResponseCurveStruct(); + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + void Describe(std::string &sDescription); + + icMeasurementUnitSig GetMeasurementType() const {return m_measurementUnitSig;} + icUInt16Number GetNumChannels() const {return m_nChannels;} + + icXYZNumber *GetXYZ(icUInt32Number index) {return &m_maxColorantXYZ[index];} + CIccResponse16List *GetResponseList(icUInt16Number nChannel) {return &m_Response16ListArray[nChannel];} + CIccResponseCurveStruct* GetThis() {return this;} + icValidateStatus Validate(std::string &sReport) const; + +protected: + icUInt16Number m_nChannels; + icMeasurementUnitSig m_measurementUnitSig; + icXYZNumber *m_maxColorantXYZ; + CIccResponse16List *m_Response16ListArray; +}; + + +/** +**************************************************************************** +* List Class: CIccResponseCurveSet +* +* Purpose: List of response curve structures +***************************************************************************** +*/ + +typedef std::list CIccResponseCurveSet; + +class CIccResponseCurveSetIter +{ +public: + bool inited; + CIccResponseCurveSet::iterator item; +}; + + +/** +**************************************************************************** +* Class: CIccTagResponseCurveSet16 +* +* Purpose: The responseCurveSet16 Tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagResponseCurveSet16 : public CIccTag +{ +public: + CIccTagResponseCurveSet16(); + CIccTagResponseCurveSet16(const CIccTagResponseCurveSet16 &ITRCS); + CIccTagResponseCurveSet16 &operator=(const CIccTagResponseCurveSet16 &RespCurveSet16Tag); + virtual CIccTag* NewCopy() const {return new CIccTagResponseCurveSet16(*this);} + virtual ~CIccTagResponseCurveSet16(); + + virtual icTagTypeSignature GetType() const { return icSigResponseCurveSet16Type; } + virtual const icChar *GetClassName() const { return "CIccTagResponseCurveSet16"; } + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + virtual void Describe(std::string &sDescription); + + void SetNumChannels(icUInt16Number nChannels); + icUInt16Number GetNumChannels() const {return m_nChannels;} + + CIccResponseCurveStruct *NewResponseCurves(icMeasurementUnitSig sig); + CIccResponseCurveStruct *GetResponseCurves(icMeasurementUnitSig sig); + + CIccResponseCurveStruct *GetFirstCurves(); + CIccResponseCurveStruct *GetNextCurves(); + + icUInt16Number GetNumResponseCurveTypes() const; + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + CIccResponseCurveSet *m_ResponseCurves; + icUInt16Number m_nChannels; + CIccResponseCurveSetIter *m_Curve; +}; + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif // !defined(_ICCTAGBASIC_H) diff --git a/library/src/main/cpp/icc/IccTagDict.cpp b/library/src/main/cpp/icc/IccTagDict.cpp new file mode 100644 index 00000000..82c6e1f1 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagDict.cpp @@ -0,0 +1,1467 @@ +/** @file + File: IccTagDictTag.cpp + + Contains: Implementation of prototype dictType Tag + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak Jun-26-2009 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include +#include +#include +#include +#include "IccTagDict.h" +#include "IccUtil.h" +#include "IccIO.h" + + +//MSVC 6.0 doesn't support std::string correctly so we disable support in this case +#ifndef ICC_UNSUPPORTED_TAG_DICT + +/*============================================================================= +* CLASS CIccDictEntry +*=============================================================================*/ + +/** +****************************************************************************** +* Name: CIccDictEntry::CIccDictEntry +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccDictEntry::CIccDictEntry() +{ + m_pNameLocalized = NULL; + m_pValueLocalized = NULL; + m_bValueSet = false; +} + +/** +****************************************************************************** +* Name: CIccDictEntry::CIccDictEntry +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccDictEntry::CIccDictEntry(const CIccDictEntry& IDE) +{ + m_sName = IDE.m_sName; + m_bValueSet = IDE.m_bValueSet; + m_sValue = IDE.m_sValue; + + if (IDE.m_pNameLocalized) { + m_pNameLocalized = (CIccTagMultiLocalizedUnicode*)IDE.m_pNameLocalized->NewCopy(); + } + else + m_pNameLocalized = NULL; + + if (IDE.m_pValueLocalized) { + m_pValueLocalized = (CIccTagMultiLocalizedUnicode*)IDE.m_pValueLocalized->NewCopy(); + } + else + m_pValueLocalized = NULL; +} + +/** +****************************************************************************** +* Name: CIccDictEntry::operator= +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccDictEntry &CIccDictEntry::operator=(const CIccDictEntry &IDE) +{ + if (m_pNameLocalized) + delete m_pNameLocalized; + + if (m_pValueLocalized) + delete m_pValueLocalized; + + m_sName = IDE.m_sName; + m_bValueSet = IDE.m_bValueSet; + m_sValue = IDE.m_sValue; + + if (IDE.m_pNameLocalized) { + m_pNameLocalized = (CIccTagMultiLocalizedUnicode*)IDE.m_pNameLocalized->NewCopy(); + } + else + m_pNameLocalized = NULL; + + if (IDE.m_pValueLocalized) { + m_pValueLocalized = (CIccTagMultiLocalizedUnicode*)IDE.m_pValueLocalized->NewCopy(); + } + else + m_pValueLocalized = NULL; + + return *this; +} + +/** +****************************************************************************** +* Name: CIccDictEntry::~CIccDictEntry +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccDictEntry::~CIccDictEntry() +{ + delete m_pNameLocalized; + delete m_pValueLocalized; +} + +/** +****************************************************************************** +* Name: CIccDictEntry::Describe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccDictEntry::Describe(std::string &sDescription) +{ + std::string s; + + sDescription += "BEGIN DICT_ENTRY\r\nName="; + m_sName.ToUtf8(s); + sDescription += s; + sDescription += "\r\nValue="; + m_sValue.ToUtf8(s); + sDescription += s; + sDescription += "\r\n"; + + if (m_pNameLocalized) { + sDescription += "BEGIN NAME_LOCALIZATION\r\n"; + m_pNameLocalized->Describe(sDescription); + sDescription += "END NAME_LOCALIZATION\r\n"; + } + if (m_pValueLocalized) { + sDescription += "BEGIN VALUE_LOCALIZATION\r\n"; + m_pValueLocalized->Describe(sDescription); + sDescription += "END VALUE_LOCALIZATION\r\n"; + } + sDescription += "END DICT_ENTRY\r\n"; +} + + +/** +****************************************************************************** +* Name: CIccDictEntry::PosRecSize +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +icUInt32Number CIccDictEntry::PosRecSize() +{ + if (m_pValueLocalized) + return 32; + if (m_pNameLocalized) + return 24; + return 16; +} + + +bool CIccDictEntry::SetValue(const CIccUTF16String &sValue) +{ + bool rv = m_bValueSet && !m_sValue.Empty(); + + m_sValue = sValue; + m_bValueSet = true; + return rv; +} + +/** +****************************************************************************** +* Name: CIccDictEntry::SetNameLocalized +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccDictEntry::SetNameLocalized(CIccTagMultiLocalizedUnicode *pNameLocalized) +{ + bool rv; + + if (m_pNameLocalized) { + delete m_pNameLocalized; + rv = true; + } + else + rv = false; + + m_pNameLocalized = pNameLocalized; + + return rv; +} + + +/** +****************************************************************************** +* Name: CIccDictEntry::SetValueLocalized +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccDictEntry::SetValueLocalized(CIccTagMultiLocalizedUnicode *pValueLocalized) +{ + bool rv; + + if (m_pValueLocalized) { + delete m_pValueLocalized; + rv = true; + } + else + rv = false; + + m_pValueLocalized = pValueLocalized; + + return rv; +} + +/*============================================================================= + * CLASS CIccTagDict + *============================================================================*/ + +/** + ****************************************************************************** + * Name: CIccTagDict::CIccTagDict + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagDict::CIccTagDict() +{ + m_bBadAlignment = false; + + m_Dict = new CIccNameValueDict; +} + +/** + ****************************************************************************** + * Name: CIccTagDict::CIccTagDict + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagDict::CIccTagDict(const CIccTagDict &dict) +{ + m_bBadAlignment = false; + m_Dict = new CIccNameValueDict; + + CIccNameValueDict::iterator i; + CIccDictEntryPtr ptr; + + for (i=dict.m_Dict->begin(); i!=dict.m_Dict->end(); i++) { + ptr.ptr = new CIccDictEntry(*i->ptr); + + m_Dict->push_back(ptr); + } +} + +/** + ****************************************************************************** + * Name: &operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagDict &CIccTagDict::operator=(const CIccTagDict &dict) +{ + if (&dict == this) + return *this; + + Cleanup(); + + CIccNameValueDict::iterator i; + CIccDictEntryPtr ptr; + + for (i=dict.m_Dict->begin(); i!=dict.m_Dict->end(); i++) { + ptr.ptr = new CIccDictEntry(*i->ptr); + + m_Dict->push_back(ptr); + } + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccTagDict::~CIccTagDict + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagDict::~CIccTagDict() +{ + Cleanup(); + delete m_Dict; +} + + +/** +****************************************************************************** +* Name: CIccTagDict::MaxPosRecSize; +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +icUInt32Number CIccTagDict::MaxPosRecSize() +{ + + icUInt32Number rv = 16; + + CIccNameValueDict::iterator i; + CIccDictEntry ptr; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + if (i->ptr->PosRecSize() > rv) + rv = i->ptr->PosRecSize(); + } + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccTagDict::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagDict::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sprintf(buf, "BEGIN DICT_TAG\r\n"); + sDescription += buf; + + CIccNameValueDict::iterator i; + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + sDescription += "\r\n"; + + i->ptr->Describe(sDescription); + } + + sprintf(buf, "\r\nEND DICT_TAG\r\n"); + sDescription += buf; +} + +typedef struct +{ + icPositionNumber posName; + icPositionNumber posValue; + icPositionNumber posNameLocalized; + icPositionNumber posValueLocalized; +} icDictRecordPos; + + +/** + ****************************************************************************** + * Name: CIccTagDict::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagDict::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + m_tagSize = size; + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + Cleanup(); + + m_tagStart = pIO->Tell(); + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number count, reclen, i; + + if (!pIO->Read32(&count)) + return false; + + if (!pIO->Read32(&reclen)) + return false; + + if (reclen!=16 && reclen!=24 && reclen!=32) + return false; + + if (headerSize + count*reclen > size) + return false; + + icDictRecordPos *pos = (icDictRecordPos*)calloc(count, sizeof(icDictRecordPos)); + if (!pos) + return false; + + //Read TagDir + for (i=0; iRead32(&pos[i].posName.offset) || + !pIO->Read32(&pos[i].posName.size) || + !pIO->Read32(&pos[i].posValue.offset) || + !pIO->Read32(&pos[i].posValue.size)) { + free(pos); + return false; + } + + if (reclen>=24) { + if (!pIO->Read32(&pos[i].posNameLocalized.offset) || + !pIO->Read32(&pos[i].posNameLocalized.size)) { + free(pos); + return false; + } + if (reclen>=32) { + if (!pIO->Read32(&pos[i].posValueLocalized.offset) || + !pIO->Read32(&pos[i].posValueLocalized.size)) { + free(pos); + return false; + } + } + } + + if ((pos[i].posName.offset & 0x3) || + (pos[i].posValue.offset & 0x3) || + (pos[i].posNameLocalized.offset & 0x3) || + (pos[i].posValueLocalized.offset & 0x3)) + m_bBadAlignment = true; + } + + icUInt32Number bufsize = 128, num; + icUnicodeChar *buf = (icUnicodeChar*)malloc(bufsize); + CIccDictEntryPtr ptr; + CIccUTF16String str; + + for (i=0; iSetValue(str); + } + else { + if (pos[i].posName.offset + pos[i].posName.size >size || + !pos[i].posName.size) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + //Make sure we have buf large enough for the string + if (bufsize < pos[i].posName.size) { + bufsize = pos[i].posName.size; + buf = (icUnicodeChar*)realloc(buf, bufsize+1*sizeof(icUnicodeChar)); + if (!buf) { + free(pos); + delete ptr.ptr; + return false; + } + } + + if (pIO->Seek(m_tagStart+pos[i].posName.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + num = pos[i].posName.size / sizeof(icUnicodeChar); + if (pIO->Read16(buf, num)!=(icInt32Number)num) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + buf[num] = 0; + ptr.ptr->m_sName = buf; + } + } + + //GetValue + if (pos[i].posValue.offset) { + if (!pos[i].posValue.size) { + str.Clear(); + ptr.ptr->SetValue(str); + } + else { + if (pos[i].posValue.offset + pos[i].posValue.size >size || + (pos[i].posValue.size&1)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + //Make sure we have buf large enough for the string + if (bufsize < pos[i].posValue.size) { + bufsize = pos[i].posValue.size; + buf = (icUnicodeChar*)realloc(buf, bufsize+1*sizeof(icUnicodeChar)); + if (!buf) { + free(pos); + delete ptr.ptr; + return false; + } + } + + if (pIO->Seek(m_tagStart+pos[i].posValue.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + num = pos[i].posValue.size / sizeof(icUnicodeChar); + if (pIO->Read16(buf, num)!=(icInt32Number)num) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + buf[num]=0; + ptr.ptr->SetValue(buf); + } + } + + //Get NameLocalized + if (pos[i].posNameLocalized.offset) { + if (pos[i].posNameLocalized.offset + pos[i].posNameLocalized.size > size || + pos[i].posNameLocalized.size < sizeof(icSignature)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (pIO->Seek(m_tagStart+pos[i].posNameLocalized.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + icTagTypeSignature textSig = (icTagTypeSignature)0; + if (!pIO->Read32(&textSig, 1)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (textSig != icSigMultiLocalizedUnicodeType) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (pIO->Seek(m_tagStart+pos[i].posNameLocalized.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + CIccTagMultiLocalizedUnicode *pTag = new CIccTagMultiLocalizedUnicode(); + + if (!pTag || !pTag->Read(pos[i].posNameLocalized.size, pIO)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + ptr.ptr->SetNameLocalized(pTag); + } + + //Get ValueLocalized + if (pos[i].posValueLocalized.offset) { + if (pos[i].posValueLocalized.offset + pos[i].posValueLocalized.size > size || + pos[i].posValueLocalized.size < sizeof(icSignature)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (pIO->Seek(m_tagStart+pos[i].posValueLocalized.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + icTagTypeSignature textSig = (icTagTypeSignature)0; + if (!pIO->Read32(&textSig, 1)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (textSig != icSigMultiLocalizedUnicodeType) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + if (pIO->Seek(m_tagStart+pos[i].posValueLocalized.offset, icSeekSet)<0) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + CIccTagMultiLocalizedUnicode *pTag = new CIccTagMultiLocalizedUnicode(); + + if (!pTag || !pTag->Read(pos[i].posValueLocalized.size, pIO)) { + free(pos); + free(buf); + delete ptr.ptr; + return false; + } + + ptr.ptr->SetValueLocalized(pTag); + } + + m_Dict->push_back(ptr); + } + + free(pos); + free(buf); + + return true; +} + +/** + ****************************************************************************** + * Name: CIccTagDict::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagDict::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + m_tagStart = pIO->Tell(); + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + CIccNameValueDict::iterator i, j; + icUInt32Number count; + icUInt8Number zbuf[32]; + + memset(zbuf, 0, 32); + + for (count=0, i=m_Dict->begin(); i!= m_Dict->end(); i++) { + if (i->ptr) + count++; + } + + pIO->Write32(&count); + + icUInt32Number recSize = MaxPosRecSize(); + pIO->Write32(&recSize); + + icDictRecordPos *pos = (icDictRecordPos*)calloc(count, sizeof(icDictRecordPos)); + if (!pos) + return false; + + icUInt32Number n, dirpos = pIO->Tell(); + + //Write Unintialized Dict rec offset array + for (i=m_Dict->begin(); i!= m_Dict->end(); i++) { + if (i->ptr) { + pIO->Write8(zbuf, recSize); + } + } + + //Write Dict records + for (n=0, i=m_Dict->begin(); i!= m_Dict->end(); i++) { + if (i->ptr) { + pos[n].posName.offset = pIO->Tell()-m_tagStart; + + pIO->Write16((void*)i->ptr->m_sName.c_str(), i->ptr->m_sName.Size()); + pos[n].posName.size = pIO->Tell()-m_tagStart - pos[n].posName.offset; + pIO->Align32(); + + if (i->ptr->IsValueSet()) { + const CIccUTF16String &str = i->ptr->GetValue(); + pos[n].posValue.offset = pIO->Tell()-m_tagStart; + pIO->Write16((void*)str.c_str(), str.Size()); + + pos[n].posValue.size = pIO->Tell()-m_tagStart - pos[n].posValue.offset; + pIO->Align32(); + } + + if (recSize>16 && i->ptr->GetNameLocalized()) { + pos[n].posNameLocalized.offset = pIO->Tell()-m_tagStart; + i->ptr->GetNameLocalized()->Write(pIO); + pos[n].posNameLocalized.size = pIO->Tell()-m_tagStart - pos[n].posNameLocalized.offset; + pIO->Align32(); + } + + if (recSize>24 && i->ptr->GetValueLocalized()) { + pos[n].posValueLocalized.offset = pIO->Tell()-m_tagStart; + i->ptr->GetValueLocalized()->Write(pIO); + pos[n].posValueLocalized.size = pIO->Tell()-m_tagStart - pos[n].posValueLocalized.offset; + pIO->Align32(); + } + n++; + } + } + icUInt32Number endpos = pIO->Tell(); + + pIO->Seek(dirpos, icSeekSet); + + //Write TagDir with offsets and sizes + for (n=0, i=m_Dict->begin(); i!= m_Dict->end(); i++) { + if (i->ptr) { + pIO->Write32(&pos[n].posName.offset); + pIO->Write32(&pos[n].posName.size); + pIO->Write32(&pos[n].posValue.offset); + pIO->Write32(&pos[n].posValue.size); + if (recSize>16) { + pIO->Write32(&pos[n].posNameLocalized.offset); + pIO->Write32(&pos[n].posNameLocalized.size); + if (recSize>24) { + pIO->Write32(&pos[n].posValueLocalized.offset); + pIO->Write32(&pos[n].posValueLocalized.size); + } + } + } + n++; + } + pIO->Seek(endpos, icSeekSet); + + free(pos); + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccTagDict::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccTagDict::Validate(icTagSignature sig, std::string &sReport, + const CIccProfile* pProfile /*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + // Check for duplicate tags + if (!AreNamesUnique()) { + sReport += icValidateWarning; + sReport += sSigName; + sReport += " - There are duplicate tags.\r\n"; + rv =icMaxStatus(rv, icValidateWarning); + } + + if (!AreNamesNonzero()) { + sReport += icValidateWarning; + sReport += sSigName; + sReport += " - There are duplicate tags.\r\n"; + rv =icMaxStatus(rv, icValidateWarning); + } + + // Check for duplicate tags + if (m_bBadAlignment) { + sReport += icValidateWarning; + sReport += sSigName; + sReport += " - Some Data elements are not aligned correctly\r\n"; + rv =icMaxStatus(rv, icValidateWarning); + } + + return rv; +} + +/** + *************************************************************************** + * Name: CIccTagDict::Cleanup + * + * Purpose: Detach from a pending IO object + *************************************************************************** + */ +void CIccTagDict::Cleanup() +{ + CIccNameValueDict::iterator i; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + if (i->ptr) + delete i->ptr; + } + m_Dict->clear(); +} + +/** +****************************************************************************** +* Name: CIccTagDict::AreNamesUnique +* +* Purpose: For each tag it checks to see if any other tags have the same +* signature. +* +* +* Return: +* true if all tags have unique signatures, or false if there are duplicate +* tag signatures. +******************************************************************************* +*/ +bool CIccTagDict::AreNamesUnique() const +{ + CIccNameValueDict::const_iterator i, j; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + j=i; + for (j++; j!= m_Dict->end(); j++) { + if (i->ptr->m_sName == j->ptr->m_sName) + return false; + } + } + + return true; +} + +/** +****************************************************************************** +* Name: CIccTagDict::AreNamesNonzero +* +* Purpose: For each tag it checks to see if any other tags have the same +* signature. +* +* +* Return: +* true if all tags have unique signatures, or false if there are duplicate +* tag signatures. +******************************************************************************* +*/ +bool CIccTagDict::AreNamesNonzero() const +{ + CIccNameValueDict::const_iterator i, j; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + if (i->ptr->m_sName.Empty()) + return false; + } + +return true; +} + + +/** + **************************************************************************** + * Name: CIccTagDict::Get + * + * Purpose: Get a dictionary entry with a given name + * + * Args: + * sName - name to find in dictionary + * + * Return: + * Pointer to desired dictionary entry, or NULL if not found. + ***************************************************************************** + */ +CIccDictEntry* CIccTagDict::Get(const CIccUTF16String &sName) const +{ + CIccNameValueDict::const_iterator i; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + if (i->ptr->m_sName == sName) + return i->ptr; + } + + return NULL; +} + +/** +**************************************************************************** +* Name: CIccTagDict::Get +* +* Purpose: Get a dictionary entry with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* Pointer to desired dictionary entry, or NULL if not found. +***************************************************************************** +*/ +CIccDictEntry* CIccTagDict::Get(const icUnicodeChar *szName) const +{ + CIccUTF16String sName(szName); + + return Get(sName); +} + + +/** +**************************************************************************** +* Name: CIccTagDict::Get +* +* Purpose: Get a dictionary entry with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* Pointer to desired dictionary entry, or NULL if not found. +***************************************************************************** +*/ +CIccDictEntry* CIccTagDict::Get(const char *szName) const +{ + CIccUTF16String sName(szName); + + return Get(sName); +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetValue +* +* Purpose: Get a value associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* Pointer to desired dictionary entry, or NULL if not found. +***************************************************************************** +*/ +CIccUTF16String CIccTagDict::GetValue(const CIccUTF16String &sName, bool *bIsSet) const +{ + CIccDictEntry *de = Get(sName); + + if (de) { + + if (bIsSet) + *bIsSet = de->IsValueSet(); + + return de->GetValue(); + } + + if (bIsSet) + *bIsSet = false; + + CIccUTF16String str; + return str; +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetValue +* +* Purpose: Get a value associated with a given name +* +* Args: +* szName - name to find in dictionary +* +* Return: +* Pointer to desired dictionary entry, or NULL if not found. +***************************************************************************** +*/ +CIccUTF16String CIccTagDict::GetValue(const icUnicodeChar *szName, bool *bIsSet) const +{ + CIccUTF16String sName(szName); + + return GetValue(sName, bIsSet); +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetValue +* +* Purpose: Get a value associated with a given name +* +* Args: +* szName - name to find in dictionary +* +* Return: +* Pointer to desired dictionary entry, or NULL if not found. +***************************************************************************** +*/ +CIccUTF16String CIccTagDict::GetValue(const char *szName, bool *bIsSet) const +{ + CIccUTF16String sName(szName); + + return GetValue(sName, bIsSet); +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetNameLocalized +* +* Purpose: Get a localized name information associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name +***************************************************************************** +*/ +CIccTagMultiLocalizedUnicode* CIccTagDict::GetNameLocalized(const CIccUTF16String &sName) const +{ + CIccDictEntry *de = Get(sName); + + if (de) + return de->GetNameLocalized(); + + return NULL; +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetNameLocalized +* +* Purpose: Get a localized name information associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name +***************************************************************************** +*/ +CIccTagMultiLocalizedUnicode* CIccTagDict::GetNameLocalized(const icUnicodeChar *szName) const +{ + CIccUTF16String sName(szName); + + return GetNameLocalized(sName); +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetNameLocalized +* +* Purpose: Get a localized name information associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name +***************************************************************************** +*/ + +CIccTagMultiLocalizedUnicode* CIccTagDict::GetNameLocalized(const char *szName) const +{ + CIccUTF16String sName(szName); + + return GetNameLocalized(sName); +} + + +/** +**************************************************************************** +* Name: CIccTagDict::GetValueLocalized +* +* Purpose: Get a localized name information for value associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name's value +***************************************************************************** +*/ +CIccTagMultiLocalizedUnicode* CIccTagDict::GetValueLocalized(const CIccUTF16String &sName) const +{ + CIccDictEntry *de = Get(sName); + + if (de) + return de->GetValueLocalized(); + + return NULL; +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetValueLocalized +* +* Purpose: Get a localized name information for value associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name's value +***************************************************************************** +*/ +CIccTagMultiLocalizedUnicode* CIccTagDict::GetValueLocalized(const icUnicodeChar *szName) const +{ + CIccUTF16String sName(szName); + + return GetValueLocalized(sName); +} + +/** +**************************************************************************** +* Name: CIccTagDict::GetValueLocalized +* +* Purpose: Get a localized name information for value associated with a given name +* +* Args: +* sName - name to find in dictionary +* +* Return: +* localized information for name's value +***************************************************************************** +*/ + +CIccTagMultiLocalizedUnicode* CIccTagDict::GetValueLocalized(const char *szName) const +{ + CIccUTF16String sName(szName); + + return GetValueLocalized(sName); +} + + +/** +****************************************************************************** +* Name: CIccTagDict::Remove +* +* Purpose: Remove a name value pair from the dictionary +* +* Args: +* sName = the name to look for in the dictionary +* +* Return: +* true if sName exists and was removed, or false otherwise +******************************************************************************* +*/ +bool CIccTagDict::Remove(const CIccUTF16String &sName) +{ + CIccNameValueDict::iterator i; + + for (i=m_Dict->begin(); i!=m_Dict->end(); i++) { + if (i->ptr->m_sName == sName) { + delete i->ptr; + + m_Dict->erase(i); + return true; + } + } + + return false; +} + + +/** +****************************************************************************** +* Name: CIccTagDict::Remove +* +* Purpose: Remove a name value pair from the dictionary +* +* Args: +* sName = the name to look for in the dictionary +* +* Return: +* true if sName exists and was remove , or false otherwise +******************************************************************************* +*/ +bool CIccTagDict::Remove(const icUnicodeChar *szName) +{ + CIccUTF16String sName(szName); + + return Remove(sName); + +} + + +/** +****************************************************************************** +* Name: CIccTagDict::Remove +* +* Purpose: Remove a name value pair from the dictionary +* +* Args: +* sName = the name to look for in the dictionary +* +* Return: +* true if sName exists and was remove , or false otherwise +******************************************************************************* +*/ +bool CIccTagDict::Remove(const char *szName) +{ + CIccUTF16String sName(szName); + + return Remove(sName); +} + + +/** +****************************************************************************** +* Name: CIccTagDict::Set +* +* Purpose: Associate a value with a name in the dictionary +* +* Args: +* sName = the name to look for in the dictionary +* sValue = value to associate with sName +* bUnset = flag to indicate that value has not been set (no data for value in tag) +* +* Return: +* true if value associate with sName was changed, or false if no change +******************************************************************************* +*/ +bool CIccTagDict::Set(const CIccUTF16String &sName, const CIccUTF16String &sValue, bool bUnSet) +{ + CIccDictEntry *de = Get(sName); + + if (de) { + if (de->GetValue()==sValue && de->IsValueSet() && !bUnSet) + return false; + } + else { + de = new CIccDictEntry; + de->m_sName = sName; + + CIccDictEntryPtr ptr; + ptr.ptr = de; + m_Dict->push_back(ptr); + } + + if (sValue.Empty() && bUnSet) + de->UnsetValue(); + else + de->SetValue(sValue); + + return true; +} + +bool CIccTagDict::Set(const icUnicodeChar *szName, const icUnicodeChar *szValue) +{ + CIccUTF16String sName(szName); + + CIccUTF16String sValue; + + if (szValue) { + sValue = szValue; + + return Set(sName, sValue, false); + } + + return Set(sName, sValue, true); +} + +bool CIccTagDict::Set(const char *szName, const char *szValue) +{ + CIccUTF16String sName(szName); + CIccUTF16String sValue; + + if (szValue) { + sValue = szValue; + + return Set(sName, sValue, false); + } + + return Set(sName, sValue, true); +} + +bool CIccTagDict::SetNameLocalized(const CIccUTF16String &sName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccDictEntry *de = Get(sName); + + if (!de) { + de = new CIccDictEntry; + de->m_sName = sName; + + CIccDictEntryPtr ptr; + ptr.ptr = de; + m_Dict->push_back(ptr); + } + + return de->SetNameLocalized(pTag); +} + +bool CIccTagDict::SetNameLocalized(const icUnicodeChar *szName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccUTF16String sName(szName); + + return SetNameLocalized(sName, pTag); +} + +bool CIccTagDict::SetNameLocalized(const char *szName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccUTF16String sName(szName); + + return SetNameLocalized(sName, pTag); +} + +bool CIccTagDict::SetValueLocalized(const CIccUTF16String &sName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccDictEntry *de = Get(sName); + + if (!de) { + de = new CIccDictEntry; + de->m_sName = sName; + + CIccDictEntryPtr ptr; + ptr.ptr = de; + m_Dict->push_back(ptr); + } + + return de->SetValueLocalized(pTag); +} + +bool CIccTagDict::SetValueLocalized(const icUnicodeChar *szName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccUTF16String sName(szName); + + return SetValueLocalized(sName, pTag); +} + +bool CIccTagDict::SetValueLocalized(const char *szName, CIccTagMultiLocalizedUnicode *pTag) +{ + CIccUTF16String sName(szName); + + return SetValueLocalized(sName, pTag); +} + +#endif //ICC_UNSUPPORTED_TAG_DICT diff --git a/library/src/main/cpp/icc/IccTagDict.h b/library/src/main/cpp/icc/IccTagDict.h new file mode 100644 index 00000000..0eb47190 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagDict.h @@ -0,0 +1,229 @@ +/** @file +File: IccTagDictTag.h + +Contains: Header for implementation of CIccTagDict +and supporting classes + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Jun 26, 2009 +// Initial CIccDictTag prototype development +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCTAGSUBTAG_H +#define _ICCTAGSUBTAG_H + +#include "IccProfile.h" +#include "IccTag.h" +#include "IccTagFactory.h" +#include "IccUtil.h" +#include +#include +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + + +/** +**************************************************************************** +* Data Class: CIccDictEntry +* +* Purpose: Implementation of a dictionary entry with optional localization of +* name and value +***************************************************************************** +*/ +class ICCPROFLIB_API CIccDictEntry +{ +public: //member functions + CIccDictEntry(); + CIccDictEntry(const CIccDictEntry& IDE); + CIccDictEntry &operator=(const CIccDictEntry &IDE); + virtual ~CIccDictEntry(); + + void Describe(std::string &sDescription); + + icUInt32Number PosRecSize(); + + //Data + CIccUTF16String m_sName; + + const CIccUTF16String &GetValue() { return m_sValue; } + bool IsValueSet() { return m_bValueSet; } + + //GetNameLocalized and GetValueLocalized both give direct access to objects owned by the CIccDirEntry object + CIccTagMultiLocalizedUnicode* GetNameLocalized() { return m_pNameLocalized; } + CIccTagMultiLocalizedUnicode* GetValueLocalized() { return m_pValueLocalized; } + + void UnsetValue() { m_sValue.Clear(); m_bValueSet = false; } + bool SetValue(const CIccUTF16String &sValue); + + //SetNameLocalized and SetValueLocalized both transfer ownership of the argument to the CIccDirEntry object + //deleting access to previous object + bool SetNameLocalized(CIccTagMultiLocalizedUnicode *pNameLocalized); + bool SetValueLocalized(CIccTagMultiLocalizedUnicode *pValueLocalized); + +protected: + CIccUTF16String m_sValue; + bool m_bValueSet; + + CIccTagMultiLocalizedUnicode *m_pNameLocalized; + CIccTagMultiLocalizedUnicode *m_pValueLocalized; +}; + +class CIccDictEntryPtr +{ +public: + CIccDictEntry *ptr; +}; + +/** +**************************************************************************** +* List Class: CIccDictEntry +* +* Purpose: Dictionary is stored as a List of CIccDictEntry objects +***************************************************************************** +*/ +typedef std::list CIccNameValueDict; + +/** +**************************************************************************** +* Class: CIccTagDict +* +* Purpose: A name-value dictionary tag with optional localization +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagDict : public CIccTag +{ +public: + CIccTagDict(); + CIccTagDict(const CIccTagDict &dict); + CIccTagDict &operator=(const CIccTagDict &dict); + virtual CIccTag *NewCopy() const { return new CIccTagDict(*this);} + virtual ~CIccTagDict(); + + virtual icTagTypeSignature GetType() const { return icSigDictType; } + virtual const icChar *GetClassName() const { return "CIccTagDict"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + bool AreNamesUnique() const; + bool AreNamesNonzero() const; + + CIccDictEntry *Get(const char *szName) const; + CIccDictEntry *Get(const icUInt16Number *szName) const; + CIccDictEntry *Get(const CIccUTF16String &sName) const; + + CIccUTF16String GetValue(const char *szName, bool *bIsSet=NULL) const; + CIccUTF16String GetValue(const icUnicodeChar *szName, bool *bIsSet=NULL) const; + CIccUTF16String GetValue(const CIccUTF16String &sName, bool *bIsSet=NULL) const; + + CIccTagMultiLocalizedUnicode* GetNameLocalized(const CIccUTF16String &sName) const; + CIccTagMultiLocalizedUnicode* GetNameLocalized(const icUnicodeChar *szName) const; + CIccTagMultiLocalizedUnicode* GetNameLocalized(const char *szName) const; + + CIccTagMultiLocalizedUnicode* GetValueLocalized(const CIccUTF16String &sName) const; + CIccTagMultiLocalizedUnicode* GetValueLocalized(const icUnicodeChar *szName) const; + CIccTagMultiLocalizedUnicode* GetValueLocalized(const char *szName) const; + + bool Remove(const CIccUTF16String &sName); + bool Remove(const icUnicodeChar *szName); + bool Remove(const char *szName); + + bool Set(const char *szName, const char *szValue=NULL); + bool Set(const icUnicodeChar *szName, const icUnicodeChar *szValue=NULL); + bool Set(const CIccUTF16String &sName, const CIccUTF16String &sValue, bool bUnSet=false); + + bool SetNameLocalized(const char *szName, CIccTagMultiLocalizedUnicode *pTag); + bool SetNameLocalized(const icUnicodeChar *szName, CIccTagMultiLocalizedUnicode *pTag); + bool SetNameLocalized(const CIccUTF16String &sName, CIccTagMultiLocalizedUnicode *pTag); + + bool SetValueLocalized(const char *szName, CIccTagMultiLocalizedUnicode *pTag); + bool SetValueLocalized(const icUnicodeChar *szName, CIccTagMultiLocalizedUnicode *pTag); + bool SetValueLocalized(const CIccUTF16String &sName, CIccTagMultiLocalizedUnicode *pTag); + + CIccNameValueDict *m_Dict; + +protected: + bool m_bBadAlignment; + void Cleanup(); + icUInt32Number MaxPosRecSize(); + + icUInt32Number m_tagSize; + icUInt32Number m_tagStart; +}; + + +//CIccFloatTag support +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //_ICCTAGDICTTAG_H + diff --git a/library/src/main/cpp/icc/IccTagFactory.cpp b/library/src/main/cpp/icc/IccTagFactory.cpp new file mode 100644 index 00000000..62b3819e --- /dev/null +++ b/library/src/main/cpp/icc/IccTagFactory.cpp @@ -0,0 +1,579 @@ +/** @file + File: IccTagFactory.cpp + + Contains: Implementation of the CIccTag class and creation factories + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Oct 30, 2005 +// Added CICCTag Creation using factory support +// +////////////////////////////////////////////////////////////////////// + +#include "IccTag.h" +#include "IccTagFactory.h" +#include "IccUtil.h" +#include "IccProfile.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +CIccTag* CIccSpecTagFactory::CreateTag(icTagTypeSignature tagSig) +{ + switch(tagSig) { + case icSigSignatureType: + return new CIccTagSignature; + + case icSigTextType: + return new CIccTagText; + + case icSigXYZArrayType: + return new CIccTagXYZ; + + case icSigUInt8ArrayType: + return new CIccTagUInt8; + + case icSigUInt16ArrayType: + return new CIccTagUInt16; + + case icSigUInt32ArrayType: + return new CIccTagUInt32; + + case icSigUInt64ArrayType: + return new CIccTagUInt64; + + case icSigS15Fixed16ArrayType: + return new CIccTagS15Fixed16; + + case icSigU16Fixed16ArrayType: + return new CIccTagU16Fixed16; + + case icSigCurveType: + return new CIccTagCurve; + + case icSigMeasurementType: + return new CIccTagMeasurement; + + case icSigMultiLocalizedUnicodeType: + return new CIccTagMultiLocalizedUnicode; + + case icSigMultiProcessElementType: + return new CIccTagMultiProcessElement(); + + case icSigParametricCurveType: + return new CIccTagParametricCurve; + + case icSigLutAtoBType: + return new CIccTagLutAtoB; + + case icSigLutBtoAType: + return new CIccTagLutBtoA; + + case icSigLut16Type: + return new CIccTagLut16; + + case icSigLut8Type: + return new CIccTagLut8; + + case icSigTextDescriptionType: + return new CIccTagTextDescription; + + case icSigNamedColor2Type: + return new CIccTagNamedColor2; + + case icSigChromaticityType: + return new CIccTagChromaticity; + + case icSigDataType: + return new CIccTagData; + + case icSigDateTimeType: + return new CIccTagDateTime; + +#ifndef ICC_UNSUPPORTED_TAG_DICT + case icSigDictType: + return new CIccTagDict; +#endif + + case icSigColorantOrderType: + return new CIccTagColorantOrder; + + case icSigColorantTableType: + return new CIccTagColorantTable; + + case icSigViewingConditionsType: + return new CIccTagViewingConditions; + + case icSigProfileSequenceDescType: + return new CIccTagProfileSeqDesc; + + case icSigResponseCurveSet16Type: + return new CIccTagResponseCurveSet16; + + case icSigProfileSequceIdType: + return new CIccTagProfileSequenceId; + + case icSigScreeningType: + case icSigUcrBgType: + case icSigCrdInfoType: + + default: + return new CIccTagUnknown; + } +} + +const icChar* CIccSpecTagFactory::GetTagSigName(icTagSignature tagSig) +{ + switch (tagSig) { + case icSigAToB0Tag: + return "AToB0Tag"; + + case icSigAToB1Tag: + return "AToB1Tag"; + + case icSigAToB2Tag: + return "AToB2Tag"; + + case icSigBlueColorantTag: + return "blueColorantTag"; + + case icSigBlueTRCTag: + return "blueTRCTag"; + + case icSigBToA0Tag: + return "BToA0Tag"; + + case icSigBToA1Tag: + return "BToA1Tag"; + + case icSigBToA2Tag: + return "BToA2Tag"; + + case icSigBToD0Tag: + return "BToD0Tag"; + + case icSigBToD1Tag: + return "BToD1Tag"; + + case icSigBToD2Tag: + return "BToD2Tag"; + + case icSigBToD3Tag: + return "BToD3Tag"; + + case icSigCalibrationDateTimeTag: + return "calibrationDateTimeTag"; + + case icSigCharTargetTag: + return "charTargetTag"; + + case icSigChromaticityTag: + return "chromaticityTag"; + + case icSigCopyrightTag: + return "copyrightTag"; + + case icSigCrdInfoTag: + return "crdInfoTag"; + + case icSigDataTag: + return "dataTag"; + + case icSigDateTimeTag: + return "dateTimeTag"; + + case icSigDeviceMfgDescTag: + return "deviceMfgDescTag"; + + case icSigDeviceModelDescTag: + return "deviceModelDescTag"; + + case icSigMetaDataTag: + return "metaDataTag"; + + case icSigDToB0Tag: + return "DToB0Tag"; + + case icSigDToB1Tag: + return "DToB1Tag"; + + case icSigDToB2Tag: + return "DToB2Tag"; + + case icSigDToB3Tag: + return "DToB3Tag"; + + case icSigGamutTag: + return "gamutTag"; + + case icSigGrayTRCTag: + return "grayTRCTag"; + + case icSigGreenColorantTag: + return "greenColorantTag"; + + case icSigGreenTRCTag: + return "greenTRCTag"; + + case icSigLuminanceTag: + return "luminanceTag"; + + case icSigMeasurementTag: + return "measurementTag"; + + case icSigMediaBlackPointTag: + return "mediaBlackPointTag"; + + case icSigMediaWhitePointTag: + return "mediaWhitePointTag"; + + case icSigNamedColor2Tag: + return "namedColor2Tag"; + + case icSigPreview0Tag: + return "preview0Tag"; + + case icSigPreview1Tag: + return "preview1Tag"; + + case icSigPreview2Tag: + return "preview2Tag"; + + case icSigPrintConditionTag: + return "printConditionTag"; + + case icSigProfileDescriptionTag: + return "profileDescriptionTag"; + + case icSigProfileSequenceDescTag: + return "profileSequenceDescTag"; + + case icSigProfileSequceIdTag: + return "profileSequenceIdentifierTag"; + + case icSigPs2CRD0Tag: + return "ps2CRD0Tag"; + + case icSigPs2CRD1Tag: + return "ps2CRD1Tag"; + + case icSigPs2CRD2Tag: + return "ps2CRD2Tag"; + + case icSigPs2CRD3Tag: + return "ps2CRD3Tag"; + + case icSigPs2CSATag: + return "ps2CSATag"; + + case icSigPs2RenderingIntentTag: + return "ps2RenderingIntentTag"; + + case icSigRedColorantTag: + return "redColorantTag"; + + case icSigRedTRCTag: + return "redTRCTag"; + + case icSigScreeningDescTag: + return "screeningDescTag"; + + case icSigScreeningTag: + return "screeningTag"; + + case icSigTechnologyTag: + return "technologyTag"; + + case icSigUcrBgTag: + return "ucrBgTag"; + + case icSigViewingCondDescTag: + return "viewingCondDescTag"; + + case icSigViewingConditionsTag: + return "viewingConditionsTag"; + + case icSigColorantOrderTag: + return "colorantOrderTag"; + + case icSigColorantTableTag: + return "colorantTableTag"; + + case icSigChromaticAdaptationTag: + return "chromaticAdaptationTag"; + + case icSigColorantTableOutTag: + return "colorantTableOutTag"; + + case icSigOutputResponseTag: + return "outputResponseTag"; + + case icSigPerceptualRenderingIntentGamutTag: + return "perceptualRenderingIntentGamutTag"; + + case icSigSaturationRenderingIntentGamutTag: + return "saturationRenderingIntentGamutTag"; + + default: + return NULL; + } + return NULL; +} + +const icChar* CIccSpecTagFactory::GetTagTypeSigName(icTagTypeSignature tagSig) +{ + switch (tagSig) { + case icSigChromaticityType: + return "chromaticityType"; + + case icSigColorantOrderType: + return "colorantOrderType"; + + case icSigColorantTableType: + return "colorantTableType"; + + case icSigCrdInfoType: + return "crdInfoType"; + + case icSigCurveType: + return "curveType"; + + case icSigDataType: + return "dataType"; + + case icSigDateTimeType: + return "dateTimeType"; + + case icSigDeviceSettingsType: + return "deviceSettingsType"; + + case icSigDictType: + return "dictType"; + + case icSigLut16Type: + return "lut16Type"; + + case icSigLut8Type: + return "lut8Type"; + + case icSigLutAtoBType: + return "lutAtoBType"; + + case icSigLutBtoAType: + return "lutBtoAType"; + + case icSigMeasurementType: + return "measurementType"; + + case icSigMultiLocalizedUnicodeType: + return "multiLocalizedUnicodeType"; + + case icSigMultiProcessElementType: + return "multiProcessElementType"; + + case icSigNamedColor2Type: + return "namedColor2Type"; + + case icSigParametricCurveType: + return "parametricCurveType"; + + case icSigResponseCurveSet16Type: + return "responseCurveSet16Type"; + + case icSigProfileSequenceDescType: + return "profileSequenceDescType"; + + case icSigS15Fixed16ArrayType: + return "s15Fixed16 ArrayType"; + + case icSigScreeningType: + return "screeningType"; + + case icSigSignatureType: + return "signatureType"; + + case icSigTextType: + return "textType"; + + case icSigTextDescriptionType: + return "textDescriptionType"; + + case icSigU16Fixed16ArrayType: + return "u16Fixed16 Type"; + + case icSigUcrBgType: + return "ucrBgType"; + + case icSigUInt16ArrayType: + return "uInt16 Type"; + + case icSigUInt32ArrayType: + return "uInt32 Type"; + + case icSigUInt64ArrayType: + return "uInt64 Type"; + + case icSigUInt8ArrayType: + return "uInt8 Type"; + + case icSigViewingConditionsType: + return "viewingConditionsType"; + + case icSigXYZArrayType: + return "XYZ Type"; + + case icSigProfileSequceIdType: + return "profileSequenceIdentifierType"; + + default: + return NULL; + } + + return NULL; +} + +std::auto_ptr CIccTagCreator::theTagCreator; + +CIccTagCreator::~CIccTagCreator() +{ + IIccTagFactory *pFactory = DoPopFactory(true); + + while (pFactory) { + delete pFactory; + pFactory = DoPopFactory(true); + } +} + +CIccTagCreator* CIccTagCreator::GetInstance() +{ + if (!theTagCreator.get()) { + theTagCreator = CIccTagCreatorPtr(new CIccTagCreator); + + theTagCreator->DoPushFactory(new CIccSpecTagFactory); + } + + return theTagCreator.get(); +} + +CIccTag* CIccTagCreator::DoCreateTag(icTagTypeSignature tagTypeSig) +{ + CIccTagFactoryList::iterator i; + CIccTag *rv = NULL; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + rv = (*i)->CreateTag(tagTypeSig); + if (rv) + break; + } + return rv; +} + +const icChar* CIccTagCreator::DoGetTagSigName(icTagSignature tagSig) +{ + CIccTagFactoryList::iterator i; + const icChar* rv; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + rv = (*i)->GetTagSigName(tagSig); + if (rv) + return rv; + } + + return NULL; +} + +const icChar* CIccTagCreator::DoGetTagTypeSigName(icTagTypeSignature tagTypeSig) +{ + CIccTagFactoryList::iterator i; + const icChar* rv; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + rv = (*i)->GetTagTypeSigName(tagTypeSig); + if (rv) + return rv; + } + + return NULL; +} + +void CIccTagCreator::DoPushFactory(IIccTagFactory *pFactory) +{ + factoryStack.push_front(pFactory); +} + +IIccTagFactory* CIccTagCreator::DoPopFactory(bool bAll /*=false*/) +{ +// int nNum = (bAll ? 0 : 1); + + if (factoryStack.size()>0) { + CIccTagFactoryList::iterator i=factoryStack.begin(); + IIccTagFactory* rv = (*i); + factoryStack.pop_front(); + return rv; + } + return NULL; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccTagFactory.h b/library/src/main/cpp/icc/IccTagFactory.h new file mode 100644 index 00000000..e9e4c2fb --- /dev/null +++ b/library/src/main/cpp/icc/IccTagFactory.h @@ -0,0 +1,322 @@ +/** @file + File: IccTagFactory.h + + Contains: Header for implementation of CIccTagFactory class and + creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2005-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Oct 30, 2005 +// A CIccTagCreator singleton class has been added to provide general +// support for dynamically creating tag classes using a tag signature. +// Prototype and private tag type support can be added to the system +// by pushing additional IIccTagFactory based objects to the +// singleton CIccTagCreator object. +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCTAGFACTORY_H +#define _ICCTAGFACTORY_H + +#include "IccDefs.h" +#include +#include +#include + +//CIccTag factory support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class CIccTag; + +/** + *********************************************************************** + * Class: IIccTagFactory + * + * Purpose: + * IIccTagFactory is a factory pattern interface for CIccTag creation. + * This class is pure virtual. + *********************************************************************** + */ +class IIccTagFactory +{ +public: + virtual ~IIccTagFactory() {} + + /** + * Function: CreateTag(tagTypeSig) + * Create a tag of type tagTypeSig. + * + * Parameter(s): + * tagTypeSig = signature of the ICC tag type for the tag to be created + * + * Returns a new CIccTag object of the given signature type. If the tag + * factory doesn't support creation of tags of type tagTypeSig then it + * should return NULL. + */ + virtual CIccTag* CreateTag(icTagTypeSignature tagTypeSig)=0; + + /** + * Function: GetTagSigName(tagSig) + * Get display name of tagSig. + * + * Parameter(s): + * tagSig = signature of the ICC tag to get a name for + * + * Returns pointer to string containing name of tag if tag is recognized + * by the factory, NULL if the factory doesn't create tagSig tags. + */ + virtual const icChar* GetTagSigName(icTagSignature tagSig)=0; + + /** + * Function: GetTagTypeSigName(tagTypeSig) + * Get display name of tagTypeSig. + * + * Parameter(s): + * tagTypeSig = signature of the ICC tag type to get a name for + * + * Returns pointer to string containing name of tag type if tag is recognized + * by the factory, NULL if the factory doesn't create tagTypeSig tags. + */ + virtual const icChar* GetTagTypeSigName(icTagTypeSignature tagTypeSig)=0; +}; + + +//A CIccTagFactoryList is used by CIccTagCreator to keep track of tag +//creation factories +typedef std::list CIccTagFactoryList; + + +/** + *********************************************************************** + * Class: CIccSpecTagFactory + * + * Purpose: + * CIccSpecTagFactory provides creation of CIccTag's defined by the ICC profile + * specification. The CIccTagCreator always creates a CIccSpecTagFactory. + *********************************************************************** + */ +class CIccSpecTagFactory : public IIccTagFactory +{ +public: + /** + * Function: CreateTag(tagTypeSig) + * Create a tag of type tagTypeSig. + * + * Parameter(s): + * tagTypeSig = signature of the ICC tag type for the tag to be created + * + * Returns a new CIccTag object of the given signature type. + * Unrecognized tagTypeSig's will be created as a CIccTagUnknown object. + */ + virtual CIccTag* CreateTag(icTagTypeSignature tagSig); + + /** + * Function: GetTagSigName(tagSig) + * Get display name of tagSig. + * + * Parameter(s): + * tagName = string to put tag name into, + * tagSig = signature of the ICC tag type to get a name for + * + * Returns pointer to string containing name of tag if tag is recognized + * by the factory, NULL if the factory doesn't create tagSig tags. + */ + virtual const icChar* GetTagSigName(icTagSignature tagSig); + + /** + * Function: GetTagTypeSigName(tagTypeSig) + * Get display name of tagTypeSig. + * + * Parameter(s): + * tagName = string to put tag name into, + * tagTypeSig = signature of the ICC tag type to get a name for + * + * Returns pointer to string containing name of tag type if tag is recognized + * by the factory, NULL if the factory doesn't create tagTypeSig tags. + */ + virtual const icChar* GetTagTypeSigName(icTagTypeSignature tagTypeSig); +}; + +class CIccTagCreator; + +typedef std::auto_ptr CIccTagCreatorPtr; + +/** + *********************************************************************** + * Class: CIccTagCreator + * + * Purpose: + * CIccTagCreator uses a singleton pattern to provide dynamically + * upgradeable CIccTag derived object creation based on tag signature. + *********************************************************************** + */ +class CIccTagCreator +{ +public: + ~CIccTagCreator(); + + /** + * Function: CreateTag(tagTypeSig) + * Create a tag of type tagTypeSig. + * + * Parameter(s): + * tagTypeSig = signature of the ICC tag type for the tag to be created + * + * Returns a new CIccTag object of the given signature type. + * Each factory in the factoryStack is used until a factory supports the + * signature type. + */ + static CIccTag* CreateTag(icTagTypeSignature tagTypeSig) + { return CIccTagCreator::GetInstance()->DoCreateTag(tagTypeSig); } + + /** + * Function: GetTagSigName(tagSig) + * Get display name of tagSig. + * + * Parameter(s): + * tagSig = signature of the ICC tag to get a name for + * + * Returns ptr to string containing name of tag type if it is recognized + * by any factory, NULL if all factories do not create tagTypeSig tags. + */ + static const icChar* GetTagSigName(icTagSignature tagTypeSig) + { return CIccTagCreator::GetInstance()->DoGetTagSigName(tagTypeSig); } + + + /** + * Function: GetTagTypeSigName(tagTypeSig) + * Get display name of tagTypeSig. + * + * Parameter(s): + * tagTypeSig = signature of the ICC tag type to get a name for + * + * Returns ptr to string containing name of tag type if it is recognized by + * any factory, NULL if all factories do not create tagTypeSig tags. + */ + static const icChar* GetTagTypeSigName(icTagTypeSignature tagTypeSig) + { return CIccTagCreator::GetInstance()->DoGetTagTypeSigName(tagTypeSig); } + + /** + * Function: PushFactory(pFactory) + * Add an IIccTagFactory to the stack of tag factories tracked by the system. + * + * Parameter(s): + * pFactory = pointer to an IIccTagFactory object to add to the system. + * The pFactory must be created with new, and will be owned CIccTagCreator + * until popped off the stack using PopFactory(). Any factories not + * popped off will be taken care of properly on application shutdown. + * + */ + static void PushFactory(IIccTagFactory *pFactory) + { CIccTagCreator::GetInstance()->CIccTagCreator::DoPushFactory(pFactory); } + + /** + * Function: PopFactory() + * Remove the top IIccTagFactory from the stack of tag factories tracked by the system. + * + * Parameter(s): + * None + * + * Returns the top IIccTagFactory from the stack of tag factories tracked by the system. + * The returned tag factory is no longer owned by the system and needs to be deleted + * to avoid memory leaks. + * + * Note: The initial CIccSpecTagFactory cannot be popped off the stack. + */ + static IIccTagFactory* PopFactory() + { return CIccTagCreator::GetInstance()->DoPopFactory(); } + +private: + /**Only GetInstance() can create the signleton*/ + CIccTagCreator() { } + + /** + * Function: GetInstance() + * Private static function to access singleton CiccTagCreator Object. + * + * Parameter(s): + * None + * + * Returns the singleton CIccTagCreator object. It will allocate + * a new one and push a single CIccSpecTag Factory object onto the factory + * stack if the singleton has not been intialized. + */ + static CIccTagCreator* GetInstance(); + + CIccTag* DoCreateTag(icTagTypeSignature tagTypeSig); + const icChar *DoGetTagSigName(icTagSignature tagSig); + const icChar *DoGetTagTypeSigName(icTagTypeSignature tagTypeSig); + void DoPushFactory(IIccTagFactory *pFactory); + IIccTagFactory* DoPopFactory(bool bAll=false); + + static CIccTagCreatorPtr theTagCreator; + + CIccTagFactoryList factoryStack; +}; + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif //_ICCTAGFACTORY_H diff --git a/library/src/main/cpp/icc/IccTagLut.cpp b/library/src/main/cpp/icc/IccTagLut.cpp new file mode 100644 index 00000000..1aa7b313 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagLut.cpp @@ -0,0 +1,4842 @@ +/** @file + File: IccTagLut.cpp + + Contains: Implementation of the Lut Tag classes + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +// -Moved LUT tags to separate file 4-30-2005 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) + #pragma warning( disable: 4786) //disable warning in + #include +#endif +#include +#include +#include +#include +#include "IccTag.h" +#include "IccUtil.h" +#include "IccProfile.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** +**************************************************************************** +* Name: CIccCurve::Find +* +* Purpose: Read in the tag contents into a data block +* +* Args: +* v = index to be searched, +* v0 = index less than/equal to v, +* p0 = the value at index v0, +* v1 = index greater than/equal to v, +* p1 = value at index v1 +* +* Return: The value at the requested index +* +***************************************************************************** +*/ +icFloatNumber CIccCurve::Find(icFloatNumber v, + icFloatNumber p0, icFloatNumber v0, + icFloatNumber p1, icFloatNumber v1) +{ + if (v<=v0) + return p0; + if (v>=v1) + return p1; + + if (p1-p0 <= 0.00001) { + icFloatNumber d0 = fabs(v-v0); + icFloatNumber d1 = fabs(v1-v); + + if (d00) + m_Curve = (icFloatNumber*)calloc(nSize, sizeof(icFloatNumber)); + else + m_Curve = NULL; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::CIccTagCurve +* +* Purpose: Copy Constructor +* +* Args: +* ITCurve = The CIccTagCurve object to be copied +***************************************************************************** +*/ +CIccTagCurve::CIccTagCurve(const CIccTagCurve &ITCurve) +{ + m_nSize = ITCurve.m_nSize; + m_nMaxIndex = ITCurve.m_nMaxIndex; + + m_Curve = (icFloatNumber*)calloc(m_nSize, sizeof(icFloatNumber)); + memcpy(m_Curve, ITCurve.m_Curve, m_nSize*sizeof(icFloatNumber)); +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::operator= +* +* Purpose: Copy Operator +* +* Args: +* CurveTag = The CIccTagCurve object to be copied +***************************************************************************** +*/ +CIccTagCurve &CIccTagCurve::operator=(const CIccTagCurve &CurveTag) +{ + if (&CurveTag == this) + return *this; + + m_nSize = CurveTag.m_nSize; + m_nMaxIndex = CurveTag.m_nMaxIndex; + + if (m_Curve) + free(m_Curve); + m_Curve = (icFloatNumber*)calloc(m_nSize, sizeof(icFloatNumber)); + memcpy(m_Curve, CurveTag.m_Curve, m_nSize*sizeof(icFloatNumber)); + + return *this; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::~CIccTagCurve +* +* Purpose: Destructor +* +***************************************************************************** +*/ +CIccTagCurve::~CIccTagCurve() +{ + if (m_Curve) + free(m_Curve); +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::Read +* +* Purpose: Read in the tag contents into a data block +* +* Args: +* size - # of bytes in tag, +* pIO - IO object to read tag from +* +* Return: +* true = successful, false = failure +***************************************************************************** +*/ +bool CIccTagCurve::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number) > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number nSize; + + if (!pIO->Read32(&nSize)) + return false; + + SetSize(nSize, icInitNone); + + if (m_nSize) { + if (pIO->Read16Float(m_Curve, m_nSize)!=(icInt32Number)m_nSize) + return false; + } + + return true; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::Write +* +* Purpose: Write the tag to a file +* +* Args: +* pIO - The IO object to write tag to. +* +* Return: +* true = succesful, false = failure +***************************************************************************** +*/ +bool CIccTagCurve::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write32(&m_nSize)) + return false; + + if (m_nSize) + if (pIO->Write16Float(m_Curve, m_nSize)!=(icInt32Number)m_nSize) + return false; + + pIO->Align32(); + + return true; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::Describe +* +* Purpose: Dump data associated with the tag to a string +* +* Args: +* sDescription - string to concatenate tag dump to +***************************************************************************** +*/ +void CIccTagCurve::Describe(std::string &sDescription) +{ + icChar buf[128], *ptr; + + if (!m_nSize) { + sprintf(buf, "BEGIN_CURVE In_Out\r\n"); + sDescription += buf; + sDescription += "Y = X\r\n"; + } + else if (m_nSize==1) { + icFloatNumber dGamma = (icFloatNumber)(m_Curve[0] * 256.0); + sprintf(buf, "BEGIN_CURVE In_Out\r\n"); + sDescription += buf; + sprintf(buf, "Y = X ^ %.4lf\r\n", dGamma); + sDescription += buf; + } + else { + int i; + + sprintf(buf, "BEGIN_LUT In_Out 1 1\r\n"); + sDescription += buf; + sDescription += "IN OUT\r\n"; + + for (i=0; i<(int)m_nSize; i++) { + ptr = buf; + + icColorValue(buf, (icFloatNumber)i/(m_nSize-1), icSigMCH1Data, 1); + ptr += strlen(buf); + + strcpy(ptr, " "); + ptr ++; + + icColorValue(ptr, m_Curve[i], icSigMCH1Data, 1); + + ptr += strlen(ptr); + + strcpy(ptr, "\r\n"); + + sDescription += buf; + } + } + sDescription += "\r\n"; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::DumpLut +* +* Purpose: Dump data associated with the tag to a string. Basically has +* the same function as Describe() +* +* Args: +* sDescription = string to concatenate tag dump to, +* szName = name of the curve to be printed, +* csSig = color space signature of the LUT data, +* nIndex = the channel number of color space +***************************************************************************** +*/ +void CIccTagCurve::DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csSig, int nIndex) +{ + icChar buf[128], *ptr; + + if (!m_nSize) { + sprintf(buf, "BEGIN_CURVE %s\r\n", szName); + sDescription += buf; + sDescription += "Y = X\r\n"; + } + else if (m_nSize==1) { + icFloatNumber dGamma = (icFloatNumber)(m_Curve[0] * 256.0); + sprintf(buf, "BEGIN_CURVE %s\r\n", szName); + sDescription += buf; + sprintf(buf, "Y = X ^ %.4lf\r\n", dGamma); + sDescription += buf; + } + else { + int i; + + sprintf(buf, "BEGIN_LUT %s 1 1\r\n", szName); + sDescription += buf; + sDescription += "IN OUT\r\n"; + + sDescription.reserve(sDescription.size() + m_nSize * 20); + + for (i=0; i<(int)m_nSize; i++) { + ptr = buf; + + icColorValue(buf, (icFloatNumber)i/(m_nSize-1), csSig, nIndex); + ptr += strlen(buf); + + strcpy(ptr, " "); + ptr ++; + + icColorValue(ptr, m_Curve[i], csSig, nIndex); + + ptr += strlen(ptr); + + strcpy(ptr, "\r\n"); + + sDescription += buf; + } + } + sDescription += "\r\n"; +} + + +/** +**************************************************************************** +* Name: CIccTagCurve::SetSize +* +* Purpose: Sets the size of the curve array. +* +* Args: +* nSize - number of entries in the curve, +* nSizeOpt - flag to zero newly formed values +***************************************************************************** +*/ +void CIccTagCurve::SetSize(icUInt32Number nSize, icTagCurveSizeInit nSizeOpt/*=icInitZero*/) +{ + if (nSize==m_nSize) + return; + + if (!nSize && m_Curve) { + free(m_Curve); + m_Curve = NULL; + } + else { + if (!m_Curve) + m_Curve = (icFloatNumber*)malloc(nSize*sizeof(icFloatNumber)); + else + m_Curve = (icFloatNumber*)realloc(m_Curve, nSize*sizeof(icFloatNumber)); + + switch (nSizeOpt) { + case icInitNone: + default: + break; + + case icInitZero: + if (m_nSize < nSize) { + memset(&m_Curve[m_nSize], 0, (nSize-m_nSize)*sizeof(icFloatNumber)); + } + break; + + case icInitIdentity: + if (nSize>1) { + icUInt32Number i; + icFloatNumber last = (icFloatNumber)(nSize-1); + + for (i=0; i(1.0-VERYSMALLNUM) && num<(1.0+VERYSMALLNUM)); +} + +/** +**************************************************************************** +* Name: CIccTagCurve::IsIdentity +* +* Purpose: Checks if this is an identity curve. +* +* Return: true if the curve is an identity +* +***************************************************************************** +*/ +bool CIccTagCurve::IsIdentity() +{ + if (!m_nSize) { + return true; + } + + if (m_nSize==1) { + return IsUnity(icFloatNumber(m_Curve[0]*65535.0/256.0)); + } + + icUInt32Number i; + for (i=0; iVERYSMALLNUM) { + return false; + } + } + + return true; +} + +/** +**************************************************************************** +* Name: CIccTagCurve::Apply +* +* Purpose: Applies the curve to the value passed. +* +* Args: +* v = value to be passed through the curve. +* +* Return: The value modified by the curve. +* +***************************************************************************** +*/ +icFloatNumber CIccTagCurve::Apply(icFloatNumber v) +{ + if(v<0.0) v = 0.0; + else if(v>1.0) v = 1.0; + + icUInt32Number nIndex = (icUInt32Number)(v * m_nMaxIndex); + + if (!m_nSize) { + return v; + } + if (m_nSize==1) { + //Convert 0.0 to 1.0 float to 16bit and then convert from u8Fixed8Number + icFloatNumber dGamma = (icFloatNumber)(m_Curve[0] * 65535.0 / 256.0); + return pow(v, dGamma); + } + if (nIndex == m_nMaxIndex) { + return m_Curve[nIndex]; + } + else { + icFloatNumber nDif = v*m_nMaxIndex - nIndex; + icFloatNumber p0 = m_Curve[nIndex]; + + icFloatNumber rv = p0 + (m_Curve[nIndex+1]-p0)*nDif; + if (rv>1.0) + rv=1.0; + + return rv; + } +} + + +/** +****************************************************************************** +* Name: CIccTagCurve::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagCurve::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (sig==icSigBlueTRCTag || sig==icSigRedTRCTag || sig==icSigGreenTRCTag || sig==icSigGrayTRCTag) { + if (m_nSize>1) { + if (m_Curve) { + if (m_Curve[0]>0.0 || m_Curve[m_nSize-1]<1.0) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Curve cannot be accurately inverted.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + } + } + + return rv; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::CIccTagParametricCurve +* +* Purpose: Constructor +* +***************************************************************************** +*/ +CIccTagParametricCurve::CIccTagParametricCurve() +{ + m_nFunctionType = 0xffff; + m_nNumParam = 0; + m_dParam = NULL; + m_nReserved2 = 0; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::CIccTagParametricCurve +* +* Purpose: Copy Constructor +* +* Args: +* ITPC = The CIccTagParametricCurve object to be copied +***************************************************************************** +*/ +CIccTagParametricCurve::CIccTagParametricCurve(const CIccTagParametricCurve &ITPC) +{ + m_nFunctionType = ITPC.m_nFunctionType; + m_nNumParam = ITPC.m_nNumParam; + + m_dParam = new icFloatNumber[m_nNumParam]; + memcpy(m_dParam, ITPC.m_dParam, m_nNumParam*sizeof(icFloatNumber)); +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::operator= +* +* Purpose: Copy Operator +* +* Args: +* ParamCurveTag = The CIccTagParametricCurve object to be copied +***************************************************************************** +*/ +CIccTagParametricCurve &CIccTagParametricCurve::operator=(const CIccTagParametricCurve &ParamCurveTag) +{ + if (&ParamCurveTag == this) + return *this; + + m_nFunctionType = ParamCurveTag.m_nFunctionType; + m_nNumParam = ParamCurveTag.m_nNumParam; + + if (m_dParam) + delete [] m_dParam; + m_dParam = new icFloatNumber[m_nNumParam]; + memcpy(m_dParam, ParamCurveTag.m_dParam, m_nNumParam*sizeof(icFloatNumber)); + + return *this; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::~CIccTagParametricCurve +* +* Purpose: Destructor +* +***************************************************************************** +*/ +CIccTagParametricCurve::~CIccTagParametricCurve() +{ + if (m_dParam) + delete [] m_dParam; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::Read +* +* Purpose: Read in the tag contents into a data block +* +* Args: +* size - # of bytes in tag, +* pIO - IO object to read tag from +* +* Return: +* true = successful, false = failure +***************************************************************************** +*/ +bool CIccTagParametricCurve::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt16Number nFunctionType; + + icUInt32Number nHdrSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + 2*sizeof(icUInt16Number); + + if ( nHdrSize > size) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read16(&nFunctionType) || + !pIO->Read16(&m_nReserved2)) + return false; + + SetFunctionType(nFunctionType); + + if (!m_nNumParam) { + m_nNumParam = (icUInt16Number)((size-nHdrSize) / sizeof(icS15Fixed16Number)); + m_dParam = new icFloatNumber[m_nNumParam]; + } + + if (m_nNumParam) { + int i; + if (nHdrSize + m_nNumParam*sizeof(icS15Fixed16Number) > size) + return false; + + for (i=0; iRead32(&num, 1)) + return false; + m_dParam[i]=icFtoD(num); + } + } + + return true; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::Write +* +* Purpose: Write the tag to a file +* +* Args: +* pIO - The IO object to write tag to. +* +* Return: +* true = succesful, false = failure +***************************************************************************** +*/ +bool CIccTagParametricCurve::Write(CIccIO *pIO) +{ + icTagTypeSignature sig; + + if (!pIO) { + return false; + } + + sig = GetType(); + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write16(&m_nFunctionType) || + !pIO->Write16(&m_nReserved2)) + return false; + + if (m_nNumParam) { + int i; + for (i=0; iWrite32(&num, 1)) + return false; + } + } + + if (!pIO->Align32()) + return false; + + return true; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::Describe +* +* Purpose: Dump data associated with the tag to a string +* +* Args: +* sDescription - string to concatenate tag dump to +***************************************************************************** +*/ +void CIccTagParametricCurve::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sprintf(buf, "FunctionType: %04Xh\r\n", m_nFunctionType); + sDescription += buf; + + switch(m_nFunctionType) { +case 0x0000: + sprintf(buf, "Y = X ^ %.4lf\r\n", m_dParam[0]); + sDescription += buf; + return; + +case 0x0001: + sprintf(buf, "Y = 0 when (X < %.4lf / %.4lf)\r\n", + -m_dParam[2], m_dParam[1]); + sDescription += buf; + + sprintf(buf, "Y = (%.4lf * X + %.4lf) ^ %.4lf when (X >= %.4lf / %.4lf)\r\n", + m_dParam[1], m_dParam[2], m_dParam[0], + m_dParam[2], m_dParam[1]); + sDescription += buf; + return; + +case 0x0002: + sprintf(buf, "Y = %.4lf when (X < %.4lf / %.4lf)\r\n", m_dParam[3], + -m_dParam[2], m_dParam[1]); + sDescription += buf; + + sprintf(buf, "Y = (%.4lf * X + %.4lf) ^ %.4lf + %.4lf when (X >= %.4lf / %.4lf)\r\n", + m_dParam[1], m_dParam[2], m_dParam[0], + m_dParam[3], + -m_dParam[2], m_dParam[1]); + sDescription += buf; + return; + +case 0x0003: + sprintf(buf, "Y = %lf * X when (X < %.4lf)\r\n", + m_dParam[3], m_dParam[4]); + sDescription += buf; + + sprintf(buf, "Y = (%.4lf * X + %.4lf) ^ %.4lf when (X >= %.4lf)\r\n", + m_dParam[1], m_dParam[2], m_dParam[0], + m_dParam[4]); + sDescription += buf; + return; + +case 0x0004: + sprintf(buf, "Y = %lf * X + %.4lf when (X < %.4lf)\r\n", + m_dParam[3], m_dParam[6], m_dParam[4]); + sDescription += buf; + + sprintf(buf, "Y = (%.4lf * X + %.4lf) ^ %.4lf + %.4lf when (X >= %.4lf)\r\n", + m_dParam[1], m_dParam[2], m_dParam[0], + m_dParam[5], m_dParam[4]); + sDescription += buf; + return; + +default: + int i; + sprintf(buf, "Unknown Function with %d parameters:\r\n", m_nNumParam); + sDescription += buf; + + for (i=0; i= -b/a) { + return (icFloatNumber)pow((double)a*X + b, (double)m_dParam[0]); + } + else { + return 0; + } + + case 0x0002: + a=m_dParam[1]; + b=m_dParam[2]; + + if (X >= -b/a) { + return (icFloatNumber)pow((double)a*X + b, (double)m_dParam[0]) + m_dParam[3]; + } + else { + return m_dParam[3]; + } + + case 0x0003: + if (X >= m_dParam[4]) { + return (icFloatNumber)pow((double)m_dParam[1]*X + m_dParam[2], (double)m_dParam[0]); + } + else { + return m_dParam[3]*X; + } + + case 0x0004: + if (X >= m_dParam[4]) { + return (icFloatNumber)pow((double)m_dParam[1]*X + m_dParam[2], (double)m_dParam[0]) + m_dParam[5]; + } + else { + return m_dParam[3]*X + m_dParam[6]; + } + + default: + return X; + } +} + + +/** +****************************************************************************** +* Name: CIccTagParametricCurve::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagParametricCurve::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (m_nReserved2!=0) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Reserved Value must be zero.\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + switch(m_nFunctionType) { +case 0x0000: + if (m_nNumParam!=1) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of parameters inconsistent with function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + +case 0x0001: + if (m_nNumParam!=3) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of parameters inconsistent with function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + +case 0x0002: + if (m_nNumParam!=4) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of parameters inconsistent with function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + +case 0x0003: + if (m_nNumParam!=5) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of parameters inconsistent with function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + +case 0x0004: + if (m_nNumParam!=7) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Number of parameters inconsistent with function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + break; + +default: + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Unknown function type.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + if (sig==icSigBlueTRCTag || sig==icSigRedTRCTag || sig==icSigGreenTRCTag || sig==icSigGrayTRCTag) { + icFloatNumber lval = DoApply(0.0); + icFloatNumber uval = DoApply(1.0); + if (lval>0.0 || uval<1.0) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Curve cannot be accurately inverted.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + + return rv; +} + +/** +**************************************************************************** +* Name: CIccMatrix::CIccMatrix +* +* Purpose: Constructor +* +* Args: +* bUseConstants = true if the matrix contains additional row for constants +***************************************************************************** +*/ +CIccMatrix::CIccMatrix(bool bUseConstants/*=true*/) +{ + m_bUseConstants = bUseConstants; + m_e[0] = m_e[4] = m_e[8] = 1.0; + m_e[1] = m_e[2] = m_e[3] = + m_e[5] = m_e[6] = m_e[7] = 0.0; + + if (!m_bUseConstants) { + m_e[9] = m_e[10] = m_e[11] = 0.0; + } +} + + +/** +**************************************************************************** +* Name: CIccMatrix::CIccMatrix +* +* Purpose: Copy Constructor +* +* Args: +* MatrixClass = The CIccMatrix object to be copied +***************************************************************************** +*/ +CIccMatrix::CIccMatrix(const CIccMatrix &MatrixClass) +{ + m_bUseConstants = MatrixClass.m_bUseConstants; + memcpy(m_e, MatrixClass.m_e, sizeof(m_e)); +} + + +/** +**************************************************************************** +* Name: CIccMatrix::operator= +* +* Purpose: Copy Operator +* +* Args: +* MatrixClass = The CIccMatrix object to be copied +***************************************************************************** +*/ +CIccMatrix &CIccMatrix::operator=(const CIccMatrix &MatrixClass) +{ + if (&MatrixClass == this) + return *this; + + m_bUseConstants = MatrixClass.m_bUseConstants; + memcpy(m_e, MatrixClass.m_e, sizeof(m_e)); + + return *this; +} + + +/** +**************************************************************************** +* Name: CIccTagParametricCurve::DumpLut +* +* Purpose: Dump the matrix data to a string. +* +* Args: +* sDescription = string to concatenate tag dump to, +* szName = name of the curve to be printed +***************************************************************************** +*/ +void CIccMatrix::DumpLut(std::string &sDescription, const icChar *szName) +{ + icChar buf[128]; + + sprintf(buf, "BEGIN_MATRIX %s\r\n", szName); + sDescription += buf; + + if (!m_bUseConstants) { + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", + m_e[0], m_e[1], m_e[2]); + sDescription += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", + m_e[3], m_e[4], m_e[5]); + sDescription += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", + m_e[6], m_e[7], m_e[8]); + sDescription += buf; + } + else { + sprintf(buf, "%8.4lf %8.4lf %8.4lf + %8.4lf\r\n", + m_e[0], m_e[1], m_e[2], m_e[9]); + sDescription += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf + %8.4lf\r\n", + m_e[3], m_e[4], m_e[5], m_e[10]); + sDescription += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf + %8.4lf\r\n", + m_e[6], m_e[7], m_e[8], m_e[11]); + sDescription += buf; + } + sDescription += "\r\n"; +} + +/** +**************************************************************************** +* Name: CIccMatrix::IsIdentity +* +* Purpose: Checks if the matrix is identity +* +* Return: +* true if matrix is identity and uses no constants, else false +* +***************************************************************************** +*/ +bool CIccMatrix::IsIdentity() +{ + if (m_bUseConstants) { + if (fabs(m_e[9])>0.0 || fabs(m_e[10])>0.0 || fabs(m_e[11])>0.0) { + return false; + } + } + + if (!IsUnity(m_e[0]) || !IsUnity(m_e[4]) || !IsUnity(m_e[8])) { + return false; + } + + if (fabs(m_e[1])>0.0 || fabs(m_e[2])>0.0 || fabs(m_e[3])>0.0 || + fabs(m_e[5])>0.0 || fabs(m_e[6])>0.0 || fabs(m_e[7])>0.0) + { + return false; + } + + return true; +} + +/** +**************************************************************************** +* Name: CIccMatrix::Apply +* +* Purpose: Multiplies the pixel by the matrix. +* +* Args: +* Pixel = Pixel to be multiplied by the matrix +* +***************************************************************************** +*/ +void CIccMatrix::Apply(icFloatNumber *Pixel) const +{ + icFloatNumber a=Pixel[0]; + icFloatNumber b=Pixel[1]; + icFloatNumber c=Pixel[2]; + + icFloatNumber x = m_e[0]*a + m_e[1]*b + m_e[2]*c; + icFloatNumber y = m_e[3]*a + m_e[4]*b + m_e[5]*c; + icFloatNumber z = m_e[6]*a + m_e[7]*b + m_e[8]*c; + + if (m_bUseConstants) { + x += m_e[9]; + y += m_e[10]; + z += m_e[11]; + } + + Pixel[0] = x; + Pixel[1] = y; + Pixel[2] = z; +} + + +/** +****************************************************************************** +* Name: CIccMatrix::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccMatrix::Validate(icTagTypeSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = icValidateOK; + + if (sig==icSigLut8Type || sig==icSigLut16Type) { + if (pProfile->m_Header.pcs!=icSigXYZData) { + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + icFloatNumber sum=0.0; + for (int i=0; i<9; i++) { + sum += m_e[i]; + } + if (m_e[0]!=1.0 || m_e[4]!=1.0 || m_e[9]!=1.0 || sum!=3.0) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Matrix must be identity.\r\n"; + rv = icValidateNonCompliant; + } + } + } + + return rv; +} + + +static icFloatNumber ClutUnitClip(icFloatNumber v) +{ + if (v<0) + return 0; + else if (v>1.0) + return 1.0; + + return v; +} + +/** + **************************************************************************** + * Name: CIccCLUT::CIccCLUT + * + * Purpose: Constructor + * + * Args: + * nInputChannels = number of input channels, + * nOutputChannels = number of output channels + * + ***************************************************************************** + */ +CIccCLUT::CIccCLUT(icUInt8Number nInputChannels, icUInt16Number nOutputChannels, icUInt8Number nPrecision/*=2*/) +{ + m_nInput = nInputChannels; + m_nOutput = nOutputChannels; + m_nPrecision = nPrecision; + m_pData = NULL; + m_nOffset = NULL; + m_g = NULL; + m_ig = NULL; + m_s = NULL; + m_df = NULL; + memset(&m_nReserved2, 0 , sizeof(m_nReserved2)); + + UnitClip = ClutUnitClip; +} + + +/** + **************************************************************************** + * Name: CIccCLUT::CIccCLUT + * + * Purpose: Copy Constructor + * + * Args: + * ICLUT = The CIccCLUT object to be copied + ***************************************************************************** + */ +CIccCLUT::CIccCLUT(const CIccCLUT &ICLUT) +{ + m_pData = NULL; + m_nOffset = NULL; + m_g = NULL; + m_ig = NULL; + m_s = NULL; + m_df = NULL; + m_nInput = ICLUT.m_nInput; + m_nOutput = ICLUT.m_nOutput; + m_nPrecision = ICLUT.m_nPrecision; + m_nNumPoints = ICLUT.m_nNumPoints; + + m_csInput = ICLUT.m_csInput; + m_csOutput = ICLUT.m_csOutput; + + memcpy(m_GridPoints, ICLUT.m_GridPoints, sizeof(m_GridPoints)); + memcpy(m_DimSize, ICLUT.m_DimSize, sizeof(m_DimSize)); + memcpy(m_GridAdr, ICLUT.m_GridAdr, sizeof(m_GridAdr)); + memcpy(&m_nReserved2, &ICLUT.m_nReserved2, sizeof(m_nReserved2)); + + int num = NumPoints()*m_nOutput; + m_pData = new icFloatNumber[num]; + memcpy(m_pData, ICLUT.m_pData, num*sizeof(icFloatNumber)); + + UnitClip = ICLUT.UnitClip; +} + + +/** + **************************************************************************** + * Name: CIccCLUT::operator= + * + * Purpose: Copy Operator + * + * Args: + * CLUTTag = The CIccCLUT object to be copied + ***************************************************************************** + */ +CIccCLUT &CIccCLUT::operator=(const CIccCLUT &CLUTTag) +{ + if (&CLUTTag == this) + return *this; + + m_nInput = CLUTTag.m_nInput; + m_nOutput = CLUTTag.m_nOutput; + m_nPrecision = CLUTTag.m_nPrecision; + m_nNumPoints = CLUTTag.m_nNumPoints; + + m_csInput = CLUTTag.m_csInput; + m_csOutput = CLUTTag.m_csOutput; + + memcpy(m_GridPoints, CLUTTag.m_GridPoints, sizeof(m_GridPoints)); + memcpy(m_DimSize, CLUTTag.m_DimSize, sizeof(m_DimSize)); + memcpy(m_GridAdr, CLUTTag.m_GridAdr, sizeof(m_GridAdr)); + memcpy(m_nReserved2, &CLUTTag.m_nReserved2, sizeof(m_nReserved2)); + + int num; + if (m_pData) + delete [] m_pData; + num = NumPoints()*m_nOutput; + m_pData = new icFloatNumber[num]; + memcpy(m_pData, CLUTTag.m_pData, num*sizeof(icFloatNumber)); + + UnitClip = CLUTTag.UnitClip; + + return *this; +} + + + +/** + **************************************************************************** + * Name: CIccCLUT::~CIccCLUT + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccCLUT::~CIccCLUT() +{ + if (m_pData) + delete [] m_pData; + + if (m_nOffset) + delete [] m_nOffset; + + if (m_g) + delete [] m_g; + + if (m_ig) + delete [] m_ig; + + if (m_s) + delete [] m_s; + + if (m_df) + delete [] m_df; +} + +/** + **************************************************************************** + * Name: CIccCLUT::Init + * + * Purpose: Initializes and sets the size of the CLUT + * + * Args: + * nGridPoints = number of grid points in the CLUT + ***************************************************************************** + */ +bool CIccCLUT::Init(icUInt8Number nGridPoints) +{ + memset(&m_GridPoints, 0, sizeof(m_GridPoints)); + memset(m_GridPoints, nGridPoints, m_nInput); + return Init(&m_GridPoints[0]); +} + +/** + **************************************************************************** + * Name: CIccCLUT::Init + * + * Purpose: Initializes and sets the size of the CLUT + * + * Args: + * pGridPoints = number of grid points in the CLUT + ***************************************************************************** + */ +bool CIccCLUT::Init(icUInt8Number *pGridPoints) +{ + memset(m_nReserved2, 0, sizeof(m_nReserved2)); + if (pGridPoints!=&m_GridPoints[0]) { + memcpy(m_GridPoints, pGridPoints, m_nInput); + if (m_nInput<16) + memset(m_GridPoints+m_nInput, 0, 16-m_nInput); + } + + if (m_pData) { + delete [] m_pData; + } + + int i=m_nInput-1; + + m_DimSize[i] = m_nOutput; + m_nNumPoints = m_GridPoints[i]; + for (i--; i>=0; i--) { + m_DimSize[i] = m_DimSize[i+1] * m_GridPoints[i+1]; + m_nNumPoints *= m_GridPoints[i]; + } + + icUInt32Number nSize = NumPoints() * m_nOutput; + + if (!nSize) + return false; + + m_pData = new icFloatNumber[nSize]; + + return (m_pData != NULL); +} + + +/** + **************************************************************************** + * Name: CIccCLUT::ReadData + * + * Purpose: Reads the CLUT data points into the data buffer + * + * Args: + * size = # of bytes in the tag, + * pIO = IO object to read data from, + * nPrecision = data precision (8bit encoded as 1 or 16bit encoded as 2) + * + * Return: + * true = data read succesfully, + * false = read data failed + ***************************************************************************** + */ +bool CIccCLUT::ReadData(icUInt32Number size, CIccIO *pIO, icUInt8Number nPrecision) +{ + icUInt32Number nNum=NumPoints() * m_nOutput; + + if (nNum * nPrecision > size) + return false; + + if (nPrecision==1) { + if (pIO->Read8Float(m_pData, nNum)!=(icInt32Number)nNum) + return false; + } + else if (nPrecision==2) { + if (pIO->Read16Float(m_pData, nNum)!=(icInt32Number)nNum) + return false; + } + else + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccCLUT::WriteData + * + * Purpose: Writes the CLUT data points from the data buffer + * + * Args: + * pIO = IO object to write data to, + * nPrecision = data precision (8bit encoded as 1 or 16bit encoded as 2) + * + * Return: + * true = data written succesfully, + * false = write operation failed + ***************************************************************************** + */ +bool CIccCLUT::WriteData(CIccIO *pIO, icUInt8Number nPrecision) +{ + icUInt32Number nNum=NumPoints() * m_nOutput; + + if (nPrecision==1) { + if (pIO->Write8Float(m_pData, nNum)!=(icInt32Number)nNum) + return false; + } + else if (nPrecision==2) { + if (pIO->Write16Float(m_pData, nNum)!=(icInt32Number)nNum) + return false; + } + else + return false; + + return true; +} + + +/** + **************************************************************************** + * Name: CIccCLUT::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccCLUT::Read(icUInt32Number size, CIccIO *pIO) +{ + if (size < 20) + return false; + + if (pIO->Read8(m_GridPoints, 16)!=16 || + !pIO->Read8(&m_nPrecision) || + pIO->Read8(&m_nReserved2[0], 3)!=3) + return false; + + Init(m_GridPoints); + + return ReadData(size-20, pIO, m_nPrecision); +} + + +/** + **************************************************************************** + * Name: CIccCLUT::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccCLUT::Write(CIccIO *pIO) +{ + if (pIO->Write8(m_GridPoints, 16)!=16 || + !pIO->Write8(&m_nPrecision) || + pIO->Write8(&m_nReserved2[0], 3)!=3) + return false; + + return WriteData(pIO, m_nPrecision); +} + +/** + **************************************************************************** + * Name: CIccCLUT::Iterate + * + * Purpose: Iterate through the CLUT to dump the data + * + * Args: + * sDescription = string to concatenate data dump to, + * nIndex = the channel number, + * nPos = the current position in the CLUT + * + ***************************************************************************** + */ +void CIccCLUT::Iterate(std::string &sDescription, icUInt8Number nIndex, icUInt32Number nPos, bool bUseLegacy) +{ + if (nIndex < m_nInput) { + int i; + for (i=0; iPixelOp(m_fGridAdr, &m_pData[index]); + + } + } + } + } + else if (m_nInput==4) { + int i,j,k,l; + icUInt32Number index=0; + for (i=0; iPixelOp(m_fGridAdr, &m_pData[index]); + + } + } + } + } + } + else + SubIterate(pExec, 0, 0); +} + + +/** + **************************************************************************** + * Name: CIccCLUT::SubIterate + * + * Purpose: Iterate through the CLUT to get the data + * + * Args: + * pExec = pointer to the IIccCLUTExec object that implements the + * IIccCLUTExec::Apply() function, + * nIndex = the channel number, + * nPos = the current position in the CLUT + * + ***************************************************************************** + */ +void CIccCLUT::SubIterate(IIccCLUTExec* pExec, icUInt8Number nIndex, icUInt32Number nPos) +{ + if (nIndex < m_nInput) { + int i; + for (i=0; iPixelOp(m_fGridAdr, &m_pData[nPos]); +} + +/** + **************************************************************************** + * Name: CIccCLUT::DumpLut + * + * Purpose: Dump data associated with the tag to a string. + * + * Args: + * sDescription = string to concatenate tag dump to, + * szName = name of the LUT to be printed, + * csInput = color space signature of the input data, + * csOutput = color space signature of the output data + ***************************************************************************** + */ +void CIccCLUT::DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csInput, icColorSpaceSignature csOutput, + bool bUseLegacy) +{ + icChar szOutText[2048], szColor[40]; + int i, len; + + sprintf(szOutText, "BEGIN_LUT %s %d %d\r\n", szName, m_nInput, m_nOutput); + sDescription += szOutText; + + for (i=0; iv) { + destPixel[i] = (p[n000] + t*(p[n110]-p[n010]) + + u*(p[n010]-p[n000]) + + v*(p[n111]-p[n110])); + } + else if (uNewCopy(); + } + else { + m_CurvesA = NULL; + } + + if (IMBB.m_CurvesM) { + nCurves = IsInputMatrix() ? m_nInput : m_nOutput; + + m_CurvesM = new LPIccCurve[nCurves]; + for (i=0; iNewCopy(); + } + else { + m_CurvesM = NULL; + } + + if (IMBB.m_CurvesB) { + nCurves = IsInputB() ? m_nInput : m_nOutput; + + m_CurvesB = new LPIccCurve[nCurves]; + for (i=0; iNewCopy(); + } + else { + m_CurvesB = NULL; + } + + if (IMBB.m_Matrix) { + m_Matrix = new CIccMatrix(*IMBB.m_Matrix); + } + else { + m_Matrix = NULL; + } +} + + +/** + **************************************************************************** + * Name: CIccMBB::operator= + * + * Purpose: Copy Operator + * + * Args: + * IMBB = The CIccMBB object to be copied + ***************************************************************************** + */ +CIccMBB &CIccMBB::operator=(const CIccMBB &IMBB) +{ + if (&IMBB == this) + return *this; + + Cleanup(); + + icUInt8Number nCurves; + int i; + + m_bInputMatrix = IMBB.m_bInputMatrix; + m_bUseMCurvesAsBCurves = IMBB.m_bUseMCurvesAsBCurves; + m_nInput = IMBB.m_nInput; + m_nOutput = IMBB.m_nOutput; + m_csInput = IMBB.m_csInput; + m_csOutput = IMBB.m_csOutput; + + if (IMBB.m_CLUT) { + m_CLUT = new CIccCLUT(*IMBB.m_CLUT); + } + else + m_CLUT = NULL; + + if (IMBB.m_CurvesA) { + nCurves = !IsInputB() ? m_nInput : m_nOutput; + + m_CurvesA = new LPIccCurve[nCurves]; + for (i=0; iNewCopy(); + } + else { + m_CurvesA = NULL; + } + + if (IMBB.m_CurvesM) { + nCurves = IsInputMatrix() ? m_nInput : m_nOutput; + + m_CurvesM = new LPIccCurve[nCurves]; + for (i=0; iNewCopy(); + } + else { + m_CurvesM = NULL; + } + + if (IMBB.m_CurvesB) { + nCurves = IsInputB() ? m_nInput : m_nOutput; + + m_CurvesB = new LPIccCurve[nCurves]; + for (i=0; iNewCopy(); + } + else { + m_CurvesB = NULL; + } + + if (IMBB.m_Matrix) { + m_Matrix = new CIccMatrix(*IMBB.m_Matrix); + } + else { + m_Matrix = NULL; + } + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccMBB::~CIccMBB + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccMBB::~CIccMBB() +{ + Cleanup(); +} + +/** + **************************************************************************** + * Name: CIccMBB::Cleanup + * + * Purpose: Frees the memory allocated to the object + * + ***************************************************************************** + */ +void CIccMBB::Cleanup() +{ + int i; + + if (IsInputMatrix()) { + if (m_CurvesB) { + for (i=0; iDumpLut(sDescription, buf, m_csInput, i); + } + } + + if (m_Matrix) + m_Matrix->DumpLut(sDescription, "Matrix"); + + if (m_CurvesM) { + for (i=0; iDumpLut(sDescription, buf, m_csInput, i); + } + } + + if (m_CLUT) + m_CLUT->DumpLut(sDescription, "CLUT", m_csInput, m_csOutput, GetType()==icSigLut16Type); + + if (m_CurvesA) { + for (i=0; iDumpLut(sDescription, buf, m_csOutput, i); + } + } + } + else { + if (m_CurvesA) { + for (i=0; iDumpLut(sDescription, buf, m_csInput, i); + } + } + + if (m_CLUT) + m_CLUT->DumpLut(sDescription, "CLUT", m_csInput, m_csOutput); + + if (m_CurvesM && this->GetType()!=icSigLut8Type) { + for (i=0; iDumpLut(sDescription, buf, m_csOutput, i); + } + } + + if (m_Matrix) + m_Matrix->DumpLut(sDescription, "Matrix"); + + if (m_CurvesB) { + for (i=0; iDumpLut(sDescription, buf, m_csOutput, i); + } + } + } +} + + +/** +****************************************************************************** +* Name: CIccMBB::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccMBB::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + return rv; + } + icUInt32Number nInput, nOutput; + + //Check # of channels + switch(sig) { + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + { + nInput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + if (m_nInput!=nInput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of input channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + nOutput = icGetSpaceSamples(pProfile->m_Header.pcs); + if (m_nOutput!=nOutput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of output channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + break; + } + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + { + nInput = icGetSpaceSamples(pProfile->m_Header.pcs); + if (m_nInput!=nInput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of input channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + nOutput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + if (m_nOutput!=nOutput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of output channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + break; + } + case icSigGamutTag: + { + nInput = icGetSpaceSamples(pProfile->m_Header.pcs); + if (m_nInput!=nInput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of input channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + nOutput = 1; + if (m_nOutput!=nOutput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of output channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + break; + } + default: + { + nInput = m_nInput; + nOutput = m_nOutput; + } + } + + //CLUT check + if (nInput!=nOutput) { + if (!m_CLUT) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - CLUT must be present.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + if (m_CLUT) { + rv = icMaxStatus(rv, m_CLUT->Validate(GetType(), sReport, pProfile)); + } + + return rv; +} + +/** + **************************************************************************** + * Name: CIccMBB::NewCurvesA + * + * Purpose: Allocates memory for a new set of A-curves + * + * Return: Pointer to the LPIccCurve object + ***************************************************************************** + */ +LPIccCurve* CIccMBB::NewCurvesA() +{ + if (m_CurvesA) + return m_CurvesA; + + icUInt8Number nCurves = !IsInputB() ? m_nInput : m_nOutput; + + m_CurvesA = new LPIccCurve[nCurves]; + memset(m_CurvesA, 0, nCurves * sizeof(LPIccCurve)); + + return m_CurvesA; +} + + +/** + **************************************************************************** + * Name: CIccMBB::NewCurvesM + * + * Purpose: Allocates memory for a new set of M-curves + * + * Return: Pointer to the LPIccCurve object + ***************************************************************************** + */ +LPIccCurve* CIccMBB::NewCurvesM() +{ + if (m_CurvesM) + return m_CurvesM; + + icUInt8Number nCurves = IsInputMatrix() ? m_nInput : m_nOutput; + + m_CurvesM = new LPIccCurve[nCurves]; + memset(m_CurvesM, 0, nCurves * sizeof(LPIccCurve)); + + return m_CurvesM; +} + +/** + **************************************************************************** + * Name: CIccMBB::NewCurvesB + * + * Purpose: Allocates memory for a new set of B-curves + * + * Return: Pointer to the LPIccCurve object + ***************************************************************************** + */ +LPIccCurve* CIccMBB::NewCurvesB() +{ + if (m_CurvesB) + return m_CurvesB; + + icUInt8Number nCurves = IsInputB() ? m_nInput : m_nOutput; + + m_CurvesB = new LPIccCurve[nCurves]; + memset(m_CurvesB, 0, nCurves * sizeof(LPIccCurve)); + + return m_CurvesB; +} + +/** + **************************************************************************** + * Name: CIccMBB::NewMatrix + * + * Purpose: Allocates memory for a new matrix + * + * Return: Pointer to the CIccMatrix object + ***************************************************************************** + */ +CIccMatrix* CIccMBB::NewMatrix() +{ + if (m_Matrix) + return m_Matrix; + + m_Matrix = new CIccMatrix; + + return m_Matrix; +} + +/** + **************************************************************************** + * Name: CIccMBB::NewCLUT + * + * Purpose: Allocates memory for a new CLUT and initializes it + * + * Args: + * pGridPoints = number of grid points in the CLUT + * + * Return: Pointer to the CIccCLUT object + ***************************************************************************** + */ +CIccCLUT* CIccMBB::NewCLUT(icUInt8Number *pGridPoints, icUInt8Number nPrecision/*=2*/) +{ + if (m_CLUT) + return m_CLUT; + + m_CLUT = new CIccCLUT(m_nInput, m_nOutput, nPrecision); + + m_CLUT->Init(pGridPoints); + + return m_CLUT; +} + +/** +**************************************************************************** +* Name: CIccMBB::SetCLUT +* +* Purpose: Assignes CLUT connection to an initialized new CLUT +* +* Args: +* clut = pointer to a previously allocated CLUT (Onwership is transfered to +* CIccMBB object). +* +* Return: Pointer to the CIccCLUT object or NULL if clut is incompatible with +* CIccMBB object. If the clut is incompatible it is deleted. +***************************************************************************** +*/ +CIccCLUT *CIccMBB::SetCLUT(CIccCLUT *clut) +{ + if (clut->GetInputDim() != m_nInput || clut->GetOutputChannels() != m_nOutput) { + delete clut; + return NULL; + } + + if (m_CLUT) { + delete m_CLUT; + } + + m_CLUT = clut; + return clut; +} + +/** + **************************************************************************** + * Name: CIccMBB::NewCLUT + * + * Purpose: Allocates memory for a new CLUT and initializes it + * + * Args: + * nGridPoints = number of grid points in the CLUT + * + * Return: Pointer to the CIccCLUT object + ***************************************************************************** + */ +CIccCLUT* CIccMBB::NewCLUT(icUInt8Number nGridPoints, icUInt8Number nPrecision/*=2*/) +{ + if (m_CLUT) + return m_CLUT; + + m_CLUT = new CIccCLUT(m_nInput, m_nOutput, nPrecision); + + m_CLUT->Init(nGridPoints); + + return m_CLUT; +} + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::CIccTagLutAtoB + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagLutAtoB::CIccTagLutAtoB() +{ + m_bInputMatrix = false; + m_nReservedWord = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::CIccTagLutAtoB + * + * Purpose: Copy Constructor + * + * Args: + * ITLA2B = The CIccTagLutAtoB object to be copied + ***************************************************************************** + */ +CIccTagLutAtoB::CIccTagLutAtoB(const CIccTagLutAtoB &ITLA2B) : CIccMBB(ITLA2B) +{ + m_nReservedWord = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITLA2B = The CIccTagLutAtoB object to be copied + ***************************************************************************** + */ +CIccTagLutAtoB &CIccTagLutAtoB::operator=(const CIccTagLutAtoB &ITLA2B) +{ + if (&ITLA2B == this) + return *this; + + CIccMBB::operator=(ITLA2B); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::~CIccTagLutAtoB + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagLutAtoB::~CIccTagLutAtoB() +{ +} + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagLutAtoB::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number Offset[5], nStart, nEnd, nPos; + icUInt8Number nCurves, i; + + if (size<8*sizeof(icUInt32Number) || !pIO) { + return false; + } + + nStart = pIO->Tell(); + nEnd = nStart + size; + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read8(&m_nInput) || + !pIO->Read8(&m_nOutput) || + !pIO->Read16(&m_nReservedWord) || + pIO->Read32(Offset, 5)!=5) + return false; + + if (sig!=GetType()) + return false; + + //B Curves + if (Offset[0]) { + nCurves = IsInputB() ? m_nInput : m_nOutput; + LPIccCurve *pCurves = NewCurvesB(); + + if (pIO->Seek(nStart + Offset[0], icSeekSet)<0) + return false; + + for (i=0; iTell(); + + if (!pIO->Read32(&sig)) + return false; + + if (pIO->Seek(nPos, icSeekSet)<0) + return false; + + if (sig!=icSigCurveType && + sig!=icSigParametricCurveType) + return false; + + pCurves[i] = (CIccCurve*)CIccTag::Create(sig); + + if (!pCurves[i]->Read(nEnd - pIO->Tell(), pIO)) + return false; + + if (!pIO->Sync32(Offset[1])) + return false; + } + } + + //Matrix + if (Offset[1]) { + icS15Fixed16Number tmp; + + if (Offset[1] + 12*sizeof(icS15Fixed16Number) >size) + return false; + + m_Matrix = new CIccMatrix(); + + if (pIO->Seek(nStart + Offset[1], icSeekSet)<0) + return false; + + for (i=0; i<12; i++) { + if (pIO->Read32(&tmp, 1)!=1) + return false; + m_Matrix->m_e[i] = icFtoD(tmp); + } + } + + + //M Curves + if (Offset[2]) { + nCurves = IsInputMatrix() ? m_nInput : m_nOutput; + LPIccCurve *pCurves = NewCurvesM(); + + if (pIO->Seek(nStart + Offset[2], icSeekSet)<0) + return false; + + for (i=0; iTell(); + + if (!pIO->Read32(&sig)) + return false; + + if (pIO->Seek(nPos, icSeekSet)<0) + return false; + + if (sig!=icSigCurveType && + sig!=icSigParametricCurveType) + return false; + + pCurves[i] = (CIccCurve*)CIccTag::Create(sig); + + if (!pCurves[i]->Read(nEnd - pIO->Tell(), pIO)) + return false; + + if (!pIO->Sync32(Offset[2])) + return false; + } + } + + //CLUT + if (Offset[3]) { + if (pIO->Seek(nStart + Offset[3], icSeekSet)<0) + return false; + + m_CLUT = new CIccCLUT(m_nInput, m_nOutput); + + if (!m_CLUT->Read(nEnd - pIO->Tell(), pIO)) + return false; + } + + //A Curves + if (Offset[4]) { + nCurves = !IsInputB() ? m_nInput : m_nOutput; + LPIccCurve *pCurves = NewCurvesA(); + + if (pIO->Seek(nStart + Offset[4], icSeekSet)<0) + return false; + + for (i=0; iTell(); + + if (!pIO->Read32(&sig)) + return false; + + if (pIO->Seek(nPos, icSeekSet)<0) + return false; + + if (sig!=icSigCurveType && + sig!=icSigParametricCurveType) + return false; + + pCurves[i] = (CIccCurve*)CIccTag::Create(sig); + + if (!pCurves[i]->Read(nEnd - pIO->Tell(), pIO)) + return false; + + if (!pIO->Sync32(Offset[4])) + return false; + } + } + return true; +} + + + +/** + **************************************************************************** + * Name: CIccTagLutAtoB::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagLutAtoB::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt32Number Offset[5], nStart, nEnd, nOffsetPos; + icUInt8Number nCurves, i; + + nStart = pIO->Tell(); + memset(&Offset[0], 0, sizeof(Offset)); + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write8(&m_nInput) || + !pIO->Write8(&m_nOutput) || + !pIO->Write16(&m_nReservedWord)) + return false; + + nOffsetPos = pIO->Tell(); + if (pIO->Write32(Offset, 5)!=5) + return false; + + //B Curves + if (m_CurvesB) { + Offset[0] = pIO->Tell() - nStart; + nCurves = IsInputB() ? m_nInput : m_nOutput; + + for (i=0; iWrite(pIO)) + return false; + + if (!pIO->Align32()) + return false; + } + } + + //Matrix + if (m_Matrix) { + icS15Fixed16Number tmp; + + Offset[1] = pIO->Tell() - nStart; + + for (i=0; i<12; i++) { + tmp = icDtoF(m_Matrix->m_e[i]); + if (pIO->Write32(&tmp, 1)!=1) + return false; + } + } + + + //M Curves + if (m_CurvesM) { + Offset[2] = pIO->Tell() - nStart; + nCurves = IsInputMatrix() ? m_nInput : m_nOutput; + + for (i=0; iWrite(pIO)) + return false; + + if (!pIO->Align32()) + return false; + } + } + + //CLUT + if (m_CLUT) { + Offset[3] = pIO->Tell() - nStart; + + if (!m_CLUT->Write(pIO)) + return false; + + if (!pIO->Align32()) + return false; + } + + //A Curves + if (m_CurvesA) { + Offset[4] = pIO->Tell() - nStart; + nCurves = !IsInputB() ? m_nInput : m_nOutput; + + for (i=0; iWrite(pIO)) + return false; + + if (!pIO->Align32()) + return false; + } + } + + nEnd = pIO->Tell(); + + if (!pIO->Seek(nOffsetPos, icSeekSet)) + return false; + + if (pIO->Write32(&Offset[0], 5)!=5) + return false; + + return pIO->Seek(nEnd, icSeekSet)>=0; +} + + +/** +****************************************************************************** +* Name: CIccTagLutAtoB::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagLutAtoB::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccMBB::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + return rv; + } + + switch(sig) { + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + { + icUInt32Number nInput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + + icUInt32Number nOutput = icGetSpaceSamples(pProfile->m_Header.pcs); + + icUInt8Number i; + if (m_CurvesB) { + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of B-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_CurvesM) { + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of M-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_CurvesA) { + if (!m_CLUT) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - CLUT must be present if using A-curves.\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of A-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + } + + break; + } + default: + { + } + } + + + return rv; +} + +/** + **************************************************************************** + * Name: CIccTagLutBtoA::CIccTagLutBtoA + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagLutBtoA::CIccTagLutBtoA() +{ + m_bInputMatrix = true; +} + + +/** + **************************************************************************** + * Name: CIccTagLutBtoA::CIccTagLutBtoA + * + * Purpose: Copy Constructor + * + * Args: + * ITLB2A = The CIccTagLutBtoA object to be copied + ***************************************************************************** + */ +CIccTagLutBtoA::CIccTagLutBtoA(const CIccTagLutBtoA &ITLB2A) : CIccTagLutAtoB(ITLB2A) +{ +} + + +/** + **************************************************************************** + * Name: CIccTagLutBtoA::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITLB2A = The CIccTagLutBtoA object to be copied + ***************************************************************************** + */ +CIccTagLutBtoA &CIccTagLutBtoA::operator=(const CIccTagLutBtoA &ITLB2A) +{ + if (&ITLB2A == this) + return *this; + + CIccMBB::operator=(ITLB2A); + + return *this; +} + + +/** +****************************************************************************** +* Name: CIccTagLutBtoA::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagLutBtoA::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccMBB::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Tag validation incomplete: Pointer to profile unavailable.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + return rv; + } + + switch(sig) { + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + case icSigGamutTag: + { + icUInt32Number nInput = icGetSpaceSamples(pProfile->m_Header.pcs); + + icUInt32Number nOutput; + if (sig==icSigGamutTag) { + nOutput = 1; + } + else { + nOutput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + } + + if (m_nOutput!=nOutput) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of output channels.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + + icUInt8Number i; + if (m_CurvesB) { + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of B-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_CurvesM) { + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of M-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_CurvesA) { + if (!m_CLUT) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - CLUT must be present if using A-curves.\r\n"; + + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + for (i=0; iValidate(sig, sReport, pProfile)); + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of A-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + } + + break; + } + default: + { + } + } + + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::CIccTagLut8 + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagLut8::CIccTagLut8() +{ + memset(m_XYZMatrix, 0, sizeof(m_XYZMatrix)); + m_XYZMatrix[0] = m_XYZMatrix[4] = m_XYZMatrix[8] = icDtoF(1.0); + m_nReservedByte = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::CIccTagLut8 + * + * Purpose: Copy Constructor + * + * Args: + * ITL = The CIccTagLut8 object to be copied + ***************************************************************************** + */ +CIccTagLut8::CIccTagLut8(const CIccTagLut8& ITL) : CIccMBB(ITL) +{ + memcpy(&m_XYZMatrix, &ITL.m_XYZMatrix, sizeof(m_XYZMatrix)); + m_nReservedByte = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITL = The CIccTagLut8 object to be copied + ***************************************************************************** + */ +CIccTagLut8 &CIccTagLut8::operator=(const CIccTagLut8 &ITL) +{ + if (&ITL==this) + return *this; + + CIccMBB::operator=(ITL); + memcpy(&m_XYZMatrix, &ITL.m_XYZMatrix, sizeof(m_XYZMatrix)); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::~CIccTagLut8 + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagLut8::~CIccTagLut8() +{ +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagLut8::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nStart, nEnd; + icUInt8Number i, nGrid; + LPIccCurve *pCurves; + CIccTagCurve *pCurve; + + if (size<13*sizeof(icUInt32Number) || !pIO) { + return false; + } + + nStart = pIO->Tell(); + nEnd = nStart + size; + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read8(&m_nInput) || + !pIO->Read8(&m_nOutput) || + !pIO->Read8(&nGrid) || + !pIO->Read8(&m_nReservedByte) || + pIO->Read32(m_XYZMatrix, 9) != 9) + return false; + + if (sig!=GetType()) + return false; + + //B Curves + pCurves = NewCurvesB(); + + for (i=0; i nEnd - pIO->Tell()) + return false; + + pCurves[i] = pCurve = (CIccTagCurve*)CIccTag::Create(icSigCurveType); + + pCurve->SetSize(256); + + if (pIO->Read8Float(&(*pCurve)[0], 256) != 256) + return false; + } + + //CLUT + m_CLUT = new CIccCLUT(m_nInput, m_nOutput); + + m_CLUT->Init(nGrid); + + if (!m_CLUT->ReadData(nEnd - pIO->Tell(), pIO, 1)) + return false; + + //A Curves + pCurves = NewCurvesA(); + + for (i=0; i nEnd - pIO->Tell()) + return false; + + pCurves[i] = pCurve = (CIccTagCurve*)CIccTag::Create(icSigCurveType); + + pCurve->SetSize(256); + + if (pIO->Read8Float(&(*pCurve)[0], 256) != 256) + return false; + } + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::SetColorSpaces + * + * Purpose: Sets the input and output color spaces + * + * Args: + * csInput = input color space signature, + * csOutput = output color space signature + * + ***************************************************************************** + */ +void CIccTagLut8::SetColorSpaces(icColorSpaceSignature csInput, icColorSpaceSignature csOutput) +{ + if (csInput==icSigXYZData) { + int i; + + if (!m_CurvesM && IsInputMatrix()) { //Transfer ownership of curves + m_CurvesM = m_CurvesB; + m_CurvesB = NULL; + + LPIccCurve *pCurves = NewCurvesB(); + CIccTagCurve *pCurve; + for (i=0; iSetSize(0); + } + + m_bUseMCurvesAsBCurves = true; + } + + if (!m_Matrix) { + CIccMatrix *pMatrix = NewMatrix(); + for (i=0; i<9; i++) { + pMatrix->m_e[i] = icFtoD(m_XYZMatrix[i]); + } + + pMatrix->m_bUseConstants=false; + } + } + else { + m_XYZMatrix[0] = m_XYZMatrix[4] = m_XYZMatrix[8] = icDtoF(1.0); + m_XYZMatrix[1] = m_XYZMatrix[2] = m_XYZMatrix[3] = + m_XYZMatrix[5] = m_XYZMatrix[6] = m_XYZMatrix[7] = 0; + } + + CIccMBB::SetColorSpaces(csInput, csOutput); +} + + +/** + **************************************************************************** + * Name: CIccTagLut8::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagLut8::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt8Number i, nGrid; + icS15Fixed16Number XYZMatrix[9]; + icUInt16Number nInputEntries, nOutputEntries; + LPIccCurve *pCurves; + CIccTagCurve *pCurve; + icFloat32Number v; + + if (m_Matrix) { + for (i=0; i<9; i++) + XYZMatrix[i] = icDtoF(m_Matrix->m_e[i]); + } + else { + memset(XYZMatrix, 0, 9*sizeof(icS15Fixed16Number)); + XYZMatrix[0] = XYZMatrix[4] = XYZMatrix[8] = icDtoF(1.0); + } + + if (m_bUseMCurvesAsBCurves) { + pCurves = m_CurvesM; + } + else { + pCurves = m_CurvesB; + } + + if (!pCurves || !m_CurvesA || !m_CLUT) + return false; + + nGrid = m_CLUT->GridPoints(); + + nInputEntries = (icUInt16Number)(((CIccTagCurve*)pCurves[0])->GetSize()); + nOutputEntries = (icUInt16Number)(((CIccTagCurve*)m_CurvesA[0])->GetSize()); + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write8(&m_nInput) || + !pIO->Write8(&m_nOutput) || + !pIO->Write8(&nGrid) || + !pIO->Write8(&m_nReservedByte) || + pIO->Write32(XYZMatrix, 9) != 9) + return false; + + //B Curves + for (i=0; iGetType()!=icSigCurveType) + return false; + + pCurve = (CIccTagCurve*)pCurves[i]; + if (!pCurve) + return false; + + if (pCurve->GetSize()!=256) { + icUInt32Number j; + + for (j=0; j<256; j++) { + v = pCurve->Apply((icFloat32Number)j / 255.0F); + if (!pIO->Write8Float(&v, 1)) + return false; + } + } + else { + if (pIO->Write8Float(&(*pCurve)[0], 256)!=256) + return false; + } + } + + //CLUT + if (!m_CLUT->WriteData(pIO, 1)) + return false; + + //A Curves + pCurves = m_CurvesA; + + for (i=0; iGetType()!=icSigCurveType) + return false; + + pCurve = (CIccTagCurve*)pCurves[i]; + + if (!pCurve) + return false; + + if (pCurve->GetSize()!=256) { + icUInt32Number j; + + for (j=0; j<256; j++) { + v = pCurve->Apply((icFloat32Number)j / 255.0F); + if (!pIO->Write8Float(&v, 1)) + return false; + } + } + else { + if (pIO->Write8Float(&(*pCurve)[0], 256)!=256) + return false; + } + } + return true; +} + + +/** +****************************************************************************** +* Name: CIccTagLut8::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagLut8::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccMBB::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + return rv; + } + + switch(sig) { + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + case icSigGamutTag: + { + icUInt32Number nInput, nOutput; + if (sig==icSigAToB0Tag || sig==icSigAToB1Tag || sig==icSigAToB2Tag || sig==icSigGamutTag) { + nInput = icGetSpaceSamples(pProfile->m_Header.pcs); + nOutput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + } + else { + nInput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + nOutput = icGetSpaceSamples(pProfile->m_Header.pcs); + } + + if (sig==icSigGamutTag) { + nOutput = 1; + } + + icUInt8Number i; + if (m_CurvesB) { + for (i=0; iValidate(sig, sReport, pProfile)); + if (m_CurvesB[i]->GetType()==icSigCurveType) { + CIccTagCurve *pTagCurve = (CIccTagCurve*)m_CurvesB[i]; + if (pTagCurve->GetSize()==1) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - lut8Tags do not support single entry gamma curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of B-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_Matrix) { + rv = icMaxStatus(rv, m_Matrix->Validate(GetType(), sReport, pProfile)); + } + else { + int sum=0; + for (int i=0; i<9; i++) { + sum += m_XYZMatrix[i]; + } + if (m_XYZMatrix[0]!=1.0 || m_XYZMatrix[4]!=1.0 || m_XYZMatrix[8]!=1.0 || sum!=3.0) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Matrix must be identity.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + + if (m_CurvesA) { + + for (i=0; iValidate(sig, sReport, pProfile)); + if (m_CurvesA[i]->GetType()==icSigCurveType) { + CIccTagCurve *pTagCurve = (CIccTagCurve*)m_CurvesA[i]; + if (pTagCurve->GetSize()==1) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - lut8Tags do not support single entry gamma curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of A-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + } + + break; + } + default: + { + } + } + + + return rv; +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::CIccTagLut16 + * + * Purpose: Constructor + * + ***************************************************************************** + */ +CIccTagLut16::CIccTagLut16() +{ + memset(m_XYZMatrix, 0, sizeof(m_XYZMatrix)); + m_XYZMatrix[0] = m_XYZMatrix[4] = m_XYZMatrix[8] = icDtoF(1.0); + m_nReservedByte = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::CIccTagLut16 + * + * Purpose: Copy Constructor + * + * Args: + * ITL = The CIccTagUnknown object to be copied + ***************************************************************************** + */ +CIccTagLut16::CIccTagLut16(const CIccTagLut16& ITL) : CIccMBB(ITL) +{ + memcpy(&m_XYZMatrix, &ITL.m_XYZMatrix, sizeof(m_XYZMatrix)); + m_nReservedByte = 0; +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::operator= + * + * Purpose: Copy Operator + * + * Args: + * ITL = The CIccTagLut16 object to be copied + ***************************************************************************** + */ +CIccTagLut16 &CIccTagLut16::operator=(const CIccTagLut16 &ITL) +{ + if (&ITL==this) + return *this; + + CIccMBB::operator=(ITL); + memcpy(&m_XYZMatrix, &ITL.m_XYZMatrix, sizeof(m_XYZMatrix)); + + return *this; +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::~CIccTagLut16 + * + * Purpose: Destructor + * + ***************************************************************************** + */ +CIccTagLut16::~CIccTagLut16() +{ +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::Read + * + * Purpose: Read in the tag contents into a data block + * + * Args: + * size - # of bytes in tag, + * pIO - IO object to read tag from + * + * Return: + * true = successful, false = failure + ***************************************************************************** + */ +bool CIccTagLut16::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + icUInt32Number nStart, nEnd; + icUInt8Number i, nGrid; + icUInt16Number nInputEntries, nOutputEntries; + LPIccCurve *pCurves; + CIccTagCurve *pCurve; + + if (size<13*sizeof(icUInt32Number) || !pIO) { + return false; + } + + nStart = pIO->Tell(); + nEnd = nStart + size; + + if (!pIO->Read32(&sig) || + !pIO->Read32(&m_nReserved) || + !pIO->Read8(&m_nInput) || + !pIO->Read8(&m_nOutput) || + !pIO->Read8(&nGrid) || + !pIO->Read8(&m_nReservedByte) || + pIO->Read32(m_XYZMatrix, 9) != 9 || + !pIO->Read16(&nInputEntries) || + !pIO->Read16(&nOutputEntries)) + return false; + + if (sig!=GetType()) + return false; + + + //B Curves + pCurves = NewCurvesB(); + + for (i=0; i nEnd - pIO->Tell()) + return false; + + pCurves[i] = pCurve = (CIccTagCurve*)CIccTag::Create(icSigCurveType); + + pCurve->SetSize(nInputEntries); + + if (pIO->Read16Float(&(*pCurve)[0], nInputEntries) != nInputEntries) + return false; + } + + //CLUT + m_CLUT = new CIccCLUT(m_nInput, m_nOutput); + + m_CLUT->Init(nGrid); + + if (!m_CLUT->ReadData(nEnd - pIO->Tell(), pIO, 2)) + return false; + + //A Curves + pCurves = NewCurvesA(); + + for (i=0; i nEnd - pIO->Tell()) + return false; + + pCurves[i] = pCurve = (CIccTagCurve*)CIccTag::Create(icSigCurveType); + + pCurve->SetSize(nOutputEntries); + + if (pIO->Read16Float(&(*pCurve)[0], nOutputEntries) != nOutputEntries) + return false; + } + return true; +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::SetColorSpaces + * + * Purpose: Sets the input and output color spaces + * + * Args: + * csInput = input color space signature, + * csOutput = output color space signature + * + ***************************************************************************** + */ +void CIccTagLut16::SetColorSpaces(icColorSpaceSignature csInput, icColorSpaceSignature csOutput) +{ + if (csInput==icSigXYZData) { + int i; + + if (!m_CurvesM && IsInputMatrix()) { //Transfer ownership of curves + m_CurvesM = m_CurvesB; + m_CurvesB = NULL; + + LPIccCurve *pCurves = NewCurvesB(); + CIccTagCurve *pCurve; + for (i=0; iSetSize(0); + } + + m_bUseMCurvesAsBCurves = true; + } + + if (!m_Matrix) { + CIccMatrix *pMatrix = NewMatrix(); + for (i=0; i<9; i++) { + pMatrix->m_e[i] = icFtoD(m_XYZMatrix[i]); + } + + pMatrix->m_bUseConstants=false; + } + } + else { + m_XYZMatrix[0] = m_XYZMatrix[4] = m_XYZMatrix[8] = icDtoF(1.0); + m_XYZMatrix[1] = m_XYZMatrix[2] = m_XYZMatrix[3] = + m_XYZMatrix[5] = m_XYZMatrix[6] = m_XYZMatrix[7] = 0; + } + + CIccMBB::SetColorSpaces(csInput, csOutput); +} + + +/** + **************************************************************************** + * Name: CIccTagLut16::Write + * + * Purpose: Write the tag to a file + * + * Args: + * pIO - The IO object to write tag to. + * + * Return: + * true = succesful, false = failure + ***************************************************************************** + */ +bool CIccTagLut16::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + icUInt8Number i, nGrid; + icS15Fixed16Number XYZMatrix[9]; + icUInt16Number nInputEntries, nOutputEntries; + LPIccCurve *pCurves; + CIccTagCurve *pCurve; + + if (m_Matrix) { + for (i=0; i<9; i++) { + XYZMatrix[i] = icDtoF(m_Matrix->m_e[i]); + } + } + else { + memset(XYZMatrix, 0, 9*sizeof(icS15Fixed16Number)); + XYZMatrix[0] = XYZMatrix[4] = XYZMatrix[8] = icDtoF(1.0); + } + + if (m_bUseMCurvesAsBCurves) { + pCurves = m_CurvesM; + } + else { + pCurves = m_CurvesB; + } + + if (!pCurves || !m_CurvesA || !m_CLUT) + return false; + + nGrid = m_CLUT->GridPoints(); + + nInputEntries = (icUInt16Number)(((CIccTagCurve*)pCurves[0])->GetSize()); + nOutputEntries = (icUInt16Number)(((CIccTagCurve*)m_CurvesA[0])->GetSize()); + + if (!pIO->Write32(&sig) || + !pIO->Write32(&m_nReserved) || + !pIO->Write8(&m_nInput) || + !pIO->Write8(&m_nOutput) || + !pIO->Write8(&nGrid) || + !pIO->Write8(&m_nReservedByte) || + pIO->Write32(XYZMatrix, 9) != 9 || + !pIO->Write16(&nInputEntries) || + !pIO->Write16(&nOutputEntries)) + return false; + + //B Curves + for (i=0; iGetType()!=icSigCurveType) + return false; + + pCurve = (CIccTagCurve*)pCurves[i]; + if (!pCurve) + return false; + + if (pIO->Write16Float(&(*pCurve)[0], nInputEntries) != nInputEntries) + return false; + } + + //CLUT + if (!m_CLUT->WriteData(pIO, 2)) + return false; + + //A Curves + pCurves = m_CurvesA; + + for (i=0; iGetType()!=icSigCurveType) + return false; + + pCurve = (CIccTagCurve*)pCurves[i]; + + if (pIO->Write16Float(&(*pCurve)[0], nOutputEntries) != nOutputEntries) + return false; + } + return true; +} + + +/** +****************************************************************************** +* Name: CIccTagLut16::Validate +* +* Purpose: Check tag data validity. +* +* Args: +* sig = signature of tag being validated, +* sReport = String to add report information to +* +* Return: +* icValidateStatusOK if valid, or other error status. +****************************************************************************** +*/ +icValidateStatus CIccTagLut16::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile/*=NULL*/) const +{ + icValidateStatus rv = CIccMBB::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!pProfile) { + rv = icMaxStatus(rv, icValidateWarning); + return rv; + } + + switch(sig) { + case icSigAToB0Tag: + case icSigAToB1Tag: + case icSigAToB2Tag: + case icSigBToA0Tag: + case icSigBToA1Tag: + case icSigBToA2Tag: + case icSigGamutTag: + { + icUInt32Number nInput, nOutput; + if (sig==icSigAToB0Tag || sig==icSigAToB1Tag || sig==icSigAToB2Tag || sig==icSigGamutTag) { + nInput = icGetSpaceSamples(pProfile->m_Header.pcs); + nOutput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + } + else { + nInput = icGetSpaceSamples(pProfile->m_Header.colorSpace); + nOutput = icGetSpaceSamples(pProfile->m_Header.pcs); + } + + if (sig==icSigGamutTag) { + nOutput = 1; + } + + icUInt8Number i; + if (m_CurvesB) { + for (i=0; iValidate(sig, sReport, pProfile)); + if (m_CurvesB[i]->GetType()==icSigCurveType) { + CIccTagCurve *pTagCurve = (CIccTagCurve*)m_CurvesB[i]; + if (pTagCurve->GetSize()==1) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - lut16Tags do not support single entry gamma curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of B-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + + if (m_Matrix) { + rv = icMaxStatus(rv, m_Matrix->Validate(GetType(), sReport, pProfile)); + } + else { + int sum=0; + for (int i=0; i<9; i++) { + sum += m_XYZMatrix[i]; + } + if (m_XYZMatrix[0]!=1.0 || m_XYZMatrix[4]!=1.0 || m_XYZMatrix[8]!=1.0 || sum!=3.0) { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " - Matrix must be identity.\r\n"; + rv = icMaxStatus(rv, icValidateWarning); + } + } + + if (m_CurvesA) { + + for (i=0; iValidate(sig, sReport, pProfile)); + if (m_CurvesA[i]->GetType()==icSigCurveType) { + CIccTagCurve *pTagCurve = (CIccTagCurve*)m_CurvesA[i]; + if (pTagCurve->GetSize()==1) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - lut16Tags do not support single entry gamma curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + } + else { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Incorrect number of A-curves.\r\n"; + rv = icMaxStatus(rv, icValidateCriticalError); + } + } + + } + + break; + } + default: + { + } + } + + + return rv; +} + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccTagLut.h b/library/src/main/cpp/icc/IccTagLut.h new file mode 100644 index 00000000..48187e76 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagLut.h @@ -0,0 +1,533 @@ +/** @file + File: IccTagLut.h + + Contains: Header for implementation of the Multi-Dimensional + Lut tag classes classes + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2005-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +// -Moved LUT tags to separate file 4-30-2005 +// +////////////////////////////////////////////////////////////////////// + +#if !defined(_ICCTAGLUT_H) +#define _ICCTAGLUT_H + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +#include "IccTagBasic.h" + +/** +**************************************************************************** +* Class: CIccCurve +* +* Purpose: The base curve class +***************************************************************************** +*/ +class ICCPROFLIB_API CIccCurve : public CIccTag +{ +public: + CIccCurve() {} + virtual CIccTag *NewCopy() const { return new CIccCurve; } + virtual ~CIccCurve() {} + + virtual void DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csSig, int nIndex) {} + + virtual void Begin() {} + virtual icFloatNumber Apply(icFloatNumber v) { return v; } + + icFloatNumber Find(icFloatNumber v) { return Find(v, 0, Apply(0), 1.0, Apply(1.0)); } + virtual bool IsIdentity() {return false;} + +protected: + icFloatNumber Find(icFloatNumber v, + icFloatNumber p0, icFloatNumber v0, + icFloatNumber p1, icFloatNumber v1); + +}; +typedef CIccCurve* LPIccCurve; + +typedef enum { + icInitNone, + icInitZero, + icInitIdentity, +} icTagCurveSizeInit; + +/** +**************************************************************************** +* Class: CIccTagCurve +* +* Purpose: The curveType tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagCurve : public CIccCurve +{ +public: + CIccTagCurve(int nSize=0); + CIccTagCurve(const CIccTagCurve &ITCurve); + CIccTagCurve &operator=(const CIccTagCurve &CurveTag); + virtual CIccTag *NewCopy() const { return new CIccTagCurve(*this);} + virtual ~CIccTagCurve(); + + virtual icTagTypeSignature GetType() const { return icSigCurveType; } + virtual const icChar *GetClassName() const { return "CIccTagCurve"; } + + virtual void Describe(std::string &sDescription); + virtual void DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csSig, int nIndex); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + icFloatNumber &operator[](icUInt32Number index) {return m_Curve[index];} + icFloatNumber *GetData(icUInt32Number index) {return &m_Curve[index];} + icUInt32Number GetSize() const { return m_nSize; } + void SetSize(icUInt32Number nSize, icTagCurveSizeInit nSizeOpt=icInitZero); + void SetGamma(icFloatNumber gamma); + + virtual void Begin() {m_nMaxIndex = (icUInt16Number)m_nSize - 1;} + virtual icFloatNumber Apply(icFloatNumber v); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + virtual bool IsIdentity(); + +protected: + icFloatNumber *m_Curve; + icUInt32Number m_nSize; + icUInt16Number m_nMaxIndex; +}; + +/** +**************************************************************************** +* Class: CIccTagParametricCurve +* +* Purpose: The parametric curve type tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagParametricCurve : public CIccCurve +{ +public: + CIccTagParametricCurve(); + CIccTagParametricCurve(const CIccTagParametricCurve &ITPC); + CIccTagParametricCurve &operator=(const CIccTagParametricCurve &ParamCurveTag); + virtual CIccTag *NewCopy() const { return new CIccTagParametricCurve(*this);} + virtual ~CIccTagParametricCurve(); + + virtual icTagTypeSignature GetType() const { return icSigParametricCurveType; } + virtual const icChar *GetClassName() const { return "CIccTagParametricCurve"; } + + virtual void Describe(std::string &sDescription); + virtual void DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csSig, int nIndex); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + + bool SetFunctionType(icUInt16Number nFunctionType); //# parameters set by + icUInt16Number GetFunctionType() const {return m_nFunctionType; } + + icUInt16Number GetNumParam() const { return m_nNumParam; } + icFloatNumber *GetParams() const { return m_dParam; } + icFloatNumber Param(int index) const { return m_dParam[index]; } + icFloatNumber& operator[](int index) { return m_dParam[index]; } + + virtual icFloatNumber Apply(icFloatNumber v) { return DoApply(v); } + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + virtual bool IsIdentity(); + + icUInt16Number m_nReserved2; +protected: + icFloatNumber DoApply(icFloatNumber v) const; + icUInt16Number m_nFunctionType; + icUInt16Number m_nNumParam; + icFloatNumber *m_dParam; +}; + + +/** +**************************************************************************** +* Class: CIccMatrix +* +* Purpose: The base matrix class +***************************************************************************** +*/ +class ICCPROFLIB_API CIccMatrix +{ +public: + CIccMatrix(bool bUseConstants=true); + CIccMatrix(const CIccMatrix &MatrixClass); + CIccMatrix &operator=(const CIccMatrix &MatrixClass); + virtual ~CIccMatrix() {} + + void DumpLut(std::string &sDescription, const icChar *szName); + + icFloatNumber m_e[12]; //e = element + bool m_bUseConstants; + + virtual void Apply(icFloatNumber *Pixel) const; + icValidateStatus Validate(icTagTypeSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + virtual bool IsIdentity(); +}; + +/** +**************************************************************************** +* Interface Class: IIccCLUTExec +* +* Purpose: Interface class that is useful to populate CLUTs +***************************************************************************** +*/ +class ICCPROFLIB_API IIccCLUTExec +{ +public: + virtual ~IIccCLUTExec() {} + + virtual void PixelOp(icFloatNumber* pGridAdr, icFloatNumber* pData)=0; +}; + +typedef icFloatNumber (*icCLUTCLIPFUNC)(icFloatNumber v); + +/** +**************************************************************************** +* Class: CIccCLUT +* +* Purpose: The base multidimensional color look-up table (CLUT) class +***************************************************************************** +*/ +class ICCPROFLIB_API CIccCLUT +{ +public: + CIccCLUT(icUInt8Number nInputChannels, icUInt16Number nOutputChannels, icUInt8Number nPrecision=2); + CIccCLUT(const CIccCLUT &ICLUT); + CIccCLUT &operator=(const CIccCLUT &CLUTClass); + virtual ~CIccCLUT(); + + bool Init(icUInt8Number nGridPoints); + bool Init(icUInt8Number *pGridPoints); + + bool ReadData(icUInt32Number size, CIccIO *pIO, icUInt8Number nPrecision); + bool WriteData(CIccIO *pIO, icUInt8Number nPrecision); + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + + void DumpLut(std::string &sDescription, const icChar *szName, + icColorSpaceSignature csInput, icColorSpaceSignature csOutput, + bool bUseLegacy=false); + + icFloatNumber& operator[](int index) { return m_pData[index]; } + icFloatNumber* GetData(int index) { return &m_pData[index]; } + icUInt32Number NumPoints() const { return m_nNumPoints; } + icUInt8Number GridPoints() const { return m_GridPoints[0]; } + icUInt8Number GridPoint(int index) const { return m_GridPoints[index]; } + icUInt32Number MaxGridPoint(int index) const { return m_MaxGridPoint[index]; } + + icUInt32Number GetDimSize(icUInt8Number nIndex) const { return m_DimSize[nIndex]; } + + icUInt8Number GetInputDim() const { return m_nInput; } + icUInt16Number GetOutputChannels() const { return m_nOutput; } + + icUInt32Number GetNumOffset() const { return m_nNodes; } + icUInt32Number GetOffset(int index) const { return m_nOffset ? m_nOffset[index] : 0; } + + + void Begin(); + void Interp3dTetra(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + void Interp3d(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + void Interp4d(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + void Interp5d(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + void Interp6d(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + void InterpND(icFloatNumber *destPixel, const icFloatNumber *srcPixel) const; + + void Iterate(IIccCLUTExec* pExec); + icValidateStatus Validate(icTagTypeSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + void SetClipFunc(icCLUTCLIPFUNC ClipFunc) { UnitClip = ClipFunc; } + + icUInt8Number GetPrecision() { return m_nPrecision; } + +protected: + void Iterate(std::string &sDescription, icUInt8Number nIndex, icUInt32Number nPos, bool bUseLegacy=false); + void SubIterate(IIccCLUTExec* pExec, icUInt8Number nIndex, icUInt32Number nPos); + + icCLUTCLIPFUNC UnitClip; + + icUInt8Number m_nReserved2[3]; + + icUInt8Number m_nInput; + icUInt16Number m_nOutput; //16 bit to support MPE CLUT elements + icUInt8Number m_nPrecision; + + icUInt8Number m_GridPoints[16]; + icUInt32Number m_nNumPoints; + + icUInt32Number m_DimSize[16]; + icFloatNumber *m_pData; + + //Iteration temporary variables + icUInt8Number m_GridAdr[16]; + icFloatNumber m_fGridAdr[16]; + icChar *m_pOutText, *m_pVal; + icColorSpaceSignature m_csInput, m_csOutput; + + //Tetrahedral interpolation variables + icUInt8Number m_MaxGridPoint[16]; + icUInt32Number n000, n001, n010, n011, n100, n101, n110, n111, n1000, n10000, n100000; + + //ND Interpolation + icUInt32Number *m_nOffset; + // Temporary ND Interp Variables + icFloatNumber *m_g, *m_s, *m_df; + icUInt32Number* m_ig; + icUInt32Number m_nNodes, m_nPower[16]; +}; + + +/** +**************************************************************************** +* Class: CIccMBB +* +* Purpose: The Multi-dimensional Black Box (MBB) base class for lut8, lut16, +* lutA2B and lutB2A tag types +***************************************************************************** +*/ +class ICCPROFLIB_API CIccMBB : public CIccTag +{ + friend class ICCPROFLIB_API CIccXform3DLut; + friend class ICCPROFLIB_API CIccXform4DLut; + friend class ICCPROFLIB_API CIccXformNDLut; +public: + CIccMBB(); + CIccMBB(const CIccMBB &IMBB); + CIccMBB &operator=(const CIccMBB &IMBB); + virtual CIccTag* NewCopy() const {return new CIccMBB(*this);} + virtual ~CIccMBB(); + + virtual bool IsMBBType() { return true;} + + virtual icUInt8Number GetPrecision() { return 2; } + virtual bool IsInputMatrix() { return m_bInputMatrix; } //Is matrix on input side of CLUT? + virtual bool UseLegacyPCS() const { return false; } //Treat Lab Encoding differently? + + bool IsInputB() { return IsInputMatrix(); } + bool SwapMBCurves() { return m_bUseMCurvesAsBCurves; } + + void Cleanup(); + void Init(icUInt8Number nInputChannels, icUInt8Number nOutputChannels); + + icUInt8Number InputChannels() const { return m_nInput; } + icUInt8Number OutputChannels() const { return m_nOutput; } + + virtual void Describe(std::string &sDescription); + + virtual void SetColorSpaces(icColorSpaceSignature csInput, icColorSpaceSignature csOutput); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + LPIccCurve* NewCurvesA(); + CIccCLUT* NewCLUT(icUInt8Number nGridPoints, icUInt8Number nPrecision=2); + CIccCLUT* NewCLUT(icUInt8Number *pGridPoints, icUInt8Number nPrecision=2); + CIccMatrix* NewMatrix(); + LPIccCurve* NewCurvesM(); + LPIccCurve* NewCurvesB(); + + CIccMatrix *GetMatrix() const {return m_Matrix; } + CIccCLUT *GetCLUT() const {return m_CLUT;} + LPIccCurve *GetCurvesA() const {return m_CurvesA;} + LPIccCurve *GetCurvesB() const {return m_CurvesB;} + LPIccCurve *GetCurvesM() const {return m_CurvesM;} + + CIccCLUT *SetCLUT(CIccCLUT *clut); + +protected: + bool m_bInputMatrix; + bool m_bUseMCurvesAsBCurves; + + icUInt8Number m_nInput; + icUInt8Number m_nOutput; + + icColorSpaceSignature m_csInput; + icColorSpaceSignature m_csOutput; + + LPIccCurve *m_CurvesA; + CIccCLUT *m_CLUT; + CIccMatrix *m_Matrix; + LPIccCurve *m_CurvesM; + LPIccCurve *m_CurvesB; + +}; + +/** +**************************************************************************** +* Class: CIccTagLutAtoB +* +* Purpose: The LutA2B tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagLutAtoB : public CIccMBB +{ +public: + CIccTagLutAtoB(); + CIccTagLutAtoB(const CIccTagLutAtoB &ITLA2B); + CIccTagLutAtoB &operator=(const CIccTagLutAtoB &ITLA2B); + virtual CIccTag* NewCopy() const { return new CIccTagLutAtoB(*this); } + virtual ~CIccTagLutAtoB(); + + virtual icTagTypeSignature GetType() const { return icSigLutAtoBType; } + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt16Number m_nReservedWord; +}; + +/** +**************************************************************************** +* Class: CIccTagLutBtoA +* +* Purpose: The LutB2A tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagLutBtoA : public CIccTagLutAtoB +{ +public: + CIccTagLutBtoA(); + CIccTagLutBtoA(const CIccTagLutBtoA &ITLB2A); + CIccTagLutBtoA &operator=(const CIccTagLutBtoA &ITLB2A); + virtual CIccTag* NewCopy() const { return new CIccTagLutBtoA(*this); } + + virtual icTagTypeSignature GetType() const { return icSigLutBtoAType; } + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; +}; + + +/** +**************************************************************************** +* Class: CIccTagLut8 +* +* Purpose: The Lut8 tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagLut8 : public CIccMBB +{ +public: + CIccTagLut8(); + CIccTagLut8(const CIccTagLut8 &ITL); + CIccTagLut8 &operator=(const CIccTagLut8 &ITL); + virtual CIccTag* NewCopy() const {return new CIccTagLut8(*this);} + virtual ~CIccTagLut8(); + + virtual icTagTypeSignature GetType() const { return icSigLut8Type; } + virtual icUInt8Number GetPrecision() { return 1; } + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + + virtual void SetColorSpaces(icColorSpaceSignature csInput, icColorSpaceSignature csOutput); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt8Number m_nReservedByte; + icS15Fixed16Number m_XYZMatrix[9]; +}; + +/** +**************************************************************************** +* Class: CIccTagLut16 +* +* Purpose: The Lut16 tag type +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagLut16 : public CIccMBB +{ +public: + CIccTagLut16(); + CIccTagLut16(const CIccTagLut16 &ITL); + CIccTagLut16 &operator=(const CIccTagLut16 &ITL); + virtual CIccTag* NewCopy() const {return new CIccTagLut16(*this);} + virtual ~CIccTagLut16(); + + virtual icTagTypeSignature GetType() const { return icSigLut16Type; } + virtual bool UseLegacyPCS() const { return true; } //Treat Lab Encoding differently? + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + + virtual void SetColorSpaces(icColorSpaceSignature csInput, icColorSpaceSignature csOutput); + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + +protected: + icUInt8Number m_nReservedByte; + icS15Fixed16Number m_XYZMatrix[9]; +}; + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif // !defined(_ICCTAG_H) diff --git a/library/src/main/cpp/icc/IccTagMPE.cpp b/library/src/main/cpp/icc/IccTagMPE.cpp new file mode 100644 index 00000000..e5bceb32 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagMPE.cpp @@ -0,0 +1,1440 @@ +/** @file + File: IccTagMpe.cpp + + Contains: Implementation of MultiProcessElementType Tag + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 1-30-2006 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include +#include +#include +#include +#include "IccTagMPE.h" +#include "IccIO.h" +#include "IccMpeFactory.h" +#include +#include "IccUtil.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** + ****************************************************************************** + * Name: CIccApplyMpe::CIccApplyMpe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccApplyMpe::CIccApplyMpe(CIccMultiProcessElement *pElem) +{ + m_pElem = pElem; +} + + +/** + ****************************************************************************** + * Name: CIccApplyMpe::~CIccApplyMpe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccApplyMpe::~CIccApplyMpe() +{ +} + + +/** + ****************************************************************************** + * Name: CIccMultiProcessElement::Create + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMultiProcessElement* CIccMultiProcessElement::Create(icElemTypeSignature sig) +{ + return CIccMpeCreator::CreateElement(sig); +} + +/** + ****************************************************************************** + * Name: CIccMultiProcessElement::GetNewApply() + * + * Purpose: + * + * Args: + * + * Return: +******************************************************************************/ +CIccApplyMpe* CIccMultiProcessElement::GetNewApply( CIccApplyTagMpe *pApplyTag ) +{ + return new CIccApplyMpe(this); +} + + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::CIccMpeUnknown + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeUnknown::CIccMpeUnknown() +{ + m_sig = icSigUnknownElemType; + m_nReserved = 0; + m_nInputChannels = 0; + m_nOutputChannels = 0; + m_nSize = 0; + m_pData = 0; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::CIccMpeUnknown + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeUnknown::CIccMpeUnknown(const CIccMpeUnknown &elem) +{ + m_sig = elem.m_sig; + m_nReserved = elem.m_nReserved; + m_nInputChannels = elem.m_nInputChannels; + m_nOutputChannels = elem.m_nOutputChannels; + m_nSize = elem.m_nSize; + if (m_nSize) { + m_pData = (icUInt8Number*)malloc(m_nSize); + memcpy(m_pData, elem.m_pData, m_nSize); + } + else + m_pData = NULL; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeUnknown &CIccMpeUnknown::operator=(const CIccMpeUnknown &elem) +{ + if (m_pData) + free(m_pData); + + m_sig = elem.m_sig; + m_nReserved = elem.m_nReserved; + m_nInputChannels = elem.m_nInputChannels; + m_nOutputChannels = elem.m_nOutputChannels; + m_nSize = elem.m_nSize; + if (m_nSize) { + m_pData = (icUInt8Number*)malloc(m_nSize); + memcpy(m_pData, elem.m_pData, m_nSize); + } + else + m_pData = NULL; + + return (*this); +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::~CIccMpeUnknown + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccMpeUnknown::~CIccMpeUnknown() +{ + if (m_pData) + free(m_pData); +} + +/** +****************************************************************************** +* Name: CIccMpeUnknown::SetType +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccMpeUnknown::SetType(icElemTypeSignature sig) +{ + m_sig = sig; +} + +/** +****************************************************************************** +* Name: CIccMpeUnknown::Describe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccMpeUnknown::SetChannels(icUInt16Number nInputChannels, icUInt16Number nOutputChannels) +{ + m_nInputChannels = nInputChannels; + m_nOutputChannels = nOutputChannels; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccMpeUnknown::Describe(std::string &sDescription) +{ + icChar buf[128], sigbuf[40]; + + sprintf(buf, "Unknown Element(%s) Type of %u Bytes.", + icGetSig(sigbuf, m_sig), m_nSize); + sDescription += buf; + + sDescription += "\r\n\r\nData Follows:\r\n"; + + icMemDump(sDescription, m_pData, m_nSize); + +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::SetDataSize + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeUnknown::SetDataSize(icUInt32Number nSize, bool bZeroData/*=true*/) +{ + bool rv = true; + if (m_pData) + free(m_pData); + + m_nSize = nSize; + if (m_nSize) { + m_pData = (icUInt8Number*)malloc(m_nSize); + if (!m_pData) { + rv = false; + m_nSize = 0; + } + } + else + m_pData = NULL; + + return rv; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeUnknown::Read(icUInt32Number nSize, CIccIO *pIO) +{ + icUInt32Number nHeaderSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt16Number) + + sizeof(icUInt16Number); + + if (nHeaderSize > nSize) + return false; + + if (!pIO) { + return false; + } + + if (!pIO->Read32(&m_sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&m_nInputChannels)) + return false; + + if (!pIO->Read16(&m_nOutputChannels)) + return false; + + icUInt32Number nDataSize = nSize - nHeaderSize; + + if (nDataSize) { + if (!SetDataSize(nDataSize, false)) + return false; + + if (pIO->Read8(m_pData, nDataSize)!=(icInt32Number)nDataSize) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccMpeUnknown::Write(CIccIO *pIO) +{ + if (!pIO) + return false; + + //icUInt32Number elemStart = pIO->Tell(); + + if (!pIO->Write32(&m_sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (m_nSize) { + if (pIO->Write8(m_pData, m_nSize)!=(icInt32Number)m_nSize) + return false; + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccMpeUnknown::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccMpeUnknown::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + CIccInfo Info; + icChar buf[40]; + std::string sSigName = Info.GetSigName(sig); + + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " - Element "; + sSigName = Info.GetSigName(GetType()); + sReport += sSigName; + sReport += " - Contains unknown processing element type ("; + icGetSig(buf, m_sig, true); + sReport += buf; + sReport += ").\r\n"; + + return icValidateCriticalError; +} + + +/** + ****************************************************************************** + * Name: CIccProcessElement::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccMultiProcessElement::Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE/*=NULL*/) const +{ + icValidateStatus rv = icValidateOK; + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + if (m_nReserved!=0) { + sReport += icValidateNonCompliantMsg; + sReport += sSigName; + sReport += " - Element "; + sSigName = Info.GetSigName(GetType()); + sReport += sSigName; + sReport += " - Reserved Value must be zero.\r\n"; + + rv = icValidateNonCompliant; + } + + return rv; +} + + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccDblPixelBuffer::CIccDblPixelBuffer() +{ + m_nMaxChannels = 0; + m_nLastNumChannels = 0; + m_pixelBuf1 = NULL; + m_pixelBuf2 = NULL; +} + + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccDblPixelBuffer::CIccDblPixelBuffer(const CIccDblPixelBuffer &buf) +{ + m_nMaxChannels = buf.m_nMaxChannels; + if (m_nMaxChannels) { + m_pixelBuf1 = (icFloatNumber*)malloc(m_nMaxChannels*sizeof(icFloatNumber)); + if (m_pixelBuf1) + memcpy(m_pixelBuf1, buf.m_pixelBuf1, m_nMaxChannels*sizeof(icFloatNumber)); + + m_pixelBuf2 = (icFloatNumber*)malloc(m_nMaxChannels*sizeof(icFloatNumber)); + if (m_pixelBuf2) + memcpy(m_pixelBuf2, buf.m_pixelBuf2, m_nMaxChannels*sizeof(icFloatNumber)); + } + else { + m_pixelBuf1 = NULL;; + m_pixelBuf2 = NULL; + } +} + + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccDblPixelBuffer& CIccDblPixelBuffer::operator=(const CIccDblPixelBuffer &buf) +{ + Clean(); + + m_nMaxChannels = buf.m_nMaxChannels; + if (m_nMaxChannels) { + m_pixelBuf1 = (icFloatNumber*)malloc(m_nMaxChannels*sizeof(icFloatNumber)); + if (m_pixelBuf1) + memcpy(m_pixelBuf1, buf.m_pixelBuf1, m_nMaxChannels*sizeof(icFloatNumber)); + + m_pixelBuf2 = (icFloatNumber*)malloc(m_nMaxChannels*sizeof(icFloatNumber)); + if (m_pixelBuf2) + memcpy(m_pixelBuf2, buf.m_pixelBuf2, m_nMaxChannels*sizeof(icFloatNumber)); + } + else { + m_pixelBuf1 = NULL;; + m_pixelBuf2 = NULL; + } + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::~CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccDblPixelBuffer::~CIccDblPixelBuffer() +{ + Clean(); +} + + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccDblPixelBuffer::Clean() +{ + if (m_pixelBuf1) { + free(m_pixelBuf1); + m_pixelBuf1 = NULL; + } + if (m_pixelBuf2) { + free(m_pixelBuf2); + m_pixelBuf2 = NULL; + } + m_nMaxChannels = 0; + m_nLastNumChannels = 0; +} + +/** + ****************************************************************************** + * Name: CIccDblPixelBuffer::CIccDblPixelBuffer + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccDblPixelBuffer::Begin() +{ + m_pixelBuf1 = (icFloatNumber*)calloc(m_nMaxChannels, sizeof(icFloatNumber)); + m_pixelBuf2 = (icFloatNumber*)calloc(m_nMaxChannels, sizeof(icFloatNumber)); + + return (!m_nMaxChannels || (m_pixelBuf1!=NULL && m_pixelBuf2!=NULL)); +} + + +/** +****************************************************************************** +* Name: CIccApplyTagMpe::CIccApplyTagMpe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccApplyTagMpe::CIccApplyTagMpe(CIccTagMultiProcessElement *pTag) +{ + m_pTag = pTag; + m_list = NULL; +} + + +/** +****************************************************************************** +* Name: CIccApplyTagMpe::~CIccApplyTagMpe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccApplyTagMpe::~CIccApplyTagMpe() +{ + if (m_list) { + CIccApplyMpeList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + delete i->ptr; + } + + delete m_list; + } +} + + +/** +****************************************************************************** +* Name: CIccApplyTagMpe::CIccApplyTagMpe +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +bool CIccApplyTagMpe::AppendElem(CIccMultiProcessElement *pElem) +{ + if (!m_list) + m_list = new CIccApplyMpeList(); + + if (!m_list) + return false; + + CIccApplyMpe *pApply = pElem->GetNewApply(this); + + if (!pApply) + return false; + + CIccApplyMpePtr ptr; + + ptr.ptr = pApply; + m_list->push_back(ptr); + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::CIccTagMultiProcessElement + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagMultiProcessElement::CIccTagMultiProcessElement(icUInt16Number nInputChannels/* =0 */, icUInt16Number nOutputChannels/* =0 */) +{ + m_nReserved = 0; + m_list = NULL; + m_nProcElements = 0; + m_position = NULL; + + m_nInputChannels = nInputChannels; + m_nOutputChannels = nOutputChannels; +} + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::CIccTagMultiProcessElement + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagMultiProcessElement::CIccTagMultiProcessElement(const CIccTagMultiProcessElement &lut) +{ + m_nReserved = lut.m_nReserved; + + if (lut.m_list) { + m_list = new CIccMultiProcessElementList(); + + CIccMultiProcessElementList::iterator i; + CIccMultiProcessElementPtr ptr; + + for (i=lut.m_list->begin(); i!= lut.m_list->end(); i++) { + ptr.ptr = (CIccMultiProcessElement*)i->ptr->NewCopy(); + m_list->push_back(ptr); + } + } + m_nInputChannels = lut.m_nInputChannels; + m_nOutputChannels = lut.m_nOutputChannels; + + if (lut.m_nProcElements && lut.m_position) { + m_position = (icPositionNumber*)malloc(lut.m_nProcElements*sizeof(icPositionNumber)); + if (m_position) { + memcpy(m_position, lut.m_position, lut.m_nProcElements*sizeof(icPositionNumber)); + } + m_nProcElements = lut.m_nProcElements; + } + +} + +/** + ****************************************************************************** + * Name: &operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagMultiProcessElement &CIccTagMultiProcessElement::operator=(const CIccTagMultiProcessElement &lut) +{ + Clean(); + + m_nReserved = lut.m_nReserved; + + if (lut.m_list) { + m_list = new CIccMultiProcessElementList(); + + CIccMultiProcessElementList::iterator i; + CIccMultiProcessElementPtr ptr; + + for (i=lut.m_list->begin(); i!= lut.m_list->end(); i++) { + ptr.ptr = (CIccMultiProcessElement*)i->ptr->NewCopy(); + m_list->push_back(ptr); + } + } + m_nInputChannels = lut.m_nInputChannels; + m_nOutputChannels = lut.m_nOutputChannels; + + if (lut.m_nProcElements && lut.m_position) { + m_position = (icPositionNumber*)malloc(lut.m_nProcElements*sizeof(icPositionNumber)); + if (m_position) { + memcpy(m_position, lut.m_position, lut.m_nProcElements*sizeof(icPositionNumber)); + } + m_nProcElements = lut.m_nProcElements; + } + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::~CIccTagMultiProcessElement + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagMultiProcessElement::~CIccTagMultiProcessElement() +{ + Clean(); +} + +typedef std::map CIccLutPtrMap; +typedef std::map CIccLutOffsetMap; +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Clean + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagMultiProcessElement::Clean() +{ + if (m_list) { + CIccLutPtrMap map; + CIccMultiProcessElementList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + if (!map[i->ptr].offset) { + map[i->ptr].offset = 1; + delete i->ptr; + } + } + + delete m_list; + m_list = NULL; + } + + if (m_position) { + free(m_position); + m_position = NULL; + } + + m_nProcElements = 0; +} + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::IsSupported + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagMultiProcessElement::IsSupported() +{ + if (m_list) { + CIccMultiProcessElementList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + if (!i->ptr->IsSupported()) + return false; + } + } + + return true; +} + + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagMultiProcessElement::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sprintf(buf, "BEGIN MULTI_PROCESS_ELEMENT_TAG %d %d\r\n", m_nInputChannels, m_nOutputChannels); + sDescription += buf; + sDescription += "\r\n"; + + CIccMultiProcessElementList::iterator i; + int j; + + for (j=0, i=m_list->begin(); i!=m_list->end(); j++, i++) { + sprintf(buf, "PROCESS_ELEMENT #%d\r\n", j+1); + sDescription += buf; + i->ptr->Describe(sDescription); + sDescription += "\r\n"; + } +} + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Attach + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagMultiProcessElement::Attach(CIccMultiProcessElement *pElement) +{ + if (!m_list) { + m_list = new CIccMultiProcessElementList(); + } + + CIccMultiProcessElementPtr ptr; + + ptr.ptr = pElement; + + m_list->push_back(ptr); +} + + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagMultiProcessElement::Read(icUInt32Number size, CIccIO *pIO) +{ + icTagTypeSignature sig; + + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt8Number) + + sizeof(icUInt8Number) + + sizeof(icUInt16Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + Clean(); + + icUInt32Number tagStart = pIO->Tell(); + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + if (!pIO->Read16(&m_nInputChannels)) + return false; + + if (!pIO->Read16(&m_nOutputChannels)) + return false; + + if (!pIO->Read32(&m_nProcElements)) + return false; + + if (headerSize + m_nProcElements*sizeof(icUInt32Number) > size) + return false; + + m_list = new CIccMultiProcessElementList(); + + if (!m_list) + return false; + + icUInt32Number i; + + m_position = (icPositionNumber*)calloc(m_nProcElements, sizeof(icPositionNumber)); + + if (!m_position) + return false; + + CIccLutOffsetMap loadedElements; + + for (i=0; iRead32(&m_position[i].offset)) + return false; + if (!pIO->Read32(&m_position[i].size)) + return false; + } + + CIccMultiProcessElementPtr ptr; + icElemTypeSignature sigElem; + + for (i=0; i size) { + return false; + } + + //Use hash to cache offset duplication + CIccMultiProcessElement *element = loadedElements[m_position[i].offset]; + if (!element) { + icUInt32Number pos = tagStart + m_position[i].offset; + + if (pIO->Seek(pos, icSeekSet)!=(icInt32Number)pos) { + return false; + } + + if (!pIO->Read32(&sigElem)) { + return false; + } + + if (pIO->Seek(pos, icSeekSet)!=(icInt32Number)pos) { + return false; + } + + element = CIccMultiProcessElement::Create(sigElem); + if (!element) { + return false; + } + + if (!element->Read(m_position[i].size, pIO)) { + delete element; + return false; + } + + loadedElements[m_position[i].offset] = element; + } + ptr.ptr = element; + + m_list->push_back(ptr); + } + + return true; +} + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagMultiProcessElement::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + icUInt32Number tagStart = pIO->Tell(); + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + if (!pIO->Write16(&m_nInputChannels)) + return false; + + if (!pIO->Write16(&m_nOutputChannels)) + return false; + + if (m_list) { + m_nProcElements = (icUInt32Number)m_list->size(); + } + else { + m_nProcElements = 0; + } + + if (!pIO->Write32(&m_nProcElements)) + return false; + + if (m_nProcElements) { + icUInt32Number offsetPos = pIO->Tell(); + + if (m_position) { + delete [] m_position; + } + + m_position = (icPositionNumber*)calloc(m_nProcElements, sizeof(icPositionNumber)); + + if (!m_position) + return false; + + //Write an empty position table + icUInt32Number j, zeros[2] = { 0, 0 }; + for (j=0; jWrite32(zeros, 2)!=2) + return false; + } + + CIccLutPtrMap map; + CIccMultiProcessElementList::iterator i; + icUInt32Number start, end; + icPositionNumber position; + + //Write out each process element + for (j=0, i=m_list->begin(); i!=m_list->end(); i++, j++) { + if (map.find(i->ptr)==map.end()) { + start = pIO->Tell(); + + if (!i->ptr->Write(pIO)) + return false; + + end = pIO->Tell(); + + if (!pIO->Sync32()) + return false; + + position.offset = start - tagStart; + position.size = end - start; + + map[i->ptr] = position; + } + m_position[j] = map[i->ptr]; + } + + icUInt32Number endPos = pIO->Tell(); + + if (pIO->Seek(offsetPos, icSeekSet)<0) + return false; + + for (j=0; jWrite32(&m_position[j].offset)) + return false; + if (!pIO->Write32(&m_position[j].size)) + return false; + } + + if (pIO->Seek(endPos, icSeekSet)<0) + return false; + } + + return true; +} + +/** +****************************************************************************** +* Name: CIccTagMultiProcessElement::GetElement +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccMultiProcessElement *CIccTagMultiProcessElement::GetElement(int nIndex) +{ + if (!m_list) + return NULL; + + CIccMultiProcessElementList::iterator i; + int j; + + for(i=m_list->begin(), j=0; jend(); i++, j++); + + if (i!=m_list->end()) + return i->ptr; + + return NULL; +} + +/** +****************************************************************************** +* Name: CIccTagMultiProcessElement::GetNextElemIterator +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +void CIccTagMultiProcessElement::GetNextElemIterator(CIccMultiProcessElementList::iterator &itr) +{ + itr++; +} + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Begin + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagMultiProcessElement::Begin(icElemInterp nInterp/* =icElemInterpLinear */) +{ + if (!m_list || !m_list->size()) { + if (m_nInputChannels != m_nOutputChannels) + return false; + else + return true; + } + + CIccMultiProcessElementList::iterator i; + + m_nBufChannels=0; + + //Now we initialize each processing element checking channel matching as we go + CIccMultiProcessElement *last=NULL; + i = m_list->begin(); + if (i->ptr->NumInputChannels() != m_nInputChannels) + return false; + + for (; i!= m_list->end(); i++) { + if (last) { + if (i->ptr->NumInputChannels() != last->NumOutputChannels()) + return false; + } + last = i->ptr; + + if (m_nBufChannelsNumOutputChannels()) + m_nBufChannels = last->NumOutputChannels(); + + if (!last->Begin(nInterp, this)) + return false; + } + + //The output channels must match + if (last && last->NumOutputChannels() != m_nOutputChannels) + return false; + + return true; +} + + + + +/** +****************************************************************************** +* Name: CIccTagMultiProcessElement::ElementIndex +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +icInt32Number CIccTagMultiProcessElement::ElementIndex(CIccMultiProcessElement *pElem) +{ + CIccMultiProcessElementList::iterator i; + icInt32Number n; + + for (n=0, i=m_list->begin(); i!= m_list->end(); i++, n++) { + if (i->ptr == pElem) + break; + } + if (i==m_list->end()) + n=-1; + + return n; +} + +CIccMultiProcessElementList::iterator CIccTagMultiProcessElement::GetFirstElem() +{ + return m_list->begin(); +} + +CIccMultiProcessElementList::iterator CIccTagMultiProcessElement::GetLastElem() +{ + return m_list->end(); +} + + +/** +****************************************************************************** +* Name: CIccTagMultiProcessElement::GetNewApply +* +* Purpose: +* +* Args: +* +* Return: +******************************************************************************/ +CIccApplyTagMpe *CIccTagMultiProcessElement::GetNewApply() +{ + CIccApplyTagMpe *pApply = new CIccApplyTagMpe(this); + + if (!pApply) + return NULL; + + CIccDblPixelBuffer *pApplyBuf = pApply->GetBuf(); + pApplyBuf->UpdateChannels(m_nBufChannels); + if (!pApplyBuf->Begin()) { + delete pApply; + return NULL; + } + + if (!m_list || !m_list->size()) + return pApply; + + CIccMultiProcessElementList::iterator i, last; + last = GetLastElem(); + for (i=GetFirstElem(); i!=last;) { + pApply->AppendElem(i->ptr); + + GetNextElemIterator(i); + } + + return pApply; +} + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Apply + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagMultiProcessElement::Apply(CIccApplyTagMpe *pApply, icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) const +{ + if (!pApply || !pApply->GetList() || !pApply->GetList()->size()) { + memcpy(pDestPixel, pSrcPixel, m_nInputChannels*sizeof(icFloatNumber)); + return; + } + + CIccDblPixelBuffer *pApplyBuf = pApply->GetBuf(); + CIccApplyMpeIter i = pApply->begin(); + CIccApplyMpeIter next; + + next = i; + next++; + + if (next==pApply->end()) { + //Elements rely on pDestPixel != pSrcPixel + if (pSrcPixel==pDestPixel) { + i->ptr->Apply(pApplyBuf->GetDstBuf(), pSrcPixel); + memcpy(pDestPixel, pApplyBuf->GetDstBuf(), m_nOutputChannels*sizeof(icFloatNumber)); + } + else { + i->ptr->Apply(pDestPixel, pSrcPixel); + } + } + else { + i->ptr->Apply(pApplyBuf->GetDstBuf(), pSrcPixel); + i++; + next++; + pApplyBuf->Switch(); + + while (next != pApply->end()) { + CIccMultiProcessElement *pElem = i->ptr->GetElem(); + + if (!pElem->IsAcs()) { + i->ptr->Apply(pApplyBuf->GetDstBuf(), pApplyBuf->GetSrcBuf()); + pApplyBuf->Switch(); + } + + i++; + next++; + } + + i->ptr->Apply(pDestPixel, pApplyBuf->GetSrcBuf()); + } +} + + +/** + ****************************************************************************** + * Name: CIccTagMultiProcessElement::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccTagMultiProcessElement::Validate(icTagSignature sig, std::string &sReport, + const CIccProfile* pProfile /*=NULL*/) const +{ + icValidateStatus rv = icValidateOK; + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + if (!m_list || !m_list->size()) { + if (m_nInputChannels != m_nOutputChannels) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " No processing elements and input and output channels do not match!\r\n"; + return icValidateCriticalError; + } + else { + sReport += icValidateWarningMsg; + sReport += sSigName; + sReport += " No processing elements.\r\n"; + return icValidateWarning; + } + } + + CIccMultiProcessElementList::iterator i = m_list->begin(); + CIccMultiProcessElement *last=NULL; + + if (i->ptr->NumInputChannels() != m_nInputChannels) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " Mis-matching number of input channels!\r\n"; + return icValidateCriticalError; + } + + for (; i!= m_list->end(); i++) { + if (last) { + if (i->ptr->NumInputChannels() != last->NumOutputChannels()) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + + sReport += "("; + sReport += last->GetClassName(); + sReport += "->"; + sReport += i->ptr->GetClassName(); + + sReport += " Mis-matching number of channels!\r\n"; + return icValidateCriticalError; + } + } + last = i->ptr; + + rv = icMaxStatus(rv, last->Validate(sig, sReport, this)); + } + + if (last && last->NumOutputChannels() != m_nOutputChannels) { + sReport += icValidateCriticalErrorMsg; + sReport += sSigName; + sReport += " Mis-matching number of output channels!\r\n"; + return icValidateCriticalError; + } + + return rv; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccTagMPE.h b/library/src/main/cpp/icc/IccTagMPE.h new file mode 100644 index 00000000..5a949211 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagMPE.h @@ -0,0 +1,410 @@ +/** @file +File: IccTagMPE.h + +Contains: Header for implementation of CIccTagMultiProcessElement +and supporting classes + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2005-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Jan 30, 2005 +// Initial CIccFloatTag prototype development +// +// -Nov 6, 2006 +// Prototype Merged into release +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCTAGMPE_H +#define _ICCTAGMPE_H + +#include "IccTag.h" +#include "IccTagFactory.h" +#include "icProfileHeader.h" +#include +#include + + +//CIccFloatTag support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +typedef enum { + icElemInterpLinear, + icElemInterpTetra, +} icElemInterp; + +class CIccTagMultiProcessElement; +class CIccMultiProcessElement; + +class CIccApplyTagMpe; +class CIccApplyMpe; + +/** +**************************************************************************** +* Class: CIccProcessElementPtr +* +* Purpose: Get std list class to work with pointers to elements rather than +* element objects so they can be shared. +***************************************************************************** +*/ +class CIccMultiProcessElementPtr +{ +public: + CIccMultiProcessElement *ptr; +}; + +typedef std::list CIccMultiProcessElementList; +typedef CIccMultiProcessElementList::iterator CIccMultiProcessElementIter; + +#define icSigMpeLevel0 ((icSignature)0x6D706530) /* 'mpe0' */ + +class CIccApplyMpePtr +{ +public: + CIccApplyMpe *ptr; +}; + +typedef std::list CIccApplyMpeList; +typedef CIccApplyMpeList::iterator CIccApplyMpeIter; + +class IIccExtensionMpe +{ +public: + virtual const char *GetExtClassName() const=0; +}; + +/** +**************************************************************************** +* Class: CIccMultiProcessElement +* +* Purpose: Base Class for Multi Process Elements +***************************************************************************** +*/ +class CIccMultiProcessElement +{ +public: + CIccMultiProcessElement() {} + + virtual ~CIccMultiProcessElement() {} + + static CIccMultiProcessElement* Create(icElemTypeSignature sig); + + virtual CIccMultiProcessElement *NewCopy() const = 0; + + virtual icElemTypeSignature GetType() const = 0; + virtual const icChar *GetClassName() const = 0; + + virtual icUInt16Number NumInputChannels() const { return m_nInputChannels; } + virtual icUInt16Number NumOutputChannels() const { return m_nOutputChannels; } + + virtual bool IsSupported() { return true; } + + virtual void Describe(std::string &sDescription) = 0; + + virtual bool Read(icUInt32Number size, CIccIO *pIO) = 0; + virtual bool Write(CIccIO *pIO) = 0; + + virtual bool Begin(icElemInterp nIterp=icElemInterpLinear, CIccTagMultiProcessElement *pMPE=NULL) = 0; + + virtual CIccApplyMpe* GetNewApply(CIccApplyTagMpe *pApplyTag); + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) const = 0; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const = 0; + + //Future Acs Expansion Element Accessors + virtual bool IsAcs() { return false; } + virtual icAcsSignature GetBAcsSig() { return icSigAcsZero; } + virtual icAcsSignature GetEAcsSig() { return icSigAcsZero; } + + // Allow MPE objects to be extended and get extended object type. + virtual IIccExtensionMpe *GetExtension() { return NULL; } + + //All elements start with a reserved value. Allocate a place to put it. + icUInt32Number m_nReserved; + +protected: + icUInt16Number m_nInputChannels; + icUInt16Number m_nOutputChannels; +}; + +/** +**************************************************************************** +* Class: CIccApplyMpe +* +* Purpose: Base Class for Apply storage for Multi Process Elements +***************************************************************************** +*/ +class CIccApplyMpe +{ +public: + CIccApplyMpe(CIccMultiProcessElement *pElem); + virtual ~CIccApplyMpe(); + + virtual icElemTypeSignature GetType() const { return icSigUnknownElemType; } + virtual const icChar *GetClassName() const { return "CIccApplyMpe"; } + + CIccMultiProcessElement *GetElem() const { return m_pElem; } + + void Apply(icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) { m_pElem->Apply(this, pDestPixel, pSrcPixel); } + +protected: + CIccApplyTagMpe *m_pApplyTag; + + CIccMultiProcessElement *m_pElem; +}; + + +/** +**************************************************************************** +* Class: CIccMpeUnknown +* +* Purpose: Base Class for Process Elements +***************************************************************************** +*/ +class CIccMpeUnknown : public CIccMultiProcessElement +{ +public: + CIccMpeUnknown(); + CIccMpeUnknown(const CIccMpeUnknown &elem); + CIccMpeUnknown &operator=(const CIccMpeUnknown &elem); + virtual CIccMultiProcessElement *NewCopy() const { return new CIccMpeUnknown(*this);} + virtual ~CIccMpeUnknown(); + + virtual icElemTypeSignature GetType() const { return m_sig; } + virtual const icChar *GetClassName() const { return "CIccMpeUnknown"; } + + virtual bool IsSupported() { return false; } + + virtual void Describe(std::string &sDescription); + + void SetType(icElemTypeSignature sig); + void SetChannels(icUInt16Number nInputChannels, icUInt16Number nOutputChannels); + + bool SetDataSize(icUInt32Number nSize, bool bZeroData=true); + icUInt8Number *GetData() { return m_pData; } + + virtual bool Read(icUInt32Number nSize, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual bool Begin(icElemInterp nIterp=icElemInterpLinear, CIccTagMultiProcessElement *pMPE=NULL) { return false; } + virtual CIccApplyMpe *GetNewApply() { return NULL; } + virtual void Apply(CIccApplyMpe *pApply, icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) const {} + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccTagMultiProcessElement* pMPE=NULL) const; + +protected: + icElemTypeSignature m_sig; + icUInt32Number m_nReserved; + icUInt16Number m_nInputChannels; + icUInt16Number m_nOutputChannels; + icUInt32Number m_nSize; + icUInt8Number *m_pData; +}; + + +/** +**************************************************************************** +* Class: CIccDblPixelBuffer +* +* Purpose: The general purpose pixel storage buffer for pixel apply +***************************************************************************** +*/ +class CIccDblPixelBuffer +{ +public: + CIccDblPixelBuffer(); + CIccDblPixelBuffer(const CIccDblPixelBuffer &buf); + CIccDblPixelBuffer &operator=(const CIccDblPixelBuffer &buf); + virtual ~CIccDblPixelBuffer(); + + void Clean(); + void Reset() { m_nLastNumChannels = 0; } + + void UpdateChannels(icUInt16Number nNumChannels) { + m_nLastNumChannels = nNumChannels; + if (nNumChannels>m_nMaxChannels) + m_nMaxChannels=nNumChannels; + } + + bool Begin(); + + icUInt16Number GetMaxChannels() { return m_nMaxChannels; } + icFloatNumber *GetSrcBuf() { return m_pixelBuf1; } + icFloatNumber *GetDstBuf() { return m_pixelBuf2; } + + void Switch() { icFloatNumber *tmp; tmp=m_pixelBuf2; m_pixelBuf2=m_pixelBuf1; m_pixelBuf1=tmp; } + + icUInt16Number GetAvailChannels() { return m_nLastNumChannels & 0x7fff; } + +protected: + //For application + icUInt16Number m_nMaxChannels; + icUInt16Number m_nLastNumChannels; + icFloatNumber *m_pixelBuf1; + icFloatNumber *m_pixelBuf2; +}; + + +/** +**************************************************************************** +* Class: CIccTagMultiProcessElement +* +* Purpose: Apply storage for MPE general purpose processing tags +***************************************************************************** +*/ +class CIccApplyTagMpe +{ +public: + CIccApplyTagMpe(CIccTagMultiProcessElement *pTag); + virtual ~CIccApplyTagMpe(); + + CIccTagMultiProcessElement *GetTag() { return m_pTag; } + + virtual bool AppendElem(CIccMultiProcessElement *pElem); + + CIccDblPixelBuffer *GetBuf() { return &m_applyBuf; } + CIccApplyMpeList *GetList() { return m_list; } + + CIccApplyMpeIter begin() { return m_list->begin(); } + CIccApplyMpeIter end() { return m_list->end(); } + +protected: + CIccTagMultiProcessElement *m_pTag; + + //List of processing elements + CIccApplyMpeList *m_list; + + //Pixel data for Apply + CIccDblPixelBuffer m_applyBuf; +}; + +/** +**************************************************************************** +* Class: CIccTagMultiProcessElement +* +* Purpose: A general purpose processing tag +***************************************************************************** +*/ +class CIccTagMultiProcessElement : public CIccTag +{ +public: + CIccTagMultiProcessElement(icUInt16Number nInputChannels=0, icUInt16Number nOutputChannels=0); + CIccTagMultiProcessElement(const CIccTagMultiProcessElement &lut); + CIccTagMultiProcessElement &operator=(const CIccTagMultiProcessElement &lut); + virtual CIccTag *NewCopy() const { return new CIccTagMultiProcessElement(*this);} + virtual ~CIccTagMultiProcessElement(); + + virtual bool IsSupported(); + + virtual icTagTypeSignature GetType() const { return icSigMultiProcessElementType; } + virtual const icChar *GetClassName() const { return "CIccTagMultiProcessElement"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual void Attach(CIccMultiProcessElement *pElement); + + CIccMultiProcessElement *GetElement(int nIndex); + void DeleteElement(int nIndex); + + virtual bool Begin(icElemInterp nInterp=icElemInterpLinear); + virtual CIccApplyTagMpe *GetNewApply(); + + virtual void Apply(CIccApplyTagMpe *pApply, icFloatNumber *pDestPixel, const icFloatNumber *pSrcPixel) const; + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + icUInt16Number NumInputChannels() const { return m_nInputChannels; } + icUInt16Number NumOutputChannels() const { return m_nOutputChannels; } + +protected: + virtual void Clean(); + virtual void GetNextElemIterator(CIccMultiProcessElementList::iterator &itr); + virtual icInt32Number ElementIndex(CIccMultiProcessElement *pElem); + + virtual CIccMultiProcessElementList::iterator GetFirstElem(); + virtual CIccMultiProcessElementList::iterator GetLastElem(); + + icUInt16Number m_nInputChannels; + icUInt16Number m_nOutputChannels; + + //List of processing elements + CIccMultiProcessElementList *m_list; + + //Offsets of loaded elements + icUInt32Number m_nProcElements; + icPositionNumber *m_position; + + //Number of Buffer Channels needed + icUInt16Number m_nBufChannels; +}; + + +//CIccMpeTag support +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //_ICCTAGMPE_H diff --git a/library/src/main/cpp/icc/IccTagProfSeqId.cpp b/library/src/main/cpp/icc/IccTagProfSeqId.cpp new file mode 100644 index 00000000..9c4965c3 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagProfSeqId.cpp @@ -0,0 +1,693 @@ +/** @file + File: IccProfSeqId.cpp + + Contains: Implementation of prototype profileSequenceIdentifier Tag + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak Oct-21-2006 +// +////////////////////////////////////////////////////////////////////// + +#if defined(WIN32) || defined(WIN64) +#pragma warning( disable: 4786) //disable warning in +#endif + +#include +#include +#include +#include +#include "IccTagProfSeqId.h" +#include "IccUtil.h" +#include "IccIO.h" + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::CIccProfileIdDesc +* +* Purpose: +* +* Args: +* +* Return: +* +***************************************************************************** +*/ +CIccProfileIdDesc::CIccProfileIdDesc() +{ + memset(&m_profileID, 0, sizeof(m_profileID)); +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::CIccProfileIdDesc +* +* Purpose: +* +* Args: +* CIccProfile &profile +* +* Return: +* +***************************************************************************** +*/ +CIccProfileIdDesc::CIccProfileIdDesc(CIccProfile &profile) +{ + m_profileID = profile.m_Header.profileID; + CIccTag *pTag = profile.FindTag(icSigProfileDescriptionTag); + + if (pTag) { + switch (pTag->GetType()) { + case icSigMultiLocalizedUnicodeType: + { + m_desc = *((CIccTagMultiLocalizedUnicode*)pTag); + } + break; + + case icSigTextDescriptionType: + { + CIccTagTextDescription *pText = (CIccTagTextDescription*)pTag; + + m_desc.SetText(pText->GetText()); + } + break; + + case icSigTextType: + { + CIccTagText *pText = (CIccTagText*)pTag; + + m_desc.SetText(pText->GetText()); + } + break; + + default: + break; + } + } +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::CIccProfileIdDesc +* +* Purpose: +* +* Args: +* icProfileID id +* CIccMultiLocalizedUnicode desc +* +* Return: +* +***************************************************************************** +*/ +CIccProfileIdDesc::CIccProfileIdDesc(icProfileID id, CIccTagMultiLocalizedUnicode desc) +{ + m_profileID = id; + m_desc = desc; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::CIccProfileIdDesc +* +* Purpose: +* +* Args: +* const CIccProfileIdDesc &pid +* +* Return: +* +***************************************************************************** +*/ +CIccProfileIdDesc::CIccProfileIdDesc(const CIccProfileIdDesc &pid) +{ + m_profileID = pid.m_profileID; + m_desc = pid.m_desc; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::operator= +* +* Purpose: +* +* Args: +* const CIccProfileIdDesc &pid +* +* Return: +* CIccProfileIdDesc & +***************************************************************************** +*/ +CIccProfileIdDesc &CIccProfileIdDesc::operator=(const CIccProfileIdDesc &pid) +{ + if (&pid == this) + return *this; + + m_profileID = pid.m_profileID; + m_desc = pid.m_desc; + + return *this; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::Describe +* +* Purpose: +* +* Args: +* std::string &sDescription +* +* Return: +* void +***************************************************************************** +*/ +void CIccProfileIdDesc::Describe(std::string &sDescription) +{ + std::string Dump; + + sDescription += "ProfileID:\r\n"; + + size_t i; + char buf[20]; + for (i=0; i size) + return false; + + if (pIO->Read8(&m_profileID, sizeof(icProfileID))!=sizeof(icProfileID)) + return false; + + if (!m_desc.Read(size - sizeof(icProfileID), pIO)) + return false; + + return true; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::Write +* +* Purpose: +* +* Args: +* CIccIO *pIO +* +* Return: +* bool +***************************************************************************** +*/ +bool CIccProfileIdDesc::Write(CIccIO *pIO) +{ + pIO->Write8(&m_profileID, sizeof(icProfileID)); + m_desc.Write(pIO); + + return true; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccProfileIdDesc::Validate +* +* Purpose: +* +* Args: +* std::string &sReport +* +* Return: +* icValidateStatus +***************************************************************************** +*/ +icValidateStatus CIccProfileIdDesc::Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile) const +{ + return m_desc.Validate(sig, sReport, pProfile); +} + + + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::CIccTagProfileSequenceId + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagProfileSequenceId::CIccTagProfileSequenceId() +{ + m_list = new CIccProfileIdDescList(); +} + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::CIccTagProfileSequenceId + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagProfileSequenceId::CIccTagProfileSequenceId(const CIccTagProfileSequenceId &psi) +{ + m_list = new CIccProfileIdDescList(); + + *m_list = *psi.m_list; +} + +/** + ****************************************************************************** + * Name: &operator= + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagProfileSequenceId &CIccTagProfileSequenceId::operator=(const CIccTagProfileSequenceId &psi) +{ + if (&psi == this) + return *this; + + *m_list = *psi.m_list; + + return *this; +} + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::~CIccTagProfileSequenceId + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagProfileSequenceId::~CIccTagProfileSequenceId() +{ + delete m_list; +} + + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::ParseMem + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +CIccTagProfileSequenceId* CIccTagProfileSequenceId::ParseMem(icUInt8Number *pMem, icUInt32Number size) +{ + CIccMemIO IO; + + if (!IO.Attach(pMem, size)) + return NULL; + + CIccTagProfileSequenceId *pProSeqId = new CIccTagProfileSequenceId; + + if (!pProSeqId->Read(size, &IO)) { + delete pProSeqId; + return NULL; + } + + return pProSeqId; +} + + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::Describe + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +void CIccTagProfileSequenceId::Describe(std::string &sDescription) +{ + icChar buf[128]; + + sprintf(buf, "BEGIN ProfileSequenceIdentification_TAG\r\n"); + sDescription += buf; + sDescription += "\r\n"; + + int i; + CIccProfileIdDescList::iterator j; + for (i=0, j=m_list->begin(); j!=m_list->end(); i++, j++) { + sprintf(buf, "ProfileDescription_%d:\r\n", i+1); + sDescription += buf; + j->Describe(sDescription); + } + + sprintf(buf, "END ProfileSequenceIdentification_TAG\r\n"); + sDescription += buf; + sDescription += "\r\n"; +} + + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::Read + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagProfileSequenceId::Read(icUInt32Number size, CIccIO *pIO) +{ + icUInt32Number headerSize = sizeof(icTagTypeSignature) + + sizeof(icUInt32Number) + + sizeof(icUInt32Number); + + if (headerSize > size) + return false; + + if (!pIO) { + return false; + } + + m_list->empty(); + + icUInt32Number sig; + icUInt32Number tagStart = pIO->Tell(); + + if (!pIO->Read32(&sig)) + return false; + + if (!pIO->Read32(&m_nReserved)) + return false; + + icUInt32Number count, i; + + if (!pIO->Read32(&count)) + return false; + + if (headerSize + count*sizeof(icUInt32Number)*2 > size) + return false; + + if (!count) { + return true; + } + + icPositionNumber *pos = new icPositionNumber[count]; + if (!pos) + return false; + + //Read TagDir + for (i=0; iRead32(&pos[i].offset) || + !pIO->Read32(&pos[i].size)) { + delete [] pos; + return false; + } + } + + CIccProfileIdDesc pid; + + for (i=0; i size) { + delete [] pos; + return false; + } + pIO->Seek(tagStart + pos[i].offset, icSeekSet); + + if (!pid.Read(pos[i].size, pIO)) { + delete [] pos; + return false; + } + + m_list->push_back(pid); + } + + delete [] pos; + + return true; +} + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::Write + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +bool CIccTagProfileSequenceId::Write(CIccIO *pIO) +{ + icTagTypeSignature sig = GetType(); + + if (!pIO) + return false; + + icUInt32Number tagStart = pIO->Tell(); + + if (!pIO->Write32(&sig)) + return false; + + if (!pIO->Write32(&m_nReserved)) + return false; + + icUInt32Number i, count = (icUInt32Number)m_list->size(); + + pIO->Write32(&count); + + icPositionNumber *pos = new icPositionNumber[count]; + if (!pos) + return false; + + icUInt32Number dirpos = pIO->Tell(); + + //Write Unintialized TagDir + for (i=0; iWrite32(&pos[i].offset); + pIO->Write32(&pos[i].size); + } + + CIccProfileIdDescList::iterator j; + + //Write Tags + for (i=0, j=m_list->begin(); j!= m_list->end(); i++, j++) { + pos[i].offset = pIO->Tell(); + + j->Write(pIO); + pos[i].size = pIO->Tell() - pos[i].offset; + pos[i].offset -= tagStart; + + pIO->Align32(); + } + + icUInt32Number endpos = pIO->Tell(); + + pIO->Seek(dirpos, icSeekSet); + + //Write TagDir with offsets and sizes + for (i=0; iWrite32(&pos[i].offset); + pIO->Write32(&pos[i].size); + } + + pIO->Seek(endpos, icSeekSet); + + return true; +} + + +/** + ****************************************************************************** + * Name: CIccTagProfileSequenceId::Validate + * + * Purpose: + * + * Args: + * + * Return: + ******************************************************************************/ +icValidateStatus CIccTagProfileSequenceId::Validate(icTagSignature sig, std::string &sReport, + const CIccProfile* pProfile /*=NULL*/) const +{ + icValidateStatus rv = CIccTag::Validate(sig, sReport, pProfile); + + CIccInfo Info; + std::string sSigName = Info.GetSigName(sig); + + CIccProfileIdDescList::iterator i; + + for (i=m_list->begin(); i!=m_list->end(); i++) { + rv = icMaxStatus(rv, i->Validate(sig, sReport, pProfile)); + } + + return rv; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccTagProfileSequenceId::AddProfileDescription +* +* Purpose: +* +* Args: +* CIccProfileIdDesc &profileDesc +* +* Return: +* bool +***************************************************************************** +*/ +bool CIccTagProfileSequenceId::AddProfileDescription(const CIccProfileIdDesc &profileDesc) +{ + m_list->push_back(profileDesc); + + return true; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccTagProfileSequenceId::GetFirst +* +* Purpose: +* +* Args: +* +* Return: +* CIccProfileIdDesc * +***************************************************************************** +*/ +CIccProfileIdDesc *CIccTagProfileSequenceId::GetFirst() +{ + if (m_list->size()) + return &(*(m_list->begin())); + + return NULL; +} + + +/** +**************************************************************************** +* Name: sampleICC::CIccTagProfileSequenceId::GetLast +* +* Purpose: +* +* Args: +* +* Return: +* CIccProfileIdDesc * +***************************************************************************** +*/ +CIccProfileIdDesc *CIccTagProfileSequenceId::GetLast() +{ + if (m_list->size()) + return &(*(m_list->rbegin())); + + return NULL; +} + diff --git a/library/src/main/cpp/icc/IccTagProfSeqId.h b/library/src/main/cpp/icc/IccTagProfSeqId.h new file mode 100644 index 00000000..54277940 --- /dev/null +++ b/library/src/main/cpp/icc/IccTagProfSeqId.h @@ -0,0 +1,157 @@ +/** @file +File: IccTagProfSeqId.h + +Contains: Header for implementation of CIccTagProfSeqId +and supporting classes + +Version: V1 + +Copyright: see ICC Software License +*/ + +/* +* The ICC Software License, Version 0.2 +* +* +* Copyright (c) 2005-2015 The International Color Consortium. All rights +* reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. In the absence of prior written permission, the names "ICC" and "The +* International Color Consortium" must not be used to imply that the +* ICC organization endorses or promotes products derived from this +* software. +* +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR +* ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +* +* This software consists of voluntary contributions made by many +* individuals on behalf of the The International Color Consortium. +* +* +* Membership in the ICC is encouraged when this software is used for +* commercial purposes. +* +* +* For more information on The International Color Consortium, please +* see . +* +* +*/ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Jun 3, 2007 +// Initial CIccTagProfSeqId development +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCTAGPROFSEQID_H +#define _ICCTAGPROFSEQID_H + +#include "IccProfile.h" +#include "IccTag.h" +#include +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +class ICCPROFLIB_API CIccProfileIdDesc +{ +public: + CIccProfileIdDesc(); + CIccProfileIdDesc(CIccProfile &profile); + CIccProfileIdDesc(icProfileID id, CIccTagMultiLocalizedUnicode desc); + CIccProfileIdDesc(const CIccProfileIdDesc &pid); + CIccProfileIdDesc &operator=(const CIccProfileIdDesc &pid); + + void Describe(std::string &sDescription); + + bool Read(icUInt32Number size, CIccIO *pIO); + bool Write(CIccIO *pIO); + + icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + CIccTagMultiLocalizedUnicode m_desc; + icProfileID m_profileID; +}; + +typedef std::list CIccProfileIdDescList; + +/** +**************************************************************************** +* Class: CIccTagProfileSequenceId +* +* Purpose: The ProfileSequenceId tag +***************************************************************************** +*/ +class ICCPROFLIB_API CIccTagProfileSequenceId : public CIccTag +{ +public: + CIccTagProfileSequenceId(); + CIccTagProfileSequenceId(const CIccTagProfileSequenceId &lut); + CIccTagProfileSequenceId &operator=(const CIccTagProfileSequenceId &lut); + virtual CIccTag *NewCopy() const { return new CIccTagProfileSequenceId(*this);} + virtual ~CIccTagProfileSequenceId(); + + static CIccTagProfileSequenceId *ParseMem(icUInt8Number *pMem, icUInt32Number size); + + virtual icTagTypeSignature GetType() const { return icSigProfileSequceIdType; } + virtual const icChar *GetClassName() const { return "CIccTagProfileSequenceId"; } + + virtual void Describe(std::string &sDescription); + + virtual bool Read(icUInt32Number size, CIccIO *pIO); + virtual bool Write(CIccIO *pIO); + + virtual icValidateStatus Validate(icTagSignature sig, std::string &sReport, const CIccProfile* pProfile=NULL) const; + + bool AddProfileDescription(CIccProfile &profile) { return AddProfileDescription(CIccProfileIdDesc(profile)); } + bool AddProfileDescription(const CIccProfileIdDesc &profileDesc); + + CIccProfileIdDesc *GetFirst(); + CIccProfileIdDesc *GetLast(); + + CIccProfileIdDescList::iterator begin() { return m_list->begin(); } + CIccProfileIdDescList::iterator end() { return m_list->end(); } + +protected: + void Cleanup(); + + CIccProfileIdDescList *m_list; +}; + + + +//CIccTagProfSeq support +#ifdef USESAMPLEICCNAMESPACE +} +#endif + +#endif //_ICCTAGPROFSEQID_H diff --git a/library/src/main/cpp/icc/IccUtil.cpp b/library/src/main/cpp/icc/IccUtil.cpp new file mode 100644 index 00000000..d6159898 --- /dev/null +++ b/library/src/main/cpp/icc/IccUtil.cpp @@ -0,0 +1,2026 @@ +/* + File: IccUtil.cpp + + Contains: Implementation of utility classes/functions + + Version: V1 + + Copyright: � see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#include "IccIO.h" +#include "IccUtil.h" +#include "IccTagFactory.h" +#include "IccConvertUTF.h" +#include +#include +#include +#include +#include +#include +#include + +#define PI 3.1415926535897932384626433832795 + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +const char *icValidateWarningMsg = "Warning! - "; +const char *icValidateNonCompliantMsg = "NonCompliant! - "; +const char *icValidateCriticalErrorMsg = "Error! - "; + +/** + ****************************************************************************** +* Name: icRoundOffset +* +* Purpose: Adds offset to floating point value for purposes of rounding +* by casting to and integer based value +* +* Args: +* v - value to offset +* +* Return: +* v with offset added - suitable for casting to some form of integer +****************************************************************************** +*/ +double icRoundOffset(double v) +{ + if (v < 0.0) + return v - 0.5; + else + return v + 0.5; +} + + +/** + ****************************************************************************** + * Name: icMaxStatus + * + * Purpose: return worst status + * + * Args: + * s1, s2 + * + * Return: + ****************************************************************************** + */ +icValidateStatus icMaxStatus(icValidateStatus s1, icValidateStatus s2) +{ + if (s1>s2) + return s1; + return s2; +} + +static icInt32Number icHexDigit(icChar digit) +{ + if (digit>='0' && digit<='9') + return digit-'0'; + if (digit>='A' && digit<='F') + return digit-'A'+10; +/* if (digit>='a' && digit<='f') + return digit-'a'+10;*/ + return -1; +} + + +bool icIsSpaceCLR(icColorSpaceSignature sig) +{ + switch(sig) { + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + return true; + + default: + return false; + } + + return false; +} + +void icColorIndexName(icChar *szName, icColorSpaceSignature csSig, + int nIndex, int nColors, const icChar *szUnknown) +{ + icChar szSig[5]; + int i; + + if (csSig!=icSigUnknownData) { + szSig[0] = (icChar)(csSig>>24); + szSig[1] = (icChar)(csSig>>16); + szSig[2] = (icChar)(csSig>>8); + szSig[3] = (icChar)(csSig); + szSig[4] = '\0'; + + for (i=3; i>0; i--) { + if (szSig[i]==' ') + szSig[i]='\0'; + } + if (nColors==1) { + strcpy(szName, szSig); + } + else if ((size_t)nColors == strlen(szSig)) { + sprintf(szName, "%s_%c", szSig, szSig[nIndex]); + } + else { + sprintf(szName, "%s_%d", szSig, nIndex+1); + } + } + else if (nColors==1) { + strcpy(szName, szUnknown); + } + else { + sprintf(szName, "%s_%d", szUnknown, nIndex+1); + } +} + +void icColorValue(icChar *szValue, icFloatNumber nValue, + icColorSpaceSignature csSig, int nIndex, + bool bUseLegacy) +{ + if (csSig==icSigLabData) { + if (!bUseLegacy) { + if (!nIndex || nIndex>2) + sprintf(szValue, "%7.3lf", nValue * 100.0); + else + sprintf(szValue, "%8.3lf", nValue * 255.0 - 128.0); + } + else { + if (!nIndex || nIndex>2) + sprintf(szValue, "%7.3lf", nValue * 100.0 * 65535.0 / 65280.0); + else + sprintf(szValue, "%8.3lf", nValue * 255.0 * 65535.0 / 65280.0 - 128.0); + } + } + else if (csSig==icSigUnknownData) { + sprintf(szValue, "%8.5lf", nValue); + } + else { + sprintf(szValue, "%7.3lf", nValue * 100.0); + } +} + +/** +************************************************************************** +* Name: icMatrixInvert3x3 +* +* Purpose: +* Inversion of a 3x3 matrix using the Adjoint Cofactor and the determinant of +* the 3x3 matrix. +* +* Note: Matrix index positions: +* 0 1 2 +* 3 4 5 +* 6 7 8 +* +* Args: +* M = matrix to invert. +* +* Return: +* true = matrix is invertible and stored back into M, false = matrix is not +* invertible. +************************************************************************** +*/ +bool icMatrixInvert3x3(icFloatNumber *M) +{ + icFloatNumber m48 = M[4]*M[8]; + icFloatNumber m75 = M[7]*M[5]; + icFloatNumber m38 = M[3]*M[8]; + icFloatNumber m65 = M[6]*M[5]; + icFloatNumber m37 = M[3]*M[7]; + icFloatNumber m64 = M[6]*M[4]; + + icFloatNumber det = M[0]*(m48 - m75) - + M[1]*(m38 - m65) + + M[2]*(m37 - m64); + + if (!det) + return false; + + icFloatNumber Co[9]; + + Co[0] = +(m48 - m75); + Co[1] = -(m38 - m65); + Co[2] = +(m37 - m64); + + Co[3] = -(M[1]*M[8] - M[7]*M[2]); + Co[4] = +(M[0]*M[8] - M[6]*M[2]); + Co[5] = -(M[0]*M[7] - M[6]*M[1]); + + Co[6] = +(M[1]*M[5] - M[4]*M[2]); + Co[7] = -(M[0]*M[5] - M[3]*M[2]); + Co[8] = +(M[0]*M[4] - M[3]*M[1]); + + M[0] = Co[0] / det; + M[1] = Co[3] / det; + M[2] = Co[6] / det; + + M[3] = Co[1] / det; + M[4] = Co[4] / det; + M[5] = Co[7] / det; + + M[6] = Co[2] / det; + M[7] = Co[5] / det; + M[8] = Co[8] / det; + + return true; +} + +/** +************************************************************************** +* Name: icMatrixMultiply3x3 +* +* Purpose: +* Multiply two 3x3 matricies resulting in a 3x3 matrix. +* +* Note: Matrix index positions: +* 0 1 2 +* 3 4 5 +* 6 7 8 +* +* Args: +* result = matrix to recieve result. +* l = left matrix to multiply (matrix multiplication is order dependent) +* r = right matrix to multiply (matrix multiplicaiton is order dependent) +* +************************************************************************** +*/ +void icMatrixMultiply3x3(icFloatNumber* result, + const icFloatNumber* l, + const icFloatNumber* r) +{ + const unsigned int e11 = 0; + const unsigned int e12 = 1; + const unsigned int e13 = 2; + const unsigned int e21 = 3; + const unsigned int e22 = 4; + const unsigned int e23 = 5; + const unsigned int e31 = 6; + const unsigned int e32 = 7; + const unsigned int e33 = 8; + result[e11] = l[e11] * r[e11] + l[e12] * r[e21] + l[e13] * r[e31]; + result[e12] = l[e11] * r[e12] + l[e12] * r[e22] + l[e13] * r[e32]; + result[e13] = l[e11] * r[e13] + l[e12] * r[e23] + l[e13] * r[e33]; + result[e21] = l[e21] * r[e11] + l[e22] * r[e21] + l[e23] * r[e31]; + result[e22] = l[e21] * r[e12] + l[e22] * r[e22] + l[e23] * r[e32]; + result[e23] = l[e21] * r[e13] + l[e22] * r[e23] + l[e23] * r[e33]; + result[e31] = l[e31] * r[e11] + l[e32] * r[e21] + l[e33] * r[e31]; + result[e32] = l[e31] * r[e12] + l[e32] * r[e22] + l[e33] * r[e32]; + result[e33] = l[e31] * r[e13] + l[e32] * r[e23] + l[e33] * r[e33]; +} + +/** +************************************************************************** +* Name: icVectorApplyMatrix3x3 +* +* Purpose: +* Applies a 3x3 matrix to a 3 element column vector. +* +* Note: Matrix index positions: +* 0 1 2 +* 3 4 5 +* 6 7 8 +* +* Note: result = m x v +* +* Args: +* result = vector to receive result. +* m = matrix to multiply +* v = vector to apply matrix to +* +************************************************************************** +*/ +void icVectorApplyMatrix3x3(icFloatNumber* result, + const icFloatNumber* m, + const icFloatNumber* v) +{ + const unsigned int e11 = 0; + const unsigned int e12 = 1; + const unsigned int e13 = 2; + const unsigned int e21 = 3; + const unsigned int e22 = 4; + const unsigned int e23 = 5; + const unsigned int e31 = 6; + const unsigned int e32 = 7; + const unsigned int e33 = 8; + result[0] = m[e11] * v[0] + m[e12] * v[1] + m[e13] * v[2]; + result[1] = m[e21] * v[0] + m[e22] * v[1] + m[e23] * v[2]; + result[2] = m[e31] * v[0] + m[e32] * v[1] + m[e33] * v[2]; +} + + +static inline icFloatNumber icSq(icFloatNumber x) +{ + return x*x; +} + + +icFloatNumber icDeltaE(icFloatNumber *lab1, icFloatNumber *lab2) +{ + return sqrt(icSq(lab1[0]-lab2[0]) + icSq(lab1[1]-lab2[1]) + icSq(lab1[2]-lab2[2])); +} + + +icS15Fixed16Number icDtoF(icFloatNumber num) +{ + icS15Fixed16Number rv; + + if (num<-32768.0) + num = -32768.0; + else if (num>32767.0) + num = 32767.0; + + rv = (icS15Fixed16Number)icRoundOffset((double)num*65536.0); + + return rv; +} + +icFloatNumber icFtoD(icS15Fixed16Number num) +{ + icFloatNumber rv = (icFloatNumber)((double)num / 65536.0); + + return rv; +} + +icU16Fixed16Number icDtoUF(icFloatNumber num) +{ + icU16Fixed16Number rv; + + if (num<0) + num = 0; + else if (num>65535.0) + num = 65535.0; + + rv = (icU16Fixed16Number)icRoundOffset((double)num*65536.0); + + return rv; +} + +icFloatNumber icUFtoD(icU16Fixed16Number num) +{ + icFloatNumber rv = (icFloatNumber)((double)num / 65536.0); + + return rv; +} + +icU1Fixed15Number icDtoUSF(icFloatNumber num) +{ + icU1Fixed15Number rv; + + if (num<0) + num = 0; + else if (num>65535.0/32768.0) + num = 65535.0/32768.0; + + rv = (icU1Fixed15Number)icRoundOffset(num*32768.0); + + return rv; +} + +icFloatNumber icUSFtoD(icU1Fixed15Number num) +{ + icFloatNumber rv = (icFloatNumber)((icFloatNumber)num / 32768.0); + + return rv; +} + +icU8Fixed8Number icDtoUCF(icFloatNumber num) +{ + icU8Fixed8Number rv; + + if (num<0) + num = 0; + else if (num>255.0) + num = 255.0; + + rv = (icU8Fixed8Number)icRoundOffset(num*256.0); + + return rv; +} + +icFloatNumber icUCFtoD(icU8Fixed8Number num) +{ + icFloatNumber rv = (icFloatNumber)((icFloatNumber)num / 256.0); + + return rv; +} + +icUInt8Number icFtoU8(icFloatNumber num) +{ + icUInt8Number rv; + + if (num<0) + num = 0; + else if (num>1.0) + num = 1.0; + + rv = (icUInt8Number)icRoundOffset(num*255.0); + + return rv; +} + +icFloatNumber icU8toF(icUInt8Number num) +{ + icFloatNumber rv = (icFloatNumber)((icFloatNumber)num / 255.0); + + return rv; +} + +icUInt16Number icFtoU16(icFloatNumber num) +{ + icUInt16Number rv; + + if (num<0) + num = 0; + else if (num>1.0) + num = 1.0; + + rv = (icUInt16Number)icRoundOffset(num*65535.0); + + return rv; +} + +icFloatNumber icU16toF(icUInt16Number num) +{ + icFloatNumber rv = (icFloatNumber)((icFloatNumber)num / 65535.0); + + return rv; +} + +icUInt8Number icABtoU8(icFloatNumber num) +{ + icFloatNumber v = num + 128.0f; + if (v<0) + v=0; + else if (v>255) + v=255; + + return (icUInt8Number)(v + 0.5); +} + +icFloatNumber icU8toAB(icUInt8Number num) +{ + return (icFloatNumber)num - 128.0f; +} + +icFloatNumber icD50XYZ[3] = { 0.9642f, 1.0000f, 0.8249f }; +icFloatNumber icD50XYZxx[3] = { 96.42f, 100.00f, 82.49f }; + +void icNormXyz(icFloatNumber *XYZ, icFloatNumber *WhiteXYZ) +{ + if (!WhiteXYZ) + WhiteXYZ = icD50XYZ; + + XYZ[0] = XYZ[0] / WhiteXYZ[0]; + XYZ[1] = XYZ[1] / WhiteXYZ[1]; + XYZ[2] = XYZ[2] / WhiteXYZ[2]; +} + +void icDeNormXyz(icFloatNumber *XYZ, icFloatNumber *WhiteXYZ) +{ + if (!WhiteXYZ) + WhiteXYZ = icD50XYZ; + + XYZ[0] = XYZ[0] * WhiteXYZ[0]; + XYZ[1] = XYZ[1] * WhiteXYZ[1]; + XYZ[2] = XYZ[2] * WhiteXYZ[2]; +} + +static icFloatNumber cubeth(icFloatNumber v) +{ + if (v> 0.008856) { + return (icFloatNumber)ICC_CBRTF(v); + } + else { + return (icFloatNumber)(7.787037037037037037037037037037*v + 16.0/116.0); + } +} + +static icFloatNumber icubeth(icFloatNumber v) +{ + if (v > 0.20689303448275862068965517241379) + return v*v*v; + else +#ifndef SAMPLEICC_NOCLIPLABTOXYZ + if (v>16.0/116.0) +#endif + return (icFloatNumber)((v - 16.0 / 116.0) / 7.787037037037037037037037037037); +#ifndef SAMPLEICC_NOCLIPLABTOXYZ + else + return 0.0; +#endif +} + +void icLabtoXYZ(icFloatNumber *XYZ, icFloatNumber *Lab /*=NULL*/, icFloatNumber *WhiteXYZ /*=NULL*/) +{ + if (!Lab) + Lab = XYZ; + + if (!WhiteXYZ) + WhiteXYZ = icD50XYZ; + + icFloatNumber fy = (icFloatNumber)((Lab[0] + 16.0) / 116.0); + + XYZ[0] = icubeth((icFloatNumber)(Lab[1]/500.0 + fy)) * WhiteXYZ[0]; + XYZ[1] = icubeth(fy) * WhiteXYZ[1]; + XYZ[2] = icubeth((icFloatNumber)(fy - Lab[2]/200.0)) * WhiteXYZ[2]; + +} + +void icXYZtoLab(icFloatNumber *Lab, icFloatNumber *XYZ /*=NULL*/, icFloatNumber *WhiteXYZ /*=NULL*/) +{ + icFloatNumber Xn, Yn, Zn; + + if (!XYZ) + XYZ = Lab; + + if (!WhiteXYZ) + WhiteXYZ = icD50XYZ; + + Xn = cubeth(XYZ[0] / WhiteXYZ[0]); + Yn = cubeth(XYZ[1] / WhiteXYZ[1]); + Zn = cubeth(XYZ[2] / WhiteXYZ[2]); + + Lab[0] = (icFloatNumber)(116.0 * Yn - 16.0); + Lab[1] = (icFloatNumber)(500.0 * (Xn - Yn)); + Lab[2] = (icFloatNumber)(200.0 * (Yn - Zn)); + +} + +void icLch2Lab(icFloatNumber *Lab, icFloatNumber *Lch /*=NULL*/) +{ + if (!Lch) { + Lch = Lab; + } + else + Lab[0] = Lch[0]; + + icFloatNumber a = (icFloatNumber)(Lch[1] * cos(Lch[2] * PI / 180.0)); + icFloatNumber b = (icFloatNumber)(Lch[1] * sin(Lch[2] * PI / 180.0)); + + Lab[1] = a; + Lab[2] = b; +} + +void icLab2Lch(icFloatNumber *Lch, icFloatNumber *Lab /*=NULL*/) +{ + if (!Lab) { + Lab = Lch; + } + else + Lch[0] = Lab[0]; + + icFloatNumber c = sqrt(Lab[1]*Lab[1] + Lab[2]*Lab[2]); + icFloatNumber h = (icFloatNumber)(atan2(Lab[2], Lab[1]) * 180.0 / PI); + while (h<0.0) + h+=360.0; + + Lch[1] = c; + Lch[2] = h; +} + +icFloatNumber icMin(icFloatNumber v1, icFloatNumber v2) +{ + return( v1 < v2 ? v1 : v2 ); +} + +icFloatNumber icMax(icFloatNumber v1, icFloatNumber v2) +{ + return( v1 > v2 ? v1 : v2 ); +} + +icUInt32Number icIntMin(icUInt32Number v1, icUInt32Number v2) +{ + return( v1 < v2 ? v1 : v2 ); +} + +icUInt32Number icIntMax(icUInt32Number v1, icUInt32Number v2) +{ + return( v1 > v2 ? v1 : v2 ); +} + + +void icLabFromPcs(icFloatNumber *Lab) +{ + Lab[0] *= 100.0; + Lab[1] = (icFloatNumber)(Lab[1]*255.0 - 128.0); + Lab[2] = (icFloatNumber)(Lab[2]*255.0 - 128.0); +} + + +void icLabToPcs(icFloatNumber *Lab) +{ + Lab[0] /= 100.0; + Lab[1] = (icFloatNumber)((Lab[1] + 128.0) / 255.0); + Lab[2] = (icFloatNumber)((Lab[2] + 128.0) / 255.0); +} + +void icXyzFromPcs(icFloatNumber *XYZ) +{ + XYZ[0] = (icFloatNumber)(XYZ[0] * 65535.0 / 32768.0); + XYZ[1] = (icFloatNumber)(XYZ[1] * 65535.0 / 32768.0); + XYZ[2] = (icFloatNumber)(XYZ[2] * 65535.0 / 32768.0); +} + +void icXyzToPcs(icFloatNumber *XYZ) +{ + XYZ[0] = (icFloatNumber)(XYZ[0] * 32768.0 / 65535.0); + XYZ[1] = (icFloatNumber)(XYZ[1] * 32768.0 / 65535.0); + XYZ[2] = (icFloatNumber)(XYZ[2] * 32768.0 / 65535.0); +} + + +#define DUMPBYTESPERLINE 16 + +void icMemDump(std::string &sDump, void *pBuf, icUInt32Number nNum) +{ + icUInt8Number *pData = (icUInt8Number *)pBuf; + icChar buf[80], num[10]; + + icInt32Number i, j; + icUInt8Number c; + + icInt32Number lines = (nNum + DUMPBYTESPERLINE - 1)/DUMPBYTESPERLINE; + sDump.reserve(sDump.size() + lines*79); + + for (i=0; i<(icInt32Number)nNum; i++, pData++) { + j=i%DUMPBYTESPERLINE; + if (!j) { + if (i) { + sDump += (const icChar*)buf; + } + memset(buf, ' ', 76); + buf[76] = '\r'; + buf[77] = '\n'; + buf[78] = '\0'; + sprintf(num, "%08X:", i); + strncpy(buf, num, 9); + } + + sprintf(num, "%02X", *pData); + strncpy(buf+10+j*3, num, 2); + + c=*pData; + if (!isprint(c)) + c='.'; + buf[10+16*3 + 1 + j] = c; + } + sDump += buf; +} + +void icMatrixDump(std::string &sDump, icS15Fixed16Number *pMatrix) +{ + icChar buf[128]; + + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", icFtoD(pMatrix[0]), icFtoD(pMatrix[1]), icFtoD(pMatrix[2])); + sDump += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", icFtoD(pMatrix[3]), icFtoD(pMatrix[4]), icFtoD(pMatrix[5])); + sDump += buf; + sprintf(buf, "%8.4lf %8.4lf %8.4lf\r\n", icFtoD(pMatrix[6]), icFtoD(pMatrix[7]), icFtoD(pMatrix[8])); + sDump += buf; +} + +const icChar *icGetSig(icChar *pBuf, icUInt32Number nSig, bool bGetHexVal) +{ + int i; + icUInt32Number sig=nSig; + icUInt8Number c; + + if (!nSig) { + strcpy(pBuf, "NULL"); + return pBuf; + } + + pBuf[0] = '\''; + for (i=1; i<5; i++) { + c=(icUInt8Number)(sig>>24); + if (!isprint(c)) + c='?'; + pBuf[i]=c; + sig <<=8; + } + + if (bGetHexVal) + sprintf(pBuf+5, "' = %08X", nSig); + else + sprintf(pBuf+5, "'"); + + return pBuf; +} + +const icChar *icGetSigStr(icChar *pBuf, icUInt32Number nSig) +{ + int i, j=-1; + icUInt32Number sig=nSig; + icUInt8Number c; + bool bGetHexVal = false; + + for (i=0; i<4; i++) { + c=(icUInt8Number)(sig>>24); + if (!c) { + j=i; + } + else if (j!=-1) { + bGetHexVal = true; + } + else if (!isprint(c)) { + c='?'; + bGetHexVal = true; + } + pBuf[i]=c; + sig <<=8; + } + + if (bGetHexVal) + sprintf(pBuf, "%08Xh", nSig); + else + pBuf[4] = '\0'; + + return pBuf; +} + +icUInt32Number icGetSigVal(const icChar *pBuf) +{ + switch(strlen(pBuf)) { + case 0: + return 0; + + case 1: + return (((unsigned long)pBuf[0])<<24) + + 0x202020; + + case 2: + return (((unsigned long)pBuf[0])<<24) + + (((unsigned long)pBuf[1])<<16) + + 0x2020; + + case 3: + return (((unsigned long)pBuf[0])<<24) + + (((unsigned long)pBuf[1])<<16) + + (((unsigned long)pBuf[2])<<8) + + 0x20; + + case 4: + default: + return (((unsigned long)pBuf[0])<<24) + + (((unsigned long)pBuf[1])<<16) + + (((unsigned long)pBuf[2])<<8) + + (((unsigned long)pBuf[3])); + + case 9: + icUInt32Number v; + sscanf(pBuf, "%x", &v); + return v; + } +} + + +icUInt32Number icGetSpaceSamples(icColorSpaceSignature sig) +{ + switch(sig) { + case icSigGrayData: + case icSigGamutData: + return 1; + + case icSig2colorData: + return 2; + + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigRgbData: + case icSigHsvData: + case icSigHlsData: + case icSigCmyData: + case icSig3colorData: + case icSigDevLabData: + case icSigDevXYZData: + return 3; + + case icSigCmykData: + case icSig4colorData: + return 4; + + case icSig5colorData: + return 5; + + case icSig6colorData: + return 6; + + case icSig7colorData: + return 7; + + case icSig8colorData: + return 8; + + case icSig9colorData: + return 9; + + case icSig10colorData: + return 10; + + case icSig11colorData: + return 11; + + case icSig12colorData: + return 12; + + case icSig13colorData: + return 13; + + case icSig14colorData: + return 14; + + case icSig15colorData: + return 15; + + case icSigNamedData: + default: + { + //check for non-ICC compliant 'MCHx' case provided by littlecms + if ((sig&0xffffff00)==0x4d434800) { + int d0=icHexDigit(sig&0xff); + if (d0>0) + return d0; + } + + } + return 0; + } +} + +const icChar *CIccInfo::GetUnknownName(icUInt32Number val) +{ + icChar buf[24]; + if (!val) + return "Unknown"; + + sprintf(m_szStr, "Unknown %s", icGetSig(buf, val)); + + return m_szStr; +} + +const icChar *CIccInfo::GetVersionName(icUInt32Number val) +{ + icFloatNumber ver = (icFloatNumber)(((val>>28)&0xf)*10.0 + ((val>>24)&0xf) + + ((val>>20)&0xf)/10.0 + ((val>>16)&0xf)/100.0); + + sprintf(m_szStr, "%.2lf", ver); + + return m_szStr; +} + +const icChar *CIccInfo::GetDeviceAttrName(icUInt64Number val) +{ + if (val & icTransparency) + strcpy(m_szStr, "Transparency"); + else + strcpy(m_szStr, "Reflective"); + + int l=(int)strlen(m_szStr); + + if (val & icMatte) + strcpy(m_szStr+l, " | Matte"); + else + strcpy(m_szStr+l, " | Glossy"); + + return m_szStr; +} + +const icChar *CIccInfo::GetProfileFlagsName(icUInt32Number val) +{ + if (val & icEmbeddedProfileTrue) + strcpy(m_szStr, "EmbeddedProfileTrue"); + else + strcpy(m_szStr, "EmbeddedProfileFalse"); + + int l=(int)strlen(m_szStr); + + if (val & icUseWithEmbeddedDataOnly) + strcpy(m_szStr+l, " | UseWithEmbeddedDataOnly"); + else + strcpy(m_szStr+l, " | UseAnywhere"); + + return m_szStr; +} + +const icChar *CIccInfo::GetTagSigName(icTagSignature sig) +{ + const icChar *rv = CIccTagCreator::GetTagSigName(sig); + if (rv) { + return rv; + } + return GetUnknownName(sig); +} + +const icChar *CIccInfo::GetTechnologySigName(icTechnologySignature sig) +{ + switch(sig) { + case icSigDigitalCamera: + return "DigitalCamera"; + + case icSigFilmScanner: + return "FilmScanner"; + + case icSigReflectiveScanner: + return "ReflectiveScanner"; + + case icSigInkJetPrinter: + return "InkJetPrinter"; + + case icSigThermalWaxPrinter: + return "ThermalWaxPrinter"; + + case icSigElectrophotographicPrinter: + return "ElectrophotographicPrinter"; + + case icSigElectrostaticPrinter: + return "ElectrostaticPrinter"; + + case icSigDyeSublimationPrinter: + return "DyeSublimationPrinter"; + + case icSigPhotographicPaperPrinter: + return "PhotographicPaperPrinter"; + + case icSigFilmWriter: + return "FilmWriter"; + + case icSigVideoMonitor: + return "VideoMonitor"; + + case icSigVideoCamera: + return "VideoCamera"; + + case icSigProjectionTelevision: + return "ProjectionTelevision"; + + case icSigCRTDisplay: + return "CRTDisplay"; + + case icSigPMDisplay: + return "PMDisplay"; + + case icSigAMDisplay: + return "AMDisplay"; + + case icSigPhotoCD: + return "PhotoCD"; + + case icSigPhotoImageSetter: + return "PhotoImageSetter"; + + case icSigGravure: + return "Gravure"; + + case icSigOffsetLithography: + return "OffsetLithography"; + + case icSigSilkscreen: + return "Silkscreen"; + + case icSigFlexography: + return "Flexography"; + + default: + return GetUnknownName(sig); + } +} + +const icChar *CIccInfo::GetTagTypeSigName(icTagTypeSignature sig) +{ + const icChar *rv = CIccTagCreator::GetTagTypeSigName(sig); + if (rv) { + return rv; + } + + return GetUnknownName(sig); +} + + +const icChar *CIccInfo::GetColorSpaceSigName(icColorSpaceSignature sig) +{ + switch (sig) { + case icSigXYZData: + case icSigDevXYZData: + return "XYZData"; + + case icSigLabData: + case icSigDevLabData: + return "LabData"; + + case icSigLuvData: + return "LuvData"; + + case icSigYCbCrData: + return "YCbCrData"; + + case icSigYxyData: + return "YxyData"; + + case icSigRgbData: + return "RgbData"; + + case icSigGrayData: + return "GrayData"; + + case icSigHsvData: + return "HsvData"; + + case icSigHlsData: + return "HlsData"; + + case icSigCmykData: + return "CmykData"; + + case icSigCmyData: + return "CmyData"; + + + case icSigMCH1Data: + return "MCH1Data/1colorData"; + + case icSigMCH2Data: + return "MCH2Data/2colorData"; + + case icSigMCH3Data: + return "MCH3Data/3colorData"; + + case icSigMCH4Data: + return "MCH4Data/4colorData"; + + case icSigMCH5Data: + return "MCH5Data/5colorData"; + + case icSigMCH6Data: + return "MCH6Data/6colorData"; + + case icSigMCH7Data: + return "MCH7Data/7colorData"; + + case icSigMCH8Data: + return "MCH8Data/8colorData"; + + case icSigMCH9Data: + return "MCH9Data/9colorData"; + + case icSigMCHAData: + return "MCHAData/10colorData"; + + case icSigMCHBData: + return "MCHBData/11colorData"; + + case icSigMCHCData: + return "MCHCData/12colorData"; + + case icSigMCHDData: + return "MCHDData/13colorData"; + + case icSigMCHEData: + return "MCHEData/14colorData"; + + case icSigMCHFData: + return "MCHFData/15colorData"; + + case icSigGamutData: + return "GamutData"; + + case icSigNamedData: + return "NamedData"; + + default: + return GetUnknownName(sig); + } +} + +const icChar *CIccInfo::GetProfileClassSigName(icProfileClassSignature sig) +{ + switch (sig) { + case icSigInputClass: + return "InputClass"; + + case icSigDisplayClass: + return "DisplayClass"; + + case icSigOutputClass: + return "OutputClass"; + + case icSigLinkClass: + return "LinkClass"; + + case icSigAbstractClass: + return "AbstractClass"; + + case icSigColorSpaceClass: + return "ColorSpaceClass"; + + case icSigNamedColorClass: + return "NamedColorClass"; + + default: + return GetUnknownName(sig); + } +} + +const icChar *CIccInfo::GetPlatformSigName(icPlatformSignature sig) +{ + switch (sig) { + case icSigMacintosh: + return "Macintosh"; + + case icSigMicrosoft: + return "Microsoft"; + + case icSigSolaris: + return "Solaris"; + + case icSigSGI: + return "SGI"; + + case icSigTaligent: + return "Taligent"; + + case icSigUnkownPlatform: + return "Unknown"; + + default: + return GetUnknownName(sig); + } +} + + +//The following signatures come from the signature registry +//Return the Description (minus CMM). +const icChar *CIccInfo::GetCmmSigName(icCmmSignature sig) +{ + switch (sig) { + case icSigAdobe: + return "Adobe"; + + case icSigApple: + return "Apple"; + + case icSigColorGear: + return "ColorGear"; + + case icSigColorGearLite: + return "ColorGear Lite"; + + case icSigFujiFilm: + return "Fuji Film"; + + case icSigHarlequinRIP: + return "Harlequin RIP"; + + case icSigArgyllCMS: + return "Argyll CMS"; + + case icSigLogoSync: + return "LogoSync"; + + case icSigHeidelberg: + return "Heidelberg"; + + case icSigLittleCMS: + return "Little CMS"; + + case icSigKodak: + return "Kodak"; + + case icSigKonicaMinolta: + return "Konica Minolta"; + + case icSigMutoh: + return "Mutoh"; + + case icSigSampleICC: + return "SampleIcc"; + + case icSigTheImagingFactory: + return "the imaging factory"; + + default: + return GetUnknownName(sig); + } +} + + +const icChar *CIccInfo::GetReferenceMediumGamutSigNameName(icReferenceMediumGamutSignature sig) +{ + switch (sig) { + case icSigPerceptualReferenceMediumGamut: + return "perceptualReferenceMediumGamut"; + + default: + return GetUnknownName(sig); + } +} + + +const icChar *CIccInfo::GetColorimetricIntentImageStateName(icColorimetricIntentImageStateSignature sig) +{ + switch (sig) { + case icSigSceneColorimetryEstimates: + return "Scene Colorimetry Estimates"; + + case icSigSceneAppearanceEstimates: + return "Scene Appearance Estimates"; + + case icSigFocalPlaneColorimetryEstimates: + return "Focal Plane Colorimetry Estimates"; + + case icSigReflectionHardcopyOriginalColorimetry: + return "Reflection Hardcopy Original Colorimetry"; + + case icSigReflectionPrintOutputColorimetry: + return "Reflection Print Output Colorimetry"; + + default: + return GetUnknownName(sig); + } +} + + +const icChar *CIccInfo::GetSigName(icUInt32Number sig) +{ + const icChar *rv; + + rv = GetTagSigName((icTagSignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetTechnologySigName((icTechnologySignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetTagTypeSigName((icTagTypeSignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetColorSpaceSigName((icColorSpaceSignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetProfileClassSigName((icProfileClassSignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetPlatformSigName((icPlatformSignature)sig); + if (rv != m_szStr) + return rv; + + rv = GetReferenceMediumGamutSigNameName((icReferenceMediumGamutSignature)sig); + if (rv != m_szStr) + return rv; + + return GetColorimetricIntentImageStateName((icColorimetricIntentImageStateSignature)sig); +} + + +const icChar *CIccInfo::GetMeasurementFlareName(icMeasurementFlare val) +{ + switch (val) { + case icFlare0: + return "Flare 0"; + + case icFlare100: + return "Flare 100"; + + case icMaxEnumFlare: + return "Max Flare"; + + default: + sprintf(m_szStr, "Unknown Flare '%d'", (int)val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetMeasurementGeometryName(icMeasurementGeometry val) +{ + switch (val) { + case icGeometryUnknown: + return "Geometry Unknown"; + + case icGeometry045or450: + return "Geometry 0-45 or 45-0"; + + case icGeometry0dord0: + return "Geometry 0-d or d-0"; + + case icMaxEnumGeometry: + return "Max Geometry"; + + default: + sprintf(m_szStr, "Unknown Geometry '%d'", (int)val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetRenderingIntentName(icRenderingIntent val) +{ + switch (val) { + case icPerceptual: + return "Perceptual"; + + case icRelativeColorimetric: + return "Relative Colorimetric"; + + case icSaturation: + return "Saturation"; + + case icAbsoluteColorimetric: + return "Absolute Colorimetric"; + + default: + sprintf(m_szStr, "Unknown Intent '%d", val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetSpotShapeName(icSpotShape val) +{ + switch (val) { + case icSpotShapeUnknown: + return "Spot Shape Unknown"; + + case icSpotShapePrinterDefault: + return "Spot Shape Printer Default"; + + case icSpotShapeRound: + return "Spot Shape Round"; + + case icSpotShapeDiamond: + return "Spot Shape Diamond"; + + case icSpotShapeEllipse: + return "Spot Shape Ellipse"; + + case icSpotShapeLine: + return "Spot Shape Line"; + + case icSpotShapeSquare: + return "Spot Shape Square"; + + case icSpotShapeCross: + return "Spot Shape Cross"; + + default: + sprintf(m_szStr, "Unknown Spot Shape '%d", val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetStandardObserverName(icStandardObserver val) +{ + switch (val) { + case icStdObsUnknown: + return "Unknown observer"; + + case icStdObs1931TwoDegrees: + return "CIE 1931 (two degree) standard observer"; + + case icStdObs1964TenDegrees: + return "CIE 964 (ten degree) standard observer"; + + default: + sprintf(m_szStr, "Unknown Observer '%d", val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetIlluminantName(icIlluminant val) +{ + switch (val) { + case icIlluminantUnknown: + return "Illuminant Unknown"; + + case icIlluminantD50: + return "Illuminant D50"; + + case icIlluminantD65: + return "Illuminant D65"; + + case icIlluminantD93: + return "Illuminant D93"; + + case icIlluminantF2: + return "Illuminant F2"; + + case icIlluminantD55: + return "Illuminant D55"; + + case icIlluminantA: + return "Illuminant A"; + + case icIlluminantEquiPowerE: + return "Illuminant EquiPowerE"; + + case icIlluminantF8: + return "Illuminant F8"; + + default: + sprintf(m_szStr, "Unknown Illuminant '%d", val); + return m_szStr; + } +} + +const icChar *CIccInfo::GetMeasurementUnit(icSignature sig) +{ + switch (sig) { + case icSigStatusA: + return "Status A"; + + case icSigStatusE: + return "Status E"; + + case icSigStatusI: + return "Status I"; + + case icSigStatusT: + return "Status T"; + + case icSigStatusM: + return "Status M"; + + case icSigDN: + return "DIN with no polarizing filter"; + + case icSigDNP: + return "DIN with polarizing filter"; + + case icSigDNN: + return "Narrow band DIN with no polarizing filter"; + + case icSigDNNP: + return "Narrow band DIN with polarizing filter"; + + default: + { + char buf[10]; + buf[0] = (char)(sig>>24); + buf[1] = (char)(sig>>16); + buf[2] = (char)(sig>>8); + buf[3] = (char)(sig); + buf[4] = '\0'; + + sprintf(m_szStr, "Unknown Measurement Type '%s'", buf); + return m_szStr; + } + } +} + + +const icChar *CIccInfo::GetProfileID(icProfileID *profileID) +{ + char *ptr = m_szStr; + int i; + + for (i=0; i<16; i++, ptr+=2) { + sprintf(ptr, "%02x", profileID->ID8[i]); + } + + return m_szStr; +} + +bool CIccInfo::IsProfileIDCalculated(icProfileID *profileID) +{ + int i; + + for (i=0; i<16; i++) { + if (profileID->ID8[i]) + break; + } + + return i<16; +} + +const icChar *CIccInfo::GetColorantEncoding(icColorantEncoding colorant) +{ + switch(colorant) { + case icColorantITU: + return "ITU-R BT.709"; + + case icColorantSMPTE: + return "SMPTE RP145-1994"; + + case icColorantEBU: + return "EBU Tech.3213-E"; + + case icColorantP22: + return "P22"; + + default: + return "Customized Encoding"; + } +} + +icValidateStatus CIccInfo::CheckData(std::string &sReport, const icXYZNumber &XYZ) +{ + icValidateStatus rv = icValidateOK; + + if (XYZ.X < 0) { + sReport += icValidateNonCompliantMsg; + sReport += " - XYZNumber: Negative X value!\r\n"; + rv = icValidateNonCompliant; + } + + if (XYZ.Y < 0) { + sReport += icValidateNonCompliantMsg; + sReport += " - XYZNumber: Negative Y value!\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + if (XYZ.Z < 0) { + sReport += icValidateNonCompliantMsg; + sReport += " - XYZNumber: Negative Z value!\r\n"; + rv = icMaxStatus(rv, icValidateNonCompliant); + } + + return rv; +} + +icValidateStatus CIccInfo::CheckData(std::string &sReport, const icDateTimeNumber &dateTime) +{ + icValidateStatus rv = icValidateOK; + + struct tm *newtime; + time_t long_time; + + time( &long_time ); /* Get time as long integer. */ + newtime = localtime( &long_time ); + + icChar buf[128]; + if (dateTime.year<1992) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid year!\r\n",dateTime.year); + sReport += buf; + rv = icValidateWarning; + } + + int year = newtime->tm_year+1900; + if (newtime->tm_mon==11 && newtime->tm_mday==31) { + if (dateTime.year>(year+1)) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid year!\r\n",dateTime.year); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + } + else { + if (dateTime.year>year) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid year!\r\n",dateTime.year); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + } + + if (dateTime.month<1 || dateTime.month>12) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid month!\r\n",dateTime.month); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (dateTime.day<1 || dateTime.day>31) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid day!\r\n",dateTime.day); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (dateTime.month==2) { + if (dateTime.day>29) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid day for February!\r\n",dateTime.day); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (dateTime.day==29) { + if ((dateTime.year%4)!=0) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid day for February, year is not a leap year(%u)!\r\n",dateTime.day, dateTime.year); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + } + } + + if (dateTime.hours>23) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid hour!\r\n",dateTime.hours); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (dateTime.minutes>59) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid minutes!\r\n",dateTime.minutes); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + if (dateTime.seconds>59) { + sReport += icValidateWarningMsg; + sprintf(buf," - %u: Invalid seconds!\r\n",dateTime.hours); + sReport += buf; + rv = icMaxStatus(rv, icValidateWarning); + } + + return rv; +} + +bool CIccInfo::IsValidSpace(icColorSpaceSignature sig) +{ + bool rv = true; + + switch(sig) { + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigRgbData: + case icSigGrayData: + case icSigHsvData: + case icSigHlsData: + case icSigCmykData: + case icSigCmyData: + case icSigMCH1Data: + case icSigNamedData: + case icSigGamutData: + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + break; + + default: + rv = false; + } + + return rv; +} + +CIccUTF16String::CIccUTF16String() +{ + m_alloc=64; + m_len = 0; + m_str = (icUInt16Number*)calloc(m_alloc, sizeof(icUInt16Number)); +} + +CIccUTF16String::CIccUTF16String(const icUInt16Number *uzStr) +{ + m_len = WStrlen(uzStr); + m_alloc = AllocSize(m_len+1); + + m_str = (icUInt16Number *)malloc(m_alloc*sizeof(icUInt16Number)); + memcpy(m_str, uzStr, (m_len+1)*sizeof(icUInt16Number)); +} + +CIccUTF16String::CIccUTF16String(const char *szStr) +{ + size_t sizeSrc = strlen(szStr); + + if (sizeSrc) { + m_alloc = AllocSize(sizeSrc*2+2); + m_str = (UTF16 *)calloc(m_alloc, sizeof(icUInt16Number)); //overallocate to allow for up to 4 bytes per character + UTF16 *szDest = m_str; + icConvertUTF8toUTF16((const UTF8 **)&szStr, (const UTF8 *)&szStr[sizeSrc], &szDest, &szDest[m_alloc], lenientConversion); + if (m_str[0]==0xfeff) { + size_t i; + for (i=1; m_str[i]; i++) + m_str[i-1] = m_str[i]; + m_str[i-1] = 0; + } + m_len = WStrlen(m_str); + } + else { + m_alloc = 64; + m_len = 0; + m_str = (icUInt16Number*)calloc(m_alloc, sizeof(icUInt16Number)); + } +} + +CIccUTF16String::CIccUTF16String(const CIccUTF16String &str) +{ + m_alloc = str.m_alloc; + m_len = str.m_len; + m_str = (icUInt16Number*)malloc(m_alloc*sizeof(icUInt16Number)); + + memcpy(m_str, str.m_str, (m_alloc)*sizeof(icUInt16Number)); +} + +CIccUTF16String::~CIccUTF16String() +{ + free(m_str); +} + +void CIccUTF16String::Clear() +{ + m_len = 0; + m_str[0] = 0; +} + +void CIccUTF16String::Resize(size_t len) +{ + if (len>m_alloc) { + size_t nAlloc = AllocSize(len+1); + + m_str = (icUInt16Number*)realloc(m_str, nAlloc*sizeof(icUInt16Number)); + m_alloc = nAlloc; + } + + if (len>m_len) { + memset(&m_str[m_len], 0x0020, (len-m_len)*sizeof(icUInt16Number)); + } + m_len = len; + m_str[m_len] = 0; +} + +size_t CIccUTF16String::WStrlen(const icUInt16Number *uzStr) +{ + size_t n=0; + while(uzStr[n]) n++; + + return n; +} + +CIccUTF16String& CIccUTF16String::operator=(const CIccUTF16String &wstr) +{ + if (m_alloc<=wstr.m_alloc) { + m_str = (icUInt16Number*)realloc(m_str, wstr.m_alloc*sizeof(icUInt16Number)); + m_alloc = wstr.m_alloc; + } + m_len = wstr.m_len; + + memcpy(m_str, wstr.m_str, (m_len+1)*sizeof(icUInt16Number)); + + return *this; +} + +CIccUTF16String& CIccUTF16String::operator=(const char *szStr) +{ + FromUtf8(szStr, 0); + + return *this; +} + +CIccUTF16String& CIccUTF16String::operator=(const icUInt16Number *uzStr) +{ + size_t n = WStrlen(uzStr); + size_t nAlloc = AllocSize(n+1); + + if (m_alloc<=nAlloc) { + m_str = (icUInt16Number*)realloc(m_str, nAlloc*sizeof(icUInt16Number)); + m_alloc =nAlloc; + } + m_len = n; + + memcpy(m_str, uzStr, (m_len+1)*sizeof(icUInt16Number)); + + return *this; +} + +bool CIccUTF16String::operator==(const CIccUTF16String &str) const +{ + if (str.m_len != m_len) + return false; + + size_t i; + + for (i=0; i. + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Initial implementation by Max Derhak 5-15-2003 +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCUTIL_H +#define _ICCUTIL_H + +#include "IccDefs.h" +#include "IccProfLibConf.h" +#include + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +double ICCPROFLIB_API icRoundOffset(double v); + +icValidateStatus ICCPROFLIB_API icMaxStatus(icValidateStatus s1, icValidateStatus s2); +bool ICCPROFLIB_API icIsSpaceCLR(icColorSpaceSignature sig); + +void ICCPROFLIB_API icColorIndexName(icChar *szName, icColorSpaceSignature csSig, + int nIndex, int nColors, const icChar *szUnknown); +void ICCPROFLIB_API icColorValue(icChar *szValue, icFloatNumber nValue, + icColorSpaceSignature csSig, int nIndex, bool bUseLegacy=false); + +bool ICCPROFLIB_API icMatrixInvert3x3(icFloatNumber *matrix); +void ICCPROFLIB_API icMatrixMultiply3x3(icFloatNumber *result, + const icFloatNumber *l, + const icFloatNumber *r); +void ICCPROFLIB_API icVectorApplyMatrix3x3(icFloatNumber *result, + const icFloatNumber *m, + const icFloatNumber *v); + +icS15Fixed16Number ICCPROFLIB_API icDtoF(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icFtoD(icS15Fixed16Number num); + +icU16Fixed16Number ICCPROFLIB_API icDtoUF(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icUFtoD(icU16Fixed16Number num); + +icU1Fixed15Number ICCPROFLIB_API icDtoUSF(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icUSFtoD(icU1Fixed15Number num); + +icU8Fixed8Number ICCPROFLIB_API icDtoUCF(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icUCFtoD(icU8Fixed8Number num); + +/*0 to 255 <-> 0.0 to 1.0*/ +icUInt8Number ICCPROFLIB_API icFtoU8(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icU8toF(icUInt8Number num); + +/*0 to 65535 <-> 0.0 to 1.0*/ +icUInt16Number ICCPROFLIB_API icFtoU16(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icU16toF(icUInt16Number num); + +/*0 to 255 <-> -128.0 to 127.0*/ +icUInt8Number ICCPROFLIB_API icABtoU8(icFloatNumber num); +icFloatNumber ICCPROFLIB_API icU8toAB(icUInt8Number num); + +extern ICCPROFLIB_API icFloatNumber icD50XYZ[3]; +extern ICCPROFLIB_API icFloatNumber icD50XYZxx[3]; + +void ICCPROFLIB_API icNormXYZ(icFloatNumber *XYZ, icFloatNumber *WhiteXYZ=NULL); +void ICCPROFLIB_API icDeNormXYZ(icFloatNumber *XYZ, icFloatNumber *WhiteXYZ=NULL); + +void ICCPROFLIB_API icXYZtoLab(icFloatNumber *Lab, icFloatNumber *XYZ=NULL, icFloatNumber *WhiteXYZ=NULL); +void ICCPROFLIB_API icLabtoXYZ(icFloatNumber *XYZ, icFloatNumber *Lab=NULL, icFloatNumber *WhiteXYZ=NULL); + +void ICCPROFLIB_API icLab2Lch(icFloatNumber *Lch, icFloatNumber *Lab=NULL); +void ICCPROFLIB_API icLch2Lab(icFloatNumber *Lab, icFloatNumber *Lch=NULL); + +icFloatNumber ICCPROFLIB_API icMin(icFloatNumber v1, icFloatNumber v2); +icFloatNumber ICCPROFLIB_API icMax(icFloatNumber v1, icFloatNumber v2); + +icUInt32Number ICCPROFLIB_API icIntMin(icUInt32Number v1, icUInt32Number v2); +icUInt32Number ICCPROFLIB_API icIntMax(icUInt32Number v1, icUInt32Number v2); + +icFloatNumber ICCPROFLIB_API icDeltaE(icFloatNumber *Lab1, icFloatNumber *Lab2); + +/**Floating point encoding of Lab in PCS is in range 0.0 to 1.0 */ +///Here are some conversion routines to convert to regular Lab encoding +void ICCPROFLIB_API icLabFromPcs(icFloatNumber *Lab); +void ICCPROFLIB_API icLabToPcs(icFloatNumber *Lab); + +/** Floating point encoding of XYZ in PCS is in range 0.0 to 1.0 + (Note: X=1.0 is encoded as about 0.5)*/ +///Here are some conversion routines to convert to regular XYZ encoding +void ICCPROFLIB_API icXyzFromPcs(icFloatNumber *XYZ); +void ICCPROFLIB_API icXyzToPcs(icFloatNumber *XYZ); + + +void ICCPROFLIB_API icMemDump(std::string &sDump, void *pBuf, icUInt32Number nNum); +void ICCPROFLIB_API icMatrixDump(std::string &sDump, icS15Fixed16Number *pMatrix); +ICCPROFLIB_API const icChar* icGetSig(icChar *pBuf, icUInt32Number sig, bool bGetHexVal=true); +ICCPROFLIB_API const icChar* icGetSigStr(icChar *pBuf, icUInt32Number nSig); + +icUInt32Number ICCPROFLIB_API icGetSigVal(const icChar *pBuf); +icUInt32Number ICCPROFLIB_API icGetSpaceSamples(icColorSpaceSignature sig); + +ICCPROFLIB_API extern const char *icValidateWarningMsg; +ICCPROFLIB_API extern const char *icValidateNonCompliantMsg; +ICCPROFLIB_API extern const char *icValidateCriticalErrorMsg; + +#ifdef ICC_BYTE_ORDER_LITTLE_ENDIAN +inline void icSwab16Ptr(void *pVoid) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + tmp = ptr[0]; ptr[0] = ptr[1]; ptr[1] = tmp; +} + +inline void icSwab16Array(void *pVoid, int num) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + while (num>0) { + tmp = ptr[0]; ptr[0] = ptr[1]; ptr[1] = tmp; + ptr += 2; + num--; + } +} + +inline void icSwab32Ptr(void *pVoid) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + tmp = ptr[0]; ptr[0] = ptr[3]; ptr[3] = tmp; + tmp = ptr[1]; ptr[1] = ptr[2]; ptr[2] = tmp; +} + +inline void icSwab32Array(void *pVoid, int num) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + while (num>0) { + tmp = ptr[0]; ptr[0] = ptr[3]; ptr[3] = tmp; + tmp = ptr[1]; ptr[1] = ptr[2]; ptr[2] = tmp; + ptr += 4; + num--; + } + +} + +inline void icSwab64Ptr(void *pVoid) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + tmp = ptr[0]; ptr[0] = ptr[7]; ptr[7] = tmp; + tmp = ptr[1]; ptr[1] = ptr[6]; ptr[6] = tmp; + tmp = ptr[2]; ptr[2] = ptr[5]; ptr[5] = tmp; + tmp = ptr[3]; ptr[3] = ptr[4]; ptr[4] = tmp; +} + +inline void icSwab64Array(void *pVoid, int num) +{ + icUInt8Number *ptr = (icUInt8Number*)pVoid; + icUInt8Number tmp; + + while (num>0) { + tmp = ptr[0]; ptr[0] = ptr[7]; ptr[7] = tmp; + tmp = ptr[1]; ptr[1] = ptr[6]; ptr[6] = tmp; + tmp = ptr[2]; ptr[2] = ptr[5]; ptr[5] = tmp; + tmp = ptr[3]; ptr[3] = ptr[4]; ptr[4] = tmp; + ptr += 8; + num--; + } + +} +#else //!ICC_BYTE_ORDER_LITTLE_ENDIAN +#define icSwab16Ptr(x) +#define icSwab16Array(x, n) +#define icSwab32Ptr(x) +#define icSwab32Array(x, n) +#define icSwab64Ptr(x) +#define icSwab64Array(x, n) +#endif + +#define icSwab16(x) icSwab16Ptr(&x) +#define icSwab32(x) icSwab32Ptr(&x) +#define icSwab64(x) icSwab64Ptr(&x) + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: + * This is a utility class which can be used to get profile info + * for printing. The member functions are used to convert signatures + * and other enum values to character strings for printing. + ************************************************************************** + */ +class ICCPROFLIB_API CIccInfo { +public: + //Signature values + const icChar *GetVersionName(icUInt32Number val); + const icChar *GetDeviceAttrName(icUInt64Number val); + const icChar *GetProfileFlagsName(icUInt32Number val); + + const icChar *GetTagSigName(icTagSignature sig); + const icChar *GetTechnologySigName(icTechnologySignature sig); + const icChar *GetTagTypeSigName(icTagTypeSignature sig); + const icChar *GetColorSpaceSigName(icColorSpaceSignature sig); + const icChar *GetProfileClassSigName(icProfileClassSignature sig); + const icChar *GetPlatformSigName(icPlatformSignature sig); + const icChar *GetCmmSigName(icCmmSignature sig); + const icChar *GetReferenceMediumGamutSigNameName(icReferenceMediumGamutSignature sig); + const icChar *GetColorimetricIntentImageStateName(icColorimetricIntentImageStateSignature sig); + + const icChar *GetSigName(icUInt32Number val); + + //Other values + const icChar *GetMeasurementFlareName(icMeasurementFlare val); + const icChar *GetMeasurementGeometryName(icMeasurementGeometry val); + const icChar *GetRenderingIntentName(icRenderingIntent val); + const icChar *GetSpotShapeName(icSpotShape val); + const icChar *GetStandardObserverName(icStandardObserver val); + const icChar *GetIlluminantName(icIlluminant val); + + const icChar *GetUnknownName(icUInt32Number val); + const icChar *GetMeasurementUnit(icSignature sig); + const icChar *GetProfileID(icProfileID *profileID); + const icChar *GetColorantEncoding(icColorantEncoding colorant); + + bool IsProfileIDCalculated(icProfileID *profileID); + icValidateStatus CheckData(std::string &sReport, const icDateTimeNumber &dateTime); + icValidateStatus CheckData(std::string &sReport, const icXYZNumber &XYZ); + + bool IsValidSpace(icColorSpaceSignature sig); + +protected: + icChar m_szStr[128]; + icChar m_szSigStr[128]; +}; + +extern ICCPROFLIB_API CIccInfo icInfo; + + +/** + ************************************************************************** + * Type: Class + * + * Purpose: + * This is a UTF16 string class that provides conversions + ************************************************************************** + */ +class ICCPROFLIB_API CIccUTF16String +{ +public: + CIccUTF16String(); + CIccUTF16String(const icUInt16Number *uzStr); + CIccUTF16String(const char *szStr); + CIccUTF16String(const CIccUTF16String &str); + virtual ~CIccUTF16String(); + + void Clear(); + bool Empty() const { return m_len==0; } + size_t Size() const { return m_len; } + void Resize(size_t len); + + CIccUTF16String& operator=(const CIccUTF16String &wstr); + CIccUTF16String& operator=(const char *szStr); + CIccUTF16String& operator=(const icUInt16Number *uzStr); + + bool operator==(const CIccUTF16String &str) const; + + icUInt16Number operator[](size_t nIndex) const { return m_str[nIndex]; } + + const icUInt16Number *c_str() const { return m_str; } + + void FromUtf8(const char *szStr, size_t sizeSrc=0); + const char *ToUtf8(std::string &buf) const; + void FromWString(const std::wstring &buf); + const wchar_t *ToWString(std::wstring &buf) const; + + static size_t WStrlen(const icUInt16Number *uzStr); + +protected: + static size_t AllocSize(size_t n) { return (((n+64)/64)*64); } + size_t m_alloc; + size_t m_len; + icUInt16Number *m_str; +}; + +const char * ICCPROFLIB_API icUtf16ToUtf8(std::string &buf, const icUInt16Number *szSrc, int sizeSrc=0); +const unsigned short * ICCPROFLIB_API icUtf8ToUtf16(CIccUTF16String &buf, const char *szSrc, int sizeSrc=0); + + + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif diff --git a/library/src/main/cpp/icc/IccXformFactory.cpp b/library/src/main/cpp/icc/IccXformFactory.cpp new file mode 100644 index 00000000..56c6dbd7 --- /dev/null +++ b/library/src/main/cpp/icc/IccXformFactory.cpp @@ -0,0 +1,171 @@ +/** @file + File: IccXformFactory.cpp + + Contains: Implementation of the CIccXform class and creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Oct 30, 2005 +// Added CICCXform Creation using factory support +// +////////////////////////////////////////////////////////////////////// + +#include "IccXformFactory.h" + +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +CIccXform* CIccBaseXformFactory::CreateXform(icXformType xformSig, CIccTag *pTag/*=NULL*/, CIccCreateXformHintManager *pHintManager/*=NULL*/) +{ + //We generally ignore pHint in the base creator (used by others to determine what form of xform to create) + switch(xformSig) { + case icXformTypeMatrixTRC: + return new CIccXformMatrixTRC(); + + case icXformType3DLut: + return new CIccXform3DLut(pTag); + + case icXformType4DLut: + return new CIccXform4DLut(pTag); + + case icXformTypeNDLut: + return new CIccXformNDLut(pTag); + + case icXformTypeNamedColor: + if (pHintManager) { + IIccCreateXformHint* pHint = pHintManager->GetHint("CIccCreateXformNamedColorHint"); + if (pHint) { + CIccCreateNamedColorXformHint *pNCHint = (CIccCreateNamedColorXformHint*)pHint; + return new CIccXformNamedColor(pTag, pNCHint->csPcs, pNCHint->csDevice); + } + } + return NULL; + + case icXformTypeMpe: + return new CIccXformMpe(pTag); + + case icXformTypeMonochrome: + return new CIccXformMonochrome(); + + default: + return NULL; + } +} + +std::auto_ptr CIccXformCreator::theXformCreator; + +CIccXformCreator::~CIccXformCreator() +{ + IIccXformFactory *pFactory = DoPopFactory(true); + + while (pFactory) { + delete pFactory; + pFactory = DoPopFactory(true); + } +} + +CIccXformCreator* CIccXformCreator::GetInstance() +{ + if (!theXformCreator.get()) { + theXformCreator = CIccXformCreatorPtr(new CIccXformCreator); + + theXformCreator->DoPushFactory(new CIccBaseXformFactory); + } + + return theXformCreator.get(); +} + +CIccXform* CIccXformCreator::DoCreateXform(icXformType xformTypeSig, CIccTag *pTag/*=NULL*/, CIccCreateXformHintManager *pHintManager/*=NULL*/) +{ + CIccXformFactoryList::iterator i; + CIccXform *rv = NULL; + + for (i=factoryStack.begin(); i!=factoryStack.end(); i++) { + rv = (*i)->CreateXform(xformTypeSig, pTag, pHintManager); + if (rv) + break; + } + return rv; +} + +void CIccXformCreator::DoPushFactory(IIccXformFactory *pFactory) +{ + factoryStack.push_front(pFactory); +} + +IIccXformFactory* CIccXformCreator::DoPopFactory(bool bAll /*=false*/) +{ + //int nNum = (bAll ? 0 : 1); + + if (factoryStack.size()>0) { + CIccXformFactoryList::iterator i=factoryStack.begin(); + IIccXformFactory* rv = (*i); + factoryStack.pop_front(); + return rv; + } + return NULL; +} + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif diff --git a/library/src/main/cpp/icc/IccXformFactory.h b/library/src/main/cpp/icc/IccXformFactory.h new file mode 100644 index 00000000..57ee9aa0 --- /dev/null +++ b/library/src/main/cpp/icc/IccXformFactory.h @@ -0,0 +1,248 @@ +/** @file + File: IccXformFactory.h + + Contains: Header for implementation of CIccXformFactory class and + creation factories + + Version: V1 + + Copyright: see ICC Software License +*/ + +/* + * The ICC Software License, Version 0.2 + * + * + * Copyright (c) 2007-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + +////////////////////////////////////////////////////////////////////// +// HISTORY: +// +// -Nov 21, 2007 +// A CIccXformCreator singleton class has been added to provide general +// support for dynamically creating xform classes using a xform type. +// Prototype and private xform type support can be added to the system +// by pushing additional IIccXformFactory based objects to the +// singleton CIccXformCreator object. +// +////////////////////////////////////////////////////////////////////// + +#ifndef _ICCXFORMFACTORY_H +#define _ICCXFORMFACTORY_H + +#include "IccCmm.h" +#include +#include +#include + +//CIccXform factory support +#ifdef USESAMPLEICCNAMESPACE +namespace sampleICC { +#endif + +/** + *********************************************************************** + * Class: IIccXformFactory + * + * Purpose: + * IIccXformFactory is a factory pattern interface for CIccXform creation. + * This class is pure virtual. + *********************************************************************** + */ +class IIccXformFactory +{ +public: + virtual ~IIccXformFactory() {} + + /** + * Function: CreateXform(xformTypeSig) + * Create a xform of type xformTypeSig. + * + * Parameter(s): + * xformTypeSig = signature of the ICC xform type for the xform to be created + * pTag = tag information for created xform + * pHintManager = contains additional information used to create xform + * + * Returns a new CIccXform object of the given signature type. If the xform + * factory doesn't support creation of xforms of type xformTypeSig then it + * should return NULL. + */ + virtual CIccXform* CreateXform(icXformType xformType, CIccTag *pTag=NULL, CIccCreateXformHintManager* pHintManager=0)=0; +}; + + +//A CIccXformFactoryList is used by CIccXformCreator to keep track of xform +//creation factories +typedef std::list CIccXformFactoryList; + + +/** + *********************************************************************** + * Class: CIccBaseXformFactory + * + * Purpose: + * CIccSpecXformFactory provides creation of Base CIccXform's. The + * CIccXformCreator always creates a CIccSpecXformFactory. + *********************************************************************** + */ +class CIccBaseXformFactory : public IIccXformFactory +{ +public: + /** + * Function: CreateXform(xformTypeSig) + * Create a xform of type xformTypeSig. + * + * Parameter(s): + * xformTypeSig = signature of the ICC xform type for the xform to be created + * pTag = tag information for created xform + * pHintManager = contains additional information used to create xform + * + * Returns a new CIccXform object of the given xform type. + * Unrecognized xformTypeSig's will be created as a CIccXformUnknown object. + */ + virtual CIccXform* CreateXform(icXformType xformType, CIccTag *pTag=NULL, CIccCreateXformHintManager *pHintManager=NULL); + +}; + +class CIccXformCreator; + +typedef std::auto_ptr CIccXformCreatorPtr; + +/** + *********************************************************************** + * Class: CIccXformCreator + * + * Purpose: + * CIccXformCreator uses a singleton pattern to provide dynamically + * upgradeable CIccXform derived object creation based on xform type. + *********************************************************************** + */ +class CIccXformCreator +{ +public: + ~CIccXformCreator(); + + /** + * Function: CreateXform(xformTypeSig) + * Create a xform of type xformTypeSig. + * + * Parameter(s): + * xformType = signature of the ICC xform type for the xform to be created + * pTag = tag information for created xform + * pHintManager = contains additional information used to create xform + * + * Returns a new CIccXform object of the given xform type. + * Each factory in the factoryStack is used until a factory supports the + * signature type. + */ + static CIccXform* CreateXform(icXformType xformType, CIccTag *pTag=NULL, CIccCreateXformHintManager *pHintManager=NULL) + { return CIccXformCreator::GetInstance()->DoCreateXform(xformType, pTag, pHintManager); } + + /** + * Function: PushFactory(pFactory) + * Add an IIccXformFactory to the stack of xform factories tracked by the system. + * + * Parameter(s): + * pFactory = pointer to an IIccXformFactory object to add to the system. + * The pFactory must be created with new, and will be owned CIccXformCreator + * until popped off the stack using PopFactory(). Any factories not + * popped off will be taken care of properly on application shutdown. + * + */ + static void PushFactory(IIccXformFactory *pFactory) + { CIccXformCreator::GetInstance()->CIccXformCreator::DoPushFactory(pFactory); } + + /** + * Function: PopFactory() + * Remove the top IIccXformFactory from the stack of xform factories tracked by the system. + * + * Parameter(s): + * None + * + * Returns the top IIccXformFactory from the stack of xform factories tracked by the system. + * The returned xform factory is no longer owned by the system and needs to be deleted + * to avoid memory leaks. + * + * Note: The initial CIccSpecXformFactory cannot be popped off the stack. + */ + static IIccXformFactory* PopFactory() + { return CIccXformCreator::GetInstance()->DoPopFactory(); } + +private: + /**Only GetInstance() can create the signleton*/ + CIccXformCreator() { } + + /** + * Function: GetInstance() + * Private static function to access singleton CiccXformCreator Object. + * + * Parameter(s): + * None + * + * Returns the singleton CIccXformCreator object. It will allocate + * a new one and push a single CIccSpecXform Factory object onto the factory + * stack if the singleton has not been intialized. + */ + static CIccXformCreator* GetInstance(); + + CIccXform* DoCreateXform(icXformType xformType, CIccTag *pTag=NULL, CIccCreateXformHintManager *pHintManager=NULL); + void DoPushFactory(IIccXformFactory *pFactory); + IIccXformFactory* DoPopFactory(bool bAll=false); + + static CIccXformCreatorPtr theXformCreator; + + CIccXformFactoryList factoryStack; +}; + +#ifdef USESAMPLEICCNAMESPACE +} //namespace sampleICC +#endif + +#endif //_ICCXFORMFACTORY_H diff --git a/library/src/main/cpp/icc/MainPage.h b/library/src/main/cpp/icc/MainPage.h new file mode 100644 index 00000000..8c23b545 --- /dev/null +++ b/library/src/main/cpp/icc/MainPage.h @@ -0,0 +1,457 @@ +/** @file + File: MainPage.h + + Note: This file was added to provide documentation in doxygen. Nothing in IccProfLib actually uses it. +*/ + +/** \mainpage + * + * The IccProfLib is an open source cross platform C++ library for reading, writing, manipulating, + * and applying ICC profiles. It is an attempt at a strict interpretation of the ICC profile + * specification. + * The structure of the library very closely follows the structure of the specification. + * A working knowledge of the ICC specification and color management workflows will aid in + * understanding the library and it's proper usage. For the latest ICC profile + * specification please visit http://www.color.org. Several useful white papers and resources + * are also available on the website. + * + * Note: More documentation on SampleICC's Color Management Modules (CMM's) can be found in the white + * paper titled "Implementation Notes for the IccLib CMM in SampleICC". + * (see http://www.color.org/ICC_white_paper_18_IccLib_Notes.pdf) + * + * Here are some of the things that the IccProfLib supports: + * - ICC profile file I/O in CIccProfile class + * - Version 4.2 profiles (read & write) + * - Version 2.x profiles (read) + * - C++ classes for all specified tag types (based on CIccTag). Default behavior for + * unrecognized private tag types is implemented using a generic Tag class. + * - Two basic Color Management Module (CMM) implementations + * - Basic pixel level transforms in CIccCmm class + * - Additional named color profile support in CIccNamedColorCmm class + * - File I/O can be re-directed + * - All operations performed using floating point. Pixel precision not limited to integers. + * - Transforms are done one pixel at a time. + * - Flexible number of profile transforms in a series (as long as the colorspaces match) + * - Multidimensional lookup table interpolation + * - Three dimensional interpolation uses either linear or tetrahedral interpolation + * (selectable at time profile is attached to the CMM). + * - Greater than three dimensional interpolation uses linear interpolation + * - Matrix/TRC support + * - Calculation of Profile ID using the MD5 fingerprinting method (see md5.h) + * - Dynamic creation and seemless use of private CIccTag derived objects that are implemented + * outside of IccProfLib (IE inside a private library or application that links with + * IccProfLib). + * + * USAGE COMMENTS + * -# The IccProfLib implements very basic CMMs. These may not + * provide the optimum speed in all situations. Profile transforms are done one pixel + * at a time for each profile in a profile transformation chain. Various techniques + * can possibly be used to improve performance. An initial thought would be to create a + * CMM that uses the basic CIccCmm to generate a single link transform (concatenating + * the profiles). Such a transform could employ integer math if needed. + * -# The IccProfLib can be used to open, generate, manipulate (by adding, removing, or + * modifying tags), and/or save profiles without needing to use the pixel transformations + * provided by the CMM classes. + * -# Several applications have been written (in SampleICC) that make use of the IccProfLib. + * It is advisable to examine these applications for additional guidance in making + * the best use of the IccProfLib. + * -# Before compiling on non-Windows and non Mac OSX platforms it will be necessary to edit + * the configuration parameters in IccProfLibConf.h. + * + * VERSION HISTORY + * - December 2015 - 1.6.10 release + * - 1.6.11 release + * - Fixed bug in validation of GamutTags + * + * - December 2015 - 1.6.10 release based on submission by Vitaly Bondar + * - 1.6.10 release + * - Fixed bug in copy data of copy constructors and copy operations of CIccTagUnkown, + * CIccTagNamedColor2, CIccTagChromaticity, CIccTagFixedNum, CIccTagNum, CIccTagData, + * and CIccTagColorantOrder + * + * - Sept 2015 - 1.6.9 release + * - 1.6.9 release + * - Added check to black point compensation to check for negative values going into square + * root calculation + * - Fixed bug in copying data in constructor and copy operator of CIccTagFixedNum + * - Updated copyright dates + * - Added use of WXVER environment variable to select wxWidgets library version for build + * of wxProfileDump + * + * - April 2014 - 1.6.8 release + * - 1.6.8 release + * - Modified CIccTagParametricCurve to use icFloatNumber rather than icS15Fixed16Number + * for internal storage purposes. Fixes crashing bug with profile verification. + * - Added check for named color profile class when icSigNamedColor2Tag is filed + * - Changed #ifdef WIN32 to #if defined(WIN32) || defined(WIN64) + * - Added zeros to end of PRMG gamut for easy detection of end of gamut + * - Removed 4 byte alignment check for profile length for versions before v4.2 + * - Fixed bug with copy of data in CIccTagColorantTable objects + * + * - August 2012 - 1.6.7 release + * - 1.6.7 release + * - Made const functions more consistent + * - Moved CIccUTF16String class from IccXML + * - Replace use of std::wstring in CIccTagDict with CIccUTF16String + * - Added ICC_ENUM_CONVENIENCE define that makes convenience enums part of the enum type + * + * - August 2011 - 1.6.6 release + * - Added iccGetBPCInfo command line tool to retrieve information about BPC connection + * - Changed CIccApplyBPC private members to protected members to allow object overridden + * and made CIccApplyBPC:calcBlackPoint virtual to support override in iccGetBPCInfo + * + * - April 2011 - 1.6.5 release + * - Modified .sln and .vcproj files to work with Visual Studio 2008 + * - Added _v8.sln and _v8.vcproj files to work with Visual Studio 2005 + * - Fixed bugs in CIccInfo::GetProfileID() and CIccInfo::IsProfileIDCalculated() + * + * - January 2011 - 1.6.4 release + * - Added CIccNullIO class that can be used by a caller to "write" the profile thus updating + * tag directory entries + * - Fixed various bugs related to setting text in CIccTagDict tags + * - Added CIccTagLut8::GetPrecision() function + * - Fixed bug in validation of CIccTextDescription class + * - Initialize m_nVendorflags in CIccTagNamedColor2 constructor + * - Fixed bug in CIccTagNamedColor2::SetSize() that was copying wrong thing + * - Fixed bug in CIccTagMultiLocalizedUnicode::Read() that was using wrong seek value at end + * - Defined initial values in CIccTagViewingCondions constructor + * - Modified CLUT interpolation in IccTagLut.cpp to perform clipping on input and no clipping + * on output (as opposed to the other way around). Fixes crashing bug found with absolute + * intent processing of colors that are whiter than media point. Also supports floating point range + * of output for MPE CLUT elements. + * - Renaming of MD5 calculation functions to avoid conflicts with other libraries + * + * - November 2010 - 1.6.3 release + * - Modification of type for CIccCLUT::m_nOutput to icUInt16Nubmer to better support MPE + * CLUT elements + * - Fixed typo in CIccTagUnknown::IsSupported() + * + * - August 2010 - 1.6.1 release + * - Fix bugs with reading and displaying metaDataTags using the dictTagType + * + * - August 2010 - 1.6.1 release + * - Modifications to CIccTagLut16 and CIccTagLut8 to correctly track curve mapping when table + * is used as an output table and PCS is XYZ. In this case M and B curves are swapped since + * the legacy Lut16 and Lut8 tags do not have M curves. + * - Removed check in CIccXform::Create() for BtoD0/DtoB0 tags if BtoDx/DtoBx tag for rendering + * intent not found (as this never made it into the approved specification). + * - Further changes from Joseph Goldstone that eliminate various compiler warnings + * + * - July 2010 - 1.6.0 release + * - Moved main Build for Windows systems to Build\MSVC folder with intent to add builds for + * other systems to Build folder + * - Incorporated changes from Joseph Goldstone that eliminate various compiler warnings + * - Modified CIccProfile::Write() to allow for options in how ProfileIDs are created + * (Example: ByProfileVersion/Always/Never) + * + * - May 2010 + * - Modifications for better support for compiling with 64 bit compilers + * - Added IccProfLibVer.h to provide a macro for defining the library version + * - Fixed crashing bug with gamut tags with XYZ PCS + * - Modified CLUT::dump to use reflect legacy encoding of when lut16 tags are used + * + * - April 2010 + * - Modified IccProfLibTest to allow modification of ProfileDescription and Copyright tags + * + * - March 2010 + * - Added support for PrintConditions tag implemented using Dictionary tag type + * + * - January 2009 + * - Added CIccCreateXformHintManager to allow for a list of hint object to be passed + * in at the time of CIccXform creation. + * - Modified PCS adjustment to use a scale and offset in new function CIccXform::AdjustPCS(). + * CIccXform::CheckSrcAbs and CIccXform::CheckDestAbs now use CIccXform::AdjustPCS()/ + * - Hint mechanism can now be used to set up scale and offset values. + * - Added CIccApplyBPCHint and CIccApplyBPC classes in IccApplyBPC.cpp and IccApplyBPC.h + * to provide optional support for Adobe Black Point Compensation + * - Since BPC is outside the scope of the ICC specifiction, users of CIccXform::Create + * must define and use a CIccApplyBPCHint object to enable BPC processing. + * - CIccApplyBPC temporarily instantiates a CIccCmm for the purpose of finding the black point + * of a profile. + * - Black point processing between two profiles is performed in two steps. The first profile's + * black point is mapped to the V4 perceptual black point. The second profile maps from the + * V4 perceptual black point to the second profile's black point. This allows BPC processing + * to be performed on a single profile. + * - iccApplyNamedCMM.cpp modified to support BPC using CIccApplyBPC + * - Added CIccProfile::ReadTags() to force all tags to be loaded into memory. (Used by CIccApplyBPC) + * - CIccTagMultiLocalizedUnicode::Read() now seeks to the end of the last record at end of the function + * - Added ICC_CBRTF macro that can allow for substitution of cbrtf() function if it is available + * - Commented several additional functions in IccCmm.cpp + * - Modified WinNT\ApplyProfiles to allow for applying an output profile to a Lab image file + * - Fixed header in cmyk8bit.txt and cmyk16bit.txt files + * + * - December 2008 + * - Added support for Monochrome ICC profile apply + * + * - November 2008 + * - Cleanup of build files for Linux + * - Revised License to version 0.2 + * + * - October 2008 + * - Added support for External extension of CIccTags and CIccMPE objects + * - Make CIccMultiProcessElement::m_nReserved public + * - Added CIccMpeUnknown:SetType() and CIccMpeUnknown::SetChannels() to allow unknown elements to be externally created + * - CIccCLUT::Init() now returns bool to indicate allocation failure + * - Added icDeltaE() funciton to IccUtil.cpp + * - Modified MPE Sampled Curve to conform to specification. First point is NOT stored in file. + * - Fixed MPE processing to not Clip PCS or apply Absolute Rendering Intent adjustments. + * - Added support for selective use of MPE tags in iccApplyNamedCmm.cpp + * - Modified IccV4ToMPE to correctly create SampledCurve segments by not saving first point. + * - wxProfileDump now supports option to perform round trip performance analysis. + * - Added fix to make CIccCmmMRU work after chages to add CIccApplyCmm architecture. +* + * - November 2007 + * - Addition of CIccXformCreator singleton factory and IIccXformFactory interface for dynamic + * creation of CIccXform objects based upon xform type. With a IIccXformFactory derived + * object properly registered using CIccXformCreator::PushFactory() overlaoded CIccXform objects + * seemleessly get created and applied. + * + * - October 2007 + * - Fixed Memory leak in CIccProfile Copy constructor and operator=. + * + * - September 2007 + * - Fixed bug with Tetrahedral Interpolation + * + * - August 2007 + * - MPE Formula Curve bug fixes. + * - Registered CMM signatures recognized and displayed correctly. + * - Unknown platform signature type added (00000000h). + * + * - June 2007 + * - Added support for optional ProfileSequenceId tags. These tags provide contents of + * profile description tags and id's for profiles used to create an device link profile. The + * CIccTagProfileSequeceId class implements these objects. + * - Added support for optional colorimetric Intent Image State tags. This tag provides information + * about the image state implied by the use of a profile containg this tag. + * + * - Febrary 2007 + * - Added a CIccMruCmm class that keeps track of the last 5 pixels that were applied. Used by the + * SampleIccCmm Windows CMM DLL project. + * + * - November 2006 + * - Added support for optional multiProcessingElementType tags. These tags provide + * an arbitrary order of curves, matricies, and N-D luts encoded using floating + * point. The CIccTagMultiProcessElement class implements these objects. MPE based tags + * can have 1 or more CIccMultiProcessElement based objects attached to them. + * See CIccMpeCurveSet, CIccMpeMatrix, CIccMpeCLUT for more details. Additional future + * placeholder elements CIccMpeBAcs and CIccMpeEAcs objects are defined, but provide no + * processing capabilities. See additions to Icc Specification for more details + * releated to optional MPE based tags. + * - Modified icProfileHeader.h to include newly approved Technololgy signatures for the + * digital motion picture industry. + * + * - October 2006 + * - Added direct accessors CIccTagMultiLocalizedUnicode::Find() and + * CIccTagMultiLocalizedUnicode::SetText() for easier creation of tags based on + * CIccTagMultiLocalizedUnicode + * - Added CIccTagCurve::SetGamma() function + * - Added validation check for single entry (gamma) curves to CIccTagLut8 and CIccTagLut16 + * - Added IsIdentity() function to the CIccCurve and CIccMatrix classes which returns true + * if they are identity + * - Modified the Xform objects in the CMM to use the IsIdentity() function. + * Now CIccXform::Apply() will not apply the curves or the matrix if + * they are identity, to improve the CMM performance + * + * - July 2006 + * - Fixed bug with displaying the icSigPerceptualRenderingIntentGamutTag tag's name correctly + * - Added icVectorApplyMatrix3x3() to IccUtil + * - Fixed bug in CIccTagChromaticity::Validate() to use fixed floating point encoding in + * comparisons rather than IEEE encoding + * + * - June 2006 + * - Added concept of device Lab and XYZ separate from PCS Lab and XYZ. The encoding for + * device Lab and XYZ can be different than that used by the PCS. + * Both CIccXform::GetSrcSpace() and CIccXform::GetDstSpace() now return icSigDevLabData + * (rather than icSigLabData) or icSigDevXYZData (rather than icSigXYZData) if the connection + * is to a device (rather than PCS). + * - The macros icSigDevLabData and icSigDevXYZData were added to IccDefs.h. + * - icGetSpaceSamples() and CIccInfo::GetColorSpaceSigName() were modified to recognize + * icSigDevLabData and icSigDevXYZData. + * + * - May 2006 + * - Added icSigSampleICC to IccDefs.h and CIccProfile.cpp now uses this to initialize + * default values for creator and cmm in header fields. + * - Renamed icMatrix3x3Invert() to icInvertMatrix3x3() in IccUtil + * - Added icMatrixMultipily3x3() to IccUtil + * + * - April 2006 + * - The CIccXform derived objects now have a virual GetType function to allow for easy + * identification and casting to an appropriate class type. + * - Modified CIccCmm to Allocate and use a single CIccPCS object rather than instantiating + * a new object on each call to Apply. The CIccPCS object creation is performed using + * a virtual member function. + * - Minor type casting for beter compilation on Linux + * - Added SAMPLEICC_NOCLIPLABTOXYZ macro to IccProfLibConf.h to remove clipping when + * converting from Lab to XYZ. This makes things round trip better but possibly results + * in imaginary (not well defined) XYZ values. + * - Added clipping to CIccTagCurve::Apply(v) to handle when v is out of range. + * - CIccLocalizedUnicode constructor now allocates enough data for a single 16 bit character. + * - CIccFileIO::Open() now appends a 'b' to szAttr if missing in WIN32. + * - Added check in profile validation for existance of colorantTableTag if output profile is xCLR. + * + * - March 2006 + * - Modified icProfileHeader.h with reduced ICC copyright notice and changed icRegionCode + * to icCountryCode to agree with ISO 3166 naming convention. + * + * - February 2006 + * - Modified CIccCLUT Interp interfaces to take separate src and dst pixel values. + * - Modified CIccCLUT interface with selectable clipping function. + * - Added IsSupported() function to CIccTag and CIccTagUnknown classes. This function + * is used to find out if tag is supported (for apply purposes). + * - Modified ToInternalEncoding and FromInternalEncoding to add icEncodeValue support + * for XYZ data. The icEncodePercent was also modified to take Y=100.0 into and out of + * XYZ internal PCS encoding. + * - TagFactory interface for GetSigName() didn't function properly. It was modified to + * provide better support for GetSigName() and GetSigTypeName(). + * - Additional cleanup of icProfileHeader.h. Noticable Difference icProfileID.ID was + * chaged to icProfileID.ID8 + * + * - December 2005 + * - Moved most of the contents of IccDefs.h to icProfileHeader.h which corresponds to the "C" + * header file published on ICC Web site. The file icProfileHeader.h has been updated to reduce + * complications with compilers, missing version 4 items were added, and basic cleanup was + * performed. + * - A cross platform GUI based ICC Profile Viewer tool named wxProfileDump was added that + * makes use of the wxWidgets (http://www.wxWidgets.org) version 2.6.2 cross platform development + * framework. + * - Addition of CIccTagCreator singleton factory and IIccTagFactory interface for dynamic + * creation of CIccTag objects based upon tag signature. The CIccTag::Create() funciton now uses + * a CIccTagCreator singleton object to create all CIccTag derived objects. With a IIccTagFactory + * derived object properly registered using CIccTagCreator::PushFactory() private CIccTag objecs + * seemleessly load, save and validate. + * - CIccProfile::Write() modified to check for version 4 before calculating ProfileID value. + * + * - October 2005 + * - Fixed bugs in copy constructors for CIccProfile, CIccTagCurve, and CIccTagText. + * - Added comments to IccDefs.h to indicate convenience enums. + * - Changed icMaxFlare to icMaxEnumFlare and icMaxGeometry to icMaxEnumGeometry to improve + * consistancy. + * - Corrected spelling of icMaxEnumIluminant to icMaxEnumIlluminant. + * + * - September 2005 + * - Moved InvertMatrix to ICCUtils + * + * - August 2005 + * - Cleaned up more warnings. + * - Added additional CIccCmm::AddXform() method for easily attaching memory based profiles. + * - Added CIccCmm::ToInternalEncoding() and CIccCmm::FromInternalEncoding() methods that + * make use of Cmm's tracking of source and destination color spaces. + * + * - July 2005 + * - Renamed IccLib to IccProfLib to avoid confusion with Graeme Gill's "ICCLIB" project. + * + * - June 2005 + * - Cleaned up warnings. + * - Added support for applying Preview and Gamut Tags in CIccCmm and CIccNamedColorCmm. This + * is accomplished through the new nLutType argument to the CIccCmm::AddXform() methods. + * + * - May 2005 + * - Fixed bug in ParametricCurve type introduced with enhanced profile validation support. + * + * - April 2005 + * - Greatly enhanced Profile Validation support. (Note: Validation is a separate step from + * reading profiles for speed purposes). + * - The CIccProfile class's ValidateProfile() function provides a Validation report + * within a string in addition to returning a validation status. + * - Additional functions were added to the profile class for Validation purposes. + * - Tags now have a Validate() member function to check out the validity of the data + * in the tags. (No check is made for color accuracy). + * - Tags now store reserved data to provide better validation reporting. + * - Added support for perceptualRenderingIntentGamutTag and saturationRederingIntentGamutTag. + * - Split Tag implementation into two files IccTagBasic and IccTagLut. + * - Fixed bug with reading testDescriptionTagType. + * + * - March 2005 + * - Fixed bugs with N-Dimensional interpolation. + * - Fixed bugs with Lut8 Writing. + * - Added new CIccCLUT::Iterate() function to allow for manipulating data in a CLUT without having + * to mess with the details of dimension and granularity. + * + * - February 2005 + * - Added ability for IccProfLib to be compiled as a DLL. + * - Fixed bugs in CIccCmm::ToInternalEncoding() and CIccCmm::FromInternalEncoding() + * + * - January 2005 + * - Complete support for version 4.2 profiles as defined in ICC specification ICC.1:2004-10. + * - Added support for all tag types + * - N-dimensional interpolation function added (NOT TESTED) + * - Added support for calculation of profile ID using MD5 fingerprinting method + * - Profile validation function added + * - Added support for named color tags + * - Additional CMM class was added which supports named color profiles + * - Added copy constructors and copy operators for all Tag classes and Profile class. + * - Comments in the code were modified to allow the use of doxygen. Additional comments + * were added, and HTML documentation pages were generated. + * - Modified IccProfLib classes so that the library can be compiled as a DLL and gain access to + * IccProfLib objects from this separate DLL. + * + * - February 2004 + * - Merged in changes to get Mac OS X compatibility with the gnu compiler. + * - Added boiler plate disclaimers to all the source files. + * + * - November 2003 \n + * - There has been some limited testing by members of the ICC, and changes have + * been made as appropriate. Development was done on a WINTEL platform using Microsoft Visual C++ 6.0. + * It should work for this environment. Modifications have been made so that the + * projects can be converted and work with Visual Studio .NET.\n + * The IccProfLib was written to be platform independent. Peter McCloud of Adobe + * was able to get IccProfLib to compile and run on Mac OS X. + * + * TODO List + * + * - Create OS specific loadable library CMM wrappers to IccProfLib CMM objects. + * - Naming Convention Cleanup of conversion functions in IccUtil. + * - Restructure profile validation to use Tag Factory mechanism. + * + * The ICC Software License, Version 0.2 + * + * Copyright � 2003-2015 The International Color Consortium. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. In the absence of prior written permission, the names "ICC" and "The + * International Color Consortium" must not be used to imply that the + * ICC organization endorses or promotes products derived from this + * software. + * + * + * ====================================================================\n + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED\n + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR\n + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\n + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\n + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n + * SUCH DAMAGE.\n + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * CONTACT + * + * Please send your questions, comments, and or suggestions to forums + * on the SampleICC project site. (http://sourceforge.net/projects/sampleicc/).\n + * + */ + diff --git a/library/src/main/cpp/icc/Makefile.am b/library/src/main/cpp/icc/Makefile.am new file mode 100644 index 00000000..c7e68a1b --- /dev/null +++ b/library/src/main/cpp/icc/Makefile.am @@ -0,0 +1,58 @@ +## Process this file with automake to produce Makefile.in + +lib_LTLIBRARIES = libSampleICC.la + +libSampleICC_la_SOURCES = \ + IccApplyBPC.cpp \ + IccCmm.cpp \ + IccConvertUTF.cpp \ + IccEval.cpp \ + IccXformFactory.cpp \ + IccIO.cpp \ + IccMpeACS.cpp \ + IccMpeBasic.cpp \ + IccMpeFactory.cpp \ + IccPrmg.cpp \ + IccProfile.cpp \ + IccTagBasic.cpp \ + IccTagDict.cpp \ + IccTagFactory.cpp \ + IccTagLut.cpp \ + IccTagMPE.cpp \ + IccTagProfSeqId.cpp \ + IccUtil.cpp \ + md5.cpp + +libSampleICC_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ + +libSampleICCincludedir = $(includedir)/SampleICC + +libSampleICCinclude_HEADERS = \ + IccApplyBPC.h \ + IccCmm.h \ + IccConvertUTF.h \ + IccEval.h \ + IccXformFactory.h \ + IccDefs.h \ + IccIO.h \ + IccMpeACS.h \ + IccMpeBasic.h \ + IccMpeFactory.h \ + IccPrmg.h \ + IccProfLibConf.h \ + IccProfLibVer.h \ + IccProfile.h \ + IccTag.h \ + IccTagBasic.h \ + IccTagDict.h \ + IccTagFactory.h \ + IccTagLut.h \ + IccTagMPE.h \ + IccTagProfSeqId.h \ + IccUtil.h \ + icProfileHeader.h \ + md5.h + +INCLUDES = -I$(top_builddir) -I$(top_srcdir)/SampleICC + +EXTRA_DIST = MainPage.h diff --git a/library/src/main/cpp/icc/Makefile.in b/library/src/main/cpp/icc/Makefile.in new file mode 100644 index 00000000..8a58ac33 --- /dev/null +++ b/library/src/main/cpp/icc/Makefile.in @@ -0,0 +1,728 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = IccProfLib +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/mkinstalldirs $(top_srcdir)/depcomp \ + $(libSampleICCinclude_HEADERS) +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(libSampleICCincludedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +libSampleICC_la_LIBADD = +am_libSampleICC_la_OBJECTS = IccApplyBPC.lo IccCmm.lo IccConvertUTF.lo \ + IccEval.lo IccXformFactory.lo IccIO.lo IccMpeACS.lo \ + IccMpeBasic.lo IccMpeFactory.lo IccPrmg.lo IccProfile.lo \ + IccTagBasic.lo IccTagDict.lo IccTagFactory.lo IccTagLut.lo \ + IccTagMPE.lo IccTagProfSeqId.lo IccUtil.lo md5.lo +libSampleICC_la_OBJECTS = $(am_libSampleICC_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libSampleICC_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(libSampleICC_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libSampleICC_la_SOURCES) +DIST_SOURCES = $(libSampleICC_la_SOURCES) +HEADERS = $(libSampleICCinclude_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_CFLAGS = @AM_CFLAGS@ +AM_CXXFLAGS = @AM_CXXFLAGS@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_VERSION = @LIBTOOL_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OSX_APPLICATION_LIBS = @OSX_APPLICATION_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SAMPLEICC_MAJOR_VERSION = @SAMPLEICC_MAJOR_VERSION@ +SAMPLEICC_MICRO_VERSION = @SAMPLEICC_MICRO_VERSION@ +SAMPLEICC_MINOR_VERSION = @SAMPLEICC_MINOR_VERSION@ +SAMPLEICC_VERSION = @SAMPLEICC_VERSION@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SICC_ICC_APPLY_PROFILES = @SICC_ICC_APPLY_PROFILES@ +STRIP = @STRIP@ +TIFF_CPPFLAGS = @TIFF_CPPFLAGS@ +TIFF_LDFLAGS = @TIFF_LDFLAGS@ +TIFF_LIBS = @TIFF_LIBS@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +lib_LTLIBRARIES = libSampleICC.la +libSampleICC_la_SOURCES = \ + IccApplyBPC.cpp \ + IccCmm.cpp \ + IccConvertUTF.cpp \ + IccEval.cpp \ + IccXformFactory.cpp \ + IccIO.cpp \ + IccMpeACS.cpp \ + IccMpeBasic.cpp \ + IccMpeFactory.cpp \ + IccPrmg.cpp \ + IccProfile.cpp \ + IccTagBasic.cpp \ + IccTagDict.cpp \ + IccTagFactory.cpp \ + IccTagLut.cpp \ + IccTagMPE.cpp \ + IccTagProfSeqId.cpp \ + IccUtil.cpp \ + md5.cpp + +libSampleICC_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ +libSampleICCincludedir = $(includedir)/SampleICC +libSampleICCinclude_HEADERS = \ + IccApplyBPC.h \ + IccCmm.h \ + IccConvertUTF.h \ + IccEval.h \ + IccXformFactory.h \ + IccDefs.h \ + IccIO.h \ + IccMpeACS.h \ + IccMpeBasic.h \ + IccMpeFactory.h \ + IccPrmg.h \ + IccProfLibConf.h \ + IccProfLibVer.h \ + IccProfile.h \ + IccTag.h \ + IccTagBasic.h \ + IccTagDict.h \ + IccTagFactory.h \ + IccTagLut.h \ + IccTagMPE.h \ + IccTagProfSeqId.h \ + IccUtil.h \ + icProfileHeader.h \ + md5.h + +INCLUDES = -I$(top_builddir) -I$(top_srcdir)/SampleICC +EXTRA_DIST = MainPage.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu IccProfLib/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu IccProfLib/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libSampleICC.la: $(libSampleICC_la_OBJECTS) $(libSampleICC_la_DEPENDENCIES) $(EXTRA_libSampleICC_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libSampleICC_la_LINK) -rpath $(libdir) $(libSampleICC_la_OBJECTS) $(libSampleICC_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccApplyBPC.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccCmm.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccConvertUTF.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccEval.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccIO.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccMpeACS.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccMpeBasic.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccMpeFactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccPrmg.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccProfile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagBasic.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagDict.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagFactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagLut.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagMPE.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccTagProfSeqId.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccUtil.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IccXformFactory.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libSampleICCincludeHEADERS: $(libSampleICCinclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(libSampleICCincludedir)" || $(MKDIR_P) "$(DESTDIR)$(libSampleICCincludedir)" + @list='$(libSampleICCinclude_HEADERS)'; test -n "$(libSampleICCincludedir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libSampleICCincludedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libSampleICCincludedir)" || exit $$?; \ + done + +uninstall-libSampleICCincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libSampleICCinclude_HEADERS)'; test -n "$(libSampleICCincludedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libSampleICCincludedir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libSampleICCincludedir)" && rm -f $$files + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libSampleICCincludedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-libSampleICCincludeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libSampleICCincludeHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libSampleICCincludeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \ + uninstall-libSampleICCincludeHEADERS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/library/src/main/cpp/icc/icProfileHeader.h b/library/src/main/cpp/icc/icProfileHeader.h new file mode 100644 index 00000000..1d14c16d --- /dev/null +++ b/library/src/main/cpp/icc/icProfileHeader.h @@ -0,0 +1,1666 @@ +/** @file + File: icProfileHeader.h + + Contains: ICC profile definitions and structures including Version 4 extensions + + Copyright: see ICC Software License + + * + * This version of the header file corresponds to the profile + * specification version 4.2 as defined in ICC Specificion ICC.1:2004-04. + * + * Some definitions only provided by version 2.x profiles are also included. + * + * This header file should not be considered as a replacement for the ICC + * profile specification. The ICC profile specification should always be + * considered the ULTIMATE authority related to the specifiation for + * contents in ICC profile file. Conflicts between this header file + * and the ICC profile specification (if they exist) should be deferred + * to the ICC profile specification. + * + * + * All header file entries are pre-fixed with "ic" to help + * avoid name space collisions. Signatures are pre-fixed with + * icSig. + * + * Note: This header assumes that int is at least a 32 bit quantity + * + * The structures defined in this header file were created to + * represent a description of an ICC profile on disk. Rather + * than use pointers a technique is used where a single byte array + * was placed at the end of each structure. This allows us in "C" + * to extend the structure by allocating more data than is needed + * to account for variable length structures. + * + * This also ensures that data following is allocated + * contiguously and makes it easier to write and read data from + * the file. + * + * For example to allocate space for a 256 count length UCR + * and BG array, and fill the allocated data. Note strlen + 1 + * to remember NULL terminator. + * + icUcrBgCurve *ucrCurve, *bgCurve; + int ucr_nbytes, bg_nbytes, string_bytes; + icUcrBg *ucrBgWrite; + char ucr_string[100], *ucr_char; + + strcpy(ucr_string, "Example ucrBG curves"); + ucr_nbytes = sizeof(icUInt32Number) + + (UCR_CURVE_SIZE * sizeof(icUInt16Number)); + bg_nbytes = sizeof(icUInt32Number) + + (BG_CURVE_SIZE * sizeof(icUInt16Number)); + string_bytes = strlen(ucr_string) + 1; + + ucrBgWrite = (icUcrBg *)malloc( + (ucr_nbytes + bg_nbytes + string_bytes)); + +ucrCurve = (icUcrBgCurve *)ucrBgWrite->data; +ucrCurve->count = UCR_CURVE_SIZE; +for (i=0; icount; i++) +ucrCurve->curve[i] = (icUInt16Number)i; + + bgCurve = (icUcrBgCurve *)((char *)ucrCurve + ucr_nbytes); +bgCurve->count = BG_CURVE_SIZE; +for (i=0; icount; i++) +bgCurve->curve[i] = 255 - (icUInt16Number)i; + + ucr_char = (char *)((char *)bgCurve + bg_nbytes); + memcpy(ucr_char, ucr_string, string_bytes); + + * Many of the structures contain variable length arrays. This + * is represented by the use of the convention. + * + * type data[icAny]; + */ + +/* + * + * + * Copyright (c) 2003-2015 The International Color Consortium. + * + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the The International Color Consortium. + * + * + * Membership in the ICC is encouraged when this software is used for + * commercial purposes. + * + * + * For more information on The International Color Consortium, please + * see . + * + * + */ + + +/***************************************************************** + Copyright (c) 2002 Heidelberger Druckmaschinen AG + + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. +******************************************************************/ + +/***************************************************************** + Copyright (c) 1994 SunSoft, Inc. + + Rights Reserved + +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 restrict- +ion, 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 NON- +INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT +COMPANY 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. + +Except as contained in this notice, the name of SunSoft, Inc. +shall not be used in advertising or otherwise to promote the +sale, use or other dealings in this Software without written +authorization from SunSoft Inc. +******************************************************************/ + + + +/* Header file guard bands */ +#ifndef icPROFILEHEADER_H +#define icPROFILEHEADER_H + + +/* In order for structures to work it is important to ensure that + * the structure packing is set to 1. On many compilers this + * can be accomplished using #pragma pack(1). Define the macro + * ICSETBYTEPACKING to enable the following.*/ +#ifdef ICSETBYTEPACKING +#pragma pack(1) +#endif + +/*------------------------------------------------------------------------*/ + +/** + * Defines used in the specification + */ +#define icMagicNumber 0x61637370 /* 'acsp' */ +#define icVersionNumber 0x02000000 /* 2.0, BCD */ +#define icVersionNumberV2_1 0x02100000 /* 2.1, BCD */ +#define icVersionNumberV4 0x04000000 /* 4.0, BCD */ +#define icVersionNumberV4_2 0x04200000 /* 4.2, BCD */ + +/** Screening Encodings */ +#define icPrtrDefaultScreensFalse 0x00000000 /* Bit position 0 */ +#define icPrtrDefaultScreensTrue 0x00000001 /* Bit position 0 */ +#define icLinesPerInch 0x00000002 /* Bit position 1 */ +#define icLinesPerCm 0x00000000 /* Bit position 1 */ + +/** + * Device attributes, currently defined values correspond + * to the low 4 bytes of the 8 byte attribute quantity, see + * the header for their location. + */ +#define icReflective 0x00000000 /* Bit position 0 */ +#define icTransparency 0x00000001 /* Bit position 0 */ +#define icGlossy 0x00000000 /* Bit position 1 */ +#define icMatte 0x00000002 /* Bit position 1 */ +#define icMediaPositive 0x00000000 /* Bit position 2 */ +#define icMediaNegative 0x00000004 /* Bit position 2 */ +#define icMediaColour 0x00000000 /* Bit position 3 */ +#define icMediaBlackAndWhite 0x00000008 /* Bit position 3 */ + +/** + * Profile header flags, the low 16 bits are reserved for consortium + * use. + */ +#define icEmbeddedProfileFalse 0x00000000 /* Bit position 0 */ +#define icEmbeddedProfileTrue 0x00000001 /* Bit position 0 */ +#define icUseAnywhere 0x00000000 /* Bit position 1 */ +#define icUseWithEmbeddedDataOnly 0x00000002 /* Bit position 1 */ + +/** Ascii or Binary data */ +#define icAsciiData 0x00000000 /* Used in dataType */ +#define icBinaryData 0x00000001 + +/** + * Define used to indicate that this is a variable length array + */ +#define icAny 1 + +/** + * Number definitions + * + * NOTE: + * Integer definitions vary from compiler to compiler. Rather than + * provide complex checking for compiler and system, default implementations + * are provided with the ability to redefine actual meaning based upon + * macros. This can be accomplished in a separate header file that first defines + * the macros and then includes this header, or by defining macro values on + * a project level. + */ + +/** Unsigned integer numbers */ +#ifdef ICUINT8TYPE +typedef ICUINT8TYPE icUInt8Number; +#else +typedef unsigned char icUInt8Number; +#endif + +#ifdef ICUINT16TYPE +typedef ICUINT16TYPE icUInt16Number; +#else +typedef unsigned short icUInt16Number; +#endif + +#ifdef ICUINT32TYPE +typedef ICUINT32TYPE icUInt32Number; +#else +typedef unsigned long icUInt32Number; +#endif + +#ifdef ICUINT64TYPE +typedef ICUINT64TYPE icUInt64Number; +#else +typedef icUInt32Number icUInt64Number[2]; +#endif + +typedef icUInt32Number icSignature; + + +/** Signed numbers */ +#ifdef ICINT8TYPE +typedef ICINT8TYPE icInt8Number; +#else +typedef char icInt8Number; +#endif + +#ifdef ICINT16TYPE +typedef ICINT16TYPE icInt16Number; +#else +typedef short icInt16Number; +#endif + +#ifdef ICINT32TYPE +typedef ICINT32TYPE icInt32Number; +#else +typedef long icInt32Number; +#endif + +#ifdef ICINT64TYPE +typedef ICINT64TYPE icInt64Number; +#else +typedef icInt32Number icInt64Number[2]; +#endif + + +/** Fixed numbers */ +typedef icInt32Number icS15Fixed16Number; +typedef icUInt32Number icU16Fixed16Number; + + +/** IEEE float storage numbers */ +typedef float icFloat32Number; +typedef double icFloat64Number; + +/** Useful macros for defining Curve Segment breakpoints **/ +#define icMaxFloat32Number 3.402823466e+38F +#define icMinFloat32Number -3.402823466e+38F + +/** 16-bit unicode characters **/ +typedef icUInt16Number icUnicodeChar; + +/*------------------------------------------------------------------------*/ + +/** + * public tags and sizes + */ +typedef enum { + icSigAToB0Tag = 0x41324230, /* 'A2B0' */ + icSigAToB1Tag = 0x41324231, /* 'A2B1' */ + icSigAToB2Tag = 0x41324232, /* 'A2B2' */ + icSigBlueColorantTag = 0x6258595A, /* 'bXYZ' */ + icSigBlueMatrixColumnTag = 0x6258595A, /* 'bXYZ' */ + icSigBlueTRCTag = 0x62545243, /* 'bTRC' */ + icSigBToA0Tag = 0x42324130, /* 'B2A0' */ + icSigBToA1Tag = 0x42324131, /* 'B2A1' */ + icSigBToA2Tag = 0x42324132, /* 'B2A2' */ + icSigCalibrationDateTimeTag = 0x63616C74, /* 'calt' */ + icSigCharTargetTag = 0x74617267, /* 'targ' */ + icSigChromaticAdaptationTag = 0x63686164, /* 'chad' */ + icSigChromaticityTag = 0x6368726D, /* 'chrm' */ + icSigColorantOrderTag = 0x636C726F, /* 'clro' */ + icSigColorantTableTag = 0x636C7274, /* 'clrt' */ + icSigColorantTableOutTag = 0x636C6F74, /* 'clot' */ + icSigColorimetricIntentImageStateTag = 0x63696973, /* 'ciis' */ + icSigCopyrightTag = 0x63707274, /* 'cprt' */ + icSigCrdInfoTag = 0x63726469, /* 'crdi' Removed in V4 */ + icSigDataTag = 0x64617461, /* 'data' Removed in V4 */ + icSigDateTimeTag = 0x6474696D, /* 'dtim' Removed in V4 */ + icSigDeviceMfgDescTag = 0x646D6E64, /* 'dmnd' */ + icSigDeviceModelDescTag = 0x646D6464, /* 'dmdd' */ + icSigDeviceSettingsTag = 0x64657673, /* 'devs' Removed in V4 */ + icSigDToB0Tag = 0x44324230, /* 'D2B0' */ + icSigDToB1Tag = 0x44324231, /* 'D2B1' */ + icSigDToB2Tag = 0x44324232, /* 'D2B2' */ + icSigDToB3Tag = 0x44324233, /* 'D2B3' */ + icSigBToD0Tag = 0x42324430, /* 'B2D0' */ + icSigBToD1Tag = 0x42324431, /* 'B2D1' */ + icSigBToD2Tag = 0x42324432, /* 'B2D2' */ + icSigBToD3Tag = 0x42324433, /* 'B2D3' */ + icSigGamutTag = 0x67616D74, /* 'gamt' */ + icSigGrayTRCTag = 0x6b545243, /* 'kTRC' */ + icSigGreenColorantTag = 0x6758595A, /* 'gXYZ' */ + icSigGreenMatrixColumnTag = 0x6758595A, /* 'gXYZ' */ + icSigGreenTRCTag = 0x67545243, /* 'gTRC' */ + icSigLuminanceTag = 0x6C756d69, /* 'lumi' */ + icSigMeasurementTag = 0x6D656173, /* 'meas' */ + icSigMediaBlackPointTag = 0x626B7074, /* 'bkpt' */ + icSigMediaWhitePointTag = 0x77747074, /* 'wtpt' */ + icSigMetaDataTag = 0x6D657461, /* 'meta' */ +#if 0 + icSigNamedColorTag = 0x6E636f6C, /* 'ncol' OBSOLETE, use ncl2 */ +#endif + icSigNamedColor2Tag = 0x6E636C32, /* 'ncl2' */ + icSigOutputResponseTag = 0x72657370, /* 'resp' */ + icSigPerceptualRenderingIntentGamutTag = 0x72696730, /* 'rig0' */ + icSigPreview0Tag = 0x70726530, /* 'pre0' */ + icSigPreview1Tag = 0x70726531, /* 'pre1' */ + icSigPreview2Tag = 0x70726532, /* 'pre2' */ + icSigPrintConditionTag = 0x7074636e, /* 'ptcn' */ + icSigProfileDescriptionTag = 0x64657363, /* 'desc' */ + icSigProfileSequenceDescTag = 0x70736571, /* 'pseq' */ + icSigProfileSequceIdTag = 0x70736964, /* 'psid' */ + icSigPs2CRD0Tag = 0x70736430, /* 'psd0' Removed in V4 */ + icSigPs2CRD1Tag = 0x70736431, /* 'psd1' Removed in V4 */ + icSigPs2CRD2Tag = 0x70736432, /* 'psd2' Removed in V4 */ + icSigPs2CRD3Tag = 0x70736433, /* 'psd3' Removed in V4 */ + icSigPs2CSATag = 0x70733273, /* 'ps2s' Removed in V4 */ + icSigPs2RenderingIntentTag = 0x70733269, /* 'ps2i' Removed in V4 */ + icSigRedColorantTag = 0x7258595A, /* 'rXYZ' */ + icSigRedMatrixColumnTag = 0x7258595A, /* 'rXYZ' */ + icSigRedTRCTag = 0x72545243, /* 'rTRC' */ + icSigSaturationRenderingIntentGamutTag = 0x72696732, /* 'rig2' */ + icSigScreeningDescTag = 0x73637264, /* 'scrd' Removed in V4 */ + icSigScreeningTag = 0x7363726E, /* 'scrn' Removed in V4 */ + icSigTechnologyTag = 0x74656368, /* 'tech' */ + icSigUcrBgTag = 0x62666420, /* 'bfd ' Removed in V4 */ + icSigViewingCondDescTag = 0x76756564, /* 'vued' */ + icSigViewingConditionsTag = 0x76696577, /* 'view' */ +} icTagSignature; + +/** Convenience Enum Definitions - Not defined in ICC specification*/ +#define icSigUnknownTag ((icTagSignature) 0x3f3f3f3f) /* '????' */ +#define icMaxEnumTag ((icTagSignature) 0xFFFFFFFF) + + + +/** + * technology signature descriptions + */ +typedef enum { + icSigDigitalCamera = 0x6463616D, /* 'dcam' */ + icSigFilmScanner = 0x6673636E, /* 'fscn' */ + icSigReflectiveScanner = 0x7273636E, /* 'rscn' */ + icSigInkJetPrinter = 0x696A6574, /* 'ijet' */ + icSigThermalWaxPrinter = 0x74776178, /* 'twax' */ + icSigElectrophotographicPrinter = 0x6570686F, /* 'epho' */ + icSigElectrostaticPrinter = 0x65737461, /* 'esta' */ + icSigDyeSublimationPrinter = 0x64737562, /* 'dsub' */ + icSigPhotographicPaperPrinter = 0x7270686F, /* 'rpho' */ + icSigFilmWriter = 0x6670726E, /* 'fprn' */ + icSigVideoMonitor = 0x7669646D, /* 'vidm' */ + icSigVideoCamera = 0x76696463, /* 'vidc' */ + icSigProjectionTelevision = 0x706A7476, /* 'pjtv' */ + icSigCRTDisplay = 0x43525420, /* 'CRT ' */ + icSigPMDisplay = 0x504D4420, /* 'PMD ' */ + icSigAMDisplay = 0x414D4420, /* 'AMD ' */ + icSigPhotoCD = 0x4B504344, /* 'KPCD' */ + icSigPhotoImageSetter = 0x696D6773, /* 'imgs' */ + icSigGravure = 0x67726176, /* 'grav' */ + icSigOffsetLithography = 0x6F666673, /* 'offs' */ + icSigSilkscreen = 0x73696C6B, /* 'silk' */ + icSigFlexography = 0x666C6578, /* 'flex' */ + icSigMotionPictureFilmScanner = 0x6D706673, /* 'mpfs' */ + icSigMotionPictureFilmRecorder = 0x6D706672, /* 'mpfr' */ + icSigDigitalMotionPictureCamera = 0x646D7063, /* 'dmpc' */ + icSigDigitalCinemaProjector = 0x64636A70, /* 'dcpj' */ +} icTechnologySignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumTechnology ((icTechnologySignature) 0xFFFFFFFF) + +/** + * type signatures + */ +typedef enum { + icSigChromaticityType = 0x6368726D, /* 'chrm' */ + icSigColorantOrderType = 0x636C726F, /* 'clro' */ + icSigColorantTableType = 0x636C7274, /* 'clrt' */ + icSigCrdInfoType = 0x63726469, /* 'crdi' Removed in V4 */ + icSigCurveType = 0x63757276, /* 'curv' */ + icSigDataType = 0x64617461, /* 'data' */ + icSigDictType = 0x64696374, /* 'dict' */ + icSigDateTimeType = 0x6474696D, /* 'dtim' */ + icSigDeviceSettingsType = 0x64657673, /* 'devs' Removed in V4 */ + icSigLut16Type = 0x6d667432, /* 'mft2' */ + icSigLut8Type = 0x6d667431, /* 'mft1' */ + icSigLutAtoBType = 0x6d414220, /* 'mAB ' */ + icSigLutBtoAType = 0x6d424120, /* 'mBA ' */ + icSigMeasurementType = 0x6D656173, /* 'meas' */ + icSigMultiLocalizedUnicodeType = 0x6D6C7563, /* 'mluc' */ + icSigMultiProcessElementType = 0x6D706574, /* 'mpet' */ +#if 0 + icSigNamedColorType = 0x6E636f6C, /* 'ncol' OBSOLETE, use ncl2 */ +#endif + icSigNamedColor2Type = 0x6E636C32, /* 'ncl2' */ + icSigParametricCurveType = 0x70617261, /* 'para' */ + icSigProfileSequenceDescType = 0x70736571, /* 'pseq' */ + icSigProfileSequceIdType = 0x70736964, /* 'psid' */ + icSigResponseCurveSet16Type = 0x72637332, /* 'rcs2' */ + icSigS15Fixed16ArrayType = 0x73663332, /* 'sf32' */ + icSigScreeningType = 0x7363726E, /* 'scrn' Removed in V4 */ + icSigSignatureType = 0x73696720, /* 'sig ' */ + icSigTextType = 0x74657874, /* 'text' */ + icSigTextDescriptionType = 0x64657363, /* 'desc' Removed in V4 */ + icSigU16Fixed16ArrayType = 0x75663332, /* 'uf32' */ + icSigUcrBgType = 0x62666420, /* 'bfd ' Removed in V4 */ + icSigUInt16ArrayType = 0x75693136, /* 'ui16' */ + icSigUInt32ArrayType = 0x75693332, /* 'ui32' */ + icSigUInt64ArrayType = 0x75693634, /* 'ui64' */ + icSigUInt8ArrayType = 0x75693038, /* 'ui08' */ + icSigViewingConditionsType = 0x76696577, /* 'view' */ + icSigXYZType = 0x58595A20, /* 'XYZ ' */ + icSigXYZArrayType = 0x58595A20, /* 'XYZ ' */ +} icTagTypeSignature; + +/** Convenience Enum Definitions - Not defined in ICC specification*/ +#define icSigUnknownType ((icTagTypeSignature) 0x3f3f3f3f) /* '????' */ +#define icMaxEnumType ((icTagTypeSignature) 0xFFFFFFFF) + +/** + * Element type signatures + */ +typedef enum { + //DMP Proposal 1.0 elements + icSigCurveSetElemType = 0x63767374, /* 'cvst' */ + icSigMatrixElemType = 0x6D617466, /* 'matf' */ + icSigCLutElemType = 0x636C7574, /* 'clut' */ + icSigBAcsElemType = 0x62414353, /* 'bACS' */ + icSigEAcsElemType = 0x65414353, /* 'eACS' */ +} icElemTypeSignature; +/** Convenience Enum Definitions - Not defined in proposal*/ +#define icSigUnknownElemType ((icElemTypeSignature) 0x3f3f3f3f) /* '????' */ +#define icMaxEnumElemType ((icElemTypeSignature) 0xFFFFFFFF) + + +/** + * Color Space Signatures. + * Note that only icSigXYZData and icSigLabData are valid + * Profile Connection Spaces (PCSs) + */ +typedef enum { + icSigXYZData = 0x58595A20, /* 'XYZ ' */ + icSigLabData = 0x4C616220, /* 'Lab ' */ + icSigLuvData = 0x4C757620, /* 'Luv ' */ + icSigYCbCrData = 0x59436272, /* 'YCbr' */ + icSigYxyData = 0x59787920, /* 'Yxy ' */ + icSigRgbData = 0x52474220, /* 'RGB ' */ + icSigGrayData = 0x47524159, /* 'GRAY' */ + icSigHsvData = 0x48535620, /* 'HSV ' */ + icSigHlsData = 0x484C5320, /* 'HLS ' */ + icSigCmykData = 0x434D594B, /* 'CMYK' */ + icSigCmyData = 0x434D5920, /* 'CMY ' */ + + icSigMCH2Data = 0x32434C52, /* '2CLR' */ + icSigMCH3Data = 0x33434C52, /* '3CLR' */ + icSigMCH4Data = 0x34434C52, /* '4CLR' */ + icSigMCH5Data = 0x35434C52, /* '5CLR' */ + icSigMCH6Data = 0x36434C52, /* '6CLR' */ + icSigMCH7Data = 0x37434C52, /* '7CLR' */ + icSigMCH8Data = 0x38434C52, /* '8CLR' */ + icSigMCH9Data = 0x39434C52, /* '9CLR' */ + icSigMCHAData = 0x41434C52, /* 'ACLR' */ + icSigMCHBData = 0x42434C52, /* 'BCLR' */ + icSigMCHCData = 0x43434C52, /* 'CCLR' */ + icSigMCHDData = 0x44434C52, /* 'DCLR' */ + icSigMCHEData = 0x45434C52, /* 'ECLR' */ + icSigMCHFData = 0x46434C52, /* 'FCLR' */ + icSigNamedData = 0x6e6d636c, /* 'nmcl' */ + + icSig2colorData = 0x32434C52, /* '2CLR' */ + icSig3colorData = 0x33434C52, /* '3CLR' */ + icSig4colorData = 0x34434C52, /* '4CLR' */ + icSig5colorData = 0x35434C52, /* '5CLR' */ + icSig6colorData = 0x36434C52, /* '6CLR' */ + icSig7colorData = 0x37434C52, /* '7CLR' */ + icSig8colorData = 0x38434C52, /* '8CLR' */ + icSig9colorData = 0x39434C52, /* '9CLR' */ + icSig10colorData = 0x41434C52, /* 'ACLR' */ + icSig11colorData = 0x42434C52, /* 'BCLR' */ + icSig12colorData = 0x43434C52, /* 'CCLR' */ + icSig13colorData = 0x44434C52, /* 'DCLR' */ + icSig14colorData = 0x45434C52, /* 'ECLR' */ + icSig15colorData = 0x46434C52, /* 'FCLR' */ + +} icColorSpaceSignature; + +/** Defined by previous versions of header file but not defined in ICC specification */ +#define icSigMCH1Data ((icColorSpaceSignature) 0x31434C52) /* '1CLR' */ +#define icSigMCHGData ((icColorSpaceSignature) 0x47434C52) /* 'GCLR' */ +#define icSig1colorData ((icColorSpaceSignature) 0x31434C52) /* '1CLR' */ +#define icSig16colorData ((icColorSpaceSignature) 0x47434C52) /* 'GCLR' */ + +/** Defined by LittleCMS **/ + +/** Convenience Enum Definitions - Not defined in ICC specification*/ +#define icSigGamutData ((icColorSpaceSignature) 0x67616D74) /* 'gamt' */ +#define icSigUnknownData ((icColorSpaceSignature) 0x3f3f3f3f) /* '????' */ +#define icMaxEnumData ((icColorSpaceSignature) 0xFFFFFFFF) + + + +/** profileClass enumerations */ +typedef enum { + icSigInputClass = 0x73636E72, /* 'scnr' */ + icSigDisplayClass = 0x6D6E7472, /* 'mntr' */ + icSigOutputClass = 0x70727472, /* 'prtr' */ + icSigLinkClass = 0x6C696E6B, /* 'link' */ + icSigAbstractClass = 0x61627374, /* 'abst' */ + icSigColorSpaceClass = 0x73706163, /* 'spac' */ + icSigNamedColorClass = 0x6e6d636c, /* 'nmcl' */ +} icProfileClassSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumClass ((icProfileClassSignature) 0xFFFFFFFF) + + + +/** Platform Signatures */ +typedef enum { + icSigMacintosh = 0x4150504C, /* 'APPL' */ + icSigMicrosoft = 0x4D534654, /* 'MSFT' */ + icSigSolaris = 0x53554E57, /* 'SUNW' */ + icSigSGI = 0x53474920, /* 'SGI ' */ + icSigTaligent = 0x54474E54, /* 'TGNT' */ + icSigUnkownPlatform = 0x00000000 +} icPlatformSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumPlatform ((icPlatformSignature) 0xFFFFFFFF) + + +/** CMM signatures from the signature registry (as of Jan 10, 2007) */ +typedef enum { + icSigAdobe = 0x41444245, /* 'ADBE' */ + icSigApple = 0x6170706C, /* 'appl' */ + icSigColorGear = 0x43434D53, /* 'CCMS' */ + icSigColorGearLite = 0x5543434D, /* 'UCCM' */ + icSigFujiFilm = 0x46462020, /* 'FF ' */ + icSigHarlequinRIP = 0x48434d4d, /* 'HCMM' */ + icSigArgyllCMS = 0x6172676C, /* 'argl' */ + icSigLogoSync = 0x44676f53, /* 'LgoS' */ + icSigHeidelberg = 0x48444d20, /* 'HDM ' */ + icSigLittleCMS = 0x6C636d73, /* 'lcms' */ + icSigKodak = 0x4b434d53, /* 'KCMS' */ + icSigKonicaMinolta = 0x4d434d44, /* 'MCML' */ + icSigMutoh = 0x5349474E, /* 'SIGN' */ + icSigSampleICC = 0x53494343, /* 'SICC' */ + icSigTheImagingFactory = 0x33324254, /* '32BT' */ +} icCmmSignature; +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumCmm ((icCmmSignature) 0xFFFFFFFF) + + +/** Rendering Intent Gamut Signatures */ +typedef enum { + icSigPerceptualReferenceMediumGamut = 0x70726d67, /* 'prmg' */ +} icReferenceMediumGamutSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumReferenceMediumGamut ((icReferenceMediumGamutSignature 0xFFFFFFFF) + + +/** Colorimetric Intent Image State Gamut Signatures */ +typedef enum { + icSigSceneColorimetryEstimates = 0x73636F65, /* 'scoe' */ + icSigSceneAppearanceEstimates = 0x73617065, /* 'sape' */ + icSigFocalPlaneColorimetryEstimates = 0x66706365, /* 'fpce' */ + icSigReflectionHardcopyOriginalColorimetry = 0x72686F63, /* 'rhoc' */ + icSigReflectionPrintOutputColorimetry = 0x72706F63, /* 'rpoc' */ +} icColorimetricIntentImageStateSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumColorimetricIntentImageState ((icColorimetricIntentImageStateSignature 0xFFFFFFFF) + + +/** + * MPE Curve segment Signatures + */ +typedef enum { + icSigFormulaCurveSeg = 0x70617266, /* 'parf' */ + icSigSampledCurveSeg = 0x73616D66, /* 'samf' */ +} icCurveSegSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxCurveSegSignature ((icCurveSegSignature 0xFFFFFFFF) + +/** + * MPE Curve Set Curve signature + */ +typedef enum { + icSigSementedCurve = 0x63757266, /* 'curf' */ +} icCurveElemSignature; + +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxCurveElemSignature ((icCurveElemSignature 0xFFFFFFFF) + +/** + * MPE Future Extension Acs signature + */ + +typedef icSignature icAcsSignature; + +/** Convenience Definition - Not defined in ICC specification*/ +#define icSigAcsZero ((icAcsSignature) 0x00000000) + +/*------------------------------------------------------------------------*/ + +/** + * Other enums + */ + +/** Measurement Flare, used in the measurmentType tag */ +typedef enum { + icFlare0 = 0x00000000, /* 0% flare */ + icFlare100 = 0x00000001, /* 100% flare */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumFlare = 0xFFFFFFFF, + icMaxFlare = 0xFFFFFFFF, /* as defined by earlier versions */ +#endif +} icMeasurementFlare; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumFlare ((icMeasurementFlare) 0xFFFFFFFF) +#define icMaxFlare ((icMeasurementFlare) 0xFFFFFFFF) /* as defined by earlier versions */ +#endif + + +/** Measurement Geometry, used in the measurmentType tag */ +typedef enum { + icGeometryUnknown = 0x00000000, /* Unknown geometry */ + icGeometry045or450 = 0x00000001, /* 0/45, 45/0 */ + icGeometry0dord0 = 0x00000002, /* 0/d or d/0 */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumGeometry = 0xFFFFFFFF, + icMaxGeometry = 0xFFFFFFFF, +#endif +} icMeasurementGeometry; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumGeometry ((icMeasurementGeometry) 0xFFFFFFFF) +#define icMaxGeometry ((icMeasurementGeometry) 0xFFFFFFFF) +#endif + + +/** Rendering Intents, used in the profile header */ +typedef enum { + icPerceptual = 0, + icRelativeColorimetric = 1, + icSaturation = 2, + icAbsoluteColorimetric = 3, + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definitions - Not defined in ICC specification*/ + icUnknownIntent = 0x3f3f3f3f, /* '????' */ + icMaxEnumIntent = 0xFFFFFFFF, +#endif +} icRenderingIntent; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definitions - Not defined in ICC specification*/ +#define icUnknownIntent ((icRenderingIntent) 0x3f3f3f3f) /* '????' */ +#define icMaxEnumIntent ((icRenderingIntent) 0xFFFFFFFF) +#endif + + + +/** Different Spot Shapes currently defined, used for screeningType */ +typedef enum { + icSpotShapeUnknown = 0, + icSpotShapePrinterDefault = 1, + icSpotShapeRound = 2, + icSpotShapeDiamond = 3, + icSpotShapeEllipse = 4, + icSpotShapeLine = 5, + icSpotShapeSquare = 6, + icSpotShapeCross = 7, + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumSpot = 0xFFFFFFFF, +#endif +} icSpotShape; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumSpot ((icSpotShape) 0xFFFFFFFF) +#endif + + + +/** Standard Observer, used in the measurmentType tag */ +typedef enum { + icStdObsUnknown = 0x00000000, /* Unknown observer */ + icStdObs1931TwoDegrees = 0x00000001, /* 1931 two degrees */ + icStdObs1964TenDegrees = 0x00000002, /* 1961 ten degrees */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definitions - Not defined in ICC specification*/ + icMaxEnumStdObs = 0xFFFFFFFF, + icMaxStdObs = 0xFFFFFFFF, +#endif +} icStandardObserver; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumStdObs ((icStandardObserver) 0xFFFFFFFF) +#define icMaxStdObs ((icStandardObserver) 0xFFFFFFFF) /* as defined by earlier versions */ +#endif + + + +/** Pre-defined illuminants, used in measurement and viewing conditions type */ +typedef enum { + icIlluminantUnknown = 0x00000000, + icIlluminantD50 = 0x00000001, + icIlluminantD65 = 0x00000002, + icIlluminantD93 = 0x00000003, + icIlluminantF2 = 0x00000004, + icIlluminantD55 = 0x00000005, + icIlluminantA = 0x00000006, + icIlluminantEquiPowerE = 0x00000007, /* Equi-Power (E) */ + icIlluminantF8 = 0x00000008, + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definitions - Not defined in ICC specification*/ + icMaxEnumIlluminant = 0xFFFFFFFF, + icMaxEnumIluminant = 0xFFFFFFFF, /* as defined by earlier versions */ +#endif +} icIlluminant; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definitions - Not defined in ICC specification*/ +#define icMaxEnumIlluminant ((icIlluminant) 0xFFFFFFFF) +#define icMaxEnumIluminant ((icIlluminant) 0xFFFFFFFF) /* as defined by earlier versions */ +#endif + + + +/** A not so exhaustive list of language codes */ +typedef enum { + icLanguageCodeEnglish = 0x656E, /* 'en' */ + icLanguageCodeGerman = 0x6465, /* 'de' */ + icLanguageCodeItalian = 0x6974, /* 'it' */ + icLanguageCodeDutch = 0x6E6C, /* 'nl' */ + icLanguageCodeSweden = 0x7376, /* 'sv' */ + icLanguageCodeSpanish = 0x6573, /* 'es' */ + icLanguageCodeDanish = 0x6461, /* 'da' */ + icLanguageCodeNorwegian = 0x6E6F, /* 'no' */ + icLanguageCodeJapanese = 0x6A61, /* 'ja' */ + icLanguageCodeFinnish = 0x6669, /* 'fi' */ + icLanguageCodeTurkish = 0x7472, /* 'tr' */ + icLanguageCodeKorean = 0x6B6F, /* 'ko' */ + icLanguageCodeChinese = 0x7A68, /* 'zh' */ + icLanguageCodeFrench = 0x6672, /* 'fr' */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumLanguageCode = 0xFFFF, +#endif +} icEnumLanguageCode; +typedef icUInt16Number icLanguageCode; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumLanguageCode ((icEnumLanguageCode) 0xFFFF) +#endif + + + +/** +* A not so exhaustive list of country codes. + * Helpful website: http://dev.krook.org ld.html */ +typedef enum { + icCountryCodeUSA = 0x5553, /* 'US' */ + icCountryCodeUnitedKingdom = 0x554B, /* 'UK' */ + icCountryCodeGermany = 0x4445, /* 'DE' */ + icCountryCodeItaly = 0x4954, /* 'IT' */ + icCountryCodeNetherlands = 0x4E4C, /* 'NL' */ + icCountryCodeSpain = 0x4543, /* 'ES' */ + icCountryCodeDenmark = 0x444B, /* 'DK' */ + icCountryCodeNorway = 0x4E4F, /* 'NO' */ + icCountryCodeJapan = 0x4A50, /* 'JP' */ + icCountryCodeFinland = 0x4649, /* 'FI' */ + icCountryCodeTurkey = 0x5452, /* 'TR' */ + icCountryCodeKorea = 0x4B52, /* 'KR' */ + icCountryCodeChina = 0x434E, /* 'CN' */ + icCountryCodeTaiwan = 0x5457, /* 'TW' */ + icCountryCodeFrance = 0x4652, /* 'FR' */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumCountryCode = 0xFFFF, +#endif +} icEnumCountryCode; +typedef icUInt16Number icCountryCode; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumCountryCode ((icEnumCountryCode) 0xFFFF) +#endif + + + +/** Measurement Unit Signatures used in ResponseCurveSet16Type */ +typedef enum { + icSigStatusA = 0x53746141, /* 'StaA' */ + icSigStatusE = 0x53746145, /* 'StaE' */ + icSigStatusI = 0x53746149, /* 'StaI' */ + icSigStatusT = 0x53746154, /* 'StaT' */ + icSigStatusM = 0x5374614D, /* 'StaM' */ + icSigDN = 0x444E2020, /* 'DN ' */ + icSigDNP = 0x444E2050, /* 'DN P' */ + icSigDNN = 0x444E4E20, /* 'DNN ' */ + icSigDNNP = 0x444E4E50, /* 'DNNP' */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumMeasurmentUnitSig = 0xffffffff, +#endif +} icMeasurementUnitSig; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumMeasurmentUnitSig ((icMeasurementUnitSig) 0xffffffff) +#endif + + + +/** Colorant and Phosphor Encodings used in chromaticity type */ +typedef enum { + icColorantUnknown = 0x0000, /* Unknown */ + icColorantITU = 0x0001, /* ITU-R BT.709 */ + icColorantSMPTE = 0x0002, /* SMPTE RP145-1994 */ + icColorantEBU = 0x0003, /* EBU Tech.3213-E */ + icColorantP22 = 0x0004, /* P22 */ + +#ifdef ICC_ENUM_CONVENIENCE + /** Convenience Enum Definition - Not defined in ICC specification*/ + icMaxEnumColorant = 0xFFFF, +#endif +} icColorantEncoding; + +#ifndef ICC_ENUM_CONVENIENCE +/** Convenience Enum Definition - Not defined in ICC specification*/ +#define icMaxEnumColorant ((icColorantEncoding) 0xFFFF) +#endif + + +/** + * Note: The next three enum types are for DeviceSettingType structures + * supported by V2 profiles. The DeviceSettingsType was removed in the + * V4 specificaiton.*/ + +/** DeviceSettingsType structure ID signatures for Microsoft 'msft' platform*/ +typedef enum { + icMSFTDevSetResolution = 0x72736C6E, /* 'rsln' */ + icMSFTDevSetMediaType = 0x6D747970, /* 'mtyp' */ + icMSFTDevSetMediaHalftone = 0x6866746E, /* 'hftn' */ + +} icMSFTDevSetSig; + +/** DeviceSettingsType media encodings for Microsoft 'msft' platform */ +typedef enum { + icDMMediaStandard = 0x0001, /* Standard paper */ + icDMMediaTransparancy = 0x0002, /* Transparency */ + icDMMediaGlossy = 0x0003, /* Glossy paper */ + icDMMediaUser = 0x0100, /* Device-specific type media + are >= 256 */ +} icDMMediaType; + +/** DeviceSettingsType media encodings for Microsoft 'msft' platform */ +typedef enum { + icDMDitherNone = 0x0001, /* No dithering */ + icDMDitherCoarse = 0x0002, /* Dither with a coarse brush */ + icDMDitherFine = 0x0003, /* Dither with a fine brush */ + icDMDitherLineArt = 0x0004, /* LineArt dithering */ + icDMDitherErrorDiffusion = 0x0005, /* Error Diffusion */ + icDMDitherReserved6 = 0x0006, + icDMDitherReserved7 = 0x0007, + icDMDitherReserved8 = 0x0008, + icDMDitherReserved9 = 0x0009, + icDMDitherGrayscale = 0x000A, /* Device does grayscaling */ + icDMDitherUser = 0x0100, /* Device-specifice halftones + are >= 256 */ +} icDMHalftoneType; + +/** +*------------------------------------------------------------------------ +* + * Arrays of numbers + */ + +/** Int8 Array */ +typedef struct { + icInt8Number data[icAny]; /* Variable array of values */ +} icInt8Array; + +/** UInt8 Array */ +typedef struct { + icUInt8Number data[icAny]; /* Variable array of values */ +} icUInt8Array; + +/** uInt16 Array */ +typedef struct { + icUInt16Number data[icAny]; /* Variable array of values */ +} icUInt16Array; + +/** Int16 Array */ +typedef struct { + icInt16Number data[icAny]; /* Variable array of values */ +} icInt16Array; + +/** uInt32 Array */ +typedef struct { + icUInt32Number data[icAny]; /* Variable array of values */ +} icUInt32Array; + +/** Int32 Array */ +typedef struct { + icInt32Number data[icAny]; /* Variable array of values */ +} icInt32Array; + +/** UInt64 Array */ +typedef struct { + icUInt64Number data[icAny]; /* Variable array of values */ +} icUInt64Array; + +/** Int64 Array */ +typedef struct { + icInt64Number data[icAny]; /* Variable array of values */ +} icInt64Array; + +/** u16Fixed16 Array */ +typedef struct { + icU16Fixed16Number data[icAny]; /* Variable array of values */ +} icU16Fixed16Array; + +/** s15Fixed16 Array */ +typedef struct { + icS15Fixed16Number data[icAny]; /* Variable array of values */ +} icS15Fixed16Array; + +/** The base date time number */ +typedef struct { + icUInt16Number year; + icUInt16Number month; + icUInt16Number day; + icUInt16Number hours; + icUInt16Number minutes; + icUInt16Number seconds; +} icDateTimeNumber; + +/** XYZ Number */ +typedef struct { + icS15Fixed16Number X; + icS15Fixed16Number Y; + icS15Fixed16Number Z; +} icXYZNumber; + +/** XYZ Array */ +typedef struct { + icXYZNumber data[icAny]; /* Variable array of XYZ numbers */ +} icXYZArray; + +/** xy Chromaticity Number */ +typedef struct { + icU16Fixed16Number x; + icU16Fixed16Number y; +} icChromaticityNumber; + +/** response16Number */ +typedef struct { + icUInt16Number deviceCode; + icUInt16Number reserved; + icS15Fixed16Number measurementValue; +} icResponse16Number; + +/** positionNumber **/ +typedef struct { + icUInt32Number offset; + icUInt32Number size; +} icPositionNumber; + + +/** Curve */ +typedef struct { + icUInt32Number count; /* Number of entries */ + icUInt16Number data[icAny]; /* The actual table data, real + * number is determined by count + * Interpretation depends on how + * data is used with a given tag + */ +} icCurve; + +/** Parametric Curve */ +typedef struct { + icUInt16Number funcType; /* Function Type */ + /* 0 = gamma only */ + icUInt16Number pad; /* Padding for byte alignment */ + icS15Fixed16Number gamma; /* xgamma */ + /* up to 7 values Y,a,b,c,d,e,f */ +} icParametricCurve; + +/** Parametric Curve */ +typedef struct { + icUInt16Number funcType; /* Function Type */ + /* 0 = gamma only */ + icUInt16Number pad; /* Padding for byte alignment */ + icS15Fixed16Number gamma; /* xgamma */ + icS15Fixed16Number a; /* a */ + icS15Fixed16Number b; /* b */ + icS15Fixed16Number c; /* c */ + icS15Fixed16Number d; /* d */ + icS15Fixed16Number e; /* e */ + icS15Fixed16Number f; /* f */ +} icParametricCurveFull; + +/** Data */ +typedef struct { + icUInt32Number dataFlag; /* 0 = ascii, 1 = binary */ + icInt8Number data[icAny]; /* Data, size determined from tag */ +} icData; + +/** lut16 */ +typedef struct { + icUInt8Number inputChan; /* Number of input channels */ + icUInt8Number outputChan; /* Number of output channels */ + icUInt8Number clutPoints; /* Number of clutTable grid points */ + icInt8Number pad; /* Padding for byte alignment */ + icS15Fixed16Number e00; /* e00 in the 3 * 3 */ + icS15Fixed16Number e01; /* e01 in the 3 * 3 */ + icS15Fixed16Number e02; /* e02 in the 3 * 3 */ + icS15Fixed16Number e10; /* e10 in the 3 * 3 */ + icS15Fixed16Number e11; /* e11 in the 3 * 3 */ + icS15Fixed16Number e12; /* e12 in the 3 * 3 */ + icS15Fixed16Number e20; /* e20 in the 3 * 3 */ + icS15Fixed16Number e21; /* e21 in the 3 * 3 */ + icS15Fixed16Number e22; /* e22 in the 3 * 3 */ + icUInt16Number inputEnt; /* Number of input table entries */ + icUInt16Number outputEnt; /* Number of output table entries */ + icUInt16Number data[icAny]; /* Data follows see spec for size */ + /** + * Data that follows is of this form + * + * icUInt16Number inputTable[inputChan][icAny]; * The input table + * icUInt16Number clutTable[icAny]; * The clut table + * icUInt16Number outputTable[outputChan][icAny]; * The output table + */ +} icLut16; + +/** lut8, input & output tables are always 256 bytes in length */ +typedef struct { + icUInt8Number inputChan; /* Number of input channels */ + icUInt8Number outputChan; /* Number of output channels */ + icUInt8Number clutPoints; /* Number of clutTable grid points */ + icInt8Number pad; + icS15Fixed16Number e00; /* e00 in the 3 * 3 */ + icS15Fixed16Number e01; /* e01 in the 3 * 3 */ + icS15Fixed16Number e02; /* e02 in the 3 * 3 */ + icS15Fixed16Number e10; /* e10 in the 3 * 3 */ + icS15Fixed16Number e11; /* e11 in the 3 * 3 */ + icS15Fixed16Number e12; /* e12 in the 3 * 3 */ + icS15Fixed16Number e20; /* e20 in the 3 * 3 */ + icS15Fixed16Number e21; /* e21 in the 3 * 3 */ + icS15Fixed16Number e22; /* e22 in the 3 * 3 */ + icUInt8Number data[icAny]; /* Data follows see spec for size */ + /** + * Data that follows is of this form + * + * icUInt8Number inputTable[inputChan][256]; * The input table + * icUInt8Number clutTable[icAny]; * The clut table + * icUInt8Number outputTable[outputChan][256]; * The output table + */ +} icLut8; + +/** icLutAToB */ +typedef struct { + icUInt8Number gridPoints[16]; /* Number of grid points in each dimension. */ + icUInt8Number prec; /* Precision of data elements in bytes. */ + icUInt8Number pad1; + icUInt8Number pad2; + icUInt8Number pad3; + /*icUInt8Number data[icAny]; Data follows see spec for size */ +} icCLutStruct; + +/** icLutAtoB */ +typedef struct { + icUInt8Number inputChan; /* Number of input channels */ + icUInt8Number outputChan; /* Number of output channels */ + icUInt8Number pad1; + icUInt8Number pad2; + icUInt32Number offsetB; /* Offset to first "B" curve */ + icUInt32Number offsetMat; /* Offset to matrix */ + icUInt32Number offsetM; /* Offset to first "M" curve */ + icUInt32Number offsetC; /* Offset to CLUT */ + icUInt32Number offsetA; /* Offset to first "A" curve */ + /*icUInt8Number data[icAny]; Data follows see spec for size */ +} icLutAtoB; + +/** icLutBtoA */ +typedef struct { + icUInt8Number inputChan; /* Number of input channels */ + icUInt8Number outputChan; /* Number of output channels */ + icUInt8Number pad1; + icUInt8Number pad2; + icUInt32Number offsetB; /* Offset to first "B" curve */ + icUInt32Number offsetMat; /* Offset to matrix */ + icUInt32Number offsetM; /* Offset to first "M" curve */ + icUInt32Number offsetC; /* Offset to CLUT */ + icUInt32Number offsetA; /* Offset to first "A" curve */ + /*icUInt8Number data[icAny]; Data follows see spec for size */ +} icLutBtoA; + +/** Measurement Data */ +typedef struct { + icStandardObserver stdObserver; /* Standard observer */ + icXYZNumber backing; /* XYZ for backing material */ + icMeasurementGeometry geometry; /* Measurement geometry */ + icMeasurementFlare flare; /* Measurement flare */ + icIlluminant illuminant; /* Illuminant */ +} icMeasurement; + +/** + * Named color + */ + +/** Entry format for each named color */ +typedef struct { + icUInt8Number rootName[32]; /* Root name for first color */ + icUInt16Number pcsCoords[3]; /* PCS coordinates of color (only Lab or XYZ allowed)*/ + icUInt16Number deviceCoords[icAny]; /* Device coordinates of color */ +} icNamedColor2Entry; + +/** + * icNamedColor2 takes the place of icNamedColor + */ +typedef struct { + icUInt32Number vendorFlag; /* Bottom 16 bits for IC use */ + icUInt32Number count; /* Count of named colors */ + icUInt32Number nDeviceCoords; /* Number of device coordinates */ + icInt8Number prefix[32]; /* Prefix for each color name */ + icInt8Number suffix[32]; /* Suffix for each color name */ + icInt8Number data[icAny]; /* Named color data follows */ + /** + * Data that follows is of this form + * + * icInt8Number root1[32]; * Root name for first color + * icUInt16Number pcsCoords1[icAny]; * PCS coordinates of first color + * icUInt16Number deviceCoords1[icAny]; * Device coordinates of first color + * icInt8Number root2[32]; * Root name for second color + * icUInt16Number pcsCoords2[icAny]; * PCS coordinates of first color + * icUInt16Number deviceCoords2[icAny]; * Device coordinates of first color + * : + * : + * + * Alternatively written if byte packing is assumed with no padding between + * structures then data can take the following form + * + * icNamedColor2Entry entry0; // Entry for first color + * icNamedColor2Entry entry1; // Entry for second color + * : + * : + * In either case repeat for name and PCS and device color coordinates up to (count-1) + * + * NOTES: + * PCS and device space can be determined from the header. + * + * PCS coordinates are icUInt16 numbers and are described in Annex A of + * the ICC spec. Only 16 bit L*a*b* and XYZ are allowed. The number of + * coordinates is consistent with the headers PCS. + * + * Device coordinates are icUInt16 numbers where 0x0000 represents + * the minimum value and 0xFFFF represents the maximum value. + * If the nDeviceCoords value is 0 this field is not given. + */ +} icNamedColor2; + +/** Profile sequence structure */ +typedef struct { + icSignature deviceMfg; /* Device Manufacturer */ + icSignature deviceModel; /* Decvice Model */ + icUInt64Number attributes; /* Device attributes */ + icTechnologySignature technology; /* Technology signature */ + icInt8Number data[icAny]; /* Descriptions text follows */ + /** + * Data that follows is of this form, this is an icInt8Number + * to avoid problems with a compiler generating bad code as + * these arrays are variable in length. + * + * icTextDescription deviceMfgDesc; * Manufacturer text + * icTextDescription modelDesc; * Model text + */ +} icDescStruct; + +/** Profile sequence description */ +typedef struct { + icUInt32Number count; /* Number of descriptions */ + icUInt8Number data[icAny]; /* Array of description struct */ +} icProfileSequenceDesc; + +/** textDescription */ +typedef struct { + icUInt32Number count; /* Description length */ + icInt8Number data[icAny]; /* Descriptions follow */ + /** + * Data that follows is of this form + * + * icInt8Number desc[count] * NULL terminated ascii string + * icUInt32Number ucLangCode; * UniCode language code + * icUInt32Number ucCount; * UniCode description length + * icInt16Number ucDesc[ucCount];* The UniCode description + * icUInt16Number scCode; * ScriptCode code + * icUInt8Number scCount; * ScriptCode count + * icInt8Number scDesc[67]; * ScriptCode Description + */ +} icTextDescription; + +/** Screening Data */ +typedef struct { + icS15Fixed16Number frequency; /* Frequency */ + icS15Fixed16Number angle; /* Screen angle */ + icSpotShape spotShape; /* Spot Shape encodings below */ +} icScreeningData; + +/** screening */ +typedef struct { + icUInt32Number screeningFlag; /* Screening flag */ + icUInt32Number channels; /* Number of channels */ + icScreeningData data[icAny]; /* Array of screening data */ +} icScreening; + +/** Text Data */ +typedef struct { + icInt8Number data[icAny]; /* Variable array of characters */ +} icText; + +/** Structure describing either a UCR or BG curve */ +typedef struct { + icUInt32Number count; /* Curve length */ + icUInt16Number curve[icAny]; /* The array of curve values */ +} icUcrBgCurve; + +/** Under color removal, black generation */ +typedef struct { + icInt8Number data[icAny]; /* The Ucr BG data */ + /** + * Data that follows is of this form, this is a icInt8Number + * to avoid problems with a compiler generating bad code as + * these arrays are variable in length. + * + * icUcrBgCurve ucr; * Ucr curve + * icUcrBgCurve bg; * Bg curve + * icInt8Number string; * UcrBg description + */ +} icUcrBg; + +/** viewingConditionsType */ +typedef struct { + icXYZNumber illuminant; /* In candelas per metre sq'd */ + icXYZNumber surround; /* In candelas per metre sq'd */ + icIlluminant stdIluminant; /* See icIlluminant defines */ +} icViewingCondition; + +/** CrdInfo type */ +typedef struct { + icUInt32Number count; /* Char count includes NULL */ + icInt8Number desc[icAny]; /* Null terminated string */ +} icCrdInfo; + +/** ColorantOrder type */ +typedef struct { + icUInt32Number count; /* Count of colorants */ + icUInt8Number data[icAny]; /* One-based number of the + colorant to be printed first, + second... */ +} icColorantOrder; + +/** ColorantTable Entry */ +typedef struct { + icInt8Number name[32]; /* First colorant name */ + icUInt16Number data[3]; /* 16 bit PCS Lab value for first */ +} icColorantTableEntry; + +/** ColorantTable */ +typedef struct { + icUInt32Number count; /* Count of colorants */ + icColorantTableEntry entry[icAny]; /* N colorant entries */ +} icColorantTable; + +/*------------------------------------------------------------------------*/ + +/** + * Tag Type definitions + */ + + +/** The base part of each tag */ +typedef struct { + icTagTypeSignature sig; /* Signature */ + icInt8Number reserved[4]; /* Reserved, set to 0 */ +} icTagBase; + +/** curveType */ +typedef struct { + icTagBase base; /* Signature, "curv" */ + icCurve curve; /* The curve data */ +} icCurveType; + +/** ParametricCurveType */ +typedef struct { + icTagBase base; /* Signature, "para" */ + icParametricCurve curve; /* The Parametric curve data*/ +} icParametricCurveType; + +/** ParametricCurveFullType */ +typedef struct { + icTagBase base; /* Signature, "para" */ + icParametricCurveFull curve; /* The Parametric curve data*/ +} icParametricCurveFullType; + +/** dataType */ +typedef struct { + icTagBase base; /* Signature, "data" */ + icData data; /* The data structure */ +} icDataType; + +/** dateTimeType */ +typedef struct { + icTagBase base; /* Signature, "dtim" */ + icDateTimeNumber date; /* The date */ +} icDateTimeType; + +/** lut16Type */ +typedef struct { + icTagBase base; /* Signature, "mft2" */ + icLut16 lut; /* Lut16 data */ +} icLut16Type; + +/** lut8Type, input & output tables are always 256 bytes in length */ +typedef struct { + icTagBase base; /* Signature, "mft1" */ + icLut8 lut; /* Lut8 data */ +} icLut8Type; + +/** lutAtoBType new format */ +typedef struct { + icTagBase base; /* Signature, "mAB " */ + icLutAtoB lut; /* icLutAtoB data */ +} icLutAtoBType; + +/** lutBtoAType new format */ +typedef struct { + icTagBase base; /* Signature, "mBA " */ + icLutBtoA lut; /* icLutBtoA data */ +} icLutBtoAType; + +/** Measurement Type */ +typedef struct { + icTagBase base; /* Signature, "meas" */ + icMeasurement measurement; /* Measurement data */ +} icMeasurementType; + +/** + * Named color type + */ + +/** icNamedColor2Type, replaces icNamedColorType */ +typedef struct { + icTagBase base; /* Signature, "ncl2" */ + icNamedColor2 ncolor; /* Named color data */ +} icNamedColor2Type; + +/** Profile sequence description type */ +typedef struct { + icTagBase base; /* Signature, "pseq" */ + icProfileSequenceDesc desc; /* The seq description */ +} icProfileSequenceDescType; + +/** textDescriptionType */ +typedef struct { + icTagBase base; /* Signature, "desc" */ + icTextDescription desc; /* The description */ +} icTextDescriptionType; + +/** s15Fixed16Type */ +typedef struct { + icTagBase base; /* Signature, "sf32" */ + icS15Fixed16Array data; /* Array of values */ +} icS15Fixed16ArrayType; + +/** screeningType */ +typedef struct { + icTagBase base; /* Signature, "scrn" */ + icScreening screen; /* Screening structure */ +} icScreeningType; + +/** sigType */ +typedef struct { + icTagBase base; /* Signature, "sig" */ + icSignature signature; /* The signature data */ +} icSignatureType; + +/** textType */ +typedef struct { + icTagBase base; /* Signature, "text" */ + icText data; /* Variable array of characters */ +} icTextType; + +/** u16Fixed16Type */ +typedef struct { + icTagBase base; /* Signature, "uf32" */ + icU16Fixed16Array data; /* Variable array of values */ +} icU16Fixed16ArrayType; + +/** Under color removal, black generation type */ +typedef struct { + icTagBase base; /* Signature, "bfd " */ + icUcrBg data; /* ucrBg structure */ +} icUcrBgType; + +/** uInt16Type */ +typedef struct { + icTagBase base; /* Signature, "ui16" */ + icUInt16Array data; /* Variable array of values */ +} icUInt16ArrayType; + +/** uInt32Type */ +typedef struct { + icTagBase base; /* Signature, "ui32" */ + icUInt32Array data; /* Variable array of values */ +} icUInt32ArrayType; + +/** uInt64Type */ +typedef struct { + icTagBase base; /* Signature, "ui64" */ + icUInt64Array data; /* Variable array of values */ +} icUInt64ArrayType; + +/** uInt8Type */ +typedef struct { + icTagBase base; /* Signature, "ui08" */ + icUInt8Array data; /* Variable array of values */ +} icUInt8ArrayType; + +/** viewingConditionsType */ +typedef struct { + icTagBase base; /* Signature, "view" */ + icViewingCondition view; /* Viewing conditions */ +} icViewingConditionType; + +/** XYZ Type */ +typedef struct { + icTagBase base; /* Signature, "XYZ" */ + icXYZArray data; /* Variable array of XYZ numbers */ +} icXYZType; + +/** + * CRDInfoType where [0] is the CRD product name count and string and + * [1] -[5] are the rendering intents 0-4 counts and strings + */ +typedef struct { + icTagBase base; /* Signature, "crdi" */ + icCrdInfo info; /* 5 sets of counts & strings */ +}icCrdInfoType; + /* icCrdInfo productName; PS product count/string */ + /* icCrdInfo CRDName0; CRD name for intent 0 */ + /* icCrdInfo CRDName1; CRD name for intent 1 */ + /* icCrdInfo CRDName2; CRD name for intent 2 */ + /* icCrdInfo CRDName3; CRD name for intent 3 */ + +/** ColorantOrderType type */ +typedef struct { + icTagBase base; /* Signature, "clro" */ + icColorantOrder order; /* ColorantOrder */ +}icColorantOrderType; + +/** ColorantTableType type */ +typedef struct { + icTagBase base; /* Signature, "clrt" */ + icColorantTable table; /* ColorantTable */ +}icColorantTableType; + +/** ChromaticAdaptation type */ +typedef struct { + icTagBase base; /* Signature, "chad" */ + icS15Fixed16Number matrix[9]; /* ChromaticAdaptation Matrix */ +}icChromaticAdaptationType; + +/** MultiLocalizedUnicodeEntry type */ +typedef struct { + icUInt16Number languageCode; /* name language code ISO-639 */ + icUInt16Number countryCode; /* name country code ISO-3166 */ + icUInt32Number len; /* string length in bytes */ + icUInt32Number off; /* offset in bytes from start of tag */ +}icMultiLocalizedUnicodeEntry; + +/** MultiLocalizedUnicode type */ +typedef struct { + icTagBase base; /* Signature, "mluc" */ + icUInt32Number count; /* Count of name records */ + icUInt32Number size; /* name record size */ +}icMultiLocalizedUnicodeType; + +/*------------------------------------------------------------------------*/ + +/** +* Lists of tags, tags, profile header and profile structure + */ + +/** A tag */ +typedef struct { + icTagSignature sig; /* The tag signature */ + icUInt32Number offset; /* Start of tag relative to + * start of header, Spec + * Clause 5 */ + icUInt32Number size; /* Size in bytes */ +} icTag; + +/** A Structure that may be used independently for a list of tags */ +typedef struct { + icUInt32Number count; /* Number of tags in the profile */ + icTag tags[icAny]; /* Variable array of tags */ +} icTagList; + +/** Profile ID */ +typedef union { + icUInt8Number ID8[16]; + icUInt16Number ID16[8]; + icUInt32Number ID32[4]; +} icProfileID; + +/** The Profile header */ +typedef struct { + icUInt32Number size; /* Profile size in bytes */ + icSignature cmmId; /* CMM for this profile */ + icUInt32Number version; /* Format version number */ + icProfileClassSignature deviceClass; /* Type of profile */ + icColorSpaceSignature colorSpace; /* Color space of data */ + icColorSpaceSignature pcs; /* PCS, XYZ or Lab only */ + icDateTimeNumber date; /* Date profile was created */ + icSignature magic; /* icMagicNumber */ + icPlatformSignature platform; /* Primary Platform */ + icUInt32Number flags; /* Various bit settings */ + icSignature manufacturer; /* Device manufacturer */ + icUInt32Number model; /* Device model number */ + icUInt64Number attributes; /* Device attributes */ + icUInt32Number renderingIntent;/* Rendering intent */ + icXYZNumber illuminant; /* Profile illuminant */ + icSignature creator; /* Profile creator */ + icProfileID profileID; /* Profile ID using RFC 1321 MD5 128bit fingerprinting */ + icInt8Number reserved[28]; /* Reserved for future use */ +} icHeader; + +/** + * A profile, + * we can't use icTagList here because its not at the end of the structure + */ +typedef struct { + icHeader header; /* The header */ + icTagList tagList; /* with tagList */ + /* Original: + icHeader header; The header + icUInt32Number count; Number of tags in the profile + icInt8Number data[icAny]; The tagTable and tagData */ +/* + * Data that follows is of the form + * + * icTag tagTable[icAny]; * The tag table + * icInt8Number tagData[icAny]; * The tag data + */ +} icProfile; + +/*------------------------------------------------------------------------*/ +/* Obsolete entries */ + +/* icNamedColor was replaced with icNamedColor2 * +typedef struct { + icUInt32Number vendorFlag; / Bottom 16 bits for IC use * + icUInt32Number count; / Count of named colors * + icInt8Number data[icAny]; / Named color data follows * + * + * Data that follows is of this form + * + * icInt8Number prefix[icAny]; * Prefix for the color name, max = 32 + * icInt8Number suffix[icAny]; * Suffix for the color name, max = 32 + * icInt8Number root1[icAny]; * Root name for first color, max = 32 + * icInt8Number coords1[icAny]; * Color coordinates of first color + * icInt8Number root2[icAny]; * Root name for first color, max = 32 + * icInt8Number coords2[icAny]; * Color coordinates of first color + * : + * : + * Repeat for root name and color coordinates up to (count-1) + * +} icNamedColor; */ + +/* icNamedColorType was replaced by icNamedColor2Type * +typedef struct { + icTagBase base; / Signature, "ncol" * + icNamedColor ncolor; / Named color data * +} icNamedColorType; */ + +#endif /* icPROFILEHEADER_H */ + + + + diff --git a/library/src/main/cpp/icc/md5.cpp b/library/src/main/cpp/icc/md5.cpp new file mode 100644 index 00000000..e5998c7f --- /dev/null +++ b/library/src/main/cpp/icc/md5.cpp @@ -0,0 +1,291 @@ +/** @file + +md5.cpp - RSA Data Security, Inc., MD5 message-digest algorithm + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include "md5.h" +#include + +static void MD5Transform (UINT4 [4], unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, unsigned char *, unsigned int); + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/** ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/** MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void ICCPROFLIB_API icMD5Init (MD5_CTX *context) +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/** MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void ICCPROFLIB_API icMD5Update (MD5_CTX *context, unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy((POINTER)&context->buffer[index], (POINTER)&input[i],inputLen-i); +} + +/** MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void ICCPROFLIB_API icMD5Final (unsigned char* digest, MD5_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + icMD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + icMD5Update (context, bits, 8); + + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + memset((POINTER)context, 0, sizeof (*context)); +} + +/** MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + memset((POINTER)x, 0, sizeof (x)); +} + +/** Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/** Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + diff --git a/library/src/main/cpp/icc/md5.h b/library/src/main/cpp/icc/md5.h new file mode 100644 index 00000000..4f0284af --- /dev/null +++ b/library/src/main/cpp/icc/md5.h @@ -0,0 +1,61 @@ + +/** @file + +md5.H - header file for md5.cpp + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* --------------------------------------------------------------------- +January 2011 +- Modified names to avoid possible conflicts - Max Derhak +- Added IccProfLibConf.h include to use ICCPROFLIB_API with functions +- Changed typedef of UINT4 to use ICCUINT64 + +August 2012 +- Change typedef of UINT4 to use ICCUINT32 (oops!) +------------------------------------------------------------------------ */ + +#include "IccProfLibConf.h" + + +/** POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/** UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/** UINT4 defines a four byte word */ +typedef ICCUINT32 UINT4; + + +/** MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void ICCPROFLIB_API icMD5Init (MD5_CTX *); +void ICCPROFLIB_API icMD5Update (MD5_CTX *, unsigned char *, unsigned int); +void ICCPROFLIB_API icMD5Final (unsigned char* , MD5_CTX *); + diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java index a2fa8681..9e3cc77a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java @@ -42,6 +42,25 @@ public COSArray() //default constructor } + /** + * Use the given list to initialize the COSArray. + * + * @param cosObjectables the initial list of COSObjectables + */ + public COSArray(List cosObjectables) + { + if (cosObjectables == null) + { + throw new IllegalArgumentException("List of COSObjectables cannot be null"); + } + for (COSObjectable objectable:cosObjectables) { + objects.add(objectable.getCOSObject()); + } +// updateState = new COSUpdateState(this); +// cosObjectables.forEach(cosObjectable -> +// objects.add(cosObjectable != null ? cosObjectable.getCOSObject() : null)); + } + /** * This will add an object to the array. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java new file mode 100644 index 00000000..c46d17a0 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +/** + * CIE-based colour spaces specify colours in a way that is independent of the characteristics + * of any particular output device. They are based on an international standard for colour + * specification created by the Commission Internationale de l'Éclairage (CIE). + * + * @author John Hewson + */ +public abstract class PDCIEBasedColorSpace extends PDColorSpace +{ + // + // WARNING: this method is performance sensitive, modify with care! + // +// @Override +// public BufferedImage toRGBImage(WritableRaster raster) throws IOException +// { +// // This method calls toRGB to convert images one pixel at a time. For matrix-based +// // CIE color spaces this is fast enough. However, it should not be used with any +// // color space which uses an ICC Profile as it will be far too slow. +// +// int width = raster.getWidth(); +// int height = raster.getHeight(); +// +// BufferedImage rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); +// WritableRaster rgbRaster = rgbImage.getRaster(); +// +// // always three components: ABC +// float[] abc = new float[3]; +// for (int y = 0; y < height; y++) +// { +// for (int x = 0; x < width; x++) +// { +// raster.getPixel(x, y, abc); +// +// // 0..255 -> 0..1 +// abc[0] /= 255; +// abc[1] /= 255; +// abc[2] /= 255; +// +// float[] rgb = toRGB(abc); +// +// // 0..1 -> 0..255 +// rgb[0] *= 255; +// rgb[1] *= 255; +// rgb[2] *= 255; +// +// rgbRaster.setPixel(x, y, rgb); +// } +// } +// +// return rgbImage; +// } + +// @Override +// public BufferedImage toRawImage(WritableRaster raster) throws IOException +// { +// // There is no direct equivalent of a CIE colorspace in Java. So we can +// // not do anything here. +// return null; +// } + + @Override + public String toString() + { + return getName(); // TODO return more info + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEDictionaryBasedColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEDictionaryBasedColorSpace.java new file mode 100644 index 00000000..dc76c089 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDCIEDictionaryBasedColorSpace.java @@ -0,0 +1,177 @@ +/* + * Copyright 2014 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSFloat; +import com.tom_roush.pdfbox.cos.COSName; + + +/** + * CIE-based colour spaces that use a dictionary. + * + * @author Ben Litchfield + * @author John Hewson + */ +public abstract class PDCIEDictionaryBasedColorSpace extends PDCIEBasedColorSpace +{ + protected final COSDictionary dictionary; + +// private static final ColorSpace CIEXYZ = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); + + // we need to cache whitepoint values, because using getWhitePoint() + // would create a new default object for each pixel conversion if the original + // PDF didn't have a whitepoint array + protected float wpX = 1; + protected float wpY = 1; + protected float wpZ = 1; + + protected PDCIEDictionaryBasedColorSpace(COSName cosName) + { + array = new COSArray(); + dictionary = new COSDictionary(); + array.add(cosName); + array.add(dictionary); + + fillWhitepointCache(getWhitepoint()); + } + + /** + * Creates a new CalRGB color space using the given COS array. + * + * @param rgb the cos array which represents this color space + */ + protected PDCIEDictionaryBasedColorSpace(COSArray rgb) + { + array = rgb; + dictionary = (COSDictionary) array.getObject(1); + + fillWhitepointCache(getWhitepoint()); + } + + /** + * Tests if the current point is the white point. + * + * @return true if the current point is the white point. + */ + protected boolean isWhitePoint() + { + return Float.compare(wpX, 1) == 0 && + Float.compare(wpY, 1) == 0 && + Float.compare(wpZ, 1) == 0; + } + + private void fillWhitepointCache(PDTristimulus whitepoint) + { + wpX = whitepoint.getX(); + wpY = whitepoint.getY(); + wpZ = whitepoint.getZ(); + } + +// protected float[] convXYZtoRGB(float x, float y, float z) +// { +// // toRGB() malfunctions with negative values +// // XYZ must be non-negative anyway: +// // http://ninedegreesbelow.com/photography/icc-profile-negative-tristimulus.html +// if (x < 0) +// { +// x = 0; +// } +// if (y < 0) +// { +// y = 0; +// } +// if (z < 0) +// { +// z = 0; +// } +// return NormalColorSpace.xyzToRgb(new float[] +// { +// x, y, z +// }); +// } + + + + /** + * This will return the whitepoint tristimulus. As this is a required field + * this will never return null. A default of 1,1,1 will be returned if the + * pdf does not have any values yet. + * + * @return the whitepoint tristimulus + */ + public final PDTristimulus getWhitepoint() + { + COSArray wp = dictionary.getCOSArray(COSName.WHITE_POINT); + if (wp == null) + { + wp = new COSArray(); + wp.add(new COSFloat(1.0f)); + wp.add(new COSFloat(1.0f)); + wp.add(new COSFloat(1.0f)); + } + return new PDTristimulus(wp); + } + + /** + * This will return the BlackPoint tristimulus. This is an optional field + * but has defaults so this will never return null. A default of 0,0,0 will + * be returned if the pdf does not have any values yet. + * + * @return the blackpoint tristimulus + */ + public final PDTristimulus getBlackPoint() + { + COSArray bp = dictionary.getCOSArray(COSName.BLACK_POINT); + if (bp == null) + { + bp = new COSArray(); + bp.add(new COSFloat(0.0f)); + bp.add(new COSFloat(0.0f)); + bp.add(new COSFloat(0.0f)); + } + return new PDTristimulus(bp); + } + + /** + * This will set the whitepoint tristimulus. As this is a required field, null should not be + * passed into this function. + * + * @param whitepoint the whitepoint tristimulus. + * @throws IllegalArgumentException if null is passed as argument. + */ + public void setWhitePoint(PDTristimulus whitepoint) + { + if (whitepoint == null) + { + throw new IllegalArgumentException("Whitepoint may not be null"); + } + dictionary.setItem(COSName.WHITE_POINT, whitepoint); + fillWhitepointCache(whitepoint); + } + + /** + * This will set the BlackPoint tristimulus. + * + * @param blackpoint the BlackPoint tristimulus + */ + public void setBlackPoint(PDTristimulus blackpoint) + { + dictionary.setItem(COSName.BLACK_POINT, blackpoint); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java index 01e732ff..aef7b1b7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java @@ -39,6 +39,17 @@ */ public abstract class PDColorSpace implements COSObjectable { + + public static final int TYPE_XYZ = 0; + + public static final int TYPE_Lab = 1; + + public static final int TYPE_RGB = 5; + + public static final int TYPE_GRAY = 6; + + public static final int TYPE_CMYK = 9; + /** * Creates a color space given a name or array. * @param colorSpace the color space COS object @@ -200,9 +211,7 @@ else if (name == COSName.SEPARATION) } else if (name == COSName.ICCBASED) { -// return PDICCBased.create(array, resources); - Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); - return PDDeviceRGB.INSTANCE; + return PDICCBased.create(array, resources); } else if (name == COSName.LAB) { @@ -331,4 +340,64 @@ public COSBase getCOSObject() { return array; } + + + public static float getMinValue(int colorType,int index) { + switch (colorType) { + case TYPE_XYZ: + return 0.0f; // X, Y, Z +// result[1] = 1.0f + (32767.0f / 32768.0f); +// break; + case TYPE_Lab: + switch (index) { + case 0: + return 0.0f; + case 1: + case 2: + return -128.0f; + } + default: + return 0.0f; + } + } + + public static float getMaxValue(int colorType,int index) { + switch (colorType) { + case TYPE_XYZ: +// return 0.0f; // X, Y, Z + return 1.0f + (32767.0f / 32768.0f); + case TYPE_Lab: + switch (index) { + case 0: + return 100.0f; + case 1: + case 2: + return 127.0f; + } + default: + return 1.0f; + } + } + + //x,y,z To r=[0],g=[1],b=[2] + public static float[] xyzToRgb(float[] xyz) { + float[] rgb = new float[3]; + rgb[0] = (xyz[0] * 3.240479f) + (xyz[1] * -1.537150f) + (xyz[2] * -.498535f); + rgb[1] = (xyz[0] * -.969256f) + (xyz[1] * 1.875992f) + (xyz[2] * .041556f); + rgb[2] = (xyz[0] * .055648f) + (xyz[1] * -.204043f) + (xyz[2] * 1.057311f); + + for (int i = 0; i < 3; i++) + { + if (rgb[i] > .0031308f) + { + rgb[i] = (1.055f * (float)Math.pow(rgb[i], (1.0f / 2.4f))) - .055f; + } + else + { + rgb[i] = rgb[i] * 12.92f; + } + } + return rgb; + } + } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDICCBased.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDICCBased.java new file mode 100644 index 00000000..6513a026 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDICCBased.java @@ -0,0 +1,607 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.util.Log; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSFloat; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.cos.COSObject; +import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.pdmodel.PDResources; +import com.tom_roush.pdfbox.pdmodel.common.PDRange; +import com.tom_roush.pdfbox.pdmodel.common.PDStream; +import com.xsooy.icc.IccUtils; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * ICCBased color spaces are based on a cross-platform color profile as defined by the + * International Color Consortium (ICC). + * + * @author Ben Litchfield + * @author John Hewson + */ +public final class PDICCBased extends PDCIEBasedColorSpace +{ + + private final PDStream stream; + private int numberOfComponents = -1; +// private ICC_Profile iccProfile; + private PDColorSpace alternateColorSpace; +// private ICC_ColorSpace awtColorSpace; + private PDColor initialColor; + private boolean isRGB = false; + // allows to force using alternate color space instead of ICC color space for performance + // reasons with LittleCMS (LCMS), see PDFBOX-4309 + // WARNING: do not activate this in a conforming reader + private boolean useOnlyAlternateColorSpace = false; +// private static final boolean IS_KCMS; + private IccUtils iccUtils; + private int colorType = TYPE_RGB; + +// static +// { +// String cmmProperty = System.getProperty("sun.java2d.cmm"); +// boolean result = false; +// if ("sun.java2d.cmm.kcms.KcmsServiceProvider".equals(cmmProperty)) +// { +// try +// { +// Class.forName("sun.java2d.cmm.kcms.KcmsServiceProvider"); +// result = true; +// } +// catch (ClassNotFoundException e) +// { +// // KCMS not available +// } +// } +// // else maybe KCMS was available, but not wished +// IS_KCMS = result; +// } + + /** + * Creates a new ICC color space with an empty stream. + * @param doc the document to store the ICC data + */ + public PDICCBased(PDDocument doc) + { + array = new COSArray(); + array.add(COSName.ICCBASED); + stream = new PDStream(doc); + array.add(stream); + } + + /** + * Creates a new ICC color space using the PDF array. + * + * @param iccArray the ICC stream object. + * @throws IOException if there is an error reading the ICC profile or if the parameter is + * invalid. + */ + private PDICCBased(COSArray iccArray) throws IOException + { + useOnlyAlternateColorSpace = System + .getProperty("org.apache.pdfbox.rendering.UseAlternateInsteadOfICCColorSpace") != null; + array = iccArray; + stream = new PDStream((COSStream) iccArray.getObject(1)); + loadICCProfile(); + } + + /** + * Creates a new ICC color space using the PDF array, optionally using a resource cache. + * + * @param iccArray the ICC stream object. + * @param resources resources to use as cache, or null for no caching. + * @return an ICC color space. + * @throws IOException if there is an error reading the ICC profile or if the parameter is + * invalid. + */ + public static PDICCBased create(COSArray iccArray, PDResources resources) throws IOException + { + checkArray(iccArray); + COSBase base = iccArray.get(1); + COSObject indirect = null; + if (base instanceof COSObject) + { + indirect = (COSObject) base; + } + if (indirect != null && resources != null && resources.getResourceCache() != null) + { + PDColorSpace space = resources.getResourceCache().getColorSpace(indirect); + if (space instanceof PDICCBased) + { + return (PDICCBased) space; + } + } + PDICCBased space = new PDICCBased(iccArray); + if (indirect != null && resources != null && resources.getResourceCache() != null) + { + resources.getResourceCache().put(indirect, space); + } + return space; + } + + private static void checkArray(COSArray iccArray) throws IOException + { + if (iccArray.size() < 2) + { + throw new IOException("ICCBased colorspace array must have two elements"); + } + if (!(iccArray.getObject(1) instanceof COSStream)) + { + throw new IOException("ICCBased colorspace array must have a stream as second element"); + } + } + + @Override + public String getName() + { + return COSName.ICCBASED.getName(); + } + + /** + * Get the underlying ICC profile stream. + * @return the underlying ICC profile stream + */ + public PDStream getPDStream() + { + return stream; + } + + private static int intFromBigEndian(byte[] array, int index) { + return (((array[index] & 0xff) << 24) | + ((array[index+1] & 0xff) << 16) | + ((array[index+2] & 0xff) << 8) | + (array[index+3] & 0xff)); + } + + private byte[] getProfileDataFromStream(InputStream s) throws IOException { + BufferedInputStream bis = new BufferedInputStream(s); + bis.mark(128); // 128 is the length of the ICC profile header + + int result = 0; + byte[] header = new byte[128]; + result = bis.read(header); +// byte[] header = bis.readNBytes(128); + if (result<128 || header[36] != 0x61 || header[37] != 0x63 || + header[38] != 0x73 || header[39] != 0x70) { + return null; /* not a valid profile */ + } + int profileSize = intFromBigEndian(header, 0); + bis.reset(); + byte[] profile = new byte[profileSize]; + try { + if (bis.read(profile) == profileSize) + return profile; + else + throw new IOException("profile load error"); + } catch (OutOfMemoryError e) { + throw new IOException("Color profile is too big"); + } + } + + /** + * Load the ICC profile, or init alternateColorSpace color space. + */ + private void loadICCProfile() throws IOException + { + if (useOnlyAlternateColorSpace) + { + try + { + fallbackToAlternateColorSpace(null); + return; + } + catch (IOException e) + { + Log.w("PdfBox-Android","Error initializing alternate color space: " + e.getLocalizedMessage()); + } + } + try + { + InputStream input = this.stream.createInputStream(); + // if the embedded profile is sRGB then we can use Java's built-in profile, which + // results in a large performance gain as it's our native color space, see PDFBOX-2587 +// ICC_Profile profile; + iccUtils = new IccUtils(); + + colorType = IccUtils.getIccColorType(iccUtils.loadProfileByData(getProfileDataFromStream(input))); + switch (colorType) { + case TYPE_GRAY: + numberOfComponents = 1; + break; + case TYPE_RGB: + numberOfComponents = 3; + isRGB = true; + break; + case TYPE_CMYK: + numberOfComponents = 4; + break; + } + + // set initial colour + float[] initial = new float[getNumberOfComponents()]; + for (int c = 0; c < initial.length; c++) + { + initial[c] = Math.max(0, getRangeForComponent(c).getMin()); + } + initialColor = new PDColor(initial, this); + + } + catch (IllegalArgumentException | + ArrayIndexOutOfBoundsException | IOException e) + { + fallbackToAlternateColorSpace(e); + } + } + + private void fallbackToAlternateColorSpace(Exception e) throws IOException + { + iccUtils = null; +// awtColorSpace = null; + alternateColorSpace = getAlternateColorSpace(); + if (alternateColorSpace.equals(PDDeviceRGB.INSTANCE)) + { + isRGB = true; + } + if (e != null) + { + Log.w("PdfBox-Android","Can't read embedded ICC profile (" + e.getLocalizedMessage() + + "), using alternate color space: " + alternateColorSpace.getName()); + } + initialColor = alternateColorSpace.getInitialColor(); + } + + /** + * Returns true if the given profile represents sRGB. + * (unreliable on the data of ColorSpace.CS_sRGB in openjdk) + */ +// private boolean is_sRGB(ICC_Profile profile) +// { +// byte[] bytes = Arrays.copyOfRange(profile.getData(ICC_Profile.icSigHead), +// ICC_Profile.icHdrModel, ICC_Profile.icHdrModel + 7); +// String deviceModel = new String(bytes, StandardCharsets.US_ASCII).trim(); +// return deviceModel.equals("sRGB"); +// } + + // PDFBOX-4114: fix profile that has the wrong display class, + // as done by Harald Kuhr in twelvemonkeys JPEGImageReader.ensureDisplayProfile() +// private static ICC_Profile ensureDisplayProfile(ICC_Profile profile) +// { +// if (profile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) +// { +// byte[] profileData = profile.getData(); // Need to clone entire profile, due to a OpenJDK bug +// +// if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) +// { +// LOG.warn("ICC profile is Perceptual, ignoring, treating as Display class"); +// intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); +// return ICC_Profile.getInstance(profileData); +// } +// } +// return profile; +// } + + private static void intToBigEndian(int value, byte[] array, int index) + { + array[index] = (byte) (value >> 24); + array[index + 1] = (byte) (value >> 16); + array[index + 2] = (byte) (value >> 8); + array[index + 3] = (byte) (value); + } + + @Override + public float[] toRGB(float[] value) throws IOException + { + if (isRGB) + { + return value; + } + if (iccUtils!=null) { + float[] xyz = new float[3]; + iccUtils.applyGray(value,xyz); + return xyzToRgb(xyz); + } +// if (awtColorSpace != null) +// { + // PDFBOX-2142: clamp bad values + // WARNING: toRGB is very slow when used with LUT-based ICC profiles +// return awtColorSpace.toRGB(clampColors(awtColorSpace, value)); +// } +// else +// { + return alternateColorSpace.toRGB(value); +// } + } + + @Override + public Bitmap toRGBImage(Bitmap raster) throws IOException { + int width = raster.getWidth(); + int height = raster.getHeight(); + Bitmap rgbImage = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); + switch (colorType) { + case TYPE_GRAY: + int[] src = new int[width]; + for (int y = 0; y < height; y++) + { + raster.getPixels(src,0,width,0,y,width,1); + for (int x = 0; x < width; x++) + { + src[x] = Color.argb(255,src[x]>>24&0xff,src[x]>>24&0xff,src[x]>>24&0xff); + } + rgbImage.setPixels(src,0,width,0,y,width,1); + } + return rgbImage; + default: + //TODO:PdfBox-Android + return raster; + } + } + +// @Override +// public Bitmap toRGBImage(int[] raster) throws IOException { +// return null; +// } + +// private float[] clampColors(ICC_ColorSpace cs, float[] value) +// { +// float[] result = new float[value.length]; +// for (int i = 0; i < value.length; ++i) +// { +// float minValue = cs.getMinValue(i); +// float maxValue = cs.getMaxValue(i); +// result[i] = value[i] < minValue ? minValue : (value[i] > maxValue ? maxValue : value[i]); +// } +// return result; +// } + +// @Override +// public BufferedImage toRGBImage(WritableRaster raster) throws IOException +// { +// if (awtColorSpace != null) +// { +// return toRGBImageAWT(raster, awtColorSpace); +// } +// else +// { +// return alternateColorSpace.toRGBImage(raster); +// } +// } +// +// @Override +// public BufferedImage toRawImage(WritableRaster raster) throws IOException +// { +// if(awtColorSpace == null) +// { +// return alternateColorSpace.toRawImage(raster); +// } +// return toRawImage(raster, awtColorSpace); +// } + + @Override + public int getNumberOfComponents() + { + if (numberOfComponents < 0) + { + numberOfComponents = stream.getCOSObject().getInt(COSName.N); + + // PDFBOX-4801 correct wrong /N values +// if (iccProfile != null) +// { +// int numIccComponents = iccProfile.getNumComponents(); +// if (numIccComponents != numberOfComponents) +// { +// LOG.warn("Using " + numIccComponents + " components from ICC profile info instead of " + +// numberOfComponents + " components from /N entry"); +// numberOfComponents = numIccComponents; +// } +// } + } + return numberOfComponents; + } + + @Override + public float[] getDefaultDecode(int bitsPerComponent) + { + if (iccUtils != null) + { + int n = getNumberOfComponents(); + float[] decode = new float[n * 2]; + for (int i = 0; i < n; i++) + { + decode[i * 2] = getMinValue(colorType,i); + decode[i * 2 + 1] = getMaxValue(colorType,i); + } + return decode; + } + else + { + return alternateColorSpace.getDefaultDecode(bitsPerComponent); + } + } + + @Override + public PDColor getInitialColor() + { + return initialColor; + } + + /** + * Returns a list of alternate color spaces for non-conforming readers. + * WARNING: Do not use the information in a conforming reader. + * @return A list of alternateColorSpace color spaces. + * @throws IOException If there is an error getting the alternateColorSpace color spaces. + */ + public PDColorSpace getAlternateColorSpace() throws IOException + { + COSBase alternate = stream.getCOSObject().getDictionaryObject(COSName.ALTERNATE); + COSArray alternateArray; + if(alternate == null) + { + alternateArray = new COSArray(); + int numComponents = getNumberOfComponents(); + COSName csName; + switch (numComponents) + { + case 1: + csName = COSName.DEVICEGRAY; + break; + case 3: + csName = COSName.DEVICERGB; + break; + case 4: + csName = COSName.DEVICECMYK; + break; + default: + throw new IOException("Unknown color space number of components:" + numComponents); + } + alternateArray.add(csName); + } + else + { + if(alternate instanceof COSArray) + { + alternateArray = (COSArray)alternate; + } + else if(alternate instanceof COSName) + { + alternateArray = new COSArray(); + alternateArray.add(alternate); + } + else + { + throw new IOException("Error: expected COSArray or COSName and not " + + alternate.getClass().getName()); + } + } + return PDColorSpace.create(alternateArray); + } + + /** + * Returns the range for a certain component number. + * This will never return null. + * If it is not present then the range 0..1 will be returned. + * @param n the component number to get the range for + * @return the range for this component + */ + public PDRange getRangeForComponent(int n) + { + COSArray rangeArray = stream.getCOSObject().getCOSArray(COSName.RANGE); + if (rangeArray == null || rangeArray.size() < getNumberOfComponents() * 2) + { + return new PDRange(); // 0..1 + } + return new PDRange(rangeArray, n); + } + + /** + * Returns the metadata stream for this object, or null if there is no metadata stream. + * @return the metadata stream, or null if there is none + */ + public COSStream getMetadata() + { + return stream.getCOSObject().getCOSStream(COSName.METADATA); + } + + /** + * Returns the type of the color space in the ICC profile. If the ICC profile is invalid, the + * type of the alternate colorspace is returned, which will be one of + * {@link #TYPE_GRAY TYPE_GRAY}, {@link #TYPE_RGB TYPE_RGB}, + * {@link #TYPE_CMYK TYPE_CMYK}, or -1 if that one is invalid. + * + * @return an ICC color space type. and the static values of + * {@link ColorSpace} for more details. + */ + public int getColorSpaceType() + { +// if (iccProfile != null) +// { +// return iccProfile.getColorSpaceType(); +// } + + // if the ICC Profile could not be read + switch (alternateColorSpace.getNumberOfComponents()) + { + case 1: + return TYPE_GRAY; + case 3: + return TYPE_RGB; + case 4: + return TYPE_CMYK; + default: + // should not happen as all ICC color spaces in PDF must have 1,3, or 4 components + return -1; + } + } + + /** + * Sets the list of alternateColorSpace color spaces. + * + * @param list the list of color space objects + */ + public void setAlternateColorSpaces(List list) + { + COSArray altArray = null; + if(list != null) + { + altArray = new COSArray(list); + } + stream.getCOSObject().setItem(COSName.ALTERNATE, altArray); + } + + /** + * Sets the range for this color space. + * @param range the new range for the a component + * @param n the component to set the range for + */ + public void setRangeForComponent(PDRange range, int n) + { + COSArray rangeArray = stream.getCOSObject().getCOSArray(COSName.RANGE); + if (rangeArray == null) + { + rangeArray = new COSArray(); + stream.getCOSObject().setItem(COSName.RANGE, rangeArray); + } + // extend range array with default values if needed + while (rangeArray.size() < (n + 1) * 2) + { + rangeArray.add(new COSFloat(0)); + rangeArray.add(new COSFloat(1)); + } + rangeArray.set(n*2, new COSFloat(range.getMin())); + rangeArray.set(n*2+1, new COSFloat(range.getMax())); + } + + /** + * Sets the metadata stream that is associated with this color space. + * @param metadata the new metadata stream + */ + public void setMetadata(COSStream metadata) + { + stream.getCOSObject().setItem(COSName.METADATA, metadata); + } + + /** + * Internal accessor to support indexed raw images. + * @return true if this colorspace is sRGB. + */ + boolean isSRGB() + { + return isRGB; + } + + @Override + public String toString() + { + return getName() + "{numberOfComponents: " + getNumberOfComponents() + "}"; + } +} + diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java index fcb45983..9f492a6b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Date; import com.tom_roush.harmony.javax.imageio.stream.ImageInputStream; import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageInputStream; @@ -319,6 +320,7 @@ private static Bitmap from1Bit(PDImage pdImage, Rect clipped, final int subsampl // use the color space to convert the image to RGB return colorSpace.toRGBImage(raster); + } finally { diff --git a/library/src/main/java/com/xsooy/icc/IccUtils.java b/library/src/main/java/com/xsooy/icc/IccUtils.java new file mode 100644 index 00000000..b9615399 --- /dev/null +++ b/library/src/main/java/com/xsooy/icc/IccUtils.java @@ -0,0 +1,42 @@ +package com.xsooy.icc; + +import static com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace.TYPE_CMYK; +import static com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace.TYPE_GRAY; +import static com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace.TYPE_RGB; + +public class IccUtils { + + static { + System.loadLibrary("icc"); + } + + public static String iccProfileDir = "/"; + + public native int loadProfile(String path); + + public native int loadProfileByData(byte[] data); + +// public native int loadProfile2(String path,String path2); + + public native float apply(float color); + + //gray to xyz + public native void applyGray(float[] in,float[] out); + + //cmyk to lab + public native void applyCmyk(float[] in,float[] out); + + public static int getIccColorType(int code) { + switch (code) { + case 0x47524159: + return TYPE_GRAY; + case 0x434D594B: + return TYPE_CMYK; +// case 0x52474220: +// return TYPE_RGB; + default: + return TYPE_RGB; + } + } + +} From cb2c0764326ae32d9968328437519ba175b5fb78 Mon Sep 17 00:00:00 2001 From: "1131777939@qq.com" Date: Mon, 26 Sep 2022 19:05:02 +0800 Subject: [PATCH 03/16] support Indexed ColorSpace --- library/src/main/cpp/icc/IccDemo.cpp | 25 +- .../pdmodel/graphics/color/PDIndexed.java | 374 ++++++++++++++++++ .../graphics/color/PDSpecialColorSpace.java | 12 + 3 files changed, 399 insertions(+), 12 deletions(-) create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDIndexed.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDSpecialColorSpace.java diff --git a/library/src/main/cpp/icc/IccDemo.cpp b/library/src/main/cpp/icc/IccDemo.cpp index d73b5152..31b41190 100644 --- a/library/src/main/cpp/icc/IccDemo.cpp +++ b/library/src/main/cpp/icc/IccDemo.cpp @@ -7,7 +7,7 @@ #include "IccProfile.h" #include -CIccCmm cmm; +CIccCmm *cmm = nullptr; icFloatNumber Pixels[16]; icUInt8Number* ConvertJByteaArrayToChars(JNIEnv *env, jbyteArray bytearray) @@ -27,16 +27,17 @@ icUInt8Number* ConvertJByteaArrayToChars(JNIEnv *env, jbyteArray bytearray) extern "C" JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfile(JNIEnv *env, jobject thiz, jstring path) { - cmm.RemoveAllIO(); - if (cmm.GetNumXforms()!=0) { + delete cmm; + cmm = new CIccCmm; + if (cmm->GetNumXforms()!=0) { return 1; } const char *nativeString = env->GetStringUTFChars(path, 0); - if (cmm.AddXform(nativeString, (icRenderingIntent)0)) { + if (cmm->AddXform(nativeString, (icRenderingIntent)0)) { // printf("Invalid Profile: %s\n", szSrcProfile); return -1; } - if (cmm.Begin() != icCmmStatOk) { + if (cmm->Begin() != icCmmStatOk) { return false; } return 0; @@ -44,23 +45,23 @@ JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfile(JNIEnv *env, jobj extern "C" JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfileByData(JNIEnv *env, jobject thiz, jbyteArray data) { - cmm.RemoveAllIO(); + delete cmm; icUInt8Number *pmsg = ConvertJByteaArrayToChars(env,data); int chars_len = env->GetArrayLength(data); CIccProfile* cIccProfile = OpenIccProfile(pmsg, chars_len); - if (cmm.AddXform(cIccProfile, (icRenderingIntent)0)) { + if (cmm->AddXform(cIccProfile, (icRenderingIntent)0)) { return -1; } - if (cmm.Begin() != icCmmStatOk) { + if (cmm->Begin() != icCmmStatOk) { return false; } - return cmm.GetSourceSpace(); + return cmm->GetSourceSpace(); } extern "C" JNIEXPORT jfloat JNICALL Java_com_xsooy_icc_IccUtils_apply(JNIEnv *env, jobject thiz, jfloat pixel) { Pixels[0] = (float) pixel; - cmm.Apply(Pixels, Pixels); + cmm->Apply(Pixels, Pixels); return Pixels[0]; } @@ -70,7 +71,7 @@ JNIEXPORT void JNICALL Java_com_xsooy_icc_IccUtils_applyGray(JNIEnv *env, jobjec jfloat *parray = env->GetFloatArrayElements(array, &isCopy); Pixels[0] = float (parray[0]); - cmm.Apply(Pixels, Pixels); + cmm->Apply(Pixels, Pixels); env->SetFloatArrayRegion(outArray,0,3,Pixels); } @@ -85,7 +86,7 @@ JNIEXPORT void JNICALL Java_com_xsooy_icc_IccUtils_applyCmyk(JNIEnv *env, jobjec Pixels[3] = float (parray[3]); //change data to 'lab' - cmm.Apply(Pixels, Pixels); + cmm->Apply(Pixels, Pixels); env->SetFloatArrayRegion(outArray,0,3,Pixels); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDIndexed.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDIndexed.java new file mode 100644 index 00000000..e4eaa02f --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDIndexed.java @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.util.Log; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSInteger; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.cos.COSNull; +import com.tom_roush.pdfbox.cos.COSNumber; +import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.cos.COSString; +import com.tom_roush.pdfbox.pdmodel.PDResources; +import com.tom_roush.pdfbox.pdmodel.common.PDStream; + +import java.io.IOException; + + +/** + * An Indexed colour space specifies that an area is to be painted using a colour table + * of arbitrary colours from another color space. + * + * @author John Hewson + * @author Ben Litchfield + */ +public final class PDIndexed extends PDSpecialColorSpace +{ + private final PDColor initialColor = new PDColor(new float[] { 0 }, this); + + private PDColorSpace baseColorSpace = null; + + // cached lookup data + private byte[] lookupData; + private float[][] colorTable; + private int actualMaxIndex; + private int[][] rgbColorTable; + + /** + * Creates a new Indexed color space. + * Default DeviceRGB, hival 255. + */ + public PDIndexed() + { + array = new COSArray(); + array.add(COSName.INDEXED); + array.add(COSName.DEVICERGB); + array.add(COSInteger.get(255)); + array.add(COSNull.NULL); + } + + /** + * Creates a new indexed color space from the given PDF array. + * @param indexedArray the array containing the indexed parameters + * @throws IOException + */ + public PDIndexed(COSArray indexedArray) throws IOException + { + this(indexedArray, null); + } + + /** + * Creates a new indexed color space from the given PDF array. + * @param indexedArray the array containing the indexed parameters + * @param resources the resources, can be null. Allows to use its cache for the colorspace. + * @throws IOException + */ + public PDIndexed(COSArray indexedArray, PDResources resources) throws IOException + { + array = indexedArray; + // don't call getObject(1), we want to pass a reference if possible + // to profit from caching (PDFBOX-4149) + baseColorSpace = PDColorSpace.create(array.get(1), resources); + readColorTable(); + initRgbColorTable(); + } + + @Override + public String getName() + { + return COSName.INDEXED.getName(); + } + + @Override + public int getNumberOfComponents() + { + return 1; + } + + @Override + public float[] getDefaultDecode(int bitsPerComponent) + { + return new float[] { 0, (float)Math.pow(2, bitsPerComponent) - 1 }; + } + + @Override + public PDColor getInitialColor() + { + return initialColor; + } + + // + // WARNING: this method is performance sensitive, modify with care! + // + private void initRgbColorTable() throws IOException + { + int numBaseComponents = baseColorSpace.getNumberOfComponents(); + + // convert the color table into a 1-row BufferedImage in the base color space, + // using a writable raster for high performance +// WritableRaster baseRaster; +// try +// { +// baseRaster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE, +// actualMaxIndex + 1, 1, numBaseComponents, new Point(0, 0)); +// } +// catch (IllegalArgumentException ex) +// { +// // PDFBOX-4503: when stream is empty or null +// throw new IOException(ex); +// } + +// int[] base = new int[numBaseComponents]; + rgbColorTable = new int[actualMaxIndex + 1][3]; + for (int i = 0, n = actualMaxIndex; i <= n; i++) + { + for (int c = 0; c < numBaseComponents; c++) + { + rgbColorTable[i][c] = (int)(colorTable[i][c] * 255f); +// base[c] = (int)(colorTable[i][c] * 255f); + } +// rgbColorTable[i][c] = base; +// baseRaster.setPixel(i, 0, base); + } + + // convert the base image to RGB +// BufferedImage rgbImage = baseColorSpace.toRGBImage(baseRaster); +// WritableRaster rgbRaster = rgbImage.getRaster(); + + // build an RGB lookup table from the raster + +// int[] nil = null; +// +// for (int i = 0, n = actualMaxIndex; i <= n; i++) +// { +// rgbColorTable[i] = rgbRaster.getPixel(i, 0, nil); +// } + } + + // + // WARNING: this method is performance sensitive, modify with care! + // + @Override + public float[] toRGB(float[] value) + { + if (value.length > 1) + { + throw new IllegalArgumentException("Indexed color spaces must have one color value"); + } + + // scale and clamp input value + int index = Math.round(value[0]); + index = Math.max(index, 0); + index = Math.min(index, actualMaxIndex); + + // lookup rgb + int[] rgb = rgbColorTable[index]; + return new float[] { rgb[0] / 255f, rgb[1] / 255f, rgb[2] / 255f }; + } + + @Override + public Bitmap toRGBImage(Bitmap raster) throws IOException { + int width = raster.getWidth(); + int height = raster.getHeight(); + + Bitmap rgbImage = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); + int[] src = new int[width]; + int test = 0; + for (int y = 0; y < height; y++) + { + raster.getPixels(src,0,width,0,y,width,1); + for (int x = 0; x < width; x++) + { + test++; + if (test<100) { + Log.w("color_test","src[x]:"+raster.getPixel(x,y)); + } + // lookup + int index = Math.min(src[x]>>24&0xff, actualMaxIndex); + int color = Color.argb(255,rgbColorTable[index][0],rgbColorTable[index][1],rgbColorTable[index][2]); + rgbImage.setPixel(x,y,color); + } + } + return rgbImage; + } + + public Bitmap toRGBImage(int[] raster,int width,int height) throws IOException { + Bitmap rgbImage = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int index = Math.min(raster[x+y*width], actualMaxIndex); + int color = Color.argb(255,rgbColorTable[index][0],rgbColorTable[index][1],rgbColorTable[index][2]); + rgbImage.setPixel(x,y,color); + } + } + return rgbImage; + } + // + // WARNING: this method is performance sensitive, modify with care! + // +// @Override +// public BufferedImage toRGBImage(WritableRaster raster) throws IOException +// { +// // use lookup table +// int width = raster.getWidth(); +// int height = raster.getHeight(); +// +// BufferedImage rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); +// WritableRaster rgbRaster = rgbImage.getRaster(); +// +// int[] src = new int[1]; +// for (int y = 0; y < height; y++) +// { +// for (int x = 0; x < width; x++) +// { +// raster.getPixel(x, y, src); +// +// // lookup +// int index = Math.min(src[0], actualMaxIndex); +// rgbRaster.setPixel(x, y, rgbColorTable[index]); +// } +// } +// +// return rgbImage; +// } + +// @Override +// public BufferedImage toRawImage(WritableRaster raster) +// { +// // We can only convert sRGB index colorspaces, depending on the base colorspace +// if (baseColorSpace instanceof PDICCBased && ((PDICCBased) baseColorSpace).isSRGB()) +// { +// byte[] r = new byte[colorTable.length]; +// byte[] g = new byte[colorTable.length]; +// byte[] b = new byte[colorTable.length]; +// for (int i = 0; i < colorTable.length; i++) +// { +// r[i] = (byte) ((int) (colorTable[i][0] * 255) & 0xFF); +// g[i] = (byte) ((int) (colorTable[i][1] * 255) & 0xFF); +// b[i] = (byte) ((int) (colorTable[i][2] * 255) & 0xFF); +// } +// ColorModel colorModel = new IndexColorModel(8, colorTable.length, r, g, b); +// return new BufferedImage(colorModel, raster, false, null); +// } +// +// // We can't handle all other cases at the moment. +// return null; +// } + + /** + * Returns the base color space. + * @return the base color space. + */ + public PDColorSpace getBaseColorSpace() + { + return baseColorSpace; + } + + // returns "hival" array element + private int getHival() + { + return ((COSNumber) array.getObject(2)).intValue(); + } + + // reads the lookup table data from the array + private void readLookupData() throws IOException + { + if (lookupData == null) + { + COSBase lookupTable = array.getObject(3); + if (lookupTable instanceof COSString) + { + lookupData = ((COSString) lookupTable).getBytes(); + } + else if (lookupTable instanceof COSStream) + { + lookupData = new PDStream((COSStream)lookupTable).toByteArray(); + } + else if (lookupTable == null) + { + lookupData = new byte[0]; + } + else + { + throw new IOException("Error: Unknown type for lookup table " + lookupTable); + } + } + } + + // + // WARNING: this method is performance sensitive, modify with care! + // + private void readColorTable() throws IOException + { + readLookupData(); + + int maxIndex = Math.min(getHival(), 255); + int numComponents = baseColorSpace.getNumberOfComponents(); + + // some tables are too short + if (lookupData.length / numComponents < maxIndex + 1) + { + maxIndex = lookupData.length / numComponents - 1; + } + actualMaxIndex = maxIndex; // TODO "actual" is ugly, tidy this up + + colorTable = new float[maxIndex + 1][numComponents]; + for (int i = 0, offset = 0; i <= maxIndex; i++) + { + for (int c = 0; c < numComponents; c++) + { + colorTable[i][c] = (lookupData[offset] & 0xff) / 255f; + offset++; + } + } + } + + /** + * Sets the base color space. + * @param base the base color space + */ + public void setBaseColorSpace(PDColorSpace base) + { + array.set(1, base.getCOSObject()); + baseColorSpace = base; + } + + /** + * Sets the highest value that is allowed. This cannot be higher than 255. + * @param high the highest value for the lookup table + */ + public void setHighValue(int high) + { + array.set(2, high); + } + + @Override + public String toString() + { + return "Indexed{base:" + baseColorSpace + " " + + "hival:" + getHival() + " " + + "lookup:(" + colorTable.length + " entries)}"; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDSpecialColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDSpecialColorSpace.java new file mode 100644 index 00000000..3e67b5d5 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDSpecialColorSpace.java @@ -0,0 +1,12 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +import com.tom_roush.pdfbox.cos.COSBase; + +public abstract class PDSpecialColorSpace extends PDColorSpace +{ + @Override + public COSBase getCOSObject() + { + return array; + } +} From dc8243acb4961436f1ee3378278d417e06f73f93 Mon Sep 17 00:00:00 2001 From: "1131777939@qq.com" Date: Wed, 28 Sep 2022 09:40:12 +0800 Subject: [PATCH 04/16] support PDPattern --- .../resources/icc/ISOcoated_v2_300_bas.icc | Bin 0 -> 1053552 bytes library/src/main/cpp/icc/IccDemo.cpp | 1 + .../operator/color/SetColor.java | 3 +- .../pdmodel/graphics/color/PDColorSpace.java | 64 ++++- .../pdmodel/graphics/color/PDDeviceCMYK.java | 166 +++++++++++++ .../pdmodel/graphics/color/PDPattern.java | 80 +++++++ .../graphics/shading/AxialShadingContext.java | 222 ++++++++++++++++++ .../graphics/shading/ShadingContext.java | 69 ++++++ .../graphics/state/PDGraphicsState.java | 31 ++- .../pdfbox/rendering/PDFRenderer.java | 4 +- .../pdfbox/rendering/PageDrawer.java | 58 ++++- .../rendering/PageDrawerParameters.java | 13 +- .../src/main/java/com/xsooy/icc/IccUtils.java | 2 - sample/build.gradle | 2 +- sample/src/main/res/layout/activity_main.xml | 2 +- 15 files changed, 680 insertions(+), 37 deletions(-) create mode 100644 library/src/main/assets/com/tom_roush/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDPattern.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/ShadingContext.java diff --git a/library/src/main/assets/com/tom_roush/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc b/library/src/main/assets/com/tom_roush/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc new file mode 100644 index 0000000000000000000000000000000000000000..71faa91f962e6dfc2f79e9aee5b33683951768e7 GIT binary patch literal 1053552 zcmZ^J2UJsCw=MQhM8U4uP*kuXf>P`>u>n#<0hQjXbdpokdm#yggb)Iu_uhMNA}US6 zidYbPuP^@peed2k?!9M>?5ym)_B?CNxz-7s93{1tVI~(YoS&ewrTN#-D(nMF|71S=4_i-Z>AJm2N?EK^Isr4g;Cp|7jELqprcJwlOXXi#YQu75fGdzhmX z=^d+xS4rtV*ERmq>F*h+5q&^MPh+0~DJ(AB-`6ivL)&wghMumj!9K-csF+uPY1oGb zMMe6DhD7YuFbN9Mu>0#6p<(A8;T<0B?WLoktn3#V8FqO8{+O5;9iMP-Z$+Sik)dHa zq2a#!WBddC_uGVdhn(^B+0I zzo&OdgtxNte+$V_5z+zOy&C@|3(H`S(|;lSBQzpHeIjGr!@V^Wc3605w7-{v87=pS zef|+zdo^PGBmF|7A~oDY;xxQt!@|8IA~Y0<`3Hvu`78Vs8^hg0BI9&4&V*=$glc$4 zdxu0SHYt*gRK({VrPv)Fq2b}J;pZLXr4i)s?h)jz;iEwK3+Uw@{5Kg5_i+CRe?_4j<9p~1o4;hz5PK^pF1iiAAf|3fVIkblDlMS3f8{hPg) zM!2_^e?(-szeiN0f~!bHu-@K5aeFl)qC5h;JtO~~{BO!Wp+P~RF@Mn&Tq)c={{?wi zS^0pD#$U-o71iP&^53%42#$(~R4^F%mn%h*;og6_R*>oKbwndBH0nRsJ%il+|FYyB z`LFqy@K8lOf6x4HWJNCjS5bv1KL7Ih@1!UqaQ~~eSCnVu-hZX1D6Ejsm>_Ro@8G`@ zDjfb|{)dZ(SEy&y-}!$Lq!AMur4UmQsDia{Zv}sUWl-?)SCzjhgnM6)@>lSskW5cU zQTYlM6%G-hQQ@B68qwb2e-(`QcSZ`3FhxQkL2(+v?%{z7H+MxzMtS=EJF5SkmA`%- z-hX-ipL17M*8fZRzavt#h5|nB|MDB|9UQ9YzrTg-{vW~ptMvZXx}!lG2#DlF~yfC8fr@|2a4Btdi2a>q<&bhbq<2Wa^kDI!0)srm9HT7bjKlIH=||I!r5#T@k#;iOB*QezEXO?GqUcnaW!34r zGtFn)&vl<4u7=u+P0ET?p5sY~(|(bc4DVppkKhI@fW zm1mpRP4D|YQ@)S=9{N8BxF0wfG#Pw9={l1*OWUOpTx7^z2{2>R)QZ1xrxp~Wno=XuxPEQMI4a4D!EbOpRy{YNg9~CCbd-- zlBS;4k$zpiN#2tYmAN%@AS*6=NA^$-n7b=?I1kO=mp@iODAX;yS41k-E1oQ&la>K%gibk)dl})Y9*ITx<+-L>bcD0SR z<2&|u-0dWE?duxu275GndU}KV)cVT$9R{>+a0kB)x!!8Mvuzj|nHVz|XWo50acGi! zf8?R&qtM4SQ*)l4nT~(<<%RRh`d1rgv9Cwp>b;}AfAsO-C(`HPuiL(besB1>^w-tj zg@67i3gz3pcME0~O)Y-7FhIw zXRFS&DD>~P?yzaQ&}Q3a*KXhG(0#Gb@y4Z_mv1}WaUOOVxiWHf&4u}jsXwB z7tjZV{^_VC`U)41H^M(4kcm3PQ4&JlMZQe|soK=rG?>1dKFYu|b(xba8rzWll*8ki za9_tK^Um_V@-qbv33C%l5?zJMgbfP)*N7U$KFO-d)e?^sm6R%}N9xklYMED>YFb^o zuY85PF(V*zO=fFWNVa-*XHIzT=G@-A==^Q@g9X?^t-{+yaPgkvu@XY5Zs|lBrChIk zqJmVZTY0yNSgl(4juf46l-d^C726j(lwPcGtiDutxyh;3xx=OFO3&54YXh!>ZbR;OJVrh5c|B0*|HSvc z- z`-sCNki3&TNQt9rQU_?UbWQpV28Owld5Zl=qUKBseYjl8}{n zN%)VjHYrH7QdBLznmj)_PvRi?m69d3lm1H0kvXI(r4^(*$rs8?Gh8!OGHbHDvsYv{ ziw`I8EE<&nx~Rov>M)zdYc+9S2o z3jI&iziJRSo^AZrl-+!(Wl>9It5@5~wwCsgjtw2%oe^D|ySlrt_pI+}?)B|c=_~HH zSM)z?@WYVft;##AhQmgB$F_~f-W{0Gm<+$)`f&Lpx5oufzCAUc=01D<;_SOJZ_sYF!g*`Tpu>xOL` zG}X7O?^wTcoz`0I)w@>iUa@ESUbTJ8byn_QqpN;ELvNS9{=vP6b{c3JZZ+C!y!Ei= zksU|1kL@|W|AgL20~2G@BWB0VPg(m*)!+PoJ+AxR4!R%qIO%!X%huc3$H(`opQFD`z}di4 zL1w|GAts?FVJ6qj!Yv|BN1ltaiMEfq9D6m+1LKPg0QZ!8%`g| z7|kBb8!xz9e6Mt(VzT;v-Gj!5t&h4M_dmHcHU9L`^uuRk&j(+0zigeUe^oVG_PY2@ z!Q1?IdGGT-6n-rERQb97OZ(S>Z=>HI{dn>7?XSk54yOFs_(uzCCPDJuY2gVRKzrG8H%eZw#g_pQ1YcE#;W{|RT{2&_eLDb6;^6mG|T=X#-y-#6}juApV*2GFXmxJhBRI z#kDPOhkxT%{&azSx#wOfL3Zr!d*+ZWYe&y1=n^BN@;Ky8H<5jUFtly-Z;+U}C_)ZR zlE*K9g*T8A4>#0pSqJAI3SebDf&xe9xuno+m_d!tN6doBMU(L&drWieU6pyiu)WQ%g)!K%S~WZa z`=|6nP${q`FTlPVI4Soscn|nUCe%d03fB1B4j}MJ%|mbC=;Ve$I&Wm;$@jH9QbVQ_PV1F8job}~dJ5#z(T zNGgIX|Ar8ef^R;!X8igWMBEiz?)VPeT-@a@FSH%EtULn+P-Wog%W>F2aKfDH+ zL31ysAvGxL@GgXm#x9RS2h(RyVsgNx`S352rO{LPIZ}F&-}Fo=zTyiR@T11@>a*gek*b^ma#e*qi#m$PBijY*KT8cMu!j z|3KEIe|xePZj)`f)dFKvcDEdcPl_)UR>JB+p4b3-E|?{4f;xF8LS92%9KyxpP%pFJ zunsDs+o+v}T*&;lFpMb=d{_tXDUP{$6q?M(H}*lSY;1loWG|0OS_$n=1qp`GisXP` z7f4lj%3&p>%-1no2|nWFsGS2nXuDsl!sO26`v>6pEp`KAP)hxg`Y7md<@;QH@L5Sx z!XL0H-w@{v=4KWI4uHwB#da@2wwPkz0bWVmu4V>wFp_3o!i8h;6FTtTTVP)+RM>A; z>jfR@7|%Wg-fyxHYzH%|Q;>3ySke&S58CGv?B;>{((4aB1ini=)NBA3mTX23?tHZM z-euV2Zdvb3sQQjt4FftduszEI9Pd=(dw>azoyZa}w93-|Hn^+E%(ek|oAv5YJfTySIE*eV zzJ{+sCX>tY=THqY8+QZM34MpNM-Mq#p!ZNCqX;wzJ*DP|o7@RS6%K{;-U@6t%ZU5mexe+8Z8#3XG-39Kf<8FUA8 zN$>?^oNj7Ai*TrbK?!0^o>BEvbO&d4C%!x_e6j)eTiP>l0~eD#TVIKei4Nx`qt_Az zi6rEsKn?d4;m4DLjv(gjH})Ig&rEj%Jy=LrR}F$UlCQmN!aXUa-}Ayb754U7p?C5Y z)t*PSv$kd1BeL}4f2mioG`=>v>JxIO!Zvdwa-?_*&jr4j`xe%Nqcf8I_rmI_hc1jm4Wh3H-$3RG zk5zFXz&JI%9dA7RVPq2LadS)O1j_5(UGWaN(e_cUh3swMaVc;~)eevcpDo7u4nj9` z_%`n#*YvdqeZi^Z$Es{_3G2jDZ@l$=bYvASV(fUQGAbDws@Q}K^leYSjA*tSbGl(^ zgAp(T@2~jbdlJejw6)2Ac4V0zGzD{|+f^HZ7S_wD`*`PR?O{C5>w%~v1m%x4mjg)S zP*GYFvar{Na~zIp?FV9@A9Ztl9zfouG8+KAom+kIENGLOuC@a(XYYOPz{~*lk4G}R z0jJ&|`g{PYX{Y`GgxM=7zkngZW%4ZOg8U*q0)GUMi9Jw??Es+`);~ClzXuO29m2;V zhR*_-OORt@GWsyGw1-8*!4ImxPzi8RmOCX0=J89(@o+EVO%fu?{-21+h`FsPAsGP< zV(>SRo~2m)b)<0`V#E@zkM_{d;$L(Z(B|O%s}E9FoX|#EVl#>VK-{b!i#I03seHxt<99vLqwf$ry;DaW=kMxZP_p7( zD^`)qIf8U4X^1t!H6zY49lD+FVjq0Mr<7bQdQ7fwbFPX zZk19!B`e!%C5?FaT}O=MxdU?vsoYgQ7xAU6R_n{S8w^;FhyI~S zmo7ogNaYV~sXvO=-b|--Upv(Bb*itJq3UYJUcr+dM48}21*yr_7anij5u1HRT_0nT7pMLc&iTdT{r+!sRL0@A1Bsri{H9wbR z+H5G66Lo8isZ#{~^25;+c+0|79y@WSS>5MMQ485q-8;xx@r|YD;SEfq@jBX`ai=~D zO3!Uty(&3ufR#5wGU+l9-6d{sE~aV`Hq~lH8{l`Ag}SHU)bsIYpQ9@?m+IyrYLcm? zk6;XHrEA_1Mj%y9U|=?kcmu)^E)#r$@sTTsZlMs*VTURmgs2l zy|cAQdybiIFm2qw!-r7< zu;Vt;*MUbpd#HGD`ut|{FNmq@MCyX=lv%{DaQ|HapAQZ6=f<}{bq#;G^B`%V0Xr6= zOTw8SAR)b(5de+F(rFLi1D>YTD=>6UMxKUm?EgS&LQW~4A$~@z#)o*Ham{_h@g_Kr zhOL}L^hZGyTOCbF?qW(%SNb-_M$|gy7fp=Xd$dq@q88`&lN(Xv{Zf(?^-|tMyp4Pt z+sivl|EJfD%b+c;8)d(t&dJwc1ycSIM=?HsmwhMUG=GmwB7jB$~&#$~;0zq|MTwMm11z zv_!Wq$|~yaGex8t@@<_;BA@h3c^!d4I6AyMesA*qu5Fwc5vQ8R8WEnz{=>8t{1KAr zcXqq9e6M>>mDv;w0R84M&>bkDOjW4B^)A8zF3E^OYlUO%$V> z{@jAPd#z;lKvhclLe|2v%km_KZ{ZBzjW(7Wjt`=`WiGm|Pkt|>U)3RnNt8}~Cwvq3 z>@&nC2@;jbxcM}j8<)6G`u8+b*gv~=l+3X9v^-1WGNS7v<3kizibrS))wJYdXcDd@Y7ys?O>%tvvIe+EGA&pWzJX&m(%RVB(S)1KwBA_mf?%@xx6 z8`dJDRQ;;OAuq@o#gAOhlT>n3ErbME-ntiqpOYM+T#o89()w@3tK180e8t%{oKkGd zIy;!0`hyYIW6znTRke;H$Ed3H&LQ69sEP*X7sSEBUl!g3{j8$B7+i*=W66A^jq!Xy zA~}XpZ`Kz*z<88`LKKse9+_|f^NmO3@4|lPD!3{7nq$^CinxE#}DxyfMYm4ZUeMCYzJEeF|RCV zlAz}n33MiGx<{3^8lGCTkKzu0?Yo`qiv~23MLW>7#di{ikXy1Jf^tL{e~_1qM4~BN z7{P=}*j`Ayi;#HiD${7snr54 zQVQoAZv}A*!E(M4T|$ysQ-p`kWsFAzGjo*o4PUw2nM%g97cD1O<5apIh!=2=)jbmS zvR@SLNszN(sVASzT*I!9kD|B0P8=VaI9SBGN=2Ni8FrKtW{+qAxzFQQLb~jSoKr!Giu;t{7vgfCEkj> zNm>Etc~&i^f;F68?oVa(NV6TOv|=&E#FvtjIHo;78s%3l8X>q+&$nC=o$9(?Zk%|p zRh}6lfEyYUXL+h=fVnu;>YX zg!ZWUvM=z^Z$tA>r`AS*Fag);Z@)ek^T#wWb z7%XmFGK%>WQ7o#*ay^X`lY!G0KJo!@>CvlPE;y_i!On#G=YF8$V5h3fxf(!M-o?x( zz<5ezdKWOl8A>e#KH;1tN#KEq!y+n3^|+lF1%AIEoew6l#ILWq_P7?Kz_sO>i>DS*T^%0J`mn5{{ zU)peZEqLUJ0p}fl&$b%28vgX$H?%P{vfL+I$ePHG%Wz{Bh|AOL815{6=_&d?_-(QY zZN>F@NoLe-ZVwYoDQ4CaJR`F8;bx9I3EIYB1(2NP>C!ggo|Ik8nn~Q2bxz(dFc+b+ z9-b?6MM@(#33?{3V^4>v30qiRZc7E7jQ7^t;!Eg$hppLNH1%zv%tC7IJcw#VI9GBk z%Qsz;aZP?&woh0sJC-7)UrW&ze*kU8>yzw5Qibai9$Xs~sPp5j47k7J=NTulm$2V# z(`NLr3g_LYn3AlD^s+9Md`%CRZ!U~Um`VMe`;wX_xtC>yEfO`Pmj`c2tVlJx3i3;m zAD-XKWhS9U-K_Y8?yYr<{k+BVmy)B%UkeUqNt)-%h;pmCSAr$7m$+@4q-O!cU zmG)=r68fH`HS_7D-87A~E!BCLZ3z#`IhYIdb;WE97Anq<#@xCt%D#%V@>(E2hrNH% zD)k`1GXIji4D{4<6`De)Hf%}Q4PR1HVwu1#GD-C{;Cn)F`59oAmR)=vn1bx`jlqAe z+h%KmR-VoB^`QKsYN|4{&b(Iq4$9EmDb#~cs((+=KvWe7#_+6cUG+-%hu}lG5?oBv zD_#i4fS>a|z`@rSWIcufPfYp*EONLdy$uhWqvBR%(}DLw6(m~yuHZMiQGu`uu}#HR zd6W18t1@TeIJL0Im#6`{ZVzpKXmMr-b+v~@S~tbszBi?poNtyR zswDkB;GFP@L{hKhH<0csnK4>$B`I!|2l!l`U#Tu{Ir(wnNv0~=)k&!bF`)EeY{C(l@gymg?}TNSCNux5Wk@$Npg=wD##MU zn4lbSl6G)I1~cKg8zN)zAKIOgu;ag&B1xXyRk|4hkln7{5`UYSru3cGP0STzD*W=> zIn)yC941k{z#)?qr5&*=&cZ^wKvnrjZtOLc)V9ot3)_>6(qm3` z34v7C{ZV{^cvRhtE8tDciJ@9k%#+M3qMJ{!u9sNX>*Jmm?5$oG{WAMq+2a6zd3jOY z)#y}V-W{8?WH8(4q^HnQK7YSH|7^-C^}XCvf(3IDDL*MC!tsiz&J)bp60??I+7prs{S})5p<*KBBJhosMHHt z@9?bvLX0hMWG5i7@jvNp$Rq77QWhGxf+7N*49fN2Y ztK;@NwKsv%JLxh8l|MM$X;6aQ%;b5Ry2u! zkg633TX7TggYCU-`Qt*qoQ4zluOl9;hUNr^E_45!Al-p{dwtr{L>dPc@qe@SQg zr<5;C?Yw4Iv@u1?&N+9VxWghR!#JtH5U2Qqc7c|gxK!Y^^0H9Ms!<>)kqRh|tx)*~ z=tKjvBsPLuomRNiFQqIqH}~q9LRnU*Eu51hr>q*AqXFRgVV_hjv)BmpY3JP#|=n^YCGPPGsM>($2l4_)xzV6{6y?hPwO2?M{`IX@n|kb#D)-R;xy z+e02U)pHnLrnLgLmec79H|9Gld9eoLnaPpd`;0aEmokd!WX&tGwe)&bZpt!BlAbqny{fgonXPg*>UhfuV8^q+aIcN9=+c$1X%l2riev!K1(&Mr$iQs%h;We?} zNxK}?B&l9U`mk`brkPZY-=?N1hL}Z)s(wgfAZ6XF%8a9@TOSln2Jss*@)JGgR=ef+ zT-sKqk{N#PRzYL>^oh}Ie_5zrMEWjCt>)2`i$W_kZxNqera;(5_CbzyJ*ehJN4BPy z{|-9Z5M07@&#vB5c*wD@v?XuNIa>=P%lve(H=^e&~{lz!8+lzdF;RAY-abI6Lm zxJk~2Z*<8UE=4bH^{B}Te9^GCQs3RV>P~5$BU&0%|ZJyRPf@3COZV`6^E7P3SWP!dzq;s2bXcKNGROuLhUm z_q;O>x9}RVWhu(FpQwvRT~0G9x1v{%BE|WrK<7iA2l{1;QJq3c&KlNB+KWSr0d!>W6PX$oX_lt8tP|u&338 zv#d@ZEBnGUJ8E7S%|LV}b2c-!Z1IstF%GCaPW7dnP*nAM!b)uMz+*{t_^NK4*v|J) zn`V;9)r^L&L=$_}YDWQVIZ-;y`*?IgfmM8`PIuN!yv`Ozx+W)Psbk6*ty^)mPauY4 zmk+GX>bxG^RV4r9>)LuM&E%?b{j{{y4qFv2@wW6Rc`f!jlArIIG^NAO{4QLug_pLO zue>x%B4RvH^aY6&j;ZeVF4exixNC9YZJ((Yd7k_Vx!xdKX#1(MEMxN2*OGndyd(a3 zqSS63D063W$Ci58vBV8azb7ZN(iK(RM&5?;>EBUh6qeIjTbAjQ(Q>)i`HE%T;{q+) z%F0W*=TG$%4`x{$vCcD2r|86HRHfu>xtH1}^jM~wyqT@&O@=S|GG<-h{krln>rSt# zZ$4oybIUWXtga)JT(c!ryeXJEm0#?cyZVS_Zcm0>2hGq+U8%7m)ilX%86c*yrxaEF zo$UR?1u_L!&A39&;F-G*peT^u{SrzB+bg$2TrgIa3cZ2C>A&C=&_DzaJ_c`ba)d+R zn8PqEg?pBJ!C&CP?=YB-;$FgFJ9=>Z8~76W)4dT|hYVF*f;5opsc#?-GD1HCH6xG1 zKR|zw$;;)iDKcvO9mXR)YLDS}NZdC(xSe?K1qHMq8jmjn-3j8Z(_j+*Svdgq-~&@l zpz7PRtR^2njzn;wNqgsHRq1pif z3av};fa+91$_#j!l1aS)rji@3`$J1eHJ8>x(ZpP%YUm~*QS}3CjMw?pfejW0Kb^#; zBsdSB#@^yvwiN*jKGY4JhI~@0ecc`{TYam@RTeTU2 zh&w-YV!G4%o(y8%r!Kf1fYp@jZ5hMbi6V+@v9!caaWQs6U_^=o4C1AsJAiWbmy2h? zLrfb(I>@5kR4oAO$xGi^U^bWG9tB}ci-b2HV7&55nmjSAtoHmbn7;IpBnoz&^fiHv zjTOHPW??@h-f~zAgz-8J?g1?v6V+^>pIY;}FK()1;Qnab_h#XZXw2d|X2U|vzDl>; za*RjG*2G{;US2P*2&(|UCjPCO@_jY4;Jc4?wF{|!UYaB3ZZ=cK3!szt1@ij3Q+n2)q z7-9Vae*n`^cG{MUd75W_C>Fb4wq0!}<^$XGlK=$)lW7(7DBwT*4fze^wS&kQ@TPPz zk^#I+(L^NRIOQ1vKn~aEBW_T~B{SppVMJsDf^|$rRv`&SL}WEmsx}`nLMR`45j(ilj7j%Laqhrs6;z%74Ro4&tg7rd4>r|=oPg7RGK4=YpFkj}xY$j3syz`7*+ zi`uX)(aq2e_9b9conSg%|E&&Gm8kl#8KMbvZi=7)o@tXmbcyR-a0s$xi<6#0cC0ys zkB~dVBX|^I(#jmnpa$wZgD&U}`MT;J=n-+->(kIenf3in@L5XrjXrQl{Hs9%mL+-R zMS@ug!$KP{pC5#e2kW^XgWAB?>`?m%XfxxzfeoZX%Tv{dHjw_zw1TcBr4!e{!v$aa zkArJ-kJar3K4iw^>;)dD6({HdFH+v3W#C-#`amOaU*Z8f2k;Wl=}-lDg|kBSBDj&7 z`Qj*O+?F|h1NhRI)pHYQsp+r23B;H8WzGUoMHT!F046sc9s#%+r~LDQa_I}(oxq5w z@Q@lXn2@0A0ALyGo~eL=!@;9f;F`hYt|FkbC%dv7ptq6a(|}jQ(Rf3^xpEZR2ly5r z@v{Vca_lac0ikJQ2g!hMa=ofE@DFRlv;`P7IXf~5ZX0dryan_Pxm1h+ zu;(8#2`1RYo44@g*jr8GcymChpaVAnxQKjkBv6OQ!EFOOLoCo1=$gZQ)DwDdXpe4$ zNvdw>D)`pxQNkgZ@t_i42uI#rjNb!0HmTxz;JpR&aKW%%QZ;TH%pyEOyWx@Gb`(Wc zI0T@F5KDt$5c2uNIfs2On}STDPZT)?k-c?GviP<77-P2(@@ z%SZL&kJY|HZgaq#bqJHypJ0gCFwdgR$VPfk;2!uFP2X-aJVlWoIu2KpO_o{1$%OCE zX(&NDI*vz|NP>E1kRs7QjW%K`bjb3CUkje_^WidH0^$pkxTXOcU@z8$?Pb`KK|DC1 z=)Txx*Pt=djp@ZmchTd~m55uuW7jqKS9WJ*Jxt2jk+B)xCxheTp%0Sh5DV%R9rF`I z841J-77#xE@j(;Ff_;A(9~`9yJ&8hUTQGMhh-Q6h`wU!Ay`cOmyr*n`+7#4QXvV=o zq#Qe-8gh{z^IZk)m#(+bfaZ&~>dV0D1p8&_;C}kRql?JIt;t)_$cX`))_ZVXm!#AN z-qcc*`Wz~#N)71>8AG@R7;6n z)(7~qd>+n4K0KZoT83CoB(-?J(h=3-A$acK2`Lr=yS123(1zw`aUa3K8gH*|FtEha zsu5h6)2sIZD3E?u-HUy~%6-yJm&G*?+tS#WJ)Lyw6%10TPr+b@wd z1KAw>!R;h@K%U?zQStku?3UkG{dng5s)-ljj0%-Kl2zXoKQx8hM6WTpz*!W z;a#ZuRu^$KzYnQ3N4o(b4rV+p zKnWky$jMT|E~z$c79S~|jJbsy5E4Be<5UHE&&km%+*#cgB%d9u;sbx8l#IS7XEhw^ zsU?Z3d+OAQq;fQGA0fHuhsYn_k>^Euj91RQ6TJ|3HEoxBAetfp&vKCmNh-QqkP|8SSd_C58_bW@Qlk z`i>-S!-urfiFa{~hMou)w6=28^#n3nlze76{5jiEfv_?qP31TEhM7OA#@iK}-4nz0 zjayr{ncWXu?;+}|?FkWMVscCc+s}o&HTMhijg#yK%c}hM5 zebm8`TwuXs2VySVF)ZP}Mxwi3asrT3H3ZgMWO?o;CKrB^RKYk74^n(+)9`FmA~hb_ z{m!mq43mxsedtUg zn|zRVoTwH_qkJStU0ccRgypA;Na=XSzD8mr{^DXy!gt)tTbDTw+-2?eSkvsrN;Z?s za>_c&&|`c~_(hwcXA)mfvuJ394<(T5aqSjakD_yWF=;7z*}eecCer!E=Ltsm{+k`_ z5s^pRL6*1BvHUx8Zi0D61wD&@NWi4I#2+Gv6u$#r4L2b_XOXT>kmL-Jr5`bhHoR{g zA&SaajNrqF&<$6%LC*Zb=Ju#UL&p9l zjMRWuPZn2imQlDpAq3kd zMfstC5_>v625^8S90D*DJjr5%F3xKh1>m&V4DAgRyepqt4)0#*MOh8Ub>)i8VU5~K z;UQS5;8MbB=u=7`e+x9lJ{PYGy@D5WOyJGIE-VMw!zq=4fs@Q=w08LBE?bT$(|^+{f4lI^|E?HLM^j1x0v6`U?m&IkI*kM9&$d=^nn}fwbWyQ zp3D;z+e;7WL1atQ^VE9MHSIo1B=PA&DoKVv*ybSIl-O4pldv;kevUO?o4;4|B>q_Z z1-gvm!ogwlSrpdO03Sv*)7o){c9+gKX`^nZt=E>4+o=W%9}~w2w_27ZreqY9M+tc8 z%d$4}l2We;Z*x;63~D{QRMZ&vj(Jb0=J$lLSm1W?32j$=(#Z!DU(P0NUD6DzY0-AV z4f5Q^+lk>7%S%@YLQ1UV?|7~SSqUxNi#fN+H`tdmzQ$@ZMo4al1E?>l>l=fxI_ z(r^WI`|_39S7T0PU&%NU(=YBxGl@OMa+6+&?L>AYd&PxbKa&)PF?8RWz`|~{(d16t9bnr-tLA#q4?$nbBC_6L1X+#pNz9P=fb2Eq2Xjoi5PJ$POxDDfgiR)y z0s3w&32wkm>uw$q3_lEUokRV zd;{x?C!tkg0ZDVAOKvU+JE0QmFy2LY@nHi_BkZ%ao2>oz4{j@I%FHKfWU8fi z6Szr&)Svi|44ag#cr0{Md=9@UlqrnHJ#iHaq_{?_p7?Pb$+(tt2Ismpo|S`>%#ET! z=$hj2%pD9wKANseCkum9ooF%i!x9`d7Bm+XQ*KP{dlQ&pxjXz7eWNghY zCsu6@W&Rke)~0+E1WS)3ODJB+mqgl_ z%%r%)#vqG?A^~uvmG_iqc@E(&k`U~6^qYCaN>orh91mO!5@t*dR4shJSllI zIVZEa@bY{zM`kX|od6T5QR7%zl`JdjDX}i`g$-3%zO#bX(tDS`#19l6JRQLv%Km)l4dYwdvMpa}iNeeCHHfv;J1Lhcuf+85 zu%(#T1C#}Y`LTTL%iONGwV@iBkK!8LZ>7)0K=vL|4Xlfqh4=_?MfXu+CFr$&TJQ#9 zDeiE7hrJ}vDt=;0cq>XZG2`U)LTBtEEH4+r+J)T8OvN_4v(kD23ww=}x4=^~74ce7 zs#}_v3VE#W5|)k)dOD z&r`~v4AWuJ6ueHClo$nz))xwD5oZNLEBqz7xcmj-IQM1A27DVSvEUm12!@x##f=3& z&uGE%-Ku2oaZz@mDcf+Nrq@NTI4|8J2_JEh>-mEH_~iX(bf{n z^V_JJac$XODCdK9Gxm_VZp&mIq+hlZ5+Mn1IzOq0G;jaUgbT#V_33;MlH$%kBMj#+ z;+Iv$Yq7r;z2;02y7CR#*JGb&2e38=g~-K>r><91N9e=077{huBa?)r1JvjHTLkIU zlk2;9Sn47rJ30@amvp9VrKE~gUUXiZj=!EKNTSEcvg#B40*|LZ5nQ=;K2@EEyKpV} z0=MhrW1&B1?S4D{AnW`3s`%@SH%fK1XrgaoZRu#P15>N;Q)Zd12R8UE~5fNKN zLB&=SQBsfwQBptz5rMJopzP2h?BZn{5^&*sYF2smK*m%<|m zW*VTZMmvp6O-Zm}qJ4kC7id@aR`3fZtW5y^IDWiw3|bjuQo9Nw2T!iV!lwIeb^UPp zW?``-lCk`D;SMxp8cxY382dH^G{RD*cOMU_Mu}aw5E^Dy+g9Z8ahFCu5*~fIW)wk# z?o~P==lA`mgVEiavo-J0;pP2>rUbyK5I{6;ybD4JnG9j?FeL|h*JVq7m0i?IBS)n4 zH#{PJk4~@INa_rtRZvM)`|7mUNj;l=G?ApI%ga^A$or;A6+$x8NI>d{C+UBBXR#`f z#LiS^TlTz`n~Z^!gAH5g|Dvr80(wl)TKyB+(|yOZn`olVvx~}TQOhMtEsZj5s6ave z&qzS7lJe++o)bbJc(mgT|5X;FWj&8|>`J|q8*!wx`Xk3L@REKnJ8|EFQZY-v>7n{F zYvyvg;wEd>^zrgshL_P<56A|Z{&YOs|U1$xlEtWoa2_jd80yrZsv3;m_c7UmAqw7?nVNmeuH|bB#M7IlDZ(+687vq24Z`=c=|BSZz(5wjIN^Po04U@8 z+Y#VJ__L;T@TgyU?NP{T_p-_;Xl%W!?j$_su%$o8bdZOW*~5LA8ru!EVdS1&H(dRl4oSH;2W=#+kJBKlORS zRii>DA(cQG)}vzVg#HFJIPF{ad2~vAPTO_lZFo`RC3L)>Yt04J%k4wO2->uMf%ZGW z(_w+;J8_1Mxtc;$85O962K3+17-$v9$bdYw}S1sy174MEH@$`Q*92KWgTZ z&D>5`%plvXpP~&Q2RqEw>?J?4*<4slE;ah%BC!Om9a_fvl(V~U6Z2=9Th~FRY22gM zM8@Xul?|!%Jm0g16uN_(LZ3*x>+-dfPwRD zbSFXvYU4|)ygMs{G{L*fb=%c5oWE(dEAkx1E9+!YQ+RSq{zT(l{ebQW6%VX#yqdM; z^yWHpN^rY}At=VQ$+dD#NJj1Kau088rJ1&D=cx8&aj5gnqQ|OR4il9Dc~_?x^2Pic zMg3ViQH8kFbXF0(_f?eYC++OkQc7<-j}&3l zVuzWETlt@-yvqA6Fn0a0E$Aq|;^GnTOorWAKd?I~d|($?eAw=^4LB5--*ygKvY+3` zhZ?r^)daver{oGJB+brLw;HXm3@oM*YK^SaSMNFe-cp3L&XP)^Nk841R4I=g<`_F4AWr_ zAv$SQKZ<^jdfy#^RtI9*?9h_^X${ZO>)T2U2Et~iNPQh)VBtq?EwRM%Nbxx0d7}c0 za05T%{B`QjlMO@NRM*6NeKiz(R7;nD{4vnG^$=OL-?L#c8Qw;(eoD@DiYWh0zPj*3 z=^F~olBQWi{(lywhuDIjeBPQ}dQvp_l_g26>3zf$Miq1pF=_*MwG=QW?cZJ>LuYOy zRWGLRcM30GMfbKFSMr1Y&T^p0j9OvzMK5UtrycPX)}8bpTqp2KgnMW6-bLX$O}X`f z>zYS7hW$=;1?(%^fT}R|R;S1^;P$(dF8 zGZL+OtYn)G{b_$MNjX^C)G4~PZ>ScR3vb&}=_JHE?bXc^+SvJNiue>OlIj3!nb8*! z)Xms}vqw5 z-73?gg(ud0E86O zVo^YNLrWRXqrs3_TDKL}&)2M7^S)$E$+9a_Z<3v~jwxXB+KdV->C1D(!&_QSPPq14 zHc}IKU61R6BeAU|2Je8?4F@Y{dE^;P%V=9M<&`A?YyOo)tNz$sEG*AUvDV4o@c;k3 zFwkAUr9li}>$5XZH&A;+0X+h~49tL+fSc=Za1y9g;o-B8jA@Hl!&^@|8|Thf!@ZDh z9-5IAba`(8Vlml;!j$`_EdhTJzPVn+PoIoK)z&i5jjuF{?Lw(lBgf; z!k-r1zibIC&7InP1waJ;Rb1c^uSmWI^ye<1E(1r{;u9aCOS^1S zjMz|e-VNeL3O_&Cg?CWG7dPR_@_Stq@MrRpDy{>IB;WJ$0I^6(?g!@!x24?%PxCGW zAA^o@;x^5Mp0eiI{?A*U2tH7 zt!^jofnk9-3Lm65BTU5KEg48@1i-?Kz)9c&`Ob~?AWgd3h7b0#70=VL@2{zES>jAD zTpav{TRQZj={GK?_j&0e+_{e0+@tsnO%cd`e4XLUvHgH!*{g%LK!pa_xD=eN$h6^t z0o>|m0_^>JOKz^hjl1z~a0<@;Qf||9T*lc2C3|q)eNjRQeqqN+Sb!Hd?oO`3|Ekgl zTmllc6&wBm7gfVH^}tVF%+n9pz9)toOx(Ep9%o{3Q*Q!|iMY^9s^S8iV)(e=FYbNM zPk0*saGP7Q58hDwC7=ozr{B2I97s@yO_>d3^0Xfs(K= zeMBC>AG&WLui&w&a^y8KQ67L!Hl`#ZQA^}m`f7ALx;vx<4MTaGuA>Ze)TRO*K)c?( zMoy46J{Ulb6W?F5M2-*(y2l|Wi90IC5HfK>emSBbPNlRV^~4n?3Xu22g~Yj= zj-la1YnvEULy*4t3ZG<;zjqhLux4IxgmFyM&fTz-VO0?Tx6)VVZG>;r$YeY+iTW^2 zf_PI`2Cqh>l;f^vkgMb-8!L1L33$C5l8Hm^xnm zz}B2Bk~_SQZJsKG^O@YBX!sLjhU)>uiPm5<4N0Tqyi5SUsn~Z8fd3TR&sIP)^E+E_ zK?`K@Wj2tLWQxQCiq5@7cnv88vf~8kE6+1f2K#eNHr|Hm%+EGU;Bs2jvpe9%>IXM( zgIg=&hUS89x(Ch4pifD3X&)G?z9L!)<|_TrDd5fgnqxnqb<#-(QHYY8zR?e=;vcgK zg({ggPuzjKrzO|jfm`j52QWxkP<1*Bhy<>-d{3MYJgE6|;5+cT9J~HE@T+jx<^fR4 z%X-yE`h**~yMzSbqen_ew)mb7Q{p9Hj($Ip59G;WiT+@^aX)+(6q)*!@BnrUdPt~* zKfCr3gh;c^DncDP<&_O70Qx@aPy7TgIro()g15E15kuj9CMT%8Cf(I)G10uyb1xrkUx48M~_j3drHH;rgbc-EFmcupXfwG!$G zDUy!_76Bnbgm{AC_%}i@p)>FqVK-r9V;^BV;g$6!qlv}nS?WhZAlgDbn(_&4pmZJlgLYEpZgfQ3$vo@D=osnfQx^I| zWPRfuS}iOXtdMlEgV2D)$40wr74lIF}X$FoyhF2<|9f*J(^Ig~Pfz2dk*6mOWd)M6}6=UZZQ5osI+3lZytn{ z#<*}HKV^;eeefyw+UPh0e!w5WB9^x)ovv{H)!kL5@H*or;VkI>z(ZC9RNnOr8xC=r ztK#KQSdE462WU}Qr;7+2D|E8i4o2~8AIxXwV?8fhGU~9Odl_^+4p&E|eZ&1J!qfKS zmHc!n6@bBWlyhKa(n88~D9zuToCrs_jFMg>2P|_*XOV~ZBbd{{h)Zsa^kbhAh<;)F)6Fua4>jJqA}$jCDeZT=FO~?*M_k7b$m%C0$27Emx76QSct0Ax3!@ z)9B4;X3tdGMKrPIH_aB^UHF>{p*wgpC|A%^fQ+&pZAqL-&PP8Uup&<-EO)UWl@YKO z|43|t$LMu>1Y`F3%QQ7Tu{(p>PRloNsjI1vRR)TR>dtvgF{7NvbIIA1_@j2DKja5~ zdq^DeQD+8eHF=)JW0EP!^v+6Jm7r%Nm^#L%cMeb;cUu7OL2Ai8}c?aBR`%bRmL(bNmJy0m;j#pt|M3PaB`C;P zd)3(5({^OwI0bHUY+geytXruQk?vOAk*y-FDTiq$#KO{5Swy0}x-G_+kgEvtx=Aq2 zTfX)jnkKQfEJcP{DVOI_lg=&Zi=kwlacKBJX7o%hWso}B8zlQl6PmUg(>TdBZKu8v zMl0q-n-X@E9`SsH7OUs3-GTg(Cs`U0ch0U$cGT3Xi+VFCnde6u#*>jD?-D)9&`TD> z#J3$xGL`7rL^^elP*)Rl1R_}KO*~0xa>=^2Hb}D~!}1FJiu3Sd7&ZBhcTWW+@mg)Y zJ30Bn{o=nQ{!p9f39+v?l=PE0t^G#E9m2`RHAn2xj%s($K-5y_XH+<#ykWHrPT;P* zQZ1a5<24Y*C*{0s_TZUep|UBQUfe?2EjAT*o7T^A!}n#cVf+Qo#IWgu;E)%SriIQp zrBd(1qm%X2t%&yWKLHL{+^^u90VkUnTs6?7JIsj(C(E4Kj^HWUO6DiXBCCke3AMyn z(q(Xh*AyBPne1drJ&%k`K15xEF1hT*e~2vZ8|ASPyT*R*E@Zw|%^8QxkWyF|5N9ff zsYJl6*^DgYX0()k2zB(Vphcq4nl7pa{W95uYJ%RmsN)UN7WDdZg;Zw4eoiFiRjDg` zBPB-S!kS5bP6=atAm?RHqF*M59?{cANWb=fG^PpkYeK2ZNwUeF6daL!{x|mzujq6i z=L#388(>#*e2e>8WVWs7CNq)wmpq?wfN7rbk-mem;P3!#1Knr1n`h>jr zbVuq>iTDtQ@*=m}qo4d(VC(2h`o)Wz{EhgH9y|yc`>OV|`LR#b5i9YmP1SYEKE`YP zAkU3nqaB8S)ABV}({iY6)rH9QluY^Ay?x|3neFOsQk>Y^d=7CFGi_ia=i-?yEz8(h zJr(+JmP5zqf^Np;mdTt*I=Ai@w2gMO>gDklRL`>h@G*)*ah`h*d19f}>gB}G`SZ+| z5VUNE{s7K}3ntCG*jdBY<)>H+`rgVPG0t>}*bF+*Y6Su`&wAvz8`Zw*R(K@&Q<<}S z6zPKI;i|91YWY`l4}vedqfcr~ll*QXv6C)#mNl>@p7oNiWmNR%vYyaGyUKv)v>7c` zDZP}3b@^f6$YqrncY6}9v}M&>qK9I)xey)ZOd2rF`-2H<36ojm%&yofnUFJ}xGh?V zMY(KYFwPGK_#oab&4gP5_(g#1C*VPM0m~Z-SV?B?gz;vjbOn;tza@`}`_n9xYVb*V zSX_p`rdXSM4cN_jAv6WLpqKnDU{Wf=O^56wOxT^!RreJvXPCOumAMu=`=Y4={kN@JXMI6FKY-4n3&n1=_@^0lg z=0p@VL+B|evFEF7FD0NcN`fQ5DoYSa$m#Mm!b@ax_7uTPQVZzH3nMX8esff$<6&)V zGm`6`bxbzNazz<~Lt1J^p*arn~{5s#m##ooWo==AM+C!5@0E} zo_;RHnqy9%68eUfO53r!!q}TbUV+jdQrDZ^rmZG*bT5$9N}}pyqVJ;fr5(9za-C#F zf@}f8F!1_#_wW{6TW-d&Ic$()5jvB3jm6pRzzAXPT@g%Q!C+6^Mvb5x?zk*jTx?!L z5=E-ZinE1E)p`kuf3-kC>*3AJH^q8!(xl8JA!}GXCHN!LUPyD>OBeF@Eq_Vd&zmt3 zq6E@-Z4naFI;ZOKqD|GcMJ~el3U^T)pQdY}T;SH1EW|(@Q#JkQPga!TZBPTFGCyRO zFMXPH?eb`ERPtIjvTjC^(dg3crikv&w6>bvxw%in+;HNc&lHM3mO-oh}`(nky zge=xj5kICa zk2IG{Xk=!Yu9Ctk{mq4&|N#NT0zA3gUl{Buj5U>riQKglac*Nv-Zfv8W_nUWY%izcc_O-hii zQ2inPWjZS&h|S=M{5m52#AKNj5jc`0P9hS0p9^mh@mn1EWFl(s!*d{xj(f@cN+_+2 zE-quuE}E(S%nXv4se%|NV{t(Zy$;C9x1oJcTQ1F{B^)ji-K0M7-6z~f_1}D&_lh!V z@5s%fd>g-s=}uave_OmJcY5JDb+X`s=(e(pe~Lb_U_Ng)K06P`8BEi{p8wTgkdU6= z|Hb=KvV5b8b5!-hu9X!oJvreJRl{6X{8tk?h|lXN%uxkz6( zs*P*c#?)5W17S*K*?tvIuQP0n=hzgP+8LNnWcUd(>MG_*Ii|rBlgj^H6P064xl!4k zqrp!upNI`gW0nTtmc$T?O7K$xBb48Od3!MO1K{3udu2Az$+@6J36~ouoJ8cae0seY zcbH#b_=Ibx5GoJh?QpGS^>|_0&QeQYdCZQYMBrwit+E>|+2bZ(0#Vo5NW#$dmB@iF-lB+Ke)#Uh!{X`$ER##7)o$66CpYSL8uo@v+ z7+Lce(Pej8{$XO^+9v5{B7N>baVN>uI3ukgG|Dd4)-%XluzDGNj0EY+=qqw==w{Iv z$9cs7_1BTc!lzVmzy(DBb>HsYc|DYmYat1ja(-^S=rz?y2HCGk7Si%sXTE`RtV+() z5jpw=++&zuS|TST<(%dTJK%^}VIV6tU~@qyQ{#3uZwcepS`YC(M$}xk$b@0+{AN!h z2TC$(a0M&bj+JlnqX;|7Gh~$PrqW@FTjV)@;^YWtLR3OD7`t%PllWR%~flVMX$xBDLbxyvv z8;@Pu8^9OWSDI{S$+9@r88~_B5~UY%&PX3tqFU;W9t<9ZPIP?0_huexA>#ig>lSo1tLa>ov+#py} z9-ZDxvIV8>7l@}%6*i|5W0FnlO^GX`URCpn^A7sxXAw8YnN&tpxoY~sdH)lNz8(mwfa z5oKC{OvYVnbk-)?azcB@8^acCPK&TI>jbM|W4SWH$xx#$jYzI=D86~XMXN4~+~cZQ zSh#b;Wo4xzY$=$plnJJVNC)`wM&1q6XAxXGS{r`nylaW69X&CrVM29Ne3JpJ;DrCs zKPk)gyP}OL{k;2S(e2_H8|2Cu)z78z`5yUOrbSBw1;$(u%a)#mwsjzF&N;Z2WljBQ z()u&?3Gso3O*KoxHF{aq3_p(6s{GULrXqsYdIPBVRb;+2G4GgHgeh5CCPIi6W`8f_ii$=M-JXZ(?r@ zrez-y4S86P5RZBHSJx4VJ1><#C-ylXE{!MI+slfyq_fs@3n!BLjLteh+y~7bz%!CF zU!T4}uS?m~;Yj-(v#J@P#f4PVbyMGZOR7Foi+5I*ucXqQJxlVbQu_k+Fl~u-sPZ@E zm(dr-#1zQwZ{-;>kDs>UN{+R+A2ou~i%sopW=MM71XiPWOqDCkbSI@OnaOZoSA34? zZ?7&KV-8r06=U@2MqeByp8}`!J!f>4U{a4Ufwt$q6YWYj?10tb+K}t z;M&f)y2JeI>;BU;@!jn^RHeLX8=Hbe<_n`Q=2Kn+%lcLpTc4WN1*p4{PqmJyfFrSu zlN8QDe`~hL7ke$MNXiS{A=L7vkJl9zJ(7&rE0vwOmuIg4qN@qWF^DpAIc z&M*2~N!nJAvgL=B4cOA?pnV3F=Bg)Me^yS8sZ9w6n?ZUy@I&S>>O@?hWhp=7FCKLAn@EcRf(@Yx~nubYt?s^ zN}~c#)@dWlyU6=zysLlHZE@^g+vcYyshuYpl!>2Onrlx){a62>Iz6za`g?`QbGqKP zEOPr$>5h_P>z1ff3lH0uDXjDE+l=IG;46#@M7nFvtdXbqZE4SjrsAU#;`_GXX^}x) ze)y~Ye_OqPP>*d59^i~E)dqL)pJSdL1243V(DozQ7SlEN(IF$Ph6rnNtVU3MyW|W4_)MaWpD0Q7;Q8jf$|rs1QPM z#{3^OHp+mGT@k3{shGv6}Avg zTI|s5BfK;!SQ9cZ1?PgvKpN!?LV6SbrKggV7-`-yK$`47(tM71-(yPMF!9xv+^S*H zaz|D9QWDShXlWApka09~h-7@?2;GU1nDnzpnX}T|2gHnh@w(IfbU0$H{Wa~p|LLZm zv;YsY+Hcf9Tlkfqsbh}CWgaw3Teu{RcFv+ny^C_&=!<%8qITU_i_#Y1n;)E>)#7zIZCuk_}6W(Ys`2V zmY@=4{=Yfl5#>epxWNk=>G40k@68zzrWYgFe7j0=12N* zGGnJ)siQ<=`&9iYcY@^=g&P}hysK|fUuT^foLiZDJiB*K`Q_N(oiRG6@TD!sOPB|2 z>Jv1s?#0y+>X6Oy^8Lymj-DllLL(cZsVgAd3qySD`2f?=L-0_<`^J01dL>}35<-~;x$>@ygHdqv&~ z-UF=D!XXrN30?p%hqk%W;M4F->jZ=a?|PkzL-FQ!!*GA`;K*-02LGkQ0sjgJ(eD6K zfkBxE@CHOk3J`+!rVc>P(C;8R^cXI7wS*mEI#f6&L!Ig&H{gu@-|L|YDn{q z!#NG%*|S@iy@KGIe=z^?ehmRR2Cl048PEJJ!YF#rZ2jR$=z|)}w}e?=he3X_`qRM$3Z@P z76v!S5octfl_zouS@Co#FamYn$N?>&+k;fl8=l^51u|f#QZaZHjuqKLOW+aYH$+AD z9vg&yBb5i|!XRp~VHf-o4Y8V!h|$T9t?+p2`D-%#W$KQBVqiI?u`v#SD2q#00C&k+ z;UMTvj)KR)M)HE>9>|L{5U>@xM@n8_0f&;TEFU9g#Bq-lxCH**vGn}a zmnUK0$lQA!aY+)dy4ko!Q9zLuewHwS_YW`S=Yna#DsEfi0C0_c??5+*Wtq9$1Ya{^ zt)@d%%D=lebE?XIUQl7DYVEqKvDo5GhAQlcdXB0G=dAd~d4+4re}q@!1Ej4-gYmyb zJii7&DLCbF1RTe`Zxstt=-qeHa<;T7M<(ZRo5DN&a=zC2Rz+aFt9~h{Sbcdu>ppI6 zsWbL6?u@!L{tiA}G1FHB*vKlJJAef7VXG|Q9?SLCqMZHb`iIAI1ZP^>{^X4I2r3q0 zS9Cbbk7G$q<%~DjU$v8SX5m;BTjD_c%+h^6Wq4U(rSp3H6S;>K4v6M#y16XJ=jxB4 zPdUO1thPxx*M{Ef*J0=O&B>=>Pj*@{7Gm!H`Hyob#1nb`WfKJ8gIyPoMz&SK}$BJ;Q z>e)U{xW~Gs&eQPrDif>k_)MF{sp4j)9!;Rvu5*#zH7vP0sKhy$09GGx$&jod(8EKSfzbmBcP zNJP1KF$!v=968+xwUHm!yn`N)W7RgW1?dNG1-yaO2b95SBuU~j*g%RpFcmQ)EpnNO zxDg*){6_i+U+%61_1sVAKZ2(@PrK)VPuXV-C&7;_rK$!pPOUkwp#uy#UJ2#WtB>x4 z-qYIrPQrfFv(9}mlk(PbH~fe6P! zAdhz$mjpiI&P~__?PJIKc0w9vwR1dli?PWv26m%7xp@dbstX+E0#ix~+a~}XMfWRG z0Z-LE`9t7D!9Aw&I_AC4$pA9bL`oJ>+i*-N1*W4P*0cZ<7|JnyUyG1%U z4limTwP^7jHJb97_`ynSUMc=n*)Dntu(rfB`vO2Itc*PZj1>fXmw}sQYt|)z-r~8I zk3k0W*Od-DG#uIg6i?}=HXX+EyM@}f_=2`HX(7J7aUPY4|7xhrS_bUa&xtV>xt1h) zwE_Kwv)A4N&gKtUz5&hHZ!S0Efy;4yAMnV?n#MFd@yxK+0?+8#DjCLW+EXZf_^XYl zGO@rMgKsnh1eE>tiU#1~m20a3sNlWTRG^F_z4RKNc?<1x#HU>~X{f;`oZnv>hEE)f zku1k!dR9`L@$$BoQ$O)n8>U7t$NyKg+-oZ^Lz}U-9xziTS)~ATIAhoQs7;yaLp!O~ zS=nu;C~?_Y6{h4dOt?IcoSqZP+)es~rR7ACh`5nB3~2$d&}R|xEEv0Xgvf&m%(IAv zaMraO)Ok74gGm$=JI%;>$FOaB7jhOZApZ*40{5H2Bq{L^Fzuu*z@^wn#Ao2Jx0+Z8 z)vnz`#KBVY>BL;b`szap6~LUKk$(U;S{lhopksL`c_Mf+-;7iYUZ7tixk1sGdBo?i zN$fFV0j%_1OiV=9I=vveBYl%^5|fcDS9X*ANx%EYNTVb}vok4%6s`MgJj-LEteaRz z?59;26DHN!^N0(GMKLVGJ0i>Ll(BCC=L8WNh~VTGgxduD z{HVnd?HOOCv4ndJOKKWHLHEouAtccl(OQBVb;R>FVKH@vlNymGTXu(nOPl7#fkg^ng&GkO@80}%7J<@~bvm!kAqS=fOP7~4n z^vmW%G>`&}93n7Fc6ECaoQoQ3A@p{kgNB826v?@*sBeB1X&yRXW|pxK`63QF9FLq6 z*6j~P6#Ug|G)M^7%ls9xn?8P6L-22)bdb>pHP;Li(2Oci^=fo}d4>Q%E|#(hQ;>op zeR>d*rEEO(4DranxX&G#DqXe46mAmX&Humx=B`0J!M(q;O@8P#U$8_J?%^*!EdCZ^%6QX z__T60y1)0MG7-7isp5_zIV}!wA>ve*pSBwQT=_9_F5=-S~3#TlfoPsiPlcy#u_bK&>RgQ-o%Ug@%kJ~*TzW^W7h zvvi`PD}+~ASY$(UIVQt5IWJEMI?dRvnQ{Y<(*4msd^#-O3Hbqd* zaKo)3_|X0F3(~V`iGbZ9K2-`X@z_gw1kGFZnu3GpO*~Hi0iPYRVmITrw@0v=@WoZX znZ1BzVK!p`IL7})zX?13%18%+xPQ*)?)Q}0lksp%0Q z#foyzy@iaS1g}~|HlvK6*hiv}ru9WKuH?RL>ZE@ZW|TANcKj~|ZnOhDlrx7)=gxwb zP|mS09=}1E&0>calTR?cjhy!)W8TU;qyT#0#Q%t=$rnzaWh^UN*$_bAq#D)DrX5k> z^FC0SauenkO1F#wxKd0dt5Q~zgLBV>g_DW}>3jANfAO}hM2XRyJClwOI%(^>Qs@me z_iL}wZd9EpF;M^LO{Aq%J1s!JO7Ydaz`2uog-4UKNOuaZgnS__$_w7@K}?mbSn-cg zpIb6Xj(%f=blB0`Ph$+{X#E}gG}Y9b&F{pGlsEMm)ISu<>RUM?vb%nF;x`hxR2tks z>`{Mr^B_)Cd|weq*d_~^q(P6eCbh4l7Y;tHenhM5ODwudJ=@hPdPKS2dWpJ;{JNnZ zGfJLos7nkZ1?suMPDExYcGn|9zq)qC8uWGk(n%fYL{?E-1|7aYuU;W_OH?A+cc_R0=36y#Q6?2C5_=VQK0PVsp;HMCxy1(u49;xKV&l9+9U7Y4U8 zjlTqUkr-64VA(4AQ%E&okmd*HwtW!Y#9393=DOo(8oHnZZ!7NQJK% z5A-KZXZ3)b;4o$uE%vMk<6!FR<}E-wEbuY)s9R!Y#Y>LjO5oo5~uMa6Y9 z<_kRoKG3f5cW%E&)pL`UuB6m+ux8WAR#doF#viL}E4{-*%ggcyxwhI?j+k9pOhHOm z?rN`-KN%mC4zWw=rTO3eWwc1?gKf{Lw&HC|R1{00!fZXspPpuT%D>P)RI-*A)%;p^ ziSxGp8>@z$VHkz(Gk;W6rAILoI(l>qeP40Zfh6jGg@IciQ`!rjIs75#N_xz85bGHK zs_*jqdv6wd^A2>iNxyPN+cKE<*`bY}p?S>fwb3Wa80nRR(F^HQ%GUWkq;?k%ZpBfM z!c7jnWOrGt*+F6ulUMbV-#ENnL*Q-cza{nHbe#@kZe}~S^FWwc*i?GLj^S8aYs~ZX zRCf7gQPXr|TU{uNiy9paq@g^P8J#f9yrX+q#K`bb&MBODDoe;x6lIbrKjfRUxLBp^ za&}{iT9Sm=#|4z;X>@pdY^CHDXvH<%!@^5Z8ezMn?00P#y9_3NMT?-4jT4=9H1~URK)1FuJ zv0D`5RjaTD0k&Wiw}|{MAHtR7Xl3?z&tsk9VZbsnFBbzm^U4$K0Q+6n@D4)!g%oZU z9Bo2mu0b}Gx))X>W^#q{05X%mwqPN@~dUNk2qs zWK6^~p%dB5^8|kadEUkoTzk@g3lDSVk++VUM1MnEq7kYbxZZj81!pssIk|8NYY&?_ z?i_6nMXORM{^>%*+vWO_cIN!NB+U$Xj&w$0NX7|KpF);kE2QOr4{721$&x&Jxzoiz z)+^aoq7}9+Og_JB{CH{rEmAS6xYlqf7myQcRrIL5P1U=>G|4S}WBOeYpj#SOE!LoQaA{(x^MBf|WLk>s#?Phb>1~P~igWFVa7d16{!EkPEpKoGswJH@ zap`MBVU@Tz8=-kwY0!IKcgfqmn>kPs*(H<}qo`Z(mC+=I#;>RNFx(0{6>U9`aJ@XH z(?I=@H@USPm?050s!pgx_O;ouLcvhgp&&XBQ+~|-0cT~&AD7w8KdLhe9y2ydh~tyV zgA7h?ufaQ`nQ^J&->F6fTh^c1nzgz#E2}GcgT_Al$)Oh2g`6b^Yzt53I1Qr?RlRM$hxiQrjvI-fv3TAc!vbZb~vo7;S=?u)zq{l_)a-JSaR}rwU z{6-75;VpJTdDDO`j*XHyFnU&ixCDwbD%c}SgrBOzbE+6~DrRA~!M(b-*o&DtCAGMt zN$jEw{Om)ERF3#DKdJmRFtpQ0HUc&|7D$#t@>wC`IQakb$#)1(aJ`BOFQHrMec=62 zzs?yxd`eaP8HN(Cs2gG9m{6I6*!r2vcOa4-Ph_jnNsg&v7D|{EBdYxW%$yy9_6U|$ zHjz7N2g?7C&7W%1$le*3i#LrWhjWe4%_XNxy?6g-PX(J4IJXBeTf4 z{bYaRj(d%%bv#xHuCNdLOlcby$y7 zza>1>9jG+P=_y%OZjt&^eOqf4TcutEL}dF423otw-y0vzy*xM8!|*g~uPLsWo;nmC7ZR zZNZ=N@0Zg(!=wuB_N}W$p+)U0zX*H_XtSpBoO4&01kl#dbCH0~1*fiJI4$oo&!nwx zxS3TEZ(h@tEe=npRA6*|o65kPySr^l;&B_+?^2WS)r-F=kAn`@Q3a#W86y$9h4drq zI^@|xOlk{=aZj^s$i%#kdsY*ZqX^$!xetr+n^U$Lm+khoWG-H`evR54xUl$(ac66S zHKsrb#~2mrk(XXFtaa* zv6rf2NtCc)eFQ1V*Gua~N_7j=>>vqU234m>mlnS(2qXvDSm!5_9gW_6MLY?)v{INa zvY3rz=CFUdvxcAF6nn_bX(s?yk(yJGl6~ zgt4=|uuE+3+*R;Ew0W^EFI(tiGa$LZ_BJ}}4)rj8cXMKSe+IQaN0*T7Y9N=qKayO5 z(VPh0s5_#b;>9jLp!~dZR-v0>gY(XUgZb@?gJowWdu#^9&$-5L80JEnFRr|KtKnG2 z_Qkyu>euA0~HiOQ2|jA1r(4_ zKuRT~QySKBcFww+v9YmYW23t}6&1V1Zhh6)_`Y`QZ@+)`=Y8$E_T1+@``qy{!uMR# z{j8k1)j~5vn`G}%UZi+reW|QaY|@grz3eJrZJ&8J30~5*u!E43(YB(MoNQ?P+SGEG zT6>{kcJL=-Z*9SzDxJaj*X^BJsVlbkP`p;USbvsHmMk$LxU;Weg%^Dujm7@Xx-qy2$4<2C>BX;!`qdGEf4VQF`5$1)>uSA#@WzBdgluis8S;q9 zR(o|Zq_-wb?FnTa7CP<%xD-7bc@D6%CI;$(`-yXU4ibE#-n73VT;3Pa%mrm$XY1V{ zoJ&{r1|r&avtc2LZk1W_g*<9P&{6WSA;D*4n;D z^oI$Iu5!92irN-Hrv`m$d`RbbiEEkkJ{P@_#IUwa(1$b5S$))eW|;awMBiyLOw1UZ zcdhW+kUMW}=IP#_Tv&p8XFun3luv6OCoE{FaVGn%*U_39_K-`7LB&3ATcnHQ;OF?N zZ?F!S5N6W5(E8CLS!&_DL09RjjITZKC3oTx9Ze-wk;0ZEVtJ5b{YO!g=cj6^@P|uu zB}v$0%d40zOr6uLI>cRVdgF{SH>0XX6cqynYX(v@7cv(0*sES1QMBJuu8x>!W+V=4Y|q@PQP~TZX?l%pvn@$GS3;cgT4~2GHu2&V%NO}>_-DO+epdgbniJ_U-4#`^ zxclt~3~M76HNDi&3V2w@tMK0SU)4s9%(+GPP1$Rkt_hUAn7gEWoiM?KFpa$ci5))K zlAZUvU*32!?Pqsdy-OUoeOgUN_=zS>6*fRux7$G3HDbFI4OV`qNs}58EbF<~2L>Em6%h(K9{^|LcdD&MdnlZ}c$+1YRV|3c!G3@0~W3MBw%`d$} zh#%b%-ZBmN?9^X>158{|S91e8Z^5fPMY?O!n(ZlmFv6rB$I5;>;fH&Yd~@_U-aoov zFd5$)+R$?nNcPL`*h=u)aiqB(-0d`4&w{d6^whA36&7ukc=BEoFWymnVbsZO1jp>x z$FCCNk{^r$1VME1z9obA%0=X# zCIkb83QIiYM^4H zwc?-ZUDOfFCHh6wCnjFppkj)DpWMcHon1QqfFVz=G-WpSMDOiiN`Dn9>5|Y-`lYu1 zpr6=rw6T!>(dl{Z0Y&Wns->&`-|2%O;s-tZL)zXb%LL>oi&>{ z6{e>>pSiXOf83$M`l#i|Ma`xpy8*N+?$A`vjBbirnpH3ZB zmeO}NDm_qAGMlb?VA@S%CSbB`i)@eO)|m{IBWtQV6E*!8jk$+nx??K+g3q;0>Qa1O zG;oRh`pk)X#7EY zeAet?R*N>#x!=CY^5E+(WqnU@aqF+zNS~U<*r~+@Qv3}a88Ki6M?v!NN)F9JOU+Kf}f#l=ou?KQ zsW-=8VwO?M+kaqQQ;RC+V9!%y#Vc`x)HG0vAE3gH{sUa67KM)zK2T$~)`OR+yQ~6< zA(Vi72N2i!f{P1~H+kAIJJdC9Q(G9?)T*i@VR|?-g`L=b_8~$7?lP+)>pK1~^FjC` zf&+8)R(H^X5pER^v1yri9N?GATF%c$j7Wcv97GP4MzzRM{U!a{=jbu9i@+aqL)Zz} zVt)zTGRN?{c{O1Nfh_I@x3h#GcC!@>JjFoWB*PTdf-@L+t>NY10HR6n-(-hu&@$C3 zRG(^sw*@^_ZjOt@T9xg{NXA8!#vj;VBv>?wS15769KU9k41T1ar?{ z9W5T7Ty>%ycI9|f?=<-3k)m1|;zWO*A`E%5%a64S^`zwo>MUkyJug*?-Dmt6VvEbD zIPUsCyo0jUstmZye|Y75@yI3ri7T*)v(`O!@SzhuHEoFMu^pxEJAg6^`e%e zAGdU;nq$rDgu#=z#fHnSXK)wQZ>)L%Kp?-eym)8|IbjcLzx=iv1y`QiUSp2Ho{TEH zg3KD*!nlos_y2=bqGg@wDLt5)rkdb-tf9uk^%gEdi<)DFe<65z4-XW>N-r{j6YwWv z_Xze#$M$6eE-G1nfbbRFA|ir0SXXc%_yRX{v>Aec=+yTo}gxL=TOv0brQ=wj|&mtXL-{6yf;7>x4J>4gvsDfG0<7pv|C7cnEY4^4+?P zh$4DgEhGU%&YcNdKYrEuM|f}iz-TOf6o_dp1NH&WE2e;(1fg&fAs>tfo`BZS)+{AB zNwf%?f;hy>ZjXrTNT}JbNP9?#Ti>xx)OF{UVb4-6hdDSjz zaw!k+X9^RH>^-MWoeVC zXhG?{0W(avbyEwzLf>@utM zkS&9E^&qm(`1iy(3aT_oV`z_xUv)m{cbbgywU|ETDfU{dOtA&sfuqS-Y5sUnay>)~ zq=|QJ*#Nu|F0e`iH?zYpRU;R7eL21vd9yvadj?A0+*|z;9bW&b>;q=7Yfp z`4;=EVo%B)+jK>ETn4uK*t%>fgz=F0`;r+ zl=L-vxZ?`l7o%t%LHJ`?b@XH=uGr|hzZ@S>;qE#Q-=*}o5)mx;C(qX-&YizMwhl?3 zeAfOLwdMG@;R)*22q0aCmiAAlftc*hT(}Gy*{n|bg>$X#*dKuVWN>$RflpDxtong^ z0eJ2+V))v#Q3NvgqOpBD%JR&5gBPl9f>Me{$Bt;IFEMs~9Qbm~pN>6A4cH5fqx;w3 znALQb|8NgAZ{{opmIy9PJ%t3YX=jQd9=vF1C!|CiYnp;iqh@OIh_lcc{OQEqn1Aq= zL>i8ixq^5VSaV>I(4{RZRgywG1lMWH>X8V#i5Dhmn372t&llQ^Jc$)zu zn1mm1j0DSpqpFYK{|L*u9Z)dgKkRL&1Dwh*CptkRp@YN*qH4<_k~JxM_A=5p66qQf z2&H(QG6!lYJNsS`rc+$%`w2Oel}Zs|in4)I42DqRFekyYl#=wNPzvQl$S3G4<)iC$ zVm5WoYztBjrT7X8@5oI&u?NrOboRR8-?P8hIRo+R1LgL>4OSD|iIB*0LU$0}G3(M? z!BVDU$RTJe!{Ew;v<$o10Yn20y3~)elrB7u#IYp<-AMf0lJFW19xeJ$UWxxK)G#jr z96n1+H`ou_jw(l7 zhT2C~fO}JZoPH6{mq#GZ1D?_|$@hR)lED3a1fFQhwrX zMH9F`O~eEvUQ=^5Ce8mL8^yVeuPlL&Qogi>nmXcOia}Ih^8(~^W^-Btyt@0 z^`cu?#?W6<8_v3ST~R2mxqVqeB;KpxZjcYYqw1Qo4u3)uZB-6*33%rwXwQmQkFBB| zh1GTpQ$Hh?8NX2nklC``R4(c~T}M5Pi9=ARo3I~~5tQHf%6osE9A&Q&czKPO`n=oKZ4Q08dHz-uVm6k}!C#-;H zQPRN0Nq9;UWV6qjQcSemtf#PvkImmuElCGYmyq9)>V_VWJ4hX^6f%|6ssBjEkuFH^ zWIX9N)t-zahr;5>baG{4Em=*O7L-dqMTyz$Lw-upTTG)slpUuokSf?|gS$u^)}7|p zBpfSJ2a<@)lO=0NB4#-GHK~&Ew)g;PgdtBjL%PG*A9#suMgO$ff^17~vj`-Q(l(xG zBAzX2=}#usiOZTIh%!-u_M2%#jk{=+I4-atH4`85J{IjHS#cl6za}|xE(QjXBH8CR zO_5^QmKG|~N5+cd*-*ExsOKA`(5BX3fw*cPbtR-N|0z&F!}39B0D3NC6>x|q73PS6 z=vT5bppcj0#+%I5QIumcP+Q#lei(0i^aPW82;o5xgb-k4`4*Xf&&BH>=4TlJ) zp}>l_dDkIO4aDt)YReb;r$Z-XuQq;#RHX{bV4^GM?Z{bha1_~*4Ym%ZSI2?s-mQu_ zu)5cBNCGhaX*0TFx$mmkG8yMPu1~&;NcctfS1BoqDG0Q+{y_?@% zu*`_vxE=IVeX<+{JNSL$_T1hge)lrYmEv!80qj5U1XTb#1kuBfV`))aKqd1vdORP_ z48Z2cIWcPRHhUWw>wvBe-{|i^-o7gwTqYI!HSay+CH`RSC?gYS+v`vNMEJHrN52HQ%{0YLiSeVooG^@Yr^aNF`(1O5 zbqjm0`~}M!$K)+z*5T6#0CN!#pNC*@2wAa-jQJqi?_YW+1ly2LmlES=O6lpOWh3j^ zGl@$(X0sBAzSaKBabk`F&)h}SaV8mk#MeML!-Eu)TTCA&osCh`6Ujb)hw0174I5U{ zK2w&??55oz-xmpLiaYn3=V%SB0C?LtI>BW2$%ad-w!(xBY zs<>s@Fj@$w_waY>7xpz@KWZa;+WLNKE{i-Xm>N$f_l42>G{U9~dW@<~ca`?M{8VWU z4KF{&aG@=hjbI$9{iWx!rl{E^Hx7+Zmxvzw6i|)}m#xpHWC~`@>Y^NB{@Z<-Zqst6&{B@oCZ~^3a*ZBQTPgE( zOLvpVt!jm1Jb8sYzm-rMT72ifkocHsW?KC+FyPI<6y)m0OsvCuw-u?GI^>CuKD|XsHuf%73YsM!yM8 zQv(LWNFOO9Jy>`KCA6KM)<%BNM2$R1#?*4W?~r~~{^z)Y!H~g@rHb z*NFcVXJ{=%)v$}=8^S`wPU=a4H*yI1jrRu~opG6a9y>iMkJF9o@cx&55(rwij9pAP zG2Mar0lL_EzGMJSt}7JhBAPULB75W&vA^Iy)Env^{u#^$B*fF1zDODu#9fYBz`+6A zycKLE0kXf%js)wc&u88wx_7)Tu}1H%Z5G|Y*s8~bM$8|PUO>Q}pw#i>am|R&JWqUA z`fKi5;6h|SXEWi6cQ`v4d}iOnUJPBFZp&;WK4_{VB54B0{26s+LA$ooua*8?E23MKNN4m@AF;h_#eAopVvUhEzjM9BiaW1$ zgi*(t)#QjPV^6OmcgYeeqiUMr9ccPu{NJ!S5o0GCDH- z=n5$c^*t7nEmvHG)(fQy(PCx{Iz9HQmANALs7(I!M2)DzQepkqopg6qtOPe7-Dafc}-hS59CPr zV~q(#OK1gk1;u%IZZ#9uUv$l&K`hF^={k_&SdnHNZ6Cr_UdLSVqA3>Om>Vr*w}E8~ z&&#R_A*Si#DdIJLOv5DX3vEH|L&PHduByMt%|$H322^Ng%>V4j6UDXF+P zuVnc-{DjkcnVzt4;bU1QXzGUL8%Z;Gj~cvDX4Hn-IMfT=ohlH0y2!b*1Jj;(&ZM_Q+MH#Ni0xn}e7>xw~ z!VLX2!brMn#SAbmra-+3vJRG%$3b^I@01M_g-)Z=YSNBHtE9U~_e>McljKIu`?~$) zE97(45cvysx?zA~QNYoCqAX4;);d$XqDNFo6zcxpic-oQPrU3G)yYX+dXLInSb zTEVe>?6N2Pm%9q2;e1D@C6al(tBaW8e>tWED94%+!8lo6qx}t@GSJlh1jaoZ{G-H`b7oJxlYhwki>&vZ;CDAS92q z8`d+$=<=wA*M*bfHzovob~5!t)q`$pz}(=|;SS^I_^p9QnVOEKn8azytM#bJrwWUj z&VYw9KjXh1t`f5D_WC2DpURI5&k3DMPMeuA)$9gS7I;o^AdKI=9hQ;JYRAB-NgtZe zBgPL+HLOOh5B97%if-AhG7Mr5ZP}zt!q2jE(mV$gRzy`PSZ&e{_K{BF`ufoDYp{f_ z7Q|$BR@)!sVA6%=RMfFUz4iU*{{^qEF~|DvW*IVZty|XW<^t>NTs0a3*2<^~hCEFO zD@fOHnBGSy0c>^GW^`iqmNpi~Hc8*~4)fn3VSNPl_5SbGmH0Wk3o8EtL0cA8bP&jP z9-6(N&T3Tof@pe93a^saV*ly61RO5bbZ!CKkG8hz2v$k1O>+tSL!0YRpzHqHssh8$NC?3%kn)HhxDu9u6{bpV(if&J2 zVzNlhrD&H*SIns+C?Z!n6No&`2>S(;$|kT z2d(Su5|av(+B`)4narkc;mrj1`mKU5QSfSmVEeueh8cXm*N=*PzNKrhdJ*rmU08V( z@6(*OWdUrHCx_?C2tY-3JW;t8IJf4M4`h%VUnq9R@2N{K8;UHcx-5$iO0V20UE*~_ zTTyE6a#Q6aDX?3u$Po9>T_9V^GyN7^KPCbh*|D?galT8-6~otbpfN%J^~k>3ixqDp z8mqjtGXmxM8g;m5vv!H{jZ2iWN^#wGOx`M0&YdS+`Ttv|Ic!!Y;%@to2EV+{7Ezs5 zx>@7$>hp0^wQQp#V!!cCB^cJyA;LOlZKosB{Q$Fd3yQh7uyHH;mq$iz z5LUWTZS2SIvGJ}v44kpRX>LI|CQS`aUJKhWf=5WRKMydG87W>pI@Gq9`5gmj`vdrv zNsRa2jD|~CibqP#3*4iPI%5_PW)q_SL(p32G;HXW31Ju6uK4Hhzvyk*mj`B{@21%G zIAa*mUpqpu-Ul*T5^?MI9&E_QdwImvP=I_Bf*C<)6RlT+6Bc8dHAGYUwCD{vy;wJN z571?k`eyMHy+M(>I$k%PpEF=>k1AYZyM4{gE@5%)GvLWRsB{`-Jwc5_2{zi5J$}b-u$K;8Am&78i?_~TkqAm zAKctzR~5$)3CtZRiCVz=TxF$t1jAHQf`+G&b}&b5t!b*95kytcjd$^ z_=?o0qaWc9;yecbhiHt5?7fd{4w~CJihAfB-Kxa+x(zk*upFKk7)ACmAI4La(*29h?$#ub>tU@9k*q09(H;}SkF(aGH71MV_dFx zLd!9HuG`s$UV!DGsNF#5TGCwg3mlrYPkWvC&cutyWJJ#S@u!5Xsm;S52=Q^&0~Q2& z#Fid&!qdRd?cYFzcUbdH$lmQ-{b}f3JSQc6*dcx-}v zDiu9+hujx?yYCyhD#EPmFS#@DYTGyR8}IE+Hz@vYV|B+ULk@k_o2c1Kml@8}f@i%{ zm(xtW2O=kGby73l6;nrL`%oubS zty;tMSQ=1S%N#KGP^B^KOuTTWO=j1QXhj!O$_HwM&tk26It0JN_jim6b_H&28Rl1d zbvCH^>)a4EB;Hep5yJ_-+ftR@lDFEtv%HtpV>;E(7(25!4=0u@QfBqD6f(z%xzwEkKaVX5S?S)1c;N0{?z1j0d-4)f`EvKtalrtRw{o}IMrMI=GOByXg zVX-BNXihkFgN1;K zb0@ozg|P7fHYyXbrqKZ%heWF`V!}|5xrNwFOdOVsV`6Wo58ylTs8A4i3cPonB-{t> zEhC{@py>KAJPY~!)KdhAn(UuMQqVUWGEfrCb=5p{J@y~YY0MeC1?D66H?Tc@H{OR( z5IPqifjn0%fdr*lIzw`Ze6_N;0epSp1MD*7-unV!4Hec6BDX?g<>jbMQ)-rqt|V5V zpI|6ZPjO?&f3k_ydZ5s~K3rc}q^f=^v4dF~D3nRKvf>YH zDRPH8GZBZnpy&@O#RSQAI?u$~OH`H;{6|i}_`9MV0Hv=?@<^>*PUM1BJyz7_ecdS+lcIzvBwb24U&Qe`=c z-^^chaw50-VqCvA@B5jI#$yG+C%m*<3(H5p2yYkt7$6ddVL;cl!eoS1%eVLpq@?a= zpdae3F=}%zx=Foq_ClPFUw9=Elakjt@fP#CfZ97B%PI1%{fgUG{GZ|>?g_k~^%}26 zs?oVXI$D@!LD-Hh3E2)>;vtv!U>{+lg;E=95 zA18#79W8pGaMI~>uihexl$zcJ=^EI|CEcN&kNX3%q`t>_hWSGo&k3*8d24I4{) zn!FsxqYdnPg0H5PI5U7eTA;-zkV*M?=5{ej8q>}9H{wcJB>4equGFhI37sHWmT(PIFM1i|hP@&_deEIvIF#;b(A>YqXQ7vb0`%KM5$HNT33p}OlF{m-G#8LBr)F`g>rY5lC;IhTm+r)1D%#E0J+$_{jSg|e4=wc zBqJ=cKoh@!vFB27(M7;$9B#PyPW!)jcX*W12j7o4DHQ-wC?C2xa36gc{+mF;7AO4% zH{e(8Gk{lsZs!?LENE>JM7#q2K1;^J5JMv@?7zs~_6;}y_1Hkio1y=f-p5yC*V6KV zU|cF31N_9-CGiOZ1haj9AO;NFe9GjKI$*KI)XebY^Z;5;APil>>>y0F@-VMKU;PiP z#?&i7!X-nis9W%Bp)Ob+{xdNyu?o0KG7EwTZ6wX+17HAoriD3i9_hxZ4wM%?XK*3v zKHa0a53Qp8t4qX~{=t=4V|{2PmJ zYh99mgo|4)$_z-x{S+WJ<>B*q?=0REh|K;mf0(!_vhyq)S?^x+0CBh`&@^e?X$<1! zptk7U2$#^Fn!kDfVh$;X;&x-xxX7^`*$Ue^6w`hY_P38z zbsT=Ki=(hcG`B$<2wBw>23Vn5Yk%Y(L7z7AW4>XgbPM-lu_LN)8*_2ya*CxNaFAy` z98&ye!mRBx?EL6S<3DiaV4B<$f$Vv}?nWN%*oHrhDrip4=|Pk03SvME*OFX4<}_-{u&Vq|SZUvx0=qZ>wjL!V0{#GOz;Nuiex=d)8e+lcLFt9z(8ES=Ku?L9@ ziNSt9h#ZpnhHBzI1(UG8SPQPSt_-vjj)QEC;v_$Q=>VVgm=_~_%C1r zH8FP+c$Hcl!-2f0Vm}1bLp7Pbp%2u?S<$2dO3Ppt9^jj`#^T3#zD8?c371+10vyf= zdp5zGV}UzPptI9+)`7EG!_nO!nzh3Bf8Y})al>I~5p(veMxu^Z)z^&upmc4Xfs0qv z>uYf%@_EwT_&u`yj6e9vQU>M?5L0sUs0er?HapBFo;sAJmOKzGaxi` zUrzvbL*1)}Of0R&wjv(;&`6TZ$7Sf7Xn%0;w3pB;@hsKb%&))(#qUEKfu6GEK3fTn zrO5TQgw-Voa~^n}&F=hxsqXWxLtuV)o!0Ee9%4J8Wa z*cyhY#dS5@Prrzdtcj1p;2%_8-~9$SpdEMo4_KoJG9M>A<<0DXVDE%P@{y=n7>j7G8_t?mpaggikj)K=xHz!`Wfvn0+&UmHKFhaKhWYa%smOTr8uNR zBFzTaJpCnYKbX-I!ALE5(eR3Xzlc5#DdEuivoGkeIlRNcl|sJpD42M#}7Pq0q@! zYi>{ukYA|hQEbRxL`x_ODYGa+eB{IE*{D4A%J z@?Jy^U<|AaBky3qXNbw)Xm%}j#GmpC!*k-_vYiSW;uq-|UJ>zIskdos#*C8I;wI93 zv1_U$$wAl{eu?BIu=fff`SM2AO()IgzMDCR^qwJY%prPL^YtjAn{l=5F40Nf#EB+4 zSF8o+5qGJDh3|;r^hHF1X+oS8=2#2Ttkf?jmlR}?`f z+s#UEK>f`ltXycQAqohDPS%_&h=it$JCe3T?{u;Q8N^xYZ@b{cY31MSt)O!0o0$Ny zoP)2+f=&#s)pDWg-e$>nsHDr9sf2`Wes~5{(iEGohctDOi5jT6$|v+GG^*dVYcABK zp{~6RZ7*w@DJObxSJ$c_!+50T3Pc>PmPA5n{lko}(4lT4?gJFwo}F(8g*W|8bc15* zN6bQvSc0SD_{@w&Aj%I;Tl@kFU=Y$~t(mf@02EZcou}!YJn+ zY;|E3`#WM$@^HwvP4`*{BpPRZ7C zUlrrnI<5`ui^*qs5TPvG&ZeQZC0%3`Z1d@NRZ{n5OCkvpfb0yjjoW z&w<%gRPnaKL!}I^Kcb0k#bF{Z0ms?r(8~%2*p?V%(ot3<_EG3XW;;G=rw!8^P}tcp zeF#-%M20`ashiGi#zt$mbDrSlmpXA)zZm2OU^=z*D>F+=F;ae zQr+J2-p&>u~!G&wcuE5Ea5N~Mac z%;Zvy-~(e^!lTY+c$I8LXVd#c_p{&7J%#Ec`)DJABl{<5NBDDgETVqozOk*OGP#k{ zzEfT4U-ah8RR*Ur7K5dm;$5Ww)?OperQ_9Nq>}bkx$kHwjU@jYdy8fv8``I)R+l2( zw^8?$@NL&qOdAlUJ5w;skJ{S|aqC%W4`X%nV$O4Vb%PhtpKe?00RKo+8vkaVr>)eV zixE-vT2&C1>aN0U_o56co~#y97L@Ls9!kE&R%xd(xIK!}mkjex3TFwO-L?m!(7rWY zfd|lF^@lQDsIRK~VrEct4Qm2_QQlWHY`ab=R$Hx(CO1karynI_Ii8wW27Sm->O+6u z@5_eK)4LnMe`u5KzhFZ&_m&+QJ=Cg(ebEN$@|u*u21=QsY+D)mw-&uRguGbxWx9s+ znj=>PmEX-dCTvs8$>W&zC`1(a;4P$eg?|e^l};;un7XSZ3V!imfT#|6WA6f?E&94^ zCI1E1xJ=G}hx=-pc$b4y@`my?`Tq$w$&(9-w0E+GqOZ70=~5WFpiDwXEJ}GOeux~5 z`YZ}UgMN1fXRzB{vHS*H@3Inp7v7`WUz7mf3IrFQ)J+5=iHIEJ{O?sA!xwu8ac5ypFv`T)51cMG&kuGo!~dcMG;kDD}7L zt4pTUW&lwlN!7jLQ^IYAsLUGvRo!UZ7!R-A9D0Lmt7_W4hJCO6$Yvi_d)c$4<;?Tq zcr!CvC##B|D%sKcjTR|BXUeVeML7+q;)TK)wVF&bzP@TC&Y9;|c`G!Q^HgW;-Nr80 zkT%_ArIeGGrZZDY8cYZg?5(`kl2_fP?iO)r2N(BBw4;??L>8QB!evnTN%a}AL)XL=f-R;mtMSTROUO2s=UqYsR$3Mab2e0n zigqXEXcob&4=zzM;hzKR6f=-tJQm1$(a#-TN#|h8t+PtCxc^M^$$d~aZNjw5&Wy0c z@U{>G|E$j``jY)X`v+E(+H0C&FWg0f`!Hu^ub=1(|Y5-0dVGCDVW+$Z${f^T0L5liChYl=VWL0scxT zP%ePzkumZXqGN!o>=1Ek$Li8x65K&uGC=xk{Z9OvTyC2D7gDB?T`MV60=7eEP8Agm zYPzUU<_A?F6^t({Uqcl}NXt%A2mN_c81<`ro1~cL>3|X6rb*^+6y2ninYgK^77#D% zmk5xUOznC8#li%&kiRaYO&Q9AAHgZ6anFaB%R0Dzdk0H1IVap#mZWeJ95#zOIS1wk z2#>N@CT^C}_dv^Z+2uZ{G0jJLZ~hn6P#HWuw;V5<6&ERYE3FF?OP@;;{iY<^5~uA2 zVwrf&x_;p;5n{fZAeei_#Hqe6V&|9DsP7+u zNwbvieD6yf%AL1~#B=1e_Me14Qnd9HKUrYXahTJX?}4rgOnp8=qH(Vc%$-!euQpH3 zRkRo;Vwh!FdVc78>Dr2PUv$YM4SJiKXhdagZxlQ(BUv}_E(+NuZdS080IQ1KEj;)q z_2(w%++E7%`mU50ij3L=G0VypR=p0Dm!3CN`fe=Y>XWwqCyLRG*uw?sij~#_ycHsx z31Km72)?=#%3Gbgs%>q4ZK}TMTA@p9S-r7nEPO^yA&lVv*=UbAyz`0fH7an^741@t z>&ofs!`Sl{t%?-Dr1J`DiS4k#j++JTIX7GRMV6_^rqJT#*y4J7*kIVx>Q9J0{&x%= z$VWRb=@@9$rt_K}?7o$YRbO!R7Izd@1WOYFlGqCi@7P%k%i*H>4YW>r(3yJ#lv5+_#@X4KAI4mNairhwomAFIgZU2 zP3q)WLkC70bGBB4g@wgdk#I%+M=L|{tevr-<4!y6Mk0C>Sye>Zvof?? zKsK{1lNFIo?ggHLQdkt!@((5PsJ3y0l9`-aS3${)mRBn%j02$tHf3}#q>HD{@fg;4 zP>VN(DnaUhD-S7HG@a#BX&^1##H@a5OCh#dz}uA7*MQ`zlfc?E?t;T3RmmK6sIc-N z$ItI>#b)*=kG<--?8!|Q<^JqzD--2wIkvNXByCK-iCJR$zWo21c(U|NMm-=+Ok~#- zl|~)9Yb=oXh8)wUm4x_h(gujLceJZEi`q6eC=Q8IR{E4J6x^B(D?xGAnV410Amwjr zY^`{lA*t)sMkf%e+ttSpJ~4Et+=3tL8p;QJ(==*D?v5GCQhCxZ{)sh_)*7B7uWu&sfe$r`d!t0(5~{c;aad~#j{EW zpG)f7I+nY*{JPfO=|b5>WvWe+G(^gqT_SSge=#xZ2Wxc>)SsVsJ9V`Cd4YEvp`)l! z5pkpCXYs*=ZClHYwb)IL ze)=JtZ0Rc9I(+vmxAJ6AU_x*wg=8P^oq-NZwdt%ue~Nw47KKqn9B%rIEeMLJm*LXA zZ`UlxW42-qt$@~Xr|txyWodB50#H7Ss)!`EnRro5j?A|0z63l>5q2;MiLr>*orIC_ zmyQ2`y8}Pg3BkMG;_3xZ$yVRWT4JK(mWlz=)}=+7H>5wauFKa^7MOS;pp+ejy6#aN zlE1YxD2_32TeeUf!qE*+DItO4S}{fJ?OC;$^4RTEY#~)5>D6J?zu%tFZJKrB;{H5;lEYLTpDz-?q8RgF%5>;!;E-!6Sl$X#f>`EBC zT_%KhW_iZ?w*TYkEc}{W-#<>9C>R)spp*(CDWQOLDcvweth;-xi;Xp~F}fQByW3-T z9pmV+>)2ht=llByKCkWB{l4#K_vgAkm$|p3wKv3=p5hPT?0X&LHE|4XNrToT^`C+vqJ$BJ~jZ=!BxA$MeB z>{9F|4`M+v(c?b2f5T=DGYPsiuloNZ6)gYUYeiY=w7wfkZMA7M_cDPl_4?ghKk(Xp z_b_vcYIhf5=VX;^zk!>R{9`g19~!e^Y=j^Vn>}nxvR?DPpF`HW&+5@o-JMFh-q4^n z2h9nrHGl$z>kgi^uLx%Mek?wuG|`#J;@WyPO>*u5T%^~QR^WD@XNDHu+d*JTGcQd! zF)t_c#+z9Q(U(S+u>KA`I6z_x*Iep($VqZv-}#X1>GZf`dhIM*W+R`s5a6PmXR4UH z>k==qP`Dk+3(Dkd{mEOA96YAv#YSs~=klbX_5Cp3l{FLH7x{7SksZhRpPgOWE(kQX z7aKAKYXL4${8J#vPLABZ;KMctnId!ggkCl)>C~vVbUZq2a74-uP3fB{EnK7QqDmp| zneEBar_QuiXUSK)OvAY7e}4BW;l}bOJ67t33aFDOwBjvw;|L8hDR=mfTD|e?KwbT# zkPkg?>T}oRbXL|oxu>)(Rt-2$S;iGcyJ0<40z651uqeJfVf!3QSpJ=@hs>JI-^Vab zKNHUn+c)mrnA5Ls*cKw|nPdF0`g4cTaMyiNYlIHue4x2YCAWL1E0eVWT#&@m%D!zg zw;#@L+M3q-e)HbZk1gSe#-Y0ATN~Z`7n%=+ly^6qdRAZWSlu|_?$~nBkmG!*d2anh zdk5`VIS5dgB`z)7zHMdqzI@Qc+0N^m<)g%o8HuPNySDBPfBNcL!6B*L^DN5MXWDyB z58Pc_RvDi-|27w>AKFK2Jrq^|7ZS1M_*&GfO3tozn4s$8Ls~2b^1dY=*9Xt4pN4;n zJj!`MEWoTqZz5g7^%Y&AR1s6+eo&u~5BqMVy`~P?9b=j3i;j69M*P6cO;kM9*B3`)(I)MNS-JEFM^_;5 zh_^dP$cre&;2d-~#;N%_W-IojaxX3huVwGSUnAzA;)ycyvcmt!36x)PVU$<2-M)U* zAM}rQ7M3mJ&f!(CS;Sl04Dcb+-u?_^7FlIZLA|4x>dG;_)G3x62cumSf}di6=GB#Npjfpa(^J z8~x#vB8W_f+$LJYn2+8sJOdBIUK8^2WAJZ;Yhy!+X9PEWR+1(JTkO67UBjq7t>DW> ztFg4IdwOnXVa+}5XTurjYmHQbKsc(G(ELzgsvc+q2CMYUv%$40nq%S!GWk;Pabl_T ztbHi$van+3-in5vkl~R^Q)hSE+v?W#={hB3e+yew3VUsSLdip}ZrTX3LQ{;~oFmw+ zy6w?v_%4l;w+{hbzr-F)+aUgLyBTzCTgqTN`257ymbX>cMx8a?HD3q$0u^j+?+wy0 zg4i{;T8TQ+=9k@$wQG*sD8YSd9QE3T->x6F7g5j3W^X$RBJb@TxCE}h3$Ht9fBEc<84gBnVOYe9r#S+Y5OrMS9WQeAIRg# z^nqA#*#5_so~pFnjp~XT(DrD42~<3>i`WR?KVn~HK{@oV%KD6n?n>Jjgj?Ra+iM5z zSYw9$AIdLz%%KA8-b&A@hq$fPqP|i5Fyw$~8(|FYt++%yhHPieB|XP1MBJhH;+hMt zQ;EdHO)%OR`IyfxI+=RbZi;n^u0IIH&>XFG;^P+0!;sJ$+F0*|c+ZI+l)+aR7zhh+t2y#Z;nJ_6A}V=p9X2 zxN}&JypFI8zlHIdNFy$V-zPmHz0dzZDWg1$9i$$hP57*#=@`@OIIMJL#6BJ39?@r_ z8nub!+s#Gyljk>_#yV0~$-;0VDlmJ1FrCJQJtDT#x8^S;Z)CiR`9|5va`fI$m9Wm( z5twc4%sqp!o2)fsT0|jB*m(+hgLTdri-xfyCD*Wa>>adv+z2NU`iKDK9L$R+E$61j zRFem|2faBIIQKuhA;tx+V3!$~+`DyT0@elOW;-FGg}?M;$XCLhVmx|G$fFiwNy041 zKlmi!vRo+9Mer>8FVe7J*gKw_C&;qZAncQ+gbzmCGd&?rqHZ@TxwinaT z;vZWVS6-XM^ww6_kK38UAO*vERT?a4z_-?faPR3QEI@g7I#d>7f?6{(Z{z&T#tjkp zD~*9(4umv)lKlkroow2~=1TGIeLY*Nlehmep&)Z6S11+GCu3>cC-4hHIe1|2>I(;- zLf`HDlZnS(Z9NwC1y3>m@%(`Q$5?FtfZ8lOHNLMh{?Otck7~Ppi<)lMoZ9(XX$@_d z+Q&t}Y2#IRTO@Ai4j6-`_628#Vrx3ZQUBt;wJ!2<#BXW1=nzhIkq7Su5xT%v$BT$X z)v;YFk_$v`d`Ef!`!0o&Pa)ZKE6Q&42UsBWAdZ_SrJW@D#t7+0$z$G|8Aa4%c0p_y z{lOkO9#(T_Oil=cKJUCibcEkAW)Q!itR;Vueqj7)6p8~b8@hw)Nf75P1u8?X$N10- zsrKG>^iQ+{cFycjh6;%3Sch;J9m6k1?&(}an1L=a=!uTl)e;YqAKr@k4|xmW8Z?|D zB<;!_rS7IoMPH=dp{cyi(0TMvcGf^2I)3Ld^fdxy_$%fQQP+;c1(1w-CwvikRCJEe zO!d1QJb7pqJ;|rL-|dqE}PzGXM1=(m1SLb`Gp$HhuekkaWI5;8#umzDuF@-q5Ii9j;VT7yRmTy&2!^vakVFFY#@XZ>0<4!E zB~uV$XJQQSc5Yn`bJk__PQrCsU$YW9PyJT44`ry|&2z>Cs{{lE_Kq^SayMRIXO}gb z7$!fwVLho)rt%yneUX~&-qE)UIb$u5vz@cMQ());? zzdDMbvD-q7aM;lazU%`cd*nQ84Ebx|4@QqZ-0KWV#cDcdZ7#&ex7tK*A#|7-D^C*6 z8P3_a($>h@hZof74lHXw4z=48XE+Dr?${uMA*?0?S%Ju&F>8z$x@vH&{596MH)`_| z+~tmgkzoYS7Us%3gouWX_V1|Oa)+&vv|Zq$J{#Jbs^`tl^dLw<{S5jDY#Yym5rOm} z;2Fozot0HgD9%5V&3sR|9<`FiCugtN#kxU-*l@U87^^1aR94N#-a_g*D9VhX`N26V z80{~lqE<{NqQ~(M8B4IYz)u;M2rikNyiD8#%E*_kzD6XNhJ+%)YP@)kGNGdCu&>;fo?^!Y<^3Bz?dKL zp0SHr?(vPegq38&=ftx4BW{EO`mIhEq5}ilP((b#cr4#bI?iOWGsve|E3h+wg#L3N z4)rcuusM)+nX@^9Oy9xz;&Gc{$E~zE&Zct}Lvgq^!Q%E3JdOX!aD=d#zfYDzMDfR2 z)+8l=2>qBm$R92jQ!etiW}vB;_=m#3(GK%ZdF-Wqb`l%SQ`d!O8EWQ4R z4uun_-bq&ACzTHw&V)C0f1}os{1lhUoXJ@E@$?LeN_HYVhRT&*_K;K6(v`No%ueCj z-T>6E){o6H%reUdO&E5g`Jre&j@xvF7KT6FZ~!4DE-|#1^pLPRM%prRho*czh@!72 zcr2o*m5*#6F+PgP-L5Fn$QzRsO&I)EpMYWZy%R)Z4|ac{2=GoFFJa#a@Yd7Cn~8^; z+ftoK-;wjRx@fSn}W{@+I8i zzA{)jVPV(lqGLo%`X%8N37SlGdT~37*2$~#&7G-FG3RSyYo_Dq|o*)VgBTqmN54w(hkE7J2_n-qxDU3 z1Hnm_*W9^PU0OBgTg_W>40k3hgYL%lL7YH_ag))prB+-r_EPEyx1E3tbKzbgU2@B< zokuxtRlqYaQu}6e?5pos(l}O-Pn!3fS+F0XK~4m622I1kqr(t8IDOcf5-sN$zCX2? z>rZ?edXY<|IJ&Lo9-)p}}TbW{ozYldnxMTAa)@2S4ttfmyQZu2;`G&TXus}!_-;nYnAMh&a1S~G+a|D z9Zp}Y3a8(skMlDa6^t@+BjYu532XtA&U{pq&zi>Ckvzm|Wh+94*q)q(WyjdSA497L zz@(duj(JoiZ)w9;>Nj3U-58C;12X#Q&b&lY0eue-1o_P<<0%S3Ol#h@q<73d-u;jy zRxp3svO!iff0lJH*PiFqT25xE(hT90`N|G?Ib}!PP0k4_PVs|4rUfdzYtm`2*Iu=~d_#pYX49~bJ;aU%J<_LP4e~^|oAJo1joiWdr#*z6=ajeUf)CMAM zgu2+!R<(orOm`yBOxvS*nQ)lSRxb!Tz_3*bmMx(FRd?BDIy*^p*X%~D8~9J-NLtzl z7Q;vvy7w~|kefT_VHZ-cZKai|)I^Imw}R$sIvT%=cDvzM;46Bu;e=}=?Tco)O$cjT z(r3yg#*XXNam3prlcH{tdT=iTOfKl#jv-LIx|_k13=G_LllO)e`(n%lIK@Mk-;zL(fBxkWUWlr_GPev>?V_!Rm(`SJi5oJnc#xtUW> zMR#tApG#ZXdM%JfW0{Y;GN?E7%WN8%C#46PhsA8LmG*|{UR9MeSCm&X%sMK32eZSG zg-r-{6-*d~`kLn=c!{lxrwGOfPV3|XEh&F7NbrC%2XKN;FsRK=qLON+HeUD;qL&;J za$zP`tS|u4gZ(Oafx2AzQP6|6&7Bk=@age>f_S2EU7;X?ELfZ@&{AFi3MR$_Gg250 zTcT+fyoSG$cnSKE6U;#Y9*xA}fqf#ak}a5ykIYTu-y?2}d&ECY4p_H}e}VFJaiqYT z<_IX1GgVDH_;_rwdLDlRu1Pe-cfucHKzV6DLPDRG0@=noC z1)S$SXE-ii!(YodZDqwj%8qTUs;cs||0(tf zrEyUkyI4WD^5XJEiRzV1peIZGgjv(IgKfne>L3%)%r|Yzpuwzg%bpT%me{;y^K{mQ z#)r`}*>jC`es|c*^&^XxuoU$7EJ0pkX*B-YK&B|{n@hf4)m=?IaWFoarR^9BcQX6@b zHWeJh+opbA#h|&=57i8#$5dMAn@XdSjVQ`LTL(s+PWfArh0Tv>msjJD`8<|gB>w9( zDEA@%&*weNw3XXvqN?+G>+1hN3aLG++ptV@g7Ow3r!uhaHj0{0Rb0UIrPRwW;{J<3 z$UYHWd``4mxtK3W%!*`L2a z5rx~9vO%6o*cTBZxn34R84lkm<ACPX43_`1dzsum^ zw2b#lxgSu-6L+$|OI8p5B!3f=`X8WUMdx~3t1gH_yVHx#30phfY?&@}Zqvr42t+M; zA+7v}=F^^C{J@4~PVT%s70{nmdr4wNHMYC}k7Ape-&Ng*{4v`?9Lf$idBZ|;9yg>R zq$y-W73#kYk9Boec)(1}H~bDajK-JPJfp4Niwtz*%2HX5RJWGp)huj8vnPZM*=CA> zRh2OtNr-}+F5?&ql@g)9f-!H{uA70ox%Q1lMtJOoRqrRsXB<);qoe~0b69IA&6a#9 z8FSWL1P_IjH=z+P%6uA*p?Y!(jE)#l@@IVvj+( z8bwk^FqW(hRJ~yOx)rEASynUS>s;8tZL=hs3nH=_zcBV8PXLpq)>hFCFs8Oxp?k>k z$a2!=vJNMHRZp^Mk=N>%a@PA_Qr_YG=N7MY<{E(UR&Q&=05(~*xAEH>ri%g*kp_rp zVWqq7wD5INu{K9|CDT@YRd_maPJO!YT;yWq5#e3`%sMeJ>A+6WDGHl8AVUg005;M5 zeYjTs9!D zQ>>ji0uY1&$WjYqFn9I0%nB%7muiXz2{cCec7d5amUZ|cKedB|b0{y*kS=|!d zZa)Y4IqmsncCusYSuPtY;z$CRAVD_7CK~_TSdx@^>1vWlui6{#Ubi23u8c zYKpt8T-~%dJX~?2;oO>oa;(wE^@uEAJ7;Ev_?80jF9h|Xf6+apq zoGlBc8v8@ZmZz-PF{((Q4eZ94bzc2NjrILSpQ-j%4;1s^M z@r)pQ?HeOk(7lqVmkT`?`0AiSsXba*ED!@^r2;uMYi~YCh!wk9=2JK>9 z>HPg_y{g}SmwZ?j0w@%SU}YED{%-wMkl$iy8O}^?Mpz1yhfU$l-Z34G*5*a)=NN98 z0{qRoU5%y{GL788n18hXLH!^5dqC8W1Hk5G(a}=z#I(v!dF`XF)i*Z>4@E+@C+76C zU?Us8^z1>L3GwQDg0^3s)(*VixM z2sWlPcuWL+pI12C0q17C9k_`2ljzd78l~EBth*MI7GmA82j{&ys%nVKFBut&+)a=#Qahn#48a;Ew* z^u%;b&o%m)_+wq!jM}Kd_7lvEV9(b1EcaD?%`~>P8`6a4SkL7(S#y5c^r<kj{~!b&US;Y>G)CO9Y&KjE>^A*v$nupp()C5lUKw3AXXf6~y-`}*4%BUs!T^Qk zqJV-$J?)muncKVEn)NB?+WXC=n@FvpCTPU==Dkh5fpSx3;|X6@!|MjCWp51}-SFHm zx&T#-?NvpCY!$#on@EyJ-R@geyV)}7T@#X&I=&Y2IJ#}50JbkoF<6V(v(~;(i@NT~ z?!Jrpxae!wG~CCT+uAFMsa96TLK@J|tE^`0^RU~%HMchFwj!XFNgKv$VGpAX!$7=# zm~h}ODtE0z?`KSmr=&Xt=fBvglYoCe^Jd#QU`&CP;Rzk6W>L;#*XET?HNmPjvnNL2 z`;u0UoR0 z4E2jukUpFVC@8LTCUcKYlo5VpoE@DYjwVuur;}_IYq`BOVp%+sF9ZI@X+i%VM0u={42&Hr&{T5Z>yuPp!+tavdw=FwapCjG`x2M>|Z z+5eLV+8Ekf!PAGryLR)Q`p31u=BIcDw?^`hE*4m(36!&~nm!8}tQV;x_&iHLP(!nX4r70`<*N^ba7|CEYRPf%_Q^ooM(9}%UNtNx5>(9Z*HuV zWLQI0zeGSKs^o9MuI%)o0^Q@Z$^J9i`|*9f>6$ki^1AkDGC~GAJk>Y-dRv8R^U9;m zpVR@1F($3bdp5YiL!M>bsInH{2Ne8;hOF0vHyXF3LHgqwR>mWGwi{<}aPRUqWQK6t z>-A^+L@l57pp}Eb&=se}aFdOC&1{O1uIR9yRI(&vz^c9|e4Aw+EN@<&=F_*+^ddg0 z$F1pT)UQrilXVEZ?L{NqkJ?h)09!d=zGHwd<}~GMZp>~sjMN>mzE^ic3hWQ!JW=T6 zFvPKl+BXH&mSUTe;L|___45#Yl{GvE6sSf@@WHHyNx{!?zDVPiCxq3Qn~`-SEBphG zG14j04C`VhiL!0$p6Z-3S?`^i-xVv(0%%|54plWATSKlzA-BL1@Ga<6#0M}LTZ$gt z!o@Rjx=4Q_lo;(nAbFD=trMB6smzIel^x)NJ>RNHRewxVkW@&lvLCh{HpV@H*nnJ% zpMxqw*H!$$$gm%_EWjNiFe9!K_LEL|Y#@>-sn*eq3pCo;F_0R;?p_6MK`m=?sycyg zs|%|6fUV&0U?KP*><5ID@UFrc^^&w_^A>C-MG+B-uc7(4-^G8YyIQ9)W-!J_MoRY) zD?1?|J5ogh0bD^&lJBoNMk!-QK{io^m|L*pv{sN0vXs6%Lxq08xD(!mo58%{uEQ}| z$<};&AnVuAtztLU@pe;b2Ya>QA}EAiEK38QW$$Nxs%CM5(TAY^oR;!f_;ap9`bCt6 zOAdFz9OE8yhhnF3`>l7~Lox?cEThpsS4K*&q3mkh zq+X>LVKXF{vYB6BLA&Bw0tMZTajdgqudhy)7_s#9*8qt z%qRB_))y6#yM7ANN{Jn*6h27QDu#WmY-#Q)4zJnOG?Fp|8#f*deUF@`_gzjzLev(U z?G&WMx$An)((wwDOa7dZO4U%&jKL&+cd0|)D)M8{;;v6HRAo@RrRZo)W{Y1+5iG@Y zF7yPVwc*Kf4C1GD$mSWvLvpVxG5g+jev>ip=wzrWqHuidC_lWUXE=|{D`|a$!e`3cAiCtV)X-N0F(ZD8(hz zaAhXP1LIRe$C|MxU|i5XJR51)JeL@O`8#4R=_LNS`+0IEDF|5A?bOgw40L)~cIPh` z9rUT;J^X2a?&7%_@FmRzr&odcFcCfJlq2`8nv1*UiJ)= zO_HXIaVQFW{cS=7&DDJg!AuXfE@i|sXAFozt%NwHw-Y$ zi|T6{W4CiBtnbrWxx}7n z#W8Z4Ij6K+_PYK(Xo0L)@E&ZC?xU`&UM=;9>mlbQ#^T*@hWKl$9rCBRJZv%ghiL!u zTJ#^0t4$@fjSuQX6ntsA-uS-Q*yy7?Qx;^@^FD&E=wFd*D&@Ll=S!Hyxfkl1Vxi3h<(u$*J0oA)*WNI*D6J<{@uAedbE-DBd|Uex;uSEn zg$yxQ=bCpEu7~3c+a@bOsbP!H3d4#kf84X~!N5~HCCfi_2sN{Cr z!n`k2Q--63<_V+xXh|Hf9j+>SHu#J1cSUbsea+!2X4kg@86>NHbCL!2*)kqtL3}mk zE}xBDqT6mOC-0Z04IV=_6-BgdMVU%t^?##xf|f~(Fn24%7~gQKYQU&dcmoVkmPVY9 z6sEl)X)q_&Cy@WaUt2DqM3DTfezVq64M3jpgR-TqPmzm3C-kdOMU^FzDfDQKC!-SU z1^t0az->kBD7`_*M)T6t#HTpSdK>a)qVMudayNN})f?t3TJ(TDECjrwbsoH}ilRd! zzSkU(*q{Wk4fMkpcf=9oPV8xPU}+^@iXBPwC$1;NhwUJZkOr2!lYUV0to~zK=stZm zH3YcWvL0#;biW*cZ9`oX$q|*99vT|;e^XlqVh$^xth=T+!N+yyStO^rLmHCYeR*K_slm&!6_h=yvALi)Qd4SZfIt8J&F%vByz;XhRjlt|pVs@h2UWwClBzF|G}Ga{=ddS@%Mv}1n+@FH zDD*;I#InoimG#GME>dTTKQ|{8)ehg&CYHP&+#qQvhxE}|c@+n{^|&onaUH-fTywv* zF^>bQ1=>6>A|5sogPl>m#=ndXq~g>dVI3M@Sm{}dCt$0u5J!lO&{jwo=_sPOfJw1N`y^hc`r@2| zZqZf}b}v0o%OG_F3Oi_~wzrry6{AKac2(63#ZlaX8Zh@bJ`Z}7IG50dh_4wUK0|FQ zuqDT0BNEvZF8)f8fhs4hTRK4IIV2q>)%T!=5Fptn^c|EfCmEZBzKOqp zJAge=Z6HMAhw~Q`4-i`uPLaLHT|uiUDOA!@5~YZyx3XdlG4Yms*lzqZT>!$L5GW-g z+lUFQXXq$WEv^moh8nQ8yF9nN@O{cjYXkDSl*RPtPSgIZa(e`n;Fj{)N+CYYlufUHS1q|C)!d1GHynOF9B1rwQCXY> zcUd%h-7dmP;i08bgt#-T?cJ)N zEPut0<;yT@B(5ZnO zITC!Z_ib%{)q!pdnOpOq;~xAM%&9G=Y!_maMUh#KLYvOVLNWP`t^v{5Cd2t9ZP=q~ zko9AlT#}^o1~Eo|%5H*D!&TgWs$c`HD*8$R8~`V#Cna z%>w};SjWcNC5Ny9n*9LXvn4x#d#>)nXDUARbIB*63$3{P3GEc^5%?-t z34NONl5q`b8@r#$$1Gdx%W}cJa)Gjn2v305#3TeRcG52WUb9AyA` zb7epOZ;C_pN2;E38M+18OdWxrD1SqfqBdnY(lMB`u^x;he2)Ji#xJ7UrGS}6`T{6u z=rThg{y*p=g^choyp*p-y%0$9L+qlmN0jII*_nPo4%M6(0qrAsx<8A4 zjPjRD0%IN(xQF4~W5RUXvBlWE^4T~Ueg^k0ek&n{^p$XzD2JDjTu4t!Ka*1^sat9& z9O_i`TWTA9iGLuC%P4e-rk(my_NHx;3NR$vTaFaFgRUkX!OvoBf~68L zOxx0F#6!$en_I{(tj1_CrI=mgcbl5Xad8QyDml>rn?>BbdNiU_A7Oq*39(EVxDiR(A)MhCMeY^6a9Kog5cC0T*6{vQu7l-k zuL^D9?=`1@E@Y*868j%|O?`9qW6Twmq{s+e`p% zwD<|M`Z_JUxU^7!V$(s>>G#=Ma3{8eeX2UVdMRfv6YGEND981&UQk zV6rjf{4th2j+%0a)j$wM>|?Jcp?nkBy_A{GCJupe9#C*%!lk>Y)|lp6Yib|%JEfnN zf!~B8(tZ(|zzX_Kl6~Go29nH8SDAraJ2x?M#(Z2|p$-Xhv##))JCeLHh9{3=7iQv2Ltda_fUuQDIA zkpP=eZiMhFA%?%7RZH~X|3j=K&E>y>v&l~UC*^M_i}|l|TB-B-ACp384*WmspV0mj z*!ZN;1%m0$TNySe8#}s;kWoCIjVo ziB^Bnzzy};MSP^u@CSmO*%*p0oU-Z z)SK91%*^s5oz{0HtRnxX{Sy{Jd7!~~{iK$wS2%mnCKZ4K!VVWz)>dOQ9fxUau-n?+ zW7BaDTWxDj6Q*0{m24%3nwMmrCPAB)#Q#lJH>?Q#NMRZ>ybe+x>E<}w(YDm}18hDP zJ8;iqJo@HPhp;(4(=c~%%Fa7AIDCKmcnO?vrj?SZA^xzei=QHUn7)KwCI4!C;nhdU zHk3MhP$!g600ppE#>$g@D@rD%qxSzSH9ph$cI zvNK&HE`?)bx<$WHL4g&blbHSPFwrC2g;}#jR|x-eil5WXjCT?=Xoxsl{ISv&!4lV3 zXMnB6v5>m_XQB_V_vvdzEl5mEwkQ|nk4tbb5@ivV&TrQ@{n8eehCZE<7pK_ckJHiF2M)<#dUiH{}Kn>HwfpFJ!d5f z11XyTH|jZalIAB^fO?G+@c+RuVQcx@u#d{W@dxmEd7k_(!p+o9zLErww&SzNzt@%V z>#2D6<@^IQ{Va&Uo=yc6*04#Gn>=R{4x3y1mW+Tdt-VbFl2L2lQloPdc{6D7Dff7@ z={Xx~c+L!Hz$acD6X9;fqqD+iNqM(ep8&e?TyHXxgXOlPO&k&TV@)}yr8b~6o%6i5 zGJ6|0u(mt7mRnZ)WU{Y602De zQq{!zC^r^=W(Ub#vf9~vS#wf7`;5#j3d(Vp_O4ZMvZV3LS8|Y2yIE>(tjG+oX%P6} z)yy>wIf#5F*_cp?W*#>zEIQ3{*FDWNvDn&uiNmZr8g1kOcBq={|A>vRXSqq)1m(9` zIUK6=9Khx`;UMlKqp$f6{2=3-Sq*-~EHtes%43ca&>PIou`e)E1AqP|^5u-jz+68LdLXz7v4 z8@fB7l#&2#19)%tDGj}PRmwRv7_uWKvOWwB3aM9lqUQJj=N|^|lA^4`x!dQ-6~q`o zp^RaISLy9RJ(XnbN$`i_TN+JuSTy5C3(VtZ;WN=8Kl8PSjDXu$-z3!4( z_Y9wF&y>$30o4nV8I0esOHDAza6l)a$M4mu`VN>B3UOGg=Kmul%A<~oO!K-~jy<1O67g;%#J z#H^iV-{gDQq4~RIzu7&T&&tX<%Mz>i;=Nu| zBDVO9%K}k~xWPd!cq1$U$U4G*2!1YItPLy?OX@VDT(S6%`e^zMF-JWkzF+jUz9{m7 zh^HD`_giG6TIm%fR4Vr`>=H&OvK^!X7YT4~0wJZoV6a5lkX$lDVr{I>T`cAs;OT79 zPdzmrBx35+k$TZ|?bN!CvLvMoxT?@KXaZk|LBES?i7pT@2g2>;yvH;6f7rt?H|Tj_)X20w|P; zrWfXPCKQil`?tH5g{Ki(oj`lyK3gIx<0C(s5!J7QFijHZua`~ZJUbGEowFNkX}b0Pkb z=B8^fLXdZ(BjSRu#YjN=x!yNWu~Ci!eGBe^)p_MbGEiA0Tflr=;MwxI#yiWw5&(Ig zT4tugy5nq`ZXl$QUX8IRao|a#5~KGm)PKiWTujiZav|_-&NMilT z=QMvn9L&r!XQGl)hMOkPJ2$}^eX&UqZN_fgi@-AdZ$h*0OdXY!?7Bt$U&4Q?C2$()^%# zy0&TT={U#l^-mcM){}A&+X9fK;hxH+H%>8TY}PhxWb8`zHk@IW#yabNW53|Rk6gB5}UzN#ICW*v9OUzZS2uewyXz`qM6{L!q4rmOd=yVeKQGt{vG)vEaVkKXOd`SllE7byNw z`Ob-weU)d}G>T)yCO|q_RQl6(FwBg8%I>Ia>QI?&Nx z84Ww~UQ_suE8q!xDH z1hvMU>YM`q71i5*sM;pPs`WagV$E~QIoQ<|yP9p0u`ao0S2TD=OXDW&XF$P$0?;il zVgAfq+w%(aG1af@Eci!USI54ppHb|#`w*+(+pTu6fHfB_(-6!RL*@k39+x5$2-7uV zcLNu13-sZ#DF=a7{goBAh1~U{#x6zJ@g1@wZbth|SY?!LYZxLtm~VlhRv#2oV zgO`}kx~0gX^JU0}KgWix1oqF5(a*ysSe?dw zWR)qesNDr{v4+P_r!=4EYZC991Ng%+yP7)qhawysXY;=Wem0T>8LP|nmj!(u=X6QJ zMJ@)-TjAu)Pb#_4-Ktf#S^#_(Q5wG|ZI3xXaX2xwsZGI+@oZeE2n<&k)e5^nfx$uk zakagUBmeE8*G^ZgcfqMgM_;VEv+A+-uIh#dMMG8TUEu0jb<$Zg>KL+lRyQU4L_YzAB*BL?=cdDY zw}hU?XkA0}sPU+7R(OD6y>>D%Q8%a+uezrV(pGxZsxN9XT{!hyRGeA9z+9-m!9 zV&G0pa7$R6a(g7b1Rr-|@O@e1hVcGT(7w?4UPI+<|7+dSYOdFpPAKG;>!)@L9OLw} zZ3wx;VZC`NRs^J}|DXWx4+rx&iJ3~+75B> z`q7jLLR02g~$DakJfysN)& z`qk$M(M0KbY+>#pySiN9_x;fwA;`U6JKNTxdt9Hj;IK^RSPLF+>oC^vkoXPYVkbQe zSk*V!iOBgRk( z!t=H@^5_^+3t#RS!D_xIyA^C@4w9Ynb8iyM{_(uma8q!jJH;+^f8f^eX5{{gk}%rji9S0vU1GNy78oPr{~EAFXdxbnPEg3 z>ioCfUVeMJuZkp{20Vd3A||k^KL`#ba9aAcl`)PME6t;bWoC$m6tu5tN|Up#~IJAc-#uj`(ktL&EnZ`4w_I1W$%IPLfYvd#)?TvBm3cYj@TWkcbr zT6nd#kh(jZOCRN> zG;AurSV&e}thiDl=kBPoEk8@RS(97oTN4N!ui@mUAXdQs21>tMQF(y`%>Qw8m2pvR zUmHYF6eI;f1(j};Zlt>ghUxC^?jB}e`%-<2r&HH6Vv~dEYgvEK*v?qVVFL(@1ms1C=CkOt-D_K`aXw}o^l!pK zxgB9Ab+W#p8z`T#UAQnhwCOeBH}gmlg?yYdCrL+rz*{f_PM`n3TjHV%qAepyjb)S` z$5qfLTE6i%_z?Z3Yyor&Q_Z}H_=BB~v7@hYJ~jE^-tqwc5}yfhiN7h{!W%Q9sE0*3 zCnwVt3Fo^ILx)Zrsbg!`h>n47@(321; zd7Nk`L8aSg_)*Bxvj9T49Nxd7a)+kCzP09uCPnAd0MP`Cu7Iv<{Al-EshU946!;{~ zw1z3DMH-jFcJ+THe_f}f3a z+rQM@mbA8~i1pAZ*3)$#5SJ}L0yn1ItWW5}4VpNCkBQHWB~DKKP1>05z7oSCspVEh z;>Z$}y!!3XM!u!~*uXaOGtlO~EpSKclAh7JAh@nem468x(n(7oVpZ)c1HTh<+vrX$ z{02R(>q+s46dj^=MdE$=B$ zU|=VA70!qtJhBU%y7@B=d0m|1p0z*CH_Ibdla&KimsjlK?W$Y5%$*$DG&EKLp8)P0 z<Ik*epSUHqn zgC1k;Au*65Y$I@YAhc;4jZ3&z6wk0y)+g~;!Hn56T3I_-XuzsD{IJn^$bL{>*BH7A zj58^*0Z_hdGky|cKWiQ_05cPFk(`U?H5F1hr0WaM(O1)Il0ukuOyZ2Wtm*6)03nKh zXCxi2ZiPEeAPKN2<4!ad@sIR7R*!zbwBQfm#xdojsl>O97bqmE(?S+)A7ggnB?h1M zDDVZ7!Z86>eO_>RsJ3+-n%n6LeTqG92tnlGe@OaK%SnxlCTt|-06GtUfF9NGjs#~8 z7aphF=eQ+S(6;bm0$0+1@#_EtC-Kw)Nb__`V0$UJmwHXt2Ti0)#JTXB%xHQnYJ~L$ zm5mj1wl)M2nt0N}S>%a=+=Ppi=fY!wN?M=;!7s!cal1&K|a;9`J5Kpa;0_uSf22>UOSTm~5w-(m# zR3la6O%K!*{yVU@nn|gE!qs~CNyJ`tM_njps(LtoCEj1XBK{N6OFbIMCht%KKA`{) z^a8b3YC0yG18c6ePgVSG$gq3zl0d6$zNDS4ac!Y69_*ksq}CTzWC_lDjAfbqo#YB3`SnnPv4%*m#w59;@ z)^R!yiYe^46A!~d+fxEH#2;-y9tt0)r*_OOyRmG_f+%z;(n$b`PhA{3t_e`_3}Pni>PX^2*A8 zWbE47qYL`o;(M^@p0>cB#GrPgb1Ltg zVeQ~moVgy<(TTs%bXLEHmgu&kI)ZMFh~w+JuM0w(12$+5zZ7QGH+1^6OOWz z8SVkU*soX>fK9&VTL*q(n4ksiXRxop4Z8PuGZZEUTuQ`hdJ@?WYr6d##sm6OmxjHK*dscDKY-p%8zLUT z9Yd}o-zA={H`AOb4+>GW?2Nz5MBk$@$vEgTAf01}4wdBQfL18nh#wOEm61<+Cz z=)H{@Crza?@tG7EVij?SHeR=d{ELatzey`#--zErzsYk6;4(q{B*3cM#8o{Ft?uL@ zs~(D`64W!{XXrr!UsN39G^Gr)o7D$jg@(wsR zFR+Ky5xEx#Ms;bNnzT?234l-o)c$}~zgF$EkJN0j|DjjbAGf`h?rM5%o6e2|`?n?F z$Dt@|L+eMxP79^#Z}c~_I@cff&@>b`ld#%&DxjI-WjN|I3&?iQYCBYA8)j+u)?OKW zEt%FBIZ)5s*-Y-!;nuZY=-C5)2`}z?QWbz&C$j#PnCky|P zzP>H8^2L%b+K}3$F)uNv!LVpDGZ}Ph_#Q61b-~~Ua2ITIzp(NqD!4Zzhk=dno*4Tc zKgE$6u!!`*rg1vTUt$Qb9;+0s>elS4*}4KIo?QQXnG54Ss9?;F{Q+LR$Q?|Ag%7JM zk0LtD;x`sVM7|l7xoj_@dIm&aTukfSu)l5M7myHU%I@P3LP^j@LISX7O zAE0oc&71-16NHxVgw8{sg~*wc@iVHPvDT3UIZVz1>Wvr+w~}$&uZH)TH3}d=1WP;2 zgi`Q(qdUtw?OK1tZf)m$dg$KPWxoFPL#!CKHXHPXB=mBsMZ{fIY|^ z?2Q%osK>eXtb6p+e0p>n(^u&3=fyrI+zc>FC_QX7K<~2qG>!0|oYf*bYCd-zV+Lk` zcM0pjb)H}0?b+^ziWB~RGp_Oe?oYQ zGXf3lvC^Mbgltzfpb?k~|1TW2R-K~b#16COnw4*$i`V4wOFh6TCGfneMW( z>-&`JSofO#<6mX30oPJR96NLeiomrZvztbE-I(L08~LjVg!CnXgXH%S(}gCwpI4oD zE7JvtXKfSkO<{~?P_W_`GX5dmJ>{-W0}d$6YKf}8=T_=t_9ou0@DR>gLA6)9V6NZ;z^uj6 zQyMj}s;7!C;Y4f%%S3>3M1*CeM($SFbV>{FcikOYqoBIT$jA|nreau5qVcdtY-fp! zS1s?E1O_ncn*6k?2Jx57CftttEO%z~VS%nUxF`4q`5%xjVxJqR>G}He5(?r^Op^VOjJrgPWIx1JWG^5Rx<-WL2#vd&Rs&SXmF4%0`^;o`RW< zu5%m!&&A&9&)*f2{5i>kfx+5MP;F5=!^(*id6O84b3 z56US=t=B!)M(Z5_VbcG4*9$(?j|=7sn?WhGyTY?zU{?^8LHC2NimoFQt51lznDqty zk|09nd_Tz}@=)kMQXP#ywNQSX0jP_rdx94VcYYlxicjNT1}D%e1T1J6W>h#8kqoXB z?nfi5t3@(ge?fw{hXp|Gh$g23p<+9@Q(s|oC^OBVO zf4M(H^939DvZ*{tivZ9_6=$WFL_5g87}r<>R3*!s_?kX}orhS=P;=UwHnS#i-&Q2D zHT>q>4$cd~-XuR>iO4@RkoQX*HFdq%PYh(b$=}Kk2yBEgNe`o+6fT`b@F$;=4#Hii zdfBYTN_x3$W7#jpZ+T(PFbn7jn8@J-DwrXcImZ?Erk)oLDgi%AK1G$yV__A>P}%`} zi{Ug*O3X3Pq3z@-LrBA9YOwxx=`*^EzAt+p^_?jYCQ;iFSsz3;wa+Y_MeVlF&Yn#-*rF1!%&<0SNGkJ! z)i(7Z|C&(*aPuu<^p&dE(HJ@*G0}w2_eH=ejF3`V5i^&_7V4%4A!JDl|2xS9>bwi|z zz7-`0DcIi7tcNsI_qv24`j0MANG4-($CyVj@0Fz-K$xS?XUH@&8W4mZ>I0x5#2vK* ze5g4}&4s?Mx}+u`a|#|U)m`~sS%Re$dMY)9nlumP8FFgWeN`^? zQQ!g9b^3ibcTG5}3qVK`t)L}KyO2(}J<@;C$*>(V6c*AHEBgb#u5yArj1*XqFYlmq zq@op0^hr^4g`JrjxJ)^RbkIT17JDJfb7iE#`$fzLMdCq@<4tX=L)D5bnjbNNl; z8qQYXGSpr!NhE5W!D|vn)_vg@i0_xp7Su{syd_rKE7%CUL zU6MyD#sF^KR|VjoFt=#o$Vk>EZB@&4_CalQ?Iq3~ZB%I>FHoDD>&g$)=FHzIsMOX) z+!uOlRe`gF8?-vNk1|h<4nPQ1zr`J<8||y$Ul_-1FPir-f7oJc&a%_mbR{4T&-yT@ zg1gsRH$MxopR*BE{sjvw;66}qr+kvDv_by|!0u5k8|OnC@45)*GyEJ+n@gE>ogZr0 zELG>!5Ki=*aaEJfXx@*!@$r~e}cq^K8K3F|X&|d+c zN;maYf<7~L^rqEhG4J)vE`HB;?{>|Zz=3y7neWH#cO*w3c&g5nfRB8TZSSNUpvF!H z!0zX|KKKROy~gsEIGa~4@pW3u`I+aj`K60#@`ItW!_3U z<<(%mMfu^(H+HgsJXS-xcmcerO%6KOeB8PZJX*WK>J259{cf_QS#U|h`winDioqE%`%Q%y@4^TK?i3?%ptU+LHbBhHJO{lFmpMYO1-C!<8 zvGUlaGuY(RF%ts+J9e|llXNP?YVxBNdEre3bQkB%hJH4X6Qmy$FN3}?MIb;RiE%dy zIJz4Vm@6gYMrZt-+@pqVM02W_fkyron{UXX5ke{rbqs-*z@TG3b$+Sm^FV;Yc}wyG zvP|EPBRA3XDFkBmTHRw(RY{C)7bQJ6P}f7tNYUsRjKY}9x)#>LkPw}mGtJAa+s7@Q z5U+5l$#lC5erYF#Mb8|9k)G;7sOjg>L6n5Ho@tvPg!g&n%!gGR&IA4AkAc@4AQ zYBut3crDes3+_yyYiCOq0SGCIT(C^3;`=r%R4VwNDt9Q=g3Co^KrNu%**KM}Xybwa zRlIm(^iCB*vSD_IiX+|NHKe*CLryrR9#$9uZq8L@H!qc)RXXav$nGi6RIHG@D}ODN z$XiuaS-a&&RqN+#6w}n6(fJCZT0FZ#p;f>1>QcsNW=!~{lB!Pw+ytvnf;LLlm=D$d zmOM9W%a2P_%&CQ^qk%lYMrU?5k}p z_RXW(08q8~I*>N8LNd!fx11u8*p?TnBqwbNS>Dp1wj=XBrG0JuXg8T_ThZ)TS*`V) z*ETuH^unc3S)yMBASkunAXo7`$FW+pnB&MSj}l+%94Ty&1a!J&ewJW6MDv{_e|1cY zo-A!{zcf2b+GXc^U6AdwtZ*SKjv7_~2mw03s+OT&`AJ*sm#Fij zr`RXc^Fs@5G-kR#&sM^EKKYDgGZ%OR#w((u0J9#+Ys)`&M3C7%;UA*al<#1Z}?(-}#=dxPP!i~x)cQEUTv z@l2&I-EV#){jcyYP|?DcgE#Gzd#24crOJmAD~%@<8IfO%t%{c+LxwlXCH@k_3KiJB zT_2}T0mlAR1J9tHroLJlVtl5D6eb&s^vdjmh6DQc)GkB0eq+Kr{bT*D$OC$#Atpqt zFEr@wUL0yPB!2Pba&~VLZfo81^Sfey?+M1F8LyT>tz@i_pnX~Kl zVKzc)k#3o-CgBfVgsm}hyY?@eC}c>x!j|oSL7Q!@nKDOX1Tx&|novD(TV8!qyT5qC z;>C@(@-B^@ZeE({IpWhwnr|B#g{8%+2Mdw&<~<&mfbkD@>yO5bd#U<3#67MBjxd@B zkOTV{7x1IZF_JGu4;E!N&&^vmq5&Vucro+@+B%;-XoP==We((`*3G-u?}_CEd-dfJ z0=$g94$^X0R40xG+#zYha`ywgxFoq$w0YzTcu}5gI0fpHv1f1#Ts^;ffPj1&Ti2h6 z;mteUHx2I-~m3Zi7uT~lr-cG-<{_)uo8JWqpH6J z{ZI1CzIa?vtZT0y0Y7i3$DOn<=xcW^<(=1H*K*oluIuc#SuB7Tiv+-v7<**17IySA zu^zdUz6{*U^c}rkgoDY99%s^um}T8gl#zK&UGB88pzDr$hS6(%r-Rkv`oI><1>)Ui zthfNM%q{Zv!pFS{qyss}yT4GX(!;v$QZFUFbv&cvVitD(WKNvt)#=OH8MM0t#;Ni; z)NbH@ahqXf2?5RDlrH_>o^?m@xnP^)I-8vRuJaP7Ep4FVK6hCXzr&q(F#7NIZ2s%H zx9luoY|v)g2~msJ*S3q|Rc>Z;p{xyIuWh#cbdpJD8>352pRAwf4wxuyH9=>M zpRGr{mmB^y@0m2BdtpQXvv=sD0E7;$DOb7tN#oND#?lwfo%1g)@o!CwlPn&9{vAHD zC=;=1_JUC-w9KD7(u$itwS4d)kvm~ecQ8!_M2c&8^MG&qRvMoRUS?^I%&1?o3cP&& z?!_;l!Ew|@N_b!R&}bqmVRqKY1k8E=wqYbbcPeJ!3#oO&&8`+Yuuj@e@PPMV?vQ-W zjaV879?b|F!$Mxo?_RVQo)yOi0D~A3IU}TxZRYRQ#-O7GnU>sjKE2f_YZ{-a${Exd?a~?d-Xpc zf1LHM?;nJ84MwBP0s26 zK#hyd>$^+a8g{Jr8lz&?v7T$p*Z%E2k!`?mvmPuD{&%$+uXxvgY4qr_}Qf6nkW;908k)in&R@dIB)y~>x zAJBREkJ|%udXF{MrMiZRk4^E~OTg^swZN{Yy{YNT{NxBVEJ!Wx3^0I`d^+L{su)~* zuHk%GzP;I4GHa`Csqu{e7aMR1!Q-suPlL(T$7t7=0SHj-8vtQY^DeWyGu!+ibxKE) z`BmcA_9DxKm{xnG1r%0jgII3OI?}evI%9fp+dNB_$1n3$6Wg`UaMREaJpUyf@J+Ru zTJhjB=%wm?9kKBKdZIoa$!Yo_rlQ-y7isUY7ht=Q`w2m)RrMf}2)8KzIpuHCrg$+; zP0OARW1M0DPa+=W?ije)%C6tv4uVoZ{yHpN0Y-^-Aum90(qNc!BmubqcNFux?g6o$ zur%L6zDH?^52D)Wmg&Ka1x#R-6F2iDK#qk!=%Jn4ng{+@+YYrtp9{Yse36r>dr^BZ z3lXcZM7*hvNSH~wnx8^CL5qw#N-;8B{IAlx{U}-m$ztW95NAdjKP2{MFg_&HQUfQ4m$U59D`RCyNyuHf9&5vxhepB z0AZb^%JHjW1@n^`RDGJArSzzu$`SD=bl8fo$Uho>mqQc_WsjJE&iH0D^5H|j zx;<@fxfjNqH75#hEQ%HTmQEN+VXUew8k&dgsvRBhZ27zKOYiH-aBxQV$!rU3mSa=Q zW5j|^=jpEa*KHU8VTs;m-JV;yLa(6}mM)`>wON>_@GuZ6g1HTbfr!eMH(6=S5GaW&87)OBjI4Dn7-V-}4hTtA0CBgZ4gX zsyYmL7~IG|g1!K~K;D6yh(yBf6Us1OYaWsY2qU>~DGw;svBzl@biIEub1D zpZ?wq8cPEpMDqXXngSBTZ<{k)&ZD4;^NajOBVZWSeT2G?T=aHFQljnpV_Y(7QF;!s;x(ZUEHG_Ro2fem9KfoTlD_ux>OLk#|ESK$+9Q3~=-@cB zqno#Ii}1%=$9co8LU;*(ylMifLs*b~5UUoo$IQk1h)4YvQ3PUOtrP5$U2l6*c3$qI zT~#$ht`uLZRm&eRYa0PQ9v9j?s8E4tLP`|pD}KVSE9YeUqP{BG(f?q&luP{1ksc{) z0h>Iod|??YcCu>JE6P?_9*MlFu$CP9*1CAJ3A3$fs_8-V>y|I3sEX;(BSub^5b13= z5Z#a3ZkX!#jQCg&c$NHgjn(X2$Qg)H1(a0vofZsKWcNa8_?npRKhgGv1zmB??#;!W z9pz$3di&d~F!&BTGI|?QZky;|N(?an1T5;FZl{S;ux3%W(p0P-xhC)}7YzNRzOQBt zx}p>7HT_dSCqN^;Q_GuMhq~RfX2KI4{?T`k&K(l}Ho`XR7XV?SUTX?0n7!Op8CNvE z)R}+0?B?QI)SXpVMu*T7>&Ay`L0dqs1K-PGtyB7zXQjY4_O!$VAY>BTug4-+oqza#4*)ubtyCskvV zaDp*sA}yX07ZXND(Xqa7SUhF|fZ)O}>U4oE2Q4v{BPifiviGPaXg~X3Of%vlp%ACQ zc($G+?87%zrI3D+mSu0D&ZWMMen`VHuKONk-e3XVlz6>hLAxiI1RvJhAx6|}DIazc z)6QCkoQV&@&p{6n4}!1bqA0XV5@9<%G&=}TqfbQBDXkn|-(?I97x2VHk3ee%EX>uCrtW=Srxr}XM1hjf{&S5^mJa`GspOEoWp_t?=rM?kw0C=%b_QO1-^1UoZHKW#5woTw@NRubh>OfSv8jS=WlRpQogrzDf z%dR0!icOiN7^2cAN`O76g!%5Jv?{{@UNk7b8(x(~S&)j1$`12g-qTtyb3Ns7!?0;1 z@@sQ}X-1Q}^^H+h`WY@aJjmFJQW?sl=A$3#H~T&y*XaOVj=x`{(dQIv`cBD4%42%X zTzu7&o;0$c&e63E@u&&mNN-#LUeK|p6b^mbo|Az?&a|D5szj}BYxm0|>C9>X0je9; ztti?y8Z7fHgAI#0*Q;g@9wIx{p6h>rFf=afo6v}DG4w=~rbA1*$}=Dcd#5mpj*4!7 z=O+b9SOX_kexx3+D=7+Hs*!FjJv`RUQCBJ!*+_?L<3= z_c8|fH+)#Q0PxO3(BVnDaqg$=tSyv z*mh7Ny#Qq{^I%-V(KB3GrKHLTHG39yx7S|YFdaB6N#F7#%xiJd`qL^5ehVl-pd?NM zqLa@^OQ35}8z{BNxlIRY6EW*b*V2y?3evYT!Q?X$IjkJo-(G8Y(-^?ll&<6NHJ!o` zL4C?4*puKP{(XEs)Ix0~PDTu(Vo49s$D67shjG74t7t1p<>^=kgsO{hVpcF}z52Kk z=1l+rBIp6~>uceW3Llg+atTj}zJ-}WnTs9A4Isk_(}{(R6jBL!yrhFdqb*K5No!yh zg^$uJ*%~h)CzAu**q027cIgEW2p+ib1_Kk*x!wo~c?Rh()E%lf!VOzbpWDF2A7>^P zA0oxF%hJ**3%O0WoiD$>I45cVpTd;Q6XPyiWTB5&n8 zWkwC#lpuIt&o`cB_%wAGQ8;qTal>oKTTw*zb7rsjn@;05>n_U1hck{bDfBQN3t%CG|ty z<>9-S?xqEU-@rpH%Llw_+@Q1iQVO{UZVx=A7M4EM#X?<9C%pc1XX-Yt;mjcO+C zObuDoL6_FQWjZmiAQaw(i3h)fU|4F{irPu+)hK+SgL4a)vcQSwLVP;sId3^N)Wb!z zln(6OiV6HN)d9-7`rE>%)KSn32Ay^QT!7=!UqY>|x0#8E4>b;!216~3Vc)>NnZK1g zi`+bC9rp|^cj_k*P)7&Q%#R3Ws3wqXAfYgwd=N~dpQ8psYq57|R768-HT^o8U*pHD z#Vsp1%sN5*Yd)S6MfqH`6eGZl!b?H4_gokrcm)$>v? z%L#C*7tT&9Ml%RnN>NKB$wY%yWmAj{Zhjh#&+1IBVnlNWL&q6+c*Rq{@n`dk0A~Fw z^^!)wL9{{686=GUoqP+eVb-Az+@D4=Q-(yZ}`JwM1NFm$w$?zt)P&eM#gIcKD zUY?D0)d_NLw-e%q@&u`9%eS=e`h`MfX10GYdGvkqpt)l?JUNpfb|^- zunZ`nJ+xsJqShWzraM%>*n#`% zNDJLVFtq>YS;*XKnFb)V>1PTbatmvoFv;AL4W+<6O97|{?#%aS^=S0w?}M!=qY0#_ z#_YAig}BM_eWF;RZRQ@)b_!#1i_Afv0Vv+Ff*7F}o6z9L*vXj=T7W;y83z}_*xdC{ zaf6AcKwK_;$;V^TvI_*&_*L;`!dlYfnZv@rsQSsRvS9i@Ks@V>z$91z2)Io8R~8mr zho8qrKxacQaS9RB>tAv+(21o~UIDHl>ny*LsEYd`KvD!Vg@O;X>%dX0oAC=kSR%~i ziKw?>P+AM^8)7%MkCBabgUFa{tgY@KYX>2&1jqi5bS3jOH;1Z;L-LsPk2BNx&djHi z2@)b3(8Lr^#M8O+NOQ5PsKMk8d@n{y4JHC@WoUzBQSBK<7`3mso4Jy{BeRe_gLxzN z6Nk&bF>?~vn+u!VE{^4W2M~g!k?iHTtyDDm1_4bwiDr?a81uj)@^j|OnupZw?7X70 zbOz@_#uDaK9xir}rRI+ZzhN&FPMLg0_(=q4r1CmB&}jr^5W11(U{J!{$O|}_sHMe? zKoEOYuOf-XHx?eF$Rvx>uhZaCa_n6CD_KtP9mY4=QTJ&AiF`Z2O(c-Xdjobu3nEaF zuG%;R23@6j2Rec6)ZDEaz~9weDtttmr8$)zK}pdZkJ(3kta%oEoL;SY=w8XEX<`6o z?NaZe@gbepVfQ+`Wnogn(Z6lRuu+OX)3v&of*1)tiq#g@BhE2hmUk7iZ zC7Ns8xxCGK;G`za*K(;}TF1JN;5I|wb}fM&LNq&YO>EST&XP(qHn3w(p*z02Ju1ya zyk}33A&~#D>4Ohb*R}b&J9v#oHo(o_x@hWh@S_0-4g<~ZPlGAoYkU7}%12G;Ayu+5 zgzhN?zi^KoH_|$Y>7Bb{BxIkCTfr}=zV=%8v)pT@Edat&-Bwb(BB<7mx~_0-I0L?| z2myVm6Dh)5%S$DSJlMXR^9l|sYrczOH}*i-CuK5mlHXqC3o_efNPUPt0H`Sb!ffKO z4AdY-)yt?LAs8!L2`1G3mR*9%N+-*wBDUqwf#k4kMN(w4(dW4kemkBxBMlIg1XcirCj*qk{;4<`E_Yd@I?1d95DdZ}_@jbx=o^Gps6b>l1Nq`8d5u_f5c8>cxfq4FW@kz! zlOmJ0N_J8Xg#}32^l5$?nVd1^a#p#T1FTW%^Wq^~H+KU@3uo|1I63G~ema3#Jt&w= z;ul>PJf-wy*@Z`Fdy-;BgN$4AE{I923w}JwMvnVLXN80h>=$YmsRSFt5|evi4Qwnm zr0F(?NE@j-#N{&P7kTo`%sZL?@H^Ss#MJ^bH+){Zkjy*aCjcswY?xRruNDD5j4D@N zg_hEUymZK6dM7`rk-)ehm{O@=`3v3@erHpKPcof3+eCL0zHE zR!kg}UY7&$AZ3oS3wfO!to)-DM!_ksHk45RRNk-9(X&-k3gwv+M&Gl6)+w^;zT*^Rn_`YP31AfKNMO z`ZN6qgKA$8(pnDy##38m`DpcbzF-sXqd+`+~LyehT-d zJEcWU;CI369Hc1@W_cL7v(r)#OAYE+n?6Xp)c!HSnSpP|&i#XFvaj;n!ntB)xkANJ z20$B>GjyR~7xSaKs`^fIaU-h2Xgb|YSvcF2+}fKp$G8smI9X||L#0P z2o{eh<8*Q|pm2IH0e0){q7^NAQ)R=s`Y_{R(5{LEBOKgUc+T()+Mapaphc`mE;baP zk3|{`QMg;dW&@Ep>XBjqQzijp2bch}^b18Bo4*-0fHZZR44L4<^7Hy=8XmH;Z{Yq59pL@fwfTYFU-0^d+R zMLQY!tKfjXo()R+9M2Kr%>%{ZWVwKE8#U= zP+`zTHMdkbSYlbVDh2F=<}_ZB*AuT+|Byq;+R|>hiaIwRFF#E?pT0^li(yHWD;TWGh!n*E_K)C6 z%Gun-9_^}XzL(Q3^-?jgK4|*nXBtG}X7*6kF>ycVUjAW`xjrd^Uw5ll!p zFYOj4gm0JmiRy#S%P?ZL$0o&W$&j_+ZJ z;c9tRTA>Iczm(7*_EQkT$>Obw4?!Cw{>omDM>2t`+?k+sQym7FbwmxTi{nZR6DyAK z0`>nD{mDD8znAlaZ_uAfJs>F5pNao0^wZx9j}qS2{|Fio)f?t}e3sVgCpfQECVH1kS_9y`tZc?S&tuiKQiBb5KeUxwLIK>zQSOfH?>eYD|g!J{r4 z=t{PmLk(V$qU$t3rSYteQ3Nx5fBSPZdFI)6J+9dMX*-ed#4X(Vn{pXIn9IHjAmoc* zmfYxy0x1fH9Nyr>tRtOsAh%K)J2K(Q_@Z_OsxW-ieirLB^AEcKf5>~>4k5+3Wm^-e z_kie04+nS;hKG{GlCX|s$XLOI_GGw6R;3+=WG=X4TZO(H_m^!lt}48_%}ls3v#YIy zoZ|g=TLLxJjbIit07e+;{OJI~bSa{Ep=}nbGC!*gi9u(sw64c-7Svhi5R`Fr%PP{M zuwRxU%9)v1izn@t_qh21W1CyQaWDG=Fg8Q@0N_`i>`4*IA|OQODb3r7uQT1ve&h}F zPntGRF|j{Q&Gg8yttL0d*O}SIyR6OLUyXenq}wIKRDRq4xG8A|n8i^L7B(92Ot;)c z`nN1h21MV>-aJ26592(K)#)R7@nJaq1ipUeY~4G-ai37#3X$ieH0=Y)4}eo9`Cq_q zl`6K7uMHLnb6#jJiVM?^YZi%rB%ja-CEH_jG)So=EK$=eLk0iTG|CfwGBu@&*^~5| z66I-tQ*;&ZHZ(%DqTrZnuXt?RsBssybu_=~DY zH`NEG66q2qZBtLs9t1emrUAS`^&`#Vf+FP(gID&yN|vEG?Ypwvz)TKTh8y~09w}!S z9)`Ipqm1b@y_99fr9Nh5kKxv&w<^3|2#lSh1$;U6QmtqHCB-%KgX}d5yZLw8bp_Vq zmpomOYDtK>qVTZb!U7dZmd!J>6)lz(J~tKX&0dpX%JqhK0H?-u4*`TmEj91WFsyD< z=J?>b#>FXyfx70=gp~f-t%gW$?|-ntke|JCkxK#!dJbXq9s}K>_-ihL_L-F9KvXV` zGX&U`MADhJa`0xu%go&a7|`|TI3U}alimE>Aj=4_oDa{U4qPmfMz?3EWBhJNmR;&wu zXv9c}A#uh~DJG!K=p@VWl<6PK_q!(R+LWUJBTlP&fZtTLGK*l?sl1WqZ%9%#CYR{X zs?Nu*)??I#;k)${)%QbubmugzfCk-8t+(eNI#(^&wO!kw%>fwkUIWDaH7hi_tN{HA zomc83eW;F*9ID%|I~v=rtJ4RCuhPEM4}}D34;ZEgaJ1_T0?!xPSp6Z_^O~(XV3pID z{{JQkM)NWAl5Usr&(xi|1k=Q1Pwi2l&$UroVOkcxQghiH5t5yza}y+s^InBHA5*S#n-9 zfFO_@Ngo+fLS`?RHW&p%#sBJmhZv0t?E8kgKW9~6C^ji*s8@xz`?U536OXu;JNhYi zo&T`yWdbo)ONpQou*@Op&9q1TL&&`Ols+1|DxTGgz_vuL>`~&Y<}`QjA=U+vyTi#C zpSrHS)OvSxM+T$X`Hl4}2UsV~uA&QoW$uzUr#19k$E{Dk+kJu{iF?*{gH#lm)#XWk zKF7lWqmBj@c3!08eX2Xr%qi|_+c);>39*(M0ibOfuSoL%tG=Z;nW}a8(Y_>CcFtpv z;<`E-m{%fxwU4r=hrY6Z<4}U??K3`sRO=(cE1+z%(ecSqbVoNyA7^M17=SHG4{5}V7MI7 zX}o3xhaNVjnkEKy8J?KLzWIg$A{Zhad#dPEH-{WNQ-vhHQdQZe5AKgSu9E5l6wT&H6-mMlwWw zBl%K~`gGHW=p+DP1$S9zLgSwe{}?MkubXGdj)Bu!7qWXGH((n40eCO+LF-IZBNkE# z#d;BLW{u)kQR<_X5N^|ReDdiY48Sr)wY>H1b8DVKJ@lM<5qy(0z9|8f&q{0w!F<43 zAoKBuz&`LgQh$XMg{AT{KVauE5~DnE%`C0=JL*9;1yEV7!Wp*aia_k3_Dc06{0{N4 zx=`X-#>Yk~`8HPBe3j(}rhBw>$8il}H3}~b~#EY%A z;w7{Vn$oftj06#*s+GBhe!BKO+Y_U2T+6XFSGVxFKg+&AgZcc7(})6~;!QD9C;Zp@ zE;(4_3q&9Ok^V3j=7&k3s=$ReB<}?1(ydZG^-JXr>1))R+DkG9$gS~K} zBNbXJKNvY3_FVqU$Di0MUk4y~Dw7R2vNoHRD_rxgnzDI|i-L?7D6}$$F%Q{ab<1#| z@l$=JA+EFtWYv$PA8ehW_l)=ojn)Z#S_xfRV1?n?)z9^;^!2^PvJE-zJziXBfwk)j zxwORBfkO0FZ0~s2m|NT2t}eONc-%fMeR<1J+q%ew5Lc_n=Q93^NeLj->E`I&(sm4M zWOK5;hnzY8<}VpQkzN#M_1O?5<>Nj38$D}Ax?Yr=Yn<%}OyANny#o|!XdSgb^G(9D zEh+#Z3CIztb)t{=C_ zysAqc`KC3sbE)q-+(#=A-{gMROWPIDv5F^pB|NNVlQa==qJhlvL9J^BG7vDkAf%R? zxbp}@MJeG8W@F|S@{*-l$t+vn;|C}@+CCbYS2a| z8F8ew8haQ$3GUp|iUp(2mEXXx!i{A@iFZlj$PJ`FsRz7|G8lB=J&4ZnrrUJQqJ|UN zTVM+4hj=N()H;i?6Ltqi!A2n)k>{JQqpxC%%ZWG;;drJqVHqVi5>D)-Cwp7yH~$yM z5sCSJ){gqckf)lE#;BO3oej2@nlNUk6403;aXHhf$SHnv1K>XDBrfdmqf#OBw2KpX;W6c9*I^}Ca6l*#1 zC&7~d9!C1r`6h(N!yNUclGzUQNl4ab+JcbOACUgt@Bp+_Ml4y-+9G?Cwikw%vm%_}_vNR&4J5jJ46y2CB}i+@ zU20q>`A}#uHn7i>SPUPCrz^$`LU?@5X@gt+&4$lF36i8+7XBVKM;GCJ zm59>T02Z}I{Ym4WrRnhycjm3{8fQrtesCNjtSrmy#KJPF`Z_%7qv}4}cNKeq=GyRS zY2f8;Arbli$KF?dMfpW--&hDLs3<6)lr%`Af&oZMcXxLVF>%Lq4-DPi9TL)I0Tu>` z-I$2o@BMqeJ!`%H!TZjKy%uXR7sFipI%nT|pR>2uZ-&4wudrOI0*Vksl!A(mQi_c- zwjb{(oJkNe-CUp-W^Lh>hxLAAbDlrt@XJ9jN96QomlR&0ft80CXGD?Z#bAf-8VXaV zOQQ%Uh;Jd+BR49N)BBA-myO3SnAny)3Oit4SM<>PhRxoBC5K=3oV>fI<(=>FmkqMq zi?bgpzVVXfZQrdO+Q!I3z4{+1hQaW2vJr&d`WM;V#!LsQI$>j82F)T{H8N9pC>SL|ldd)af ztAKS+(}0&j-+aEg%!i5v3|SP0YlhGblOp9JTopG&&P5l9V$ojf;V6Q0N*t}()4{13 zH1po$x|C+?yT#*kbe+GLk6nOIptXOGi*s;cu%d-_=)3>s$C<`tbUK98Vw_0D{lzSdO{a;3fd&dRk`CIvI3JrD+4`_=pGLH*B9W7-* z3WZ`H9Wn}2OE3~uiT#imj3OALHyea>A0O*!^ zRQgngt5tReoN(;SlT3p!R|lue0q<$&S6MC&Del~?=VoHw0$76HC*O;>-3ly!LmWXA z7rBT-SGm*yLMXWP)Cjo^(|$Uf91=HTb(p*-hLL~0FHTax*~s5 zaI95FZm_45?cJPNy9y_fY+aMLZacVjdMwXi&Y?rn-j7);J3GR2X!$5YFYyYl$Y{P? zCN=itY-w~%wb`TM>fjTWp9%*(kJ!rRU$v`r49xvxy45wBFQqr`v6O9l2=AT5P1~gy zzLT*TMMx&uAy&bBo48EPkTU!I36#)VPMu*V!%f|=-d+(`y~6EBqk{dyOt(jW4ocFE zjP(qQkUJAw6S=ZIJo#3PIGS}gNpXg$fkyV*GDtyKr^NWR;FqrBp@X4_r?J=hAO?vJrs4bKU>YvK`!4YSsni#`xpBzrzOD<)>URnoaQ4HTg*^$IBA zrR^1n3GmtE^D%CxAK;G(*%hD_bjUL`C_Ut(-Tsh~@TpTRVQW$4I;SFLVm)L-B2D5; zw(BI46F#HL`iS|1r@3AX@=JZ@{xT#c=A4&CSbETkPj^JFN07f=)MZ=sz^5_)PCg6q zia)O16Xuf`EGrehKl#x1QwhgY&^%|xp-d-imm@u@CB@hIXUwtaRJ3|N9@ys@9&h39 z>w`|)IAiOtl9X{$Gw^Q8E^U*Ljj7c#tD*8~54L;63u0nYc4n7@H&yd$sgk zk>yT@(-#6HT!S)_T)%l#Wvbd7_P(8Y`-HdO?JSnoc;J4lfy|4b3GC4J?pO$iW~DIc z1mASfQis@@z_$q}W<`kF4G~TJ2AuSeKQ42w!^nd52~Q&==XjeB9`V=8@Oz4A%d7|P zgjaWLjfsPuP-Q)ahA}B-k9Z;R@s<(X=&+SDn>bOv9rkT(H|KZGfh;?#Vs|m7-Eng- zI>STD-S-eZNY>DQmB!yeimoA}`$ZZZ=}5b5dN==6+*=F(+|OZY*8ljWzTS2{IXcd* zPN}>Fs|2?rT>No8&oPdnR+vvZ>yvD%e*$xC$K~kLRCJX~^Cl*zxtdCp9FNO4&nfB) zGqTnwl=RiMeVm`*oafk>J82c?n$FigZsRGO-J;d#{e-6_d&BQ7$5`Y@)D-Okim-?H zHM1dd+-5HEcT}`}SVUX2uhUb%e=*^1*)D_V7{3s!;kZ#hbK~atcR~7^nTgv&|4El6 znntQ^UBe7UqX;KCAdbJZGDF^j6vDNiKTjH*oAI(8cyg5GjY8Vjt%edxa@mBa=Z7c zb7K_4U(<3vW={}gbT5`4dROCa+{?&q(rWP@(G^>tq;5-ArxjcmvK(+Cw9T6sbA{{OUd-mj4X=@@rsC64es$7kE!!Y@mGvH;!up9jCyXa8T=!W za5CS=9vN1fJbLI6Y#` z@jC1s(CTs#R&<|pzXor!^Y*$A3z!A^K7>9W2@a@+KB!FvmBL%3T0--nw?dB-Xb=xY z*bHIfLu{tm72y+h>#WQE3@2IUvfHw&E#sSQp$Ev2H=XzHr3W1W{2FL2Y7GImXcAJ7 zLz1Yluxb1y5(U*}9dah_ll3*eTez?7`yBNAg5v?+iW}3#ncHj|;LhO?OdGrw*(rwW zzDq2OT35gUW`(p!@MlJnFfKlhqK+aY6Su~7Tgwz0g-hEi8EW3^}g>6|;#vBANt1>vQ*H7dGirK=J164T;8oJojOOvtvs z8E`t$&so*|Wa4kPVcV*tKCcv0hh*6Ah{5v|hoG%0s;SzcWfGaGw<97q*JOdw7txG& zW-1n)V|34+AAyf+aT@UNjIVaR>eiAF?mDVtlfHpZaU2g1?RyYa81&pSN|zZf&-df8tr_K`=O+m5&!-sjGw#y{{|GqFhc z8Kh+(kR%$Krp!o|jvz=VrYJ;BZZ6HF#&)CitT$=AxUJ#Eo?~IVBcAxE`4XdC{pc>8 z(ba)!HfFIv@T*hD<3hsP^yT6^BU6>WBrHe&6<>iYCNn$Qz`r(v5?m3O5+&@N5F8RsavBJAj8(RZ47ZGXdZHy#DIr()Uet2ZX{Ez4 zn#nK4JLASvzHX7nY`|Ee%Ie1QBCmPnr$`2A`ShgR^Ze`gJ~hWNGf+1z-qJ4^hw(qY z7}}X`qkAg6E#sKt>qyB=L-EBJBy)5NDV2#upHQ;q2r}WX+^oUF0gWCb;C_z$0&jtWEj@u+`zcz z54-JW2)d7Zw9|F%3%y-vH1h|(Kd7&a8vtsVrk#zz|1YVpOnstAGiSvJ*l<%4BaV^=_d)84dMb1ymN+IQTK;21BYH_d=dYCdM zOh8>8qZiR~@H%EKdb4PDW@b#SKps9l;U9{ik+I9~N!-nV&F%~F!$E+<$%Luk@0JdU z<6%-K&L>@n@X()0z8aOSR+};%Gj))cN{>4)%1Q4@*duTiXP@GX=KmuzfBLebcZB_O zn~FIefnFtxrAA3xP~v8zubkMPARL>kuaRgO@2aMo6qLB_pm(xTQom>^W<&DG1_`Vh z23_q5t=L=MFT;Woqg~I0Ur0)`(~LZo9A};xHJg%nTqnjpm81v8UP~)h9f&`KX+5YF z{|i0ux0rG>BW^=l<`6an)#d;}+AAuk7(42+CuA@7ovm7EH*WXovhYM)kTD#27&of> zDQXR`r0Nrsh0i{?JC;R|-YJ@_N=V;uHKP$!LlJg>Up?*pTPaxQZGn!IU1yAf#mH4= zDSJK>pKhft&cXpmk+Ctj7@j&`p1kLy2%3-?4x?dy{ z>ogFc&RDj28nlg}ZdMljfL>yBA+(DwrkfDnMjKMy8W}}PIw%;undZ4OED@q4ZM011 z6VYdXTo|J2(eE3;op*x#jk%j_+5)v5*<)C(y zDbsytUBWY3;zoM93~32PkU(0UX35sJ_s&F;E<5}>jUi>Y>^g2q{NnDQj}Td29U41{ z{(hqJu|)sCAaNG)Y)Gh(1+g`}3(ZgF#o43wZn}z-84=^)Z}XkF$JybuB=W$`!1xma zd!E)mf*ATFsozAD{7dCeASOY5;u%OoXqJ#8@+N|azE>gcCTjE3wH?pGCC)o-AlSq8 zw%J+O*dxcd0{ZTqqW1#2>6f6M13e6kk{5J$}a9+-wW}zHgP$JN#;ZvffF8V9>PsR)R%nm|P7ZKm3EZKDa$fS|}b0 zi$N8MEB0N#86x$xzCg+Qj zj=nA~kIRldCzL~|kN>Y07L?kL;#|xcwd=twhFV(*q)UYhnvA5AB1VplW^9fEb=5O+ zW1Q5EX3EDMl9SDBihnLHnWdY^6q>|+Nk(H)&^rAFim(|MYr8esHg3|QH+d#u*QuG5 zfJBF*A5+DXa5~)7Tgd~evuTwnzhv)WoYM5g52kBjLWFl`UC;RM{KhTp0E+XMka)%= z-Yi4Id_7@9M(@er2@f(gk2)nzW{&GfBsFFQswO8lVt2{5r;xBi;+m;9a2djpnTmLn z1>7WI4MjK&ZLzV6F@@L7x5XYtgiaofGeks>48$uUQrc|^dk_OvrNm=Mk}NSv4`~wD zPuWcr6t2qv5ISIp{Q{$(fiMdP*$6}%QW{Us#K=%cCqBd~P+X3X<3uRx+T6IGl-;Tl z32GEYSt#)^#aP@r`3yx-crHB?oy;4IEk|CW2rT%U^^K?y#^mY3=#vc0iOCoP#(^WT zv3u#awOO%W>E){O@f!3p*}?=JdXsoYQWkA?+Xl>IiaU^x)gi8;2&M?mq$)ez?yxbP z=j3QC5G1){jPWpZ z|0Z=>NMr|EeJD#jfFkfS*r(pHKREp~dd(Jg9XGIMxp?5T#F$KPLltAjs^50mAx3t< zs>DkMH@IlW481QbVACloKk6Kcusaduf_yaN+9?l4uE!RmIQpnpf&MGHxUazgV_3JGW>h&YKscN}61 zw&H{b@l!DF=n^q3G+u8Q`4jG<*@@hUlvLh}3`MQWd`8A%mL&3#XK_V4){(Y^>P-)k zj1)Alh*W00L+xF>%5fOfkI^|QPWTf$qbEst5pSWXLwJ&qr!+=*lejK35AIJ6m8b^Y zQ#wUXfCFjXoBbijjCE8I`*7$A15XmZ8Bb!ZQwxqDSj#k?Zap>xlc}+SC8XOZF|m~y zsxoJ>k28-*l;GsDk!UQkmhCF3{PDN~kt404s|$YjERNDY<@Lz`dWnJ^lIfod=YMM!|tjM(Weh^S$4 zx($-3^F2Krna~JIhltyieACYnVVU|2Vd4jgTNzkVvdHtSdXn@OH{A38?(-66kaQHm z2!3KDhw&%hH1xz+QDk&hFrE}24Npu2rBKNRgQI+qnZ94 z1cd`=;W4WlsFdhL^h^=?PakaEba70FQh0F%}gRh9Qw?Q5J5!)eYD@m zULA8-Jyk_x4*H#TMA;9zh>@3zgeubyNWO%6GmeR=K~FQ)ww*%f(O7L4(JhAt*7YK4Vo<=LPGEaUZOTH}c(Ssb4OX8# zA|;Ovpty-mVc$_Sw(H_`$$pzY65>fgs6RIoQ8uAI#8nUNo^88yIJv~pVNSKBXx#aW z!a`w-+rtB11s6P@i0c%%`z(qa&rk6Gu;oYgLI?>kWCuo}nqmkgO`(33pIM~aQlfAF z>#$+bgwq{Wq|njzn!<2_jR*5UV1A0Xx43n_h@ZTOU*3^GQ9()GT&OqT$SRCNqggsN z=`D(2lT$hWiA!Yic5SObya;(J-#XmgPY=5*`{!D`z3geHLvl(N)&=zHxl@1VYI zgKyD#&fFCxtRcZLi5^p)rI*IW%46wraTleVX(I`G;)b+_#9om%)IZ5qTX$20(!vG! zBupl{P7=@I(5gLA4LqimO<7I4rhbAe+?q}7!Rl_1M<(!ts3Mr45^BRCFD+GKMAm6_2Jrw^Ryhp$jQu5Vg|KkF($#1m z9;`SOF(#OaauEaY&B7hBR;Z0=+^|Rl+U^`hCLP@FlHMcq9BueF&;W zvX#xD1fsdTDY!;_FWm-`Nu%P^AV_u>T>~$X<%D4HFR6K>31mb3FB*J6+<_uQBJKbn z0Bis@0-J!%z!pFd*a`>%!oW6QJFo)~0YrhFz%F1nAO`FK_5$L71h5a-4@d%1fHZIb zI0(o9vVa^Q4=4bK07XCvPzF>0RX`0;2Q≈4q*CXahQcE}#eK0|tO0a0ECC90QC1 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

Hmaos-<-0)sg#6`UDxoIN(&vf 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ⅇf~8Fl01O@{PGX7-Pkx1^KX%p|KSfbK~s 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 KKmYjSkN*MMY{3Zt literal 0 HcmV?d00001 diff --git a/library/src/main/cpp/icc/IccDemo.cpp b/library/src/main/cpp/icc/IccDemo.cpp index 31b41190..7f60f816 100644 --- a/library/src/main/cpp/icc/IccDemo.cpp +++ b/library/src/main/cpp/icc/IccDemo.cpp @@ -46,6 +46,7 @@ JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfile(JNIEnv *env, jobj extern "C" JNIEXPORT jint JNICALL Java_com_xsooy_icc_IccUtils_loadProfileByData(JNIEnv *env, jobject thiz, jbyteArray data) { delete cmm; + cmm = new CIccCmm; icUInt8Number *pmsg = ConvertJByteaArrayToChars(env,data); int chars_len = env->GetArrayLength(data); CIccProfile* cIccProfile = OpenIccProfile(pmsg, chars_len); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetColor.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetColor.java index 5928da48..3751582b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetColor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetColor.java @@ -27,6 +27,7 @@ import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDPattern; /** * sc,scn,SC,SCN: Sets the color to use for stroking or non-stroking operations. @@ -39,7 +40,7 @@ public abstract class SetColor extends OperatorProcessor public void process(Operator operator, List arguments) throws IOException { PDColorSpace colorSpace = getColorSpace(); -// if (!(colorSpace instanceof PDPattern)) TODO: PdfBox-Android + if (!(colorSpace instanceof PDPattern)) //TODO: PdfBox-Android { if (arguments.size() < colorSpace.getNumberOfComponents()) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java index aef7b1b7..c6b73dcf 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java @@ -132,9 +132,9 @@ else if (name.equals(COSName.DEVICEGRAY) && // built-in color spaces if (name == COSName.DEVICECMYK) { -// return PDDeviceCMYK.INSTANCE; - Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); - return PDDeviceRGB.INSTANCE; + return PDDeviceCMYK.INSTANCE; +// Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); +// return PDDeviceRGB.INSTANCE; } else if (name == COSName.DEVICERGB) { @@ -146,9 +146,9 @@ else if (name == COSName.DEVICEGRAY) } else if (name == COSName.PATTERN) { -// return new PDPattern(resources); - Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); - return PDDeviceRGB.INSTANCE; + return new PDPattern(resources); +// Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); +// return PDDeviceRGB.INSTANCE; } else if (resources != null) { @@ -199,9 +199,9 @@ else if (name == COSName.DEVICEN) } else if (name == COSName.INDEXED) { -// return new PDIndexed(array); - Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); - return PDDeviceRGB.INSTANCE; + return new PDIndexed(array); +// Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead"); +// return PDDeviceRGB.INSTANCE; } else if (name == COSName.SEPARATION) { @@ -380,7 +380,7 @@ public static float getMaxValue(int colorType,int index) { } //x,y,z To r=[0],g=[1],b=[2] - public static float[] xyzToRgb(float[] xyz) { + public float[] xyzToRgb(float[] xyz) { float[] rgb = new float[3]; rgb[0] = (xyz[0] * 3.240479f) + (xyz[1] * -1.537150f) + (xyz[2] * -.498535f); rgb[1] = (xyz[0] * -.969256f) + (xyz[1] * 1.875992f) + (xyz[2] * .041556f); @@ -400,4 +400,48 @@ public static float[] xyzToRgb(float[] xyz) { return rgb; } + public float[] toLab(float[] value) { + float[] lab = new float[3]; + for (int i=0;i<3;i++) { + lab[i] = (getMaxValue(TYPE_Lab,i)-getMinValue(TYPE_Lab,i))*value[i]+getMinValue(TYPE_Lab,i); + } +// lab[0] = value[0] * 100; +// float min = -128.0f; +// float max = 127.0f; +// lab[1] = (max-min)*value[1]+min; +// lab[2] = (max-min)*value[2]+min; + return lab; + } + + public float[] labToXyz(float[] value) { + float[] xyz = new float[3]; + xyz[1] = (value[0]+16.f)/116.f; + xyz[0] = value[1]/500.f+xyz[1]; + xyz[2] = xyz[1]-value[2]/200.f; + + for (int i = 0; i < 3; i++) + { + float pow = xyz[i] * xyz[i] * xyz[i]; + float ratio = (6.0f / 29.0f); + if (xyz[i] > ratio) + { + xyz[i] = pow; + } + else + { + xyz[i] = (3.0f * (6.0f / 29.0f) * (6.0f / 29.0f) * (xyz[i] - (4.0f / 29.0f))); + } + } + + xyz[0] = xyz[0] * (95.047f); + xyz[1] = xyz[1] * (100.0f); + xyz[2] = xyz[2] * (108.883f); + + xyz[0] = xyz[0] / 100f; + xyz[1] = xyz[1] / 100f; + xyz[2] = xyz[2] / 100f; + + return xyz; + } + } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java new file mode 100644 index 00000000..b05a798e --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java @@ -0,0 +1,166 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +import android.graphics.Bitmap; +import android.graphics.Color; + +import com.tom_roush.pdfbox.android.PDFBoxResourceLoader; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.io.IOUtils; +import com.xsooy.icc.IccUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class PDDeviceCMYK extends PDDeviceColorSpace +{ + + private IccUtils iccUtils; + /** The single instance of this class. */ + public static PDDeviceCMYK INSTANCE; + static + { + INSTANCE = new PDDeviceCMYK(); + } + + private final PDColor initialColor = new PDColor(new float[] { 0, 0, 0, 1 }, this); +// private ICC_ColorSpace awtColorSpace; + private volatile boolean initDone = false; + + protected PDDeviceCMYK() + { + } + + /** + * Lazy load the ICC profile, because it's slow. + */ + protected void init() throws IOException + { + // no need to synchronize this check as it is atomic + if (initDone) + { + return; + } + synchronized (this) + { + // we might have been waiting for another thread, so check again + if (initDone) + { + return; + } + + InputStream inputStream = PDFBoxResourceLoader.getStream("com/tom_roush/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc"); + byte[] buff = new byte[inputStream.available()]; + IOUtils.populateBuffer(inputStream,buff); +// if (new File(IccUtils.iccProfileDir+"/ISOcoated_v2_300_bas.icc").exists()) { + iccUtils = new IccUtils(); +// iccUtils.loadProfile(IccUtils.iccProfileDir+"/ISOcoated_v2_300_bas.icc"); + iccUtils.loadProfileByData(buff); +// } + initDone = true; + } + } + +// protected ICC_Profile getICCProfile() throws IOException +// { +// // Adobe Acrobat uses "U.S. Web Coated (SWOP) v2" as the default +// // CMYK profile, however it is not available under an open license. +// // Instead, the "ISO Coated v2 300% (basICColor)" is used, which +// // is an open alternative to the "ISO Coated v2 300% (ECI)" profile. +// +// String resourceName = "/org/apache/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc"; +// InputStream resourceAsStream = PDDeviceCMYK.class.getResourceAsStream(resourceName); +// if (resourceAsStream == null) +// { +// throw new IOException("resource '" + resourceName + "' not found"); +// } +// try (InputStream is = new BufferedInputStream(resourceAsStream)) +// { +// return ICC_Profile.getInstance(is); +// } +// } + + @Override + public String getName() + { + return COSName.DEVICECMYK.getName(); + } + + @Override + public int getNumberOfComponents() + { + return 4; + } + + @Override + public float[] getDefaultDecode(int bitsPerComponent) + { + return new float[] { 0, 1, 0, 1, 0, 1, 0, 1 }; + } + + @Override + public PDColor getInitialColor() + { + return initialColor; + } + + @Override + public float[] toRGB(float[] value) throws IOException + { + init(); + //cmyk to lab + if (iccUtils!=null) { + float[] data = new float[3]; + iccUtils.applyCmyk(value,data); + float[] lab = toLab(data); + float[] xyz = labToXyz(lab); + return xyzToRgb(xyz); + } + return new float[]{(1-value[0])*(1-value[3]),(1-value[1])*(1-value[3]),(1-value[2])*(1-value[3])}; + } + + @Override + public Bitmap toRGBImage(Bitmap raster) throws IOException { +// init(); +// int width = raster.getWidth(); +// int height = raster.getHeight(); +// +// Bitmap rgbImage = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); +// int[] src = new int[width]; +// for (int y = 0; y < height; y++) +// { +// raster.getPixels(src,0,width,0,y,width,1); +// for (int x = 0; x < width; x++) +// { +// float[] value = new float[] {(src[x]&0xff)/255.f,(src[x]>>8&0xff)/255.f,(src[x]>>16&0xff)/255.f,(src[x]>>24&0xff)/255.f}; +// float[] rgb = toRGB(value); +// int color = Color.argb(255,(int)(rgb[0]*255),(int)(rgb[0]*255),(int)(rgb[0]*255)); +// rgbImage.setPixel(x,y,color); +// } +// } +// return rgbImage; + return null; + } + + public Bitmap toRGBImage(byte[] raster,int width,int height) throws IOException { + init(); + Bitmap rgbImage = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); + int location = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float[] value = new float[4]; + for (int i=0;i<4;i++) { + value[i] = ((raster[location]&0xff)/255.f); + location++; + } + float[] rgb = toRGB(value); + int color = Color.argb(255,(int)(rgb[0]*255),(int)(rgb[1]*255),(int)(rgb[2]*255)); + rgbImage.setPixel(x,y,color); + } + } + return rgbImage; + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDPattern.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDPattern.java new file mode 100644 index 00000000..f31eb358 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDPattern.java @@ -0,0 +1,80 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.color; + +import android.graphics.Bitmap; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.PDResources; +import com.tom_roush.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern; + +import java.io.IOException; + +public class PDPattern extends PDSpecialColorSpace{ + + private static final PDColor EMPTY_PATTERN = new PDColor(new float[] { }, null); + + private final PDResources resources; + + private PDColorSpace underlyingColorSpace; + /** + * Creates a new pattern color space. + * + * @param resources The current resources. + */ + public PDPattern(PDResources resources) + { + this.resources = resources; + array = new COSArray(); + array.add(COSName.PATTERN); + } + + + @Override + public String getName() { + return COSName.PATTERN.getName(); + } + + @Override + public int getNumberOfComponents() { + throw new UnsupportedOperationException(); + } + + @Override + public float[] getDefaultDecode(int bitsPerComponent) { + throw new UnsupportedOperationException(); + } + + @Override + public PDColor getInitialColor() { + return EMPTY_PATTERN; + } + + @Override + public float[] toRGB(float[] value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Bitmap toRGBImage(Bitmap raster) throws IOException { + throw new UnsupportedOperationException(); + } + + public PDAbstractPattern getPattern(PDColor color) throws IOException + { + PDAbstractPattern pattern = resources.getPattern(color.getPatternName()); + if (pattern == null) + { + throw new IOException("pattern " + color.getPatternName() + " was not found"); + } + else + { + return pattern; + } + } + + @Override + public String toString() + { + return "Pattern"; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java new file mode 100644 index 00000000..2daccffa --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java @@ -0,0 +1,222 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.shading; + +import android.graphics.Rect; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBoolean; + +import java.io.IOException; + +public class AxialShadingContext extends ShadingContext { + + private PDShadingType2 axialShadingType; + private final float[] coords; + private final float[] domain; + private final boolean[] extend; + private final double x1x0; + private final double y1y0; + private final float d1d0; + private final double denom; + + private final int factor; + + private final int[] colorTable; + + public AxialShadingContext(PDShadingType2 shading, Rect deviceBounds) throws IOException { + super(shading); + this.axialShadingType = shading; + coords = shading.getCoords().toFloatArray(); + // domain values + if (shading.getDomain() != null) + { + domain = shading.getDomain().toFloatArray(); + } + else + { + // set default values + domain = new float[] { 0, 1 }; + } + // extend values + COSArray extendValues = shading.getExtend(); + if (extendValues != null) + { + extend = new boolean[2]; + extend[0] = ((COSBoolean) extendValues.getObject(0)).getValue(); + extend[1] = ((COSBoolean) extendValues.getObject(1)).getValue(); + } + else + { + // set default values + extend = new boolean[] { false, false }; + } + // calculate some constants to be used in getRaster + x1x0 = coords[2] - coords[0]; + y1y0 = coords[3] - coords[1]; + d1d0 = domain[1] - domain[0]; + denom = Math.pow(x1x0, 2) + Math.pow(y1y0, 2); + +// try +// { + // get inverse transform to be independent of current user / device space + // when handling actual pixels in getRaster() +// rat = matrix.createAffineTransform().createInverse(); +// rat.concatenate(xform.createInverse()); +// } +// catch (AffineTransform.NoninvertibleTransformException ex) +// { +// LOG.error(ex.getMessage() + ", matrix: " + matrix, ex); +// rat = new AffineTransform(); +// } + + // shading space -> device space +// AffineTransform shadingToDevice = (AffineTransform)xform.clone(); +// shadingToDevice.concatenate(matrix.createAffineTransform()); + + // worst case for the number of steps is opposite diagonal corners, so use that + double dist = Math.sqrt(Math.pow(deviceBounds.right - deviceBounds.left, 2) + + Math.pow(deviceBounds.bottom - deviceBounds.top, 2)); + factor = (int) Math.ceil(dist); + + // build the color table for the given number of steps + colorTable = calcColorTable(); + } + + private int[] calcColorTable() throws IOException + { + + int[] map = new int[factor + 1]; + if (factor == 0 || Float.compare(d1d0, 0) == 0) + { + float[] values = axialShadingType.evalFunction(domain[0]); + + map[0] = convertToRGB(values); + } + else + { +// IccUtils iccUtils = new IccUtils(); +// iccUtils.loadProfile("/storage/emulated/0/Android/data/com.example.test/files/HFA_Eps15000_MK_Agave-Sisal.icc"); +// iccUtils.loadProfile2("/storage/emulated/0/Android/data/com.example.test/files/ISOcoated_v2_300_bas.icc","/storage/emulated/0/Android/data/com.example.test/files/HFA_Eps15000_MK_Agave-Sisal.icc"); + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i <= factor; i++) + { + float t = domain[0] + d1d0 * i / factor; + float[] values = axialShadingType.evalFunction(t); +// builder.delete(0,builder.length()); +// for (float jj:values) +// builder.append(jj+","); +// Log.w("ceshi","calcColorTable:"+builder.toString()); +// int normRGBValues; +// normRGBValues = (int) (values[0] * 65535); +// normRGBValues |= (int) (values[1] * 65535) << 8; +// normRGBValues |= (int) (values[2] * 65535) << 16; +// normRGBValues |= (int) (values[3] * 65535) << 24; +// iccUtils.applyCmyk(values); +// builder.delete(0,builder.length()); +// for (float jj:values) +// builder.append(jj+","); +// Log.w("ceshi","转换结果:"+builder.toString()); +// float[] test = new float[] {(map[i]>>24&0xff)/255.f, (map[i]>>16&0xff)/255.f,(map[0]>>8&0xff)/255.f,(map[0]&0xff)/255.f}; + map[i] = convertToRGB(values); + +// Log.w("ceshi",String.format("r:%d,g:%d,b:%d",map[i]>>16&0xff,map[0]>>8&0xff,map[0]&0xff)); + } + } + return map; + } + + + public int[] getRaster(int x, int y, int w, int h) + { + // create writable raster +// Log.w("ceshi",String.format("x:%d,y:%d,w:%d,h:%h",x,y,w,h)); +// System.out.println(String.format("x:%d,y:%d,w:%d,h:%h",x,y,w,h)); + boolean useBackground; +// int[] data = new int[w * h * 4]; + int[] data = new int[w * h]; + float[] values = new float[2]; + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + useBackground = false; + values[0] = x + i; + values[1] = y + j; +// rat.transform(values, 0, values, 0, 1); + double inputValue = x1x0 * (values[0] - coords[0]) + y1y0 * (values[1] - coords[1]); + // TODO this happens if start == end, see PDFBOX-1442 + if (Double.compare(denom, 0) == 0) + { + if (getBackground() == null) + { + continue; + } + useBackground = true; + } + else + { + inputValue /= denom; + } + // input value is out of range + if (inputValue < 0) + { + // the shading has to be extended if extend[0] == true + if (extend[0]) + { + inputValue = domain[0]; + } + else + { + if (getBackground() == null) + { + continue; + } + useBackground = true; + } + } + // input value is out of range + else if (inputValue > 1) + { + // the shading has to be extended if extend[1] == true + if (extend[1]) + { + inputValue = domain[1]; + } + else + { + if (getBackground() == null) + { + continue; + } + useBackground = true; + } + } + int value; + if (useBackground) + { + // use the given background color values + value = getRgbBackground(); + } + else + { + int key = (int) (inputValue * factor); + value = colorTable[key]; + } + int index = (j * w + i); + data[index] = value; +// int index = (j * w + i) * 4; +// data[index] = value & 255; +// value >>= 8; +// data[index + 1] = value & 255; +// value >>= 8; +// data[index + 2] = value & 255; +// data[index + 3] = 255; +// System.out.println(String.format("r:%d,g:%d,b:%d,a:%h",data[index],data[index+1],data[index+2],data[index+3])); + } + } +// raster.setPixels(0, 0, w, h, data); + return data; + } + + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/ShadingContext.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/ShadingContext.java new file mode 100644 index 00000000..e6f6493d --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/ShadingContext.java @@ -0,0 +1,69 @@ +package com.tom_roush.pdfbox.pdmodel.graphics.shading; + +import android.util.Log; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; + +import java.io.IOException; + +public class ShadingContext { + + private float[] background; + private int rgbBackground; + private final PDShading shading; +// private ColorModel outputColorModel; + private PDColorSpace shadingColorSpace; + + public ShadingContext(PDShading shading +// , +// ColorModel cm, AffineTransform xform, +// Matrix matrix + ) throws IOException + { + this.shading = shading; + shadingColorSpace = shading.getColorSpace(); + Log.w("ceshi","shadingColorSpace==="+shadingColorSpace.getName()); + // create the output color model using RGB+alpha as color space +// shadingColorSpace = PDDeviceCMYK.INSTANCE; +// ColorSpace outputCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); +// outputColorModel = new ComponentColorModel(outputCS, true, false, Transparency.TRANSLUCENT, +// DataBuffer.TYPE_BYTE); + + // get background values if available + COSArray bg = shading.getBackground(); + if (bg != null) + { + background = bg.toFloatArray(); + rgbBackground = convertToRGB(background); + } + } + + final int convertToRGB(float[] values) throws IOException + { + int normRGBValues; + + float[] rgbValues = shadingColorSpace.toRGB(values); +// Log.w("ceshi","hadingColorSpace.toRGB::"+rgbValues[2]); + normRGBValues = (int) (rgbValues[2] * 255); + normRGBValues |= (int) (rgbValues[1] * 255) << 8; + normRGBValues |= (int) (rgbValues[0] * 255) << 16; +// StringBuilder builder = new StringBuilder(); +// for (float jj:rgbValues) +// builder.append(jj+","); +// Log.w("ceshi","转换结果:"+builder.toString()); + + return normRGBValues; + } + + float[] getBackground() + { + return background; + } + + int getRgbBackground() + { + return rgbBackground; + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java index 9bd74d5d..188c5ac4 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java @@ -633,19 +633,20 @@ public void intersectClippingPath(Region area) * * @return The current clipping path. */ - public Region getCurrentClippingPath() + public Path getCurrentClippingPath() { if (clippingPaths.size() == 1) { // If there is just a single clipping path, no intersections are needed. Path path = clippingPaths.get(0); - Region area = clippingCache.get(path); - if (area == null) - { - area = GraphicsUtil.getPathRegion(path); - clippingCache.put(path, area); - } - return area; +// Region area = clippingCache.get(path); +// if (area == null) +// { +// area = GraphicsUtil.getPathRegion(path); +// clippingCache.put(path, area); +// } + return path; +// return area; } // If there are multiple clipping paths, combine them to a single area. Path clippingPath = new Path(clippingPaths.get(0)); @@ -653,12 +654,16 @@ public Region getCurrentClippingPath() { clippingPath.op(clippingPaths.get(i), Path.Op.INTERSECT); } - Region clippingRegion = GraphicsUtil.getPathRegion(clippingPath); +// android.graphics.Matrix matrix = new android.graphics.Matrix(); +// matrix.setScale(scaleX,scaleY); +// clippingPath.transform(matrix); +// Region clippingRegion = GraphicsUtil.getPathRegion(clippingPath); // Replace the list of individual clipping paths with the intersection, and add it to the cache. - clippingPaths = new ArrayList(1); - clippingPaths.add(clippingPath); - clippingCache.put(clippingPath, clippingRegion); - return clippingRegion; +// clippingPaths = new ArrayList(1); +// clippingPaths.add(clippingPath); +// clippingCache.put(clippingPath, clippingRegion); +// return clippingRegion; + return clippingPath; } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java index a3ced3c6..a7ef829d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java @@ -311,7 +311,7 @@ public Bitmap renderImage(int pageIndex, float scale, ImageType imageType, Rende // the end-user may provide a custom PageDrawer PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed, destination, - imageDownscalingOptimizationThreshold); + imageDownscalingOptimizationThreshold,scale,scale); PageDrawer drawer = createPageDrawer(parameters); drawer.drawPage(paint, canvas, cropBox); @@ -409,7 +409,7 @@ public void renderPageToGraphics(int pageIndex, Paint paint, Canvas canvas, floa // the end-user may provide a custom PageDrawer PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed, destination, - imageDownscalingOptimizationThreshold); + imageDownscalingOptimizationThreshold,scaleX,scaleY); PageDrawer drawer = createPageDrawer(parameters); drawer.drawPage(paint, canvas, cropBox); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java index 6dc73e30..e6ea980c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java @@ -23,6 +23,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.util.Log; @@ -61,6 +62,7 @@ import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDPattern; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage; @@ -68,7 +70,11 @@ import com.tom_roush.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup; import com.tom_roush.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup.RenderState; import com.tom_roush.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentMembershipDictionary; +import com.tom_roush.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern; +import com.tom_roush.pdfbox.pdmodel.graphics.pattern.PDShadingPattern; +import com.tom_roush.pdfbox.pdmodel.graphics.shading.AxialShadingContext; import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading; +import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShadingType2; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDSoftMask; @@ -102,6 +108,8 @@ public class PageDrawer extends PDFGraphicsStreamEngine // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI) private Paint paint; + private float scaleX = 1; + private float scaleY = 1; private Canvas canvas; private AffineTransform xform; private float xformScalingFactorX; @@ -119,7 +127,7 @@ public class PageDrawer extends PDFGraphicsStreamEngine private Path linePath = new Path(); // last clipping path - private Region lastClip; + private Path lastClip; private int lastStackSize = 0; // clip when drawPage() is called, can be null, must be intersected when clipping @@ -162,6 +170,8 @@ public boolean accept(PDAnnotation annotation) public PageDrawer(PageDrawerParameters parameters) throws IOException { super(parameters.getPage()); + this.scaleX = parameters.getScaleX(); + this.scaleY = parameters.getScaleY(); this.renderer = parameters.getRenderer(); this.subsamplingAllowed = parameters.isSubsamplingAllowed(); this.destination = parameters.getDestination(); @@ -296,7 +306,7 @@ private int getColor(PDColor color) throws IOException { */ protected final void setClip() { - Region clippingPath = getGraphicsState().getCurrentClippingPath(); + Path clippingPath = getGraphicsState().getCurrentClippingPath(); if (clippingPath != lastClip) { // android canvas manage clips with save/restore in a private stack, we can not @@ -309,7 +319,8 @@ protected final void setClip() lastStackSize = canvas.save(); if (!clippingPath.isEmpty()) { - canvas.clipPath(clippingPath.getBoundaryPath()); +// Log.w("ceshi","canvas.clipPath(clippingPath.getBoundaryPath());"+clippingPath); + canvas.clipPath(clippingPath); } if (initialClip != null) { @@ -669,8 +680,8 @@ public void strokePath() throws IOException public void fillPath(Path.FillType windingRule) throws IOException { PDGraphicsState graphicsState = getGraphicsState(); - paint.setColor(getNonStrokingColor()); - setClip(); + paint.setAlpha((int)(graphicsState.getNonStrokeAlphaConstant()*255)); + linePath.setFillType(windingRule); // disable anti-aliasing for rectangular paths, this is a workaround to avoid small stripes @@ -689,7 +700,32 @@ public void fillPath(Path.FillType windingRule) throws IOException if (isContentRendered()) { paint.setStyle(Paint.Style.FILL); - canvas.drawPath(linePath, paint); + if (getGraphicsState().getNonStrokingColor().getColorSpace() instanceof PDPattern) { + PDColorSpace colorSpace = getGraphicsState().getNonStrokingColor().getColorSpace(); + PDAbstractPattern pattern = ((PDPattern)colorSpace).getPattern(getGraphicsState().getNonStrokingColor()); + PDShadingPattern shadingPattern = (PDShadingPattern)pattern; + PDShading shading = shadingPattern.getShading(); + if (shading instanceof PDShadingType2) { + getGraphicsState().intersectClippingPath(linePath); + setClip(); + canvas.scale(1/scaleX,1/scaleY); + Rect rect = new Rect((int)(bounds.left*scaleX),(int)(bounds.top*scaleY),(int)(bounds.right*scaleX),(int)(bounds.bottom*scaleY)); + Rect rect2 = new Rect((int)(bounds.left*scaleX),(int)(canvas.getHeight()-(bounds.bottom*scaleY)),(int)(bounds.right*scaleX),(int)(canvas.getHeight()-(bounds.top*scaleY))); + + AxialShadingContext axialShadingContext = new AxialShadingContext((PDShadingType2) shading,rect2); + for (int y=rect.bottom;y>rect.top;y--) { + int[] data = axialShadingContext.getRaster(rect.left,canvas.getHeight()-y,rect.right-rect.left,1); + for (int i=0;i + android:onClick="openFile" />