diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index af3cc50507..2a574a6c77 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,124 +1,126 @@ project(kritapigment) # we have to repeat platform specifics from top-level if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_SOURCE_DIR}/compositeops) set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIRS}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set(LINK_VC_LIB) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) set(LINK_VC_LIB ${Vc_LIBRARIES}) ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp) message("Following objects are generated from the per-arch lib") message("${__per_arch_factory_objs}") endif() add_subdirectory(tests) add_subdirectory(benchmarks) set(kritapigment_SRCS DebugPigment.cpp KoBasicHistogramProducers.cpp KoColor.cpp KoColorDisplayRendererInterface.cpp KoColorConversionAlphaTransformation.cpp KoColorConversionCache.cpp KoColorConversions.cpp KoColorConversionSystem.cpp KoColorConversionTransformation.cpp KoColorProofingConversionTransformation.cpp KoColorConversionTransformationFactory.cpp KoColorModelStandardIds.cpp KoColorProfile.cpp KoColorSpace.cpp KoColorSpaceEngine.cpp KoColorSpaceFactory.cpp KoColorSpaceMaths.cpp + KoCmykColorSpaceMaths.cpp + KoLabColorSpaceMaths.cpp KoColorSpaceRegistry.cpp KoColorProfileStorage.cpp KoColorTransformation.cpp KoColorTransformationFactory.cpp KoColorTransformationFactoryRegistry.cpp KoCompositeColorTransformation.cpp KoCompositeOp.cpp KoCompositeOpRegistry.cpp KoCopyColorConversionTransformation.cpp KoFallBackColorTransformation.cpp KoHistogramProducer.cpp KoMultipleColorConversionTransformation.cpp KoUniqueNumberForIdServer.cpp colorspaces/KoAlphaColorSpace.cpp colorspaces/KoLabColorSpace.cpp colorspaces/KoRgbU16ColorSpace.cpp colorspaces/KoRgbU8ColorSpace.cpp colorspaces/KoSimpleColorSpaceEngine.cpp compositeops/KoOptimizedCompositeOpFactory.cpp compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp compositeops/KoAlphaDarkenParamsWrapper.cpp ${__per_arch_factory_objs} colorprofiles/KoDummyColorProfile.cpp resources/KoAbstractGradient.cpp resources/KoColorSet.cpp resources/KisSwatch.cpp resources/KisSwatchGroup.cpp resources/KoPattern.cpp resources/KoResource.cpp resources/KoMD5Generator.cpp resources/KoHashGeneratorProvider.cpp resources/KoStopGradient.cpp resources/KoSegmentGradient.cpp ) set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB}) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() add_library(kritapigment SHARED ${kritapigment_SRCS}) generate_export_header(kritapigment) target_include_directories( kritapigment PUBLIC $ $ ) target_link_libraries( kritapigment PUBLIC kritaplugin kritastore kritaglobal ${EXTRA_LIBRARIES} KF5::I18n KF5::ConfigCore Qt5::Core Qt5::Gui Qt5::Xml ${WIN32_PLATFORM_NET_LIBS} ) set_target_properties(kritapigment PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/pigment/KoChannelInfo.h b/libs/pigment/KoChannelInfo.h index d56723f380..3b88eee6e7 100644 --- a/libs/pigment/KoChannelInfo.h +++ b/libs/pigment/KoChannelInfo.h @@ -1,277 +1,287 @@ /* * Copyright (c) 2004 Boudewijn Rempt + * Copyright (c) 2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCHANNELINFO_H_ #define KOCHANNELINFO_H_ #include #include #include #include /** * This class gives some basic information about a channel, * that is, one of the components that makes up a particular * pixel. */ class KoChannelInfo { public: /** * Used to represent a min and max range. */ struct DoubleRange { public: double minVal, maxVal; public: /// creates an invalid range of 0,0 DoubleRange(void) : minVal(0), maxVal(0) { } /// creates DoubleRange(qreal _minVal, qreal _maxVal) : minVal(_minVal), maxVal(_maxVal) { Q_ASSERT(minVal <= maxVal); } /// true if this range is usable bool isValid(void) const { return minVal < maxVal; } }; public: /// enum to define the type of the channel enum enumChannelType { COLOR, ///< The channel represents a color ALPHA ///< The channel represents the opacity of a pixel //SUBSTANCE, ///< The channel represents a real-world substance like pigments or medium //SUBSTRATE ///< The channel represents a real-world painting substrate like a canvas }; /// enum to define the value of the channel enum enumChannelValueType { UINT8, ///< use this for an unsigned integer 8bits channel UINT16, ///< use this for an integer 16bits channel UINT32, ///< use this for an unsigned integer 21bits channel FLOAT16, ///< use this for a float 16bits channel FLOAT32, ///< use this for a float 32bits channel FLOAT64, ///< use this for a float 64bits channel INT8, ///< use this for an integer 8bits channel INT16, ///< use this for an integer 16bits channel OTHER ///< Use this if the channel is neither an integer or a float }; public: KoChannelInfo() { } /** * @param name of the channel * @param npos position of the channel in the pixel (in bytes) * @param displayPosition the position of the channel in the user-visible order * @param channelType type of the channel * @param channelValueType type of the numerical data used by the channel * @param size number of bytes (not bits) of the channel (if -1, it is deduced from the channelType) * @param color a color to represent that channel (for instance in an histogram) * @param uiMinMax the UI range */ KoChannelInfo(const QString & name, qint32 npos, qint32 displayPosition, enumChannelType channelType, enumChannelValueType channelValueType, qint32 size = -1, const QColor &color = QColor(0, 0, 0), const DoubleRange &uiMinMax = DoubleRange()) : m_name(name) , m_pos(npos) , m_displayPosition(displayPosition) , m_channelType(channelType) , m_channelValueType(channelValueType) , m_size(size) , m_color(color) , m_uiMinMax(uiMinMax) { switch(m_channelValueType) { case UINT8: case INT8: Q_ASSERT(m_size == -1 || m_size == 1); m_size = 1; break; case UINT16: case INT16: Q_ASSERT(m_size == -1 || m_size == 2); m_size = 2; break; case UINT32: Q_ASSERT(m_size == -1 || m_size == 4); m_size = 4; break; case FLOAT16: Q_ASSERT(m_size == -1 || m_size == 2); m_size = 2; break; case FLOAT32: Q_ASSERT(m_size == -1 || m_size == 4); m_size = 4; break; case FLOAT64: Q_ASSERT(m_size == -1 || m_size == 8); m_size = 8; break; case OTHER: Q_ASSERT(m_size != -1); } if (!uiMinMax.isValid()) { switch (m_channelValueType) { case UINT8: m_uiMinMax.minVal = std::numeric_limits::min(); m_uiMinMax.maxVal = std::numeric_limits::max(); break; case INT8: m_uiMinMax.minVal = std::numeric_limits::min(); m_uiMinMax.maxVal = std::numeric_limits::max(); break; case UINT16: m_uiMinMax.minVal = std::numeric_limits::min(); m_uiMinMax.maxVal = std::numeric_limits::max(); break; case INT16: m_uiMinMax.minVal = std::numeric_limits::min(); m_uiMinMax.maxVal = std::numeric_limits::max(); break; case UINT32: m_uiMinMax.minVal = std::numeric_limits::min(); m_uiMinMax.maxVal = std::numeric_limits::max(); break; default: // assume real otherwise, which is 0..1 by default m_uiMinMax.minVal = 0.0; m_uiMinMax.maxVal = 1.0; break; } } Q_ASSERT(m_uiMinMax.isValid()); } public: /** * converts the display position to the pixel-order index in the channels vector. */ static int displayPositionToChannelIndex(int displayPosition, const QList &channels) { for (int i = 0; i < channels.size(); ++i) { if (channels.at(i)->displayPosition() == displayPosition) { return i; } } return -1; } static QList displayOrderSorted(const QList &channels) { QList sortedChannels; for (int i = 0; i < channels.size(); ++i) { Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->displayPosition() == i) { sortedChannels << channel; break; } } } Q_ASSERT(channels.size() == sortedChannels.size()); return sortedChannels; } /** * User-friendly name for this channel for presentation purposes in the gui */ inline QString name() const { return m_name; } /** * @return the position of the first byte of the channel in the pixel */ inline qint32 pos() const { return m_pos; } /** * @return the displayPosition of the channel in the pixel */ inline qint32 displayPosition() const { return m_displayPosition; } /** * @return the number of bytes this channel takes */ inline qint32 size() const { return m_size; } /** * @return the type of the channel */ inline enumChannelType channelType() const { return m_channelType; } /** * @return the type of the value of the channel (float, uint8 or uint16) */ inline enumChannelValueType channelValueType() const { return m_channelValueType; } /** * This is a color that can be used to represent this channel in histograms and so. * By default this is black, so keep in mind that many channels might look the same */ inline QColor color() const { return m_color; } /** * A channel is less than another channel if its pos is smaller. */ inline bool operator<(const KoChannelInfo & info) { return m_pos < info.m_pos; } /** * Gets the minimum value that this channel should have. * This is suitable for UI use. */ inline double getUIMin(void) const { return m_uiMinMax.minVal; } /** * Gets the minimum value that this channel should have. * This is suitable for UI use. */ inline double getUIMax(void) const { return m_uiMinMax.maxVal; } + /** + * @brief getUIUnitValue + * Gets the unit value for this channel. + * This is suitable for converting between representations. + */ + inline double getUIUnitValue(void) const { + return m_uiMinMax.maxVal - m_uiMinMax.minVal; + } + private: QString m_name; qint32 m_pos; qint32 m_displayPosition; enumChannelType m_channelType; enumChannelValueType m_channelValueType; qint32 m_size; QColor m_color; DoubleRange m_uiMinMax; }; #endif // KOCHANNELINFO_H_ diff --git a/libs/pigment/KoCmykColorSpaceMaths.cpp b/libs/pigment/KoCmykColorSpaceMaths.cpp new file mode 100644 index 0000000000..f203e35f2d --- /dev/null +++ b/libs/pigment/KoCmykColorSpaceMaths.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2017,2020 L. E. Segovia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include + +#include + +#include + +#ifdef HAVE_OPENEXR +const half KoCmykColorSpaceMathsTraits::zeroValueCMYK = 0.0; +const half KoCmykColorSpaceMathsTraits::unitValueCMYK = 100.0; +const half KoCmykColorSpaceMathsTraits::halfValueCMYK = 50.0; +#endif + +const float KoCmykColorSpaceMathsTraits::zeroValueCMYK = 0.0; +const float KoCmykColorSpaceMathsTraits::unitValueCMYK = 100.0; +const float KoCmykColorSpaceMathsTraits::halfValueCMYK = 50.0; + +const double KoCmykColorSpaceMathsTraits::zeroValueCMYK = 0.0; +const double KoCmykColorSpaceMathsTraits::unitValueCMYK = 100.0; +const double KoCmykColorSpaceMathsTraits::halfValueCMYK = 50.0; diff --git a/libs/pigment/KoCmykColorSpaceMaths.h b/libs/pigment/KoCmykColorSpaceMaths.h new file mode 100644 index 0000000000..46c3a89d5d --- /dev/null +++ b/libs/pigment/KoCmykColorSpaceMaths.h @@ -0,0 +1,740 @@ +/* + * Copyright (c) 2006,2007,2010 Cyrille Berger + * Copyright (c) 2017,2020 L. E. Segovia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KOCMYKCOLORSPACEMATHS_H_ +#define KOCMYKCOLORSPACEMATHS_H_ + +#include +#include + +#include "kritapigment_export.h" +#include +#include "KoChannelInfo.h" +#include "KoLut.h" + +#include + +#undef _T + +/** + * This is an empty mainWindow that needs to be "specialized" for each possible + * numerical type (quint8, quint16...). + * + * It needs to defines some static constant fields : + * - zeroValue : the zero for this numerical type + * - unitValue : the maximum value of the normal dynamic range + * - max : the maximum value + * - min : the minimum value + * - epsilon : a value close to zero but different of zero + * - bits : the bit depth + * + * And some types : + * - compositetype the type used for composite operations (usually one with + * a higher bit depth) + * + * This class is specialized to handle the floating point bounds of the CMYK color space. + */ +template +class KoCmykColorSpaceMathsTraits +{ +public: +}; + +/** + * No changes for integer color spaces. + */ +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +}; + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +}; + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +}; + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +}; + +#include +#ifdef HAVE_OPENEXR +#include + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const half zeroValueCMYK; + static const half unitValueCMYK; + static const half halfValueCMYK; +}; +#endif + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const float zeroValueCMYK; + static const float unitValueCMYK; + static const float halfValueCMYK; +}; + +template<> +class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const double zeroValueCMYK; + static const double unitValueCMYK; + static const double halfValueCMYK; +}; + +//template +//struct KoIntegerToFloat { +// inline float operator()(_T_ f) const +// { +// return f / float(KoColorSpaceMathsTraits<_T_>::max); +// } +//}; + +//struct KoLuts { + +// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint16> Uint16ToFloat; +// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint8> Uint8ToFloat; +//}; + +///** +// * This class defines some elementary operations used by various color +// * space. It's intended to be generic, but some specialization exists +// * either for optimization or just for being buildable. +// * +// * @param _T some numerical type with an existing trait +// * @param _Tdst some other numerical type with an existing trait, it is +// * only needed if different of _T +// */ +//template < typename _T, typename _Tdst = _T > +//class KoColorSpaceMaths +//{ +// typedef KoColorSpaceMathsTraits<_T> traits; +// typedef typename traits::compositetype src_compositetype; +// typedef typename KoColorSpaceMathsTraits<_Tdst>::compositetype dst_compositetype; + +//public: +// inline static _Tdst multiply(_T a, _Tdst b) { +// return (dst_compositetype(a)*b) / KoColorSpaceMathsTraits<_Tdst>::unitValue; +// } + +// inline static _Tdst multiply(_T a, _Tdst b, _Tdst c) { +// return (dst_compositetype(a)*b*c) / (dst_compositetype(KoColorSpaceMathsTraits<_Tdst>::unitValue) * KoColorSpaceMathsTraits<_T>::unitValue); +// } + +// /** +// * Division : (a * MAX ) / b +// * @param a +// * @param b +// */ +// inline static dst_compositetype divide(_T a, _Tdst b) { +// return (dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue) / b; +// } + +// /** +// * Inversion : unitValue - a +// * @param a +// */ +// inline static _T invert(_T a) { +// return traits::unitValue - a; +// } + +// /** +// * Blending : (a * alpha) + b * (1 - alpha) +// * @param a +// * @param b +// * @param alpha +// */ +// inline static _T blend(_T a, _T b, _T alpha) { +// src_compositetype c = ((src_compositetype(a) - b) * alpha) / traits::unitValue; +// return c + b; +// } + +// /** +// * This function will scale a value of type _T to fit into a _Tdst. +// */ +// inline static _Tdst scaleToA(_T a) { +// return _Tdst(dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue / KoColorSpaceMathsTraits<_T>::unitValue); +// } + +// inline static dst_compositetype clamp(dst_compositetype val) { +// return qBound(KoColorSpaceMathsTraits<_Tdst>::min, val, KoColorSpaceMathsTraits<_Tdst>::max); +// } + +// /** +// * Clamps the composite type on higher border only. That is a fast path +// * for scale-only transformations +// */ +// inline static _Tdst clampAfterScale(dst_compositetype val) { +// return qMin(val, KoColorSpaceMathsTraits<_Tdst>::max); +// } +//}; + +////------------------------------ double specialization ------------------------------// +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(double a) +//{ +// double v = a * 255; +// return float2int(CLAMP(v, 0, 255)); +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return KoLuts::Uint8ToFloat(a); +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(double a) +//{ +// double v = a * 0xFFFF; +// return float2int(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return KoLuts::Uint16ToFloat(a); +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + +////------------------------------ float specialization ------------------------------// + +//template<> +//inline float KoColorSpaceMaths::scaleToA(double a) +//{ +// return (float)a; +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(float a) +//{ +// return a; +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(float a) +//{ +// float v = a * 0xFFFF; +// return (quint16)float2int(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return KoLuts::Uint16ToFloat(a); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(float a) +//{ +// float v = a * 255; +// return (quint8)float2int(CLAMP(v, 0, 255)); +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return KoLuts::Uint8ToFloat(a); +//} + +//template<> +//inline float KoColorSpaceMaths::blend(float a, float b, float alpha) +//{ +// return (a - b) * alpha + b; +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + +////------------------------------ half specialization ------------------------------// + +//#ifdef HAVE_OPENEXR + +//template<> +//inline half KoColorSpaceMaths::scaleToA(double a) +//{ +// return (half)a; +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(float a) +//{ +// return (half) a; +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(half a) +//{ +// half v = a * 255; +// return (quint8)(CLAMP(v, 0, 255)); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return a *(1.0 / 255.0); +//} +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(half a) +//{ +// double v = a * 0xFFFF; +// return (quint16)(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return a *(1.0 / 0xFFFF); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline half KoColorSpaceMaths::blend(half a, half b, half alpha) +//{ +// return (a - b) * alpha + b; +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + + +//#endif + +////------------------------------ quint8 specialization ------------------------------// + +//template<> +//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b) +//{ +// return (quint8)UINT8_MULT(a, b); +//} + + +//template<> +//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b, quint8 c) +//{ +// return (quint8)UINT8_MULT3(a, b, c); +//} + +//template<> +//inline KoColorSpaceMathsTraits::compositetype +//KoColorSpaceMaths::divide(quint8 a, quint8 b) +//{ +// return UINT8_DIVIDE(a, b); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::invert(quint8 a) +//{ +// return ~a; +//} + +//template<> +//inline quint8 KoColorSpaceMaths::blend(quint8 a, quint8 b, quint8 c) +//{ +// return UINT8_BLEND(a, b, c); +//} + +////------------------------------ quint16 specialization ------------------------------// + +//template<> +//inline quint16 KoColorSpaceMaths::multiply(quint16 a, quint16 b) +//{ +// return (quint16)UINT16_MULT(a, b); +//} + +//template<> +//inline KoColorSpaceMathsTraits::compositetype +//KoColorSpaceMaths::divide(quint16 a, quint16 b) +//{ +// return UINT16_DIVIDE(a, b); +//} + +//template<> +//inline quint16 KoColorSpaceMaths::invert(quint16 a) +//{ +// return ~a; +//} + +////------------------------------ various specialization ------------------------------// + + +//// TODO: use more functions from KoIntegersMaths to do the computation + +///// This specialization is needed because the default implementation won't work when scaling up +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return UINT8_TO_UINT16(a); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return UINT16_TO_UINT8(a); +//} + + +//// Due to once again a bug in gcc, there is the need for those specialized functions: + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return a; +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return a; +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(float a) +//{ +// return a; +//} + +//namespace Arithmetic +//{ +// const static qreal pi = 3.14159265358979323846; + +// template +// inline T mul(T a, T b) { return KoColorSpaceMaths::multiply(a, b); } + +// template +// inline T mul(T a, T b, T c) { return KoColorSpaceMaths::multiply(a, b, c); } + +//// template +//// inline T mul(T a, T b) { +//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +//// return T(composite_type(a) * b / KoColorSpaceMathsTraits::unitValue); +//// } +//// +//// template +//// inline T mul(T a, T b, T c) { +//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +//// return T((composite_type(a) * b * c) / (composite_type(KoColorSpaceMathsTraits::unitValue) * KoColorSpaceMathsTraits::unitValue)); +//// } + +// template +// inline T inv(T a) { return KoColorSpaceMaths::invert(a); } + +// template +// inline T lerp(T a, T b, T alpha) { return KoColorSpaceMaths::blend(b, a, alpha); } + +// template +// inline TRet scale(T a) { return KoColorSpaceMaths::scaleToA(a); } + +// template +// inline typename KoColorSpaceMathsTraits::compositetype +// div(T a, T b) { return KoColorSpaceMaths::divide(a, b); } + +// template +// inline T clamp(typename KoColorSpaceMathsTraits::compositetype a) { +// return KoColorSpaceMaths::clamp(a); +// } + +// template +// inline T min(T a, T b, T c) { +// b = (a < b) ? a : b; +// return (b < c) ? b : c; +// } + +// template +// inline T max(T a, T b, T c) { +// b = (a > b) ? a : b; +// return (b > c) ? b : c; +// } + +// template +// inline T zeroValue() { return KoColorSpaceMathsTraits::zeroValue; } + +// template +// inline T halfValue() { return KoColorSpaceMathsTraits::halfValue; } + +// template +// inline T unitValue() { return KoColorSpaceMathsTraits::unitValue; } + +// template +// inline T unionShapeOpacity(T a, T b) { +// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +// return T(composite_type(a) + b - mul(a,b)); +// } + +// template +// inline T blend(T src, T srcAlpha, T dst, T dstAlpha, T cfValue) { +// return mul(inv(srcAlpha), dstAlpha, dst) + mul(inv(dstAlpha), srcAlpha, src) + mul(dstAlpha, srcAlpha, cfValue); +// } +//} + +//struct HSYType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return TReal(0.299)*r + TReal(0.587)*g + TReal(0.114)*b; +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// return Arithmetic::max(r,g,b) - Arithmetic::min(r,g,b); +// } +//}; + +//struct HSIType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return (r + g + b) * TReal(0.33333333333333333333); // (r + g + b) / 3.0 +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// TReal chroma = max - min; + +// return (chroma > std::numeric_limits::epsilon()) ? +// (TReal(1.0) - min / getLightness(r, g, b)) : TReal(0.0); +// } +//}; + +//struct HSLType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// return (max + min) * TReal(0.5); +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// TReal chroma = max - min; +// TReal light = (max + min) * TReal(0.5); +// TReal div = TReal(1.0) - std::abs(TReal(2.0)*light - TReal(1.0)); + +// if(div > std::numeric_limits::epsilon()) +// return chroma / div; + +// return TReal(1.0); +// } +//}; + +//struct HSVType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return Arithmetic::max(r,g,b); +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// return (max == TReal(0.0)) ? TReal(0.0) : (max - min) / max; +// } +//}; + +//template +//TReal getHue(TReal r, TReal g, TReal b) { +// TReal min = Arithmetic::min(r, g, b); +// TReal max = Arithmetic::max(r, g, b); +// TReal chroma = max - min; + +// TReal hue = TReal(-1.0); + +// if(chroma > std::numeric_limits::epsilon()) { + +//// return atan2(TReal(2.0)*r - g - b, TReal(1.73205080756887729353)*(g - b)); + +// if(max == r) // between yellow and magenta +// hue = (g - b) / chroma; +// else if(max == g) // between cyan and yellow +// hue = TReal(2.0) + (b - r) / chroma; +// else if(max == b) // between magenta and cyan +// hue = TReal(4.0) + (r - g) / chroma; + +// if(hue < -std::numeric_limits::epsilon()) +// hue += TReal(6.0); + +// hue /= TReal(6.0); +// } + +//// hue = (r == max) ? (b-g) : (g == max) ? TReal(2.0)+(r-b) : TReal(4.0)+(g-r); + +// return hue; +//} + +//template +//void getRGB(TReal& r, TReal& g, TReal& b, TReal hue) { +// // 0 red -> (1,0,0) +// // 1 yellow -> (1,1,0) +// // 2 green -> (0,1,0) +// // 3 cyan -> (0,1,1) +// // 4 blue -> (0,0,1) +// // 5 maenta -> (1,0,1) +// // 6 red -> (1,0,0) + +// if(hue < -std::numeric_limits::epsilon()) { +// r = g = b = TReal(0.0); +// return; +// } + +// int i = int(hue * TReal(6.0)); +// TReal x = hue * TReal(6.0) - i; +// TReal y = TReal(1.0) - x; + +// switch(i % 6){ +// case 0: { r=TReal(1.0), g=x , b=TReal(0.0); } break; +// case 1: { r=y , g=TReal(1.0), b=TReal(0.0); } break; +// case 2: { r=TReal(0.0), g=TReal(1.0), b=x ; } break; +// case 3: { r=TReal(0.0), g=y , b=TReal(1.0); } break; +// case 4: { r=x , g=TReal(0.0), b=TReal(1.0); } break; +// case 5: { r=TReal(1.0), g=TReal(0.0), b=y ; } break; +// } +//} + +//template +//inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return HSXType::getLightness(r, g, b); +//} + +//template +//inline void addLightness(TReal& r, TReal& g, TReal& b, TReal light) +//{ +// using namespace Arithmetic; + +// r += light; +// g += light; +// b += light; + +// TReal l = HSXType::getLightness(r, g, b); +// TReal n = min(r, g, b); +// TReal x = max(r, g, b); + +// if(n < TReal(0.0)) { +// TReal iln = TReal(1.0) / (l-n); +// r = l + ((r-l) * l) * iln; +// g = l + ((g-l) * l) * iln; +// b = l + ((b-l) * l) * iln; +// } + +// if(x > TReal(1.0) && (x-l) > std::numeric_limits::epsilon()) { +// TReal il = TReal(1.0) - l; +// TReal ixl = TReal(1.0) / (x - l); +// r = l + ((r-l) * il) * ixl; +// g = l + ((g-l) * il) * ixl; +// b = l + ((b-l) * il) * ixl; +// } +//} + +//template +//inline void setLightness(TReal& r, TReal& g, TReal& b, TReal light) +//{ +// addLightness(r,g,b, light - HSXType::getLightness(r,g,b)); +//} + +//template +//inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// return HSXType::getSaturation(r, g, b); +//} + +//template +//inline void setSaturation(TReal& r, TReal& g, TReal& b, TReal sat) +//{ +// int min = 0; +// int mid = 1; +// int max = 2; +// TReal rgb[3] = {r, g, b}; + +// if(rgb[mid] < rgb[min]) { +// int tmp = min; +// min = mid; +// mid = tmp; +// } + +// if(rgb[max] < rgb[mid]) { +// int tmp = mid; +// mid = max; +// max = tmp; +// } + +// if(rgb[mid] < rgb[min]) { +// int tmp = min; +// min = mid; +// mid = tmp; +// } + +// if((rgb[max] - rgb[min]) > TReal(0.0)) { +// rgb[mid] = ((rgb[mid]-rgb[min]) * sat) / (rgb[max]-rgb[min]); +// rgb[max] = sat; +// rgb[min] = TReal(0.0); + +// r = rgb[0]; +// g = rgb[1]; +// b = rgb[2]; +// } +// else r = g = b = TReal(0.0); +//} + +#endif diff --git a/libs/pigment/KoCmykColorSpaceTraits.h b/libs/pigment/KoCmykColorSpaceTraits.h index ec1f9dbb7b..5dc8c24006 100644 --- a/libs/pigment/KoCmykColorSpaceTraits.h +++ b/libs/pigment/KoCmykColorSpaceTraits.h @@ -1,238 +1,326 @@ /* * Copyright (c) 2006-2007 Cyrille Berger - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016,2017,2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_CMYK_COLORSPACE_TRAITS_H_ #define _KO_CMYK_COLORSPACE_TRAITS_H_ -#include - -#include "KoColorSpaceConstants.h" -#include "KoColorSpaceMaths.h" -#include "DebugPigment.h" - +#include /** * Base class for CMYK traits, it provides some convenient functions to * access CMYK channels through an explicit API. */ template struct KoCmykTraits : public KoColorSpaceTrait<_channels_type_, 5, 4> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 5, 4> parent; static const qint32 c_pos = 0; static const qint32 m_pos = 1; static const qint32 y_pos = 2; static const qint32 k_pos = 3; /** * An CMYK pixel */ struct Pixel { channels_type cyan; channels_type magenta; channels_type yellow; channels_type black; channels_type alpha; }; /// @return the Cyan component inline static channels_type C(quint8* data) { channels_type* d = parent::nativeArray(data); return d[c_pos]; } /// Set the Cyan component inline static void setC(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[c_pos] = nv; } /// @return the Magenta component inline static channels_type M(quint8* data) { channels_type* d = parent::nativeArray(data); return d[m_pos]; } /// Set the Magenta component inline static void setM(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[m_pos] = nv; } /// @return the Yellow component inline static channels_type Y(quint8* data) { channels_type* d = parent::nativeArray(data); return d[y_pos]; } /// Set the Yellow component inline static void setY(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[y_pos] = nv; } /// @return the Key component inline static channels_type k(quint8* data) { channels_type* d = parent::nativeArray(data); return d[k_pos]; } /// Set the Key component inline static void setK(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[k_pos] = nv; } }; struct KoCmykU8Traits : public KoCmykTraits { }; struct KoCmykU16Traits : public KoCmykTraits { }; #include #ifdef HAVE_OPENEXR #include struct KoCmykF16Traits : public KoCmykTraits { - static constexpr float MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK)); + case 4: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 4: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: b = qBound((float)0, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_CMYK); + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK * values[i], + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK); break; default: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoCmykColorSpaceMathsTraits::min, + (float)KoCmykColorSpaceMathsTraits::unitValue * values[i], + (float)KoCmykColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif struct KoCmykF32Traits : public KoCmykTraits { - static constexpr float MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK)); + case 4: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 4: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: b = qBound((float)0, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_CMYK); + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK * values[i], + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK); break; default: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoCmykColorSpaceMathsTraits::min, + (float)KoCmykColorSpaceMathsTraits::unitValue * values[i], + (float)KoCmykColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; struct KoCmykF64Traits : public KoCmykTraits { - static constexpr double MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK)); + case 4: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case c_pos: + case m_pos: + case y_pos: + case k_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValueCMYK, + (qreal)KoCmykColorSpaceMathsTraits::unitValueCMYK); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 4: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoCmykColorSpaceMathsTraits::unitValue, + (qreal)KoCmykColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: - b = qBound((double)0, - (double)KoColorSpaceMathsTraits::unitValue * values[i], - (double)MAX_CHANNEL_CMYK); + b = qBound((float)0, + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK * values[i], + (float)KoCmykColorSpaceMathsTraits::unitValueCMYK); break; default: - b = qBound((double)KoColorSpaceMathsTraits::min, - (double)KoColorSpaceMathsTraits::unitValue * values[i], - (double)KoColorSpaceMathsTraits::max); + b = qBound((float)KoCmykColorSpaceMathsTraits::min, + (float)KoCmykColorSpaceMathsTraits::unitValue * values[i], + (float)KoCmykColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif diff --git a/libs/pigment/KoLabColorSpaceMaths.cpp b/libs/pigment/KoLabColorSpaceMaths.cpp new file mode 100644 index 0000000000..12054323bb --- /dev/null +++ b/libs/pigment/KoLabColorSpaceMaths.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2017,2020 L. E. Segovia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include + +#include + +#include + +#ifdef HAVE_OPENEXR +const half KoLabColorSpaceMathsTraits::zeroValueL = 0.0; +const half KoLabColorSpaceMathsTraits::unitValueL = 100.0; +const half KoLabColorSpaceMathsTraits::halfValueL = 50.0; +const half KoLabColorSpaceMathsTraits::zeroValueAB = -128.0; +const half KoLabColorSpaceMathsTraits::unitValueAB = +127.0; +const half KoLabColorSpaceMathsTraits::halfValueAB = 0.0; +#endif + +const float KoLabColorSpaceMathsTraits::zeroValueL = 0.0; +const float KoLabColorSpaceMathsTraits::unitValueL = 100.0; +const float KoLabColorSpaceMathsTraits::halfValueL = 50.0; +const float KoLabColorSpaceMathsTraits::zeroValueAB = -128.0; +const float KoLabColorSpaceMathsTraits::unitValueAB = +127.0; +const float KoLabColorSpaceMathsTraits::halfValueAB = 0.0; + +const double KoLabColorSpaceMathsTraits::zeroValueL = 0.0; +const double KoLabColorSpaceMathsTraits::unitValueL = 100.0; +const double KoLabColorSpaceMathsTraits::halfValueL = 50.0; +const double KoLabColorSpaceMathsTraits::zeroValueAB = -128.0; +const double KoLabColorSpaceMathsTraits::unitValueAB = +127.0; +const double KoLabColorSpaceMathsTraits::halfValueAB = 0.0; diff --git a/libs/pigment/KoLabColorSpaceMaths.h b/libs/pigment/KoLabColorSpaceMaths.h new file mode 100644 index 0000000000..2a3b6dd4f7 --- /dev/null +++ b/libs/pigment/KoLabColorSpaceMaths.h @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2006,2007,2010 Cyrille Berger + * Copyright (c) 2017,2020 L. E. Segovia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KOLABCOLORSPACEMATHS_H_ +#define KOLABCOLORSPACEMATHS_H_ + +#include +#include + +#include "kritapigment_export.h" +#include +#include "KoChannelInfo.h" +#include "KoLut.h" + +#include + +#undef _T + +/** + * This is an empty mainWindow that needs to be "specialized" for each possible + * numerical type (quint8, quint16...). + * + * It needs to defines some static constant fields : + * - zeroValue : the zero for this numerical type + * - unitValue : the maximum value of the normal dynamic range + * - max : the maximum value + * - min : the minimum value + * - epsilon : a value close to zero but different of zero + * - bits : the bit depth + * + * And some types : + * - compositetype the type used for composite operations (usually one with + * a higher bit depth) + * + * This class is specialized to handle the floating point bounds of the Lab color space. + */ + +template +class KoLabColorSpaceMathsTraits +{ +public: +}; + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const quint8 zeroValueL = 0; + static const quint8 unitValueL = 0x00FF; + static const quint8 halfValueL = 0x00FF / 2; + static const quint8 zeroValueAB = 0; + static const quint8 unitValueAB = 0x00FF; + static const quint8 halfValueAB = 0x00FF / 2; +}; + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const quint16 zeroValueL = 0; + static const quint16 unitValueL = 0xFFFF; + static const quint16 halfValueL = 0xFFFF / 2; + static const quint16 zeroValueAB = 0; + static const quint16 unitValueAB = 0xFFFF; + static const quint16 halfValueAB = 0xFFFF / 2; +}; + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const qint16 zeroValueL = 0; + static const qint16 unitValueL = 32767; + static const qint16 halfValueL = 32767 / 2; + static const qint16 zeroValueAB = 0; + static const qint16 unitValueAB = 32767; + static const qint16 halfValueAB = 32767 / 2; +}; + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const quint32 zeroValueL = 0; + static const quint32 unitValueL = 0xFFFFFFFF; + static const quint32 halfValueL = 0xFFFFFFFF / 2; + static const quint32 zeroValueAB = 0; + static const quint32 unitValueAB = 0xFFFFFFFF; + static const quint32 halfValueAB = 0xFFFFFFFF / 2; +}; + +#include +#ifdef HAVE_OPENEXR +#include + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const half zeroValueL; + static const half unitValueL; + static const half halfValueL; + static const half zeroValueAB; + static const half unitValueAB; + static const half halfValueAB; +}; +#endif + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const float zeroValueL; + static const float unitValueL; + static const float halfValueL; + static const float zeroValueAB; + static const float unitValueAB; + static const float halfValueAB; +}; + +template<> +class KRITAPIGMENT_EXPORT KoLabColorSpaceMathsTraits : public KoColorSpaceMathsTraits +{ +public: + static const double zeroValueL; + static const double unitValueL; + static const double halfValueL; + static const double zeroValueAB; + static const double unitValueAB; + static const double halfValueAB; +}; + +//template +//struct KoIntegerToFloat { +// inline float operator()(_T_ f) const +// { +// return f / float(KoColorSpaceMathsTraits<_T_>::max); +// } +//}; + +//struct KoLuts { + +// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint16> Uint16ToFloat; +// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint8> Uint8ToFloat; +//}; + +///** +// * This class defines some elementary operations used by various color +// * space. It's intended to be generic, but some specialization exists +// * either for optimization or just for being buildable. +// * +// * @param _T some numerical type with an existing trait +// * @param _Tdst some other numerical type with an existing trait, it is +// * only needed if different of _T +// */ +//template < typename _T, typename _Tdst = _T > +//class KoColorSpaceMaths +//{ +// typedef KoColorSpaceMathsTraits<_T> traits; +// typedef typename traits::compositetype src_compositetype; +// typedef typename KoColorSpaceMathsTraits<_Tdst>::compositetype dst_compositetype; + +//public: +// inline static _Tdst multiply(_T a, _Tdst b) { +// return (dst_compositetype(a)*b) / KoColorSpaceMathsTraits<_Tdst>::unitValue; +// } + +// inline static _Tdst multiply(_T a, _Tdst b, _Tdst c) { +// return (dst_compositetype(a)*b*c) / (dst_compositetype(KoColorSpaceMathsTraits<_Tdst>::unitValue) * KoColorSpaceMathsTraits<_T>::unitValue); +// } + +// /** +// * Division : (a * MAX ) / b +// * @param a +// * @param b +// */ +// inline static dst_compositetype divide(_T a, _Tdst b) { +// return (dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue) / b; +// } + +// /** +// * Inversion : unitValue - a +// * @param a +// */ +// inline static _T invert(_T a) { +// return traits::unitValue - a; +// } + +// /** +// * Blending : (a * alpha) + b * (1 - alpha) +// * @param a +// * @param b +// * @param alpha +// */ +// inline static _T blend(_T a, _T b, _T alpha) { +// src_compositetype c = ((src_compositetype(a) - b) * alpha) / traits::unitValue; +// return c + b; +// } + +// /** +// * This function will scale a value of type _T to fit into a _Tdst. +// */ +// inline static _Tdst scaleToA(_T a) { +// return _Tdst(dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue / KoColorSpaceMathsTraits<_T>::unitValue); +// } + +// inline static dst_compositetype clamp(dst_compositetype val) { +// return qBound(KoColorSpaceMathsTraits<_Tdst>::min, val, KoColorSpaceMathsTraits<_Tdst>::max); +// } + +// /** +// * Clamps the composite type on higher border only. That is a fast path +// * for scale-only transformations +// */ +// inline static _Tdst clampAfterScale(dst_compositetype val) { +// return qMin(val, KoColorSpaceMathsTraits<_Tdst>::max); +// } +//}; + +////------------------------------ double specialization ------------------------------// +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(double a) +//{ +// double v = a * 255; +// return float2int(CLAMP(v, 0, 255)); +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return KoLuts::Uint8ToFloat(a); +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(double a) +//{ +// double v = a * 0xFFFF; +// return float2int(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return KoLuts::Uint16ToFloat(a); +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + +////------------------------------ float specialization ------------------------------// + +//template<> +//inline float KoColorSpaceMaths::scaleToA(double a) +//{ +// return (float)a; +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(float a) +//{ +// return a; +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(float a) +//{ +// float v = a * 0xFFFF; +// return (quint16)float2int(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return KoLuts::Uint16ToFloat(a); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(float a) +//{ +// float v = a * 255; +// return (quint8)float2int(CLAMP(v, 0, 255)); +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return KoLuts::Uint8ToFloat(a); +//} + +//template<> +//inline float KoColorSpaceMaths::blend(float a, float b, float alpha) +//{ +// return (a - b) * alpha + b; +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + +////------------------------------ half specialization ------------------------------// + +//#ifdef HAVE_OPENEXR + +//template<> +//inline half KoColorSpaceMaths::scaleToA(double a) +//{ +// return (half)a; +//} + +//template<> +//inline double KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(float a) +//{ +// return (half) a; +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(half a) +//{ +// half v = a * 255; +// return (quint8)(CLAMP(v, 0, 255)); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return a *(1.0 / 255.0); +//} +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(half a) +//{ +// double v = a * 0xFFFF; +// return (quint16)(CLAMP(v, 0, 0xFFFF)); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return a *(1.0 / 0xFFFF); +//} + +//template<> +//inline half KoColorSpaceMaths::scaleToA(half a) +//{ +// return a; +//} + +//template<> +//inline half KoColorSpaceMaths::blend(half a, half b, half alpha) +//{ +// return (a - b) * alpha + b; +//} + +//template<> +//inline double KoColorSpaceMaths::clamp(double a) +//{ +// return a; +//} + + +//#endif + +////------------------------------ quint8 specialization ------------------------------// + +//template<> +//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b) +//{ +// return (quint8)UINT8_MULT(a, b); +//} + + +//template<> +//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b, quint8 c) +//{ +// return (quint8)UINT8_MULT3(a, b, c); +//} + +//template<> +//inline KoColorSpaceMathsTraits::compositetype +//KoColorSpaceMaths::divide(quint8 a, quint8 b) +//{ +// return UINT8_DIVIDE(a, b); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::invert(quint8 a) +//{ +// return ~a; +//} + +//template<> +//inline quint8 KoColorSpaceMaths::blend(quint8 a, quint8 b, quint8 c) +//{ +// return UINT8_BLEND(a, b, c); +//} + +////------------------------------ quint16 specialization ------------------------------// + +//template<> +//inline quint16 KoColorSpaceMaths::multiply(quint16 a, quint16 b) +//{ +// return (quint16)UINT16_MULT(a, b); +//} + +//template<> +//inline KoColorSpaceMathsTraits::compositetype +//KoColorSpaceMaths::divide(quint16 a, quint16 b) +//{ +// return UINT16_DIVIDE(a, b); +//} + +//template<> +//inline quint16 KoColorSpaceMaths::invert(quint16 a) +//{ +// return ~a; +//} + +////------------------------------ various specialization ------------------------------// + + +//// TODO: use more functions from KoIntegersMaths to do the computation + +///// This specialization is needed because the default implementation won't work when scaling up +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return UINT8_TO_UINT16(a); +//} + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return UINT16_TO_UINT8(a); +//} + + +//// Due to once again a bug in gcc, there is the need for those specialized functions: + +//template<> +//inline quint8 KoColorSpaceMaths::scaleToA(quint8 a) +//{ +// return a; +//} + +//template<> +//inline quint16 KoColorSpaceMaths::scaleToA(quint16 a) +//{ +// return a; +//} + +//template<> +//inline float KoColorSpaceMaths::scaleToA(float a) +//{ +// return a; +//} + +//namespace Arithmetic +//{ +// const static qreal pi = 3.14159265358979323846; + +// template +// inline T mul(T a, T b) { return KoColorSpaceMaths::multiply(a, b); } + +// template +// inline T mul(T a, T b, T c) { return KoColorSpaceMaths::multiply(a, b, c); } + +//// template +//// inline T mul(T a, T b) { +//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +//// return T(composite_type(a) * b / KoColorSpaceMathsTraits::unitValue); +//// } +//// +//// template +//// inline T mul(T a, T b, T c) { +//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +//// return T((composite_type(a) * b * c) / (composite_type(KoColorSpaceMathsTraits::unitValue) * KoColorSpaceMathsTraits::unitValue)); +//// } + +// template +// inline T inv(T a) { return KoColorSpaceMaths::invert(a); } + +// template +// inline T lerp(T a, T b, T alpha) { return KoColorSpaceMaths::blend(b, a, alpha); } + +// template +// inline TRet scale(T a) { return KoColorSpaceMaths::scaleToA(a); } + +// template +// inline typename KoColorSpaceMathsTraits::compositetype +// div(T a, T b) { return KoColorSpaceMaths::divide(a, b); } + +// template +// inline T clamp(typename KoColorSpaceMathsTraits::compositetype a) { +// return KoColorSpaceMaths::clamp(a); +// } + +// template +// inline T min(T a, T b, T c) { +// b = (a < b) ? a : b; +// return (b < c) ? b : c; +// } + +// template +// inline T max(T a, T b, T c) { +// b = (a > b) ? a : b; +// return (b > c) ? b : c; +// } + +// template +// inline T zeroValue() { return KoColorSpaceMathsTraits::zeroValue; } + +// template +// inline T halfValue() { return KoColorSpaceMathsTraits::halfValue; } + +// template +// inline T unitValue() { return KoColorSpaceMathsTraits::unitValue; } + +// template +// inline T unionShapeOpacity(T a, T b) { +// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; +// return T(composite_type(a) + b - mul(a,b)); +// } + +// template +// inline T blend(T src, T srcAlpha, T dst, T dstAlpha, T cfValue) { +// return mul(inv(srcAlpha), dstAlpha, dst) + mul(inv(dstAlpha), srcAlpha, src) + mul(dstAlpha, srcAlpha, cfValue); +// } +//} + +//struct HSYType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return TReal(0.299)*r + TReal(0.587)*g + TReal(0.114)*b; +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// return Arithmetic::max(r,g,b) - Arithmetic::min(r,g,b); +// } +//}; + +//struct HSIType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return (r + g + b) * TReal(0.33333333333333333333); // (r + g + b) / 3.0 +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// TReal chroma = max - min; + +// return (chroma > std::numeric_limits::epsilon()) ? +// (TReal(1.0) - min / getLightness(r, g, b)) : TReal(0.0); +// } +//}; + +//struct HSLType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// return (max + min) * TReal(0.5); +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// TReal chroma = max - min; +// TReal light = (max + min) * TReal(0.5); +// TReal div = TReal(1.0) - std::abs(TReal(2.0)*light - TReal(1.0)); + +// if(div > std::numeric_limits::epsilon()) +// return chroma / div; + +// return TReal(1.0); +// } +//}; + +//struct HSVType +//{ +// template +// inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return Arithmetic::max(r,g,b); +// } + +// template +// inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// TReal max = Arithmetic::max(r, g, b); +// TReal min = Arithmetic::min(r, g, b); +// return (max == TReal(0.0)) ? TReal(0.0) : (max - min) / max; +// } +//}; + +//template +//TReal getHue(TReal r, TReal g, TReal b) { +// TReal min = Arithmetic::min(r, g, b); +// TReal max = Arithmetic::max(r, g, b); +// TReal chroma = max - min; + +// TReal hue = TReal(-1.0); + +// if(chroma > std::numeric_limits::epsilon()) { + +//// return atan2(TReal(2.0)*r - g - b, TReal(1.73205080756887729353)*(g - b)); + +// if(max == r) // between yellow and magenta +// hue = (g - b) / chroma; +// else if(max == g) // between cyan and yellow +// hue = TReal(2.0) + (b - r) / chroma; +// else if(max == b) // between magenta and cyan +// hue = TReal(4.0) + (r - g) / chroma; + +// if(hue < -std::numeric_limits::epsilon()) +// hue += TReal(6.0); + +// hue /= TReal(6.0); +// } + +//// hue = (r == max) ? (b-g) : (g == max) ? TReal(2.0)+(r-b) : TReal(4.0)+(g-r); + +// return hue; +//} + +//template +//void getRGB(TReal& r, TReal& g, TReal& b, TReal hue) { +// // 0 red -> (1,0,0) +// // 1 yellow -> (1,1,0) +// // 2 green -> (0,1,0) +// // 3 cyan -> (0,1,1) +// // 4 blue -> (0,0,1) +// // 5 maenta -> (1,0,1) +// // 6 red -> (1,0,0) + +// if(hue < -std::numeric_limits::epsilon()) { +// r = g = b = TReal(0.0); +// return; +// } + +// int i = int(hue * TReal(6.0)); +// TReal x = hue * TReal(6.0) - i; +// TReal y = TReal(1.0) - x; + +// switch(i % 6){ +// case 0: { r=TReal(1.0), g=x , b=TReal(0.0); } break; +// case 1: { r=y , g=TReal(1.0), b=TReal(0.0); } break; +// case 2: { r=TReal(0.0), g=TReal(1.0), b=x ; } break; +// case 3: { r=TReal(0.0), g=y , b=TReal(1.0); } break; +// case 4: { r=x , g=TReal(0.0), b=TReal(1.0); } break; +// case 5: { r=TReal(1.0), g=TReal(0.0), b=y ; } break; +// } +//} + +//template +//inline static TReal getLightness(TReal r, TReal g, TReal b) { +// return HSXType::getLightness(r, g, b); +//} + +//template +//inline void addLightness(TReal& r, TReal& g, TReal& b, TReal light) +//{ +// using namespace Arithmetic; + +// r += light; +// g += light; +// b += light; + +// TReal l = HSXType::getLightness(r, g, b); +// TReal n = min(r, g, b); +// TReal x = max(r, g, b); + +// if(n < TReal(0.0)) { +// TReal iln = TReal(1.0) / (l-n); +// r = l + ((r-l) * l) * iln; +// g = l + ((g-l) * l) * iln; +// b = l + ((b-l) * l) * iln; +// } + +// if(x > TReal(1.0) && (x-l) > std::numeric_limits::epsilon()) { +// TReal il = TReal(1.0) - l; +// TReal ixl = TReal(1.0) / (x - l); +// r = l + ((r-l) * il) * ixl; +// g = l + ((g-l) * il) * ixl; +// b = l + ((b-l) * il) * ixl; +// } +//} + +//template +//inline void setLightness(TReal& r, TReal& g, TReal& b, TReal light) +//{ +// addLightness(r,g,b, light - HSXType::getLightness(r,g,b)); +//} + +//template +//inline static TReal getSaturation(TReal r, TReal g, TReal b) { +// return HSXType::getSaturation(r, g, b); +//} + +//template +//inline void setSaturation(TReal& r, TReal& g, TReal& b, TReal sat) +//{ +// int min = 0; +// int mid = 1; +// int max = 2; +// TReal rgb[3] = {r, g, b}; + +// if(rgb[mid] < rgb[min]) { +// int tmp = min; +// min = mid; +// mid = tmp; +// } + +// if(rgb[max] < rgb[mid]) { +// int tmp = mid; +// mid = max; +// max = tmp; +// } + +// if(rgb[mid] < rgb[min]) { +// int tmp = min; +// min = mid; +// mid = tmp; +// } + +// if((rgb[max] - rgb[min]) > TReal(0.0)) { +// rgb[mid] = ((rgb[mid]-rgb[min]) * sat) / (rgb[max]-rgb[min]); +// rgb[max] = sat; +// rgb[min] = TReal(0.0); + +// r = rgb[0]; +// g = rgb[1]; +// b = rgb[2]; +// } +// else r = g = b = TReal(0.0); +//} + +#endif diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h index eba27527d5..df3f870140 100644 --- a/libs/pigment/KoLabColorSpaceTraits.h +++ b/libs/pigment/KoLabColorSpaceTraits.h @@ -1,400 +1,495 @@ /* * Copyright (c) 2006-2007 Cyrille Berger - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016,2017,2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_LAB_COLORSPACE_TRAITS_H_ #define _KO_LAB_COLORSPACE_TRAITS_H_ +#include /** * LAB traits, it provides some convenient functions to * access LAB channels through an explicit API. * * Use this class in conjonction with KoColorSpace::toLabA16 and * KoColorSpace::fromLabA16 data. * * Example: * quint8* p = KoLabU16Traits::allocate(1); * oneKoColorSpace->toLabA16(somepointertodata, p, 1); * KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 ); * oneKoColorSpace->fromLabA16(p, somepointertodata, 1); */ template struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent; static const qint32 L_pos = 0; static const qint32 a_pos = 1; static const qint32 b_pos = 2; /** * An Lab pixel */ struct Pixel { channels_type L; channels_type a; channels_type b; channels_type alpha; }; /// @return the L component inline static channels_type L(quint8* data) { channels_type* d = parent::nativeArray(data); return d[L_pos]; } /// Set the L component inline static void setL(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[L_pos] = nv; } /// @return the a component inline static channels_type a(quint8* data) { channels_type* d = parent::nativeArray(data); return d[a_pos]; } /// Set the a component inline static void setA(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[a_pos] = nv; } /// @return the b component inline static channels_type b(quint8* data) { channels_type* d = parent::nativeArray(data); return d[b_pos]; } /// Set the a component inline static void setB(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[b_pos] = nv; } }; //For quint* values must range from 0 to 1 - see KoColorSpaceMaths -struct KoLabU8Traits : public KoLabTraits { +// https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c +//PCS in Lab2 is encoded as: +// 8 bit Lab PCS: +// L* 0..100 into a 0..ff byte. +// a* t + 128 range is -128.0 +127.0 +// b* +// 16 bit Lab PCS: +// L* 0..100 into a 0..ff00 word. +// a* t + 128 range is -128.0 +127.9961 +// b* +//Version 4 +//--------- +//CIELAB (16 bit) L* 0 -> 100.0 0x0000 -> 0xffff +//CIELAB (16 bit) a* -128.0 -> +127 0x0000 -> 0x8080 -> 0xffff +//CIELAB (16 bit) b* -128.0 -> +127 0x0000 -> 0x8080 -> 0xffff - static const quint32 MAX_CHANNEL_L = 100; - static const quint32 MAX_CHANNEL_AB = 255; - static const quint32 CHANNEL_AB_ZERO_OFFSET = 128; +struct KoLabU8Traits : public KoLabTraits { + // Maps each channel to the ranges above. + // It works when using integers b/c it is always 0..max -- which is not in fp + // I've redone the implementation to be consistent with the fp version (for clarity) --LES inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: - channels[i] = ((qreal)c) / MAX_CHANNEL_L; + channels[i] = ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL; break; case a_pos: - channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; - break; case b_pos: - channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + channels[i] = (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB; break; case 3: - channels[i] = ((qreal)c) / UINT16_MAX; + channels[i] = ((qreal)c) / UINT8_MAX; break; default: - channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; + channels[i] = ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: - return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); + return QString().setNum(100.0 * ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL); case a_pos: - return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: - return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + return QString().setNum(100.0 * ((((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB)); case 3: - return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); + return QString().setNum(100.0 * ((qreal)c) / UINT8_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, - (float)MAX_CHANNEL_L * values[i], - (float)MAX_CHANNEL_L); + (float)KoLabColorSpaceMathsTraits::unitValueL * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueL); break; case a_pos: case b_pos: b = qBound((float)0, (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; default: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoLabColorSpaceMathsTraits::min, + (float)KoLabColorSpaceMathsTraits::unitValue * values[i], + (float)KoLabColorSpaceMathsTraits::max); break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; struct KoLabU16Traits : public KoLabTraits { - // https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c - static const quint32 MAX_CHANNEL_L = 0xFF00; - static const quint32 MAX_CHANNEL_AB = 0xFFFF; - static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000; inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: - channels[i] = ((qreal)c) / MAX_CHANNEL_L; + channels[i] = ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL; break; case a_pos: - channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; - break; case b_pos: - channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + channels[i] = (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB; break; case 3: channels[i] = ((qreal)c) / UINT16_MAX; break; default: - channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; + channels[i] = ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: - return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); + return QString().setNum(100.0 * ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL); case a_pos: - return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: - return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + return QString().setNum(100.0 * ((((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB)); case 3: return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, - (float)MAX_CHANNEL_L * values[i], - (float)MAX_CHANNEL_L); + (float)KoLabColorSpaceMathsTraits::unitValueL * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueL); break; case a_pos: case b_pos: b = qBound((float)0, (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; - default: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); - break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; -// Float values are not normalised -// XXX: is it really necessary to bind them to these ranges? +// Float values are normalized to [0..100], [-128..+127], [-128..+127] - out of range values are clipped #include #ifdef HAVE_OPENEXR #include struct KoLabF16Traits : public KoLabTraits { - static constexpr float MIN_CHANNEL_L = 0; - static constexpr float MAX_CHANNEL_L = 100; - static constexpr float MIN_CHANNEL_AB = -128; - static constexpr float MAX_CHANNEL_AB = +127; - // Lab has some... particulars - // For instance, float et al. are NOT normalised inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case L_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL)); + case a_pos: + case b_pos: + return QString().setNum(100.0 * qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB)); + case 3: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case L_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL); + break; + case a_pos: + case b_pos: + channels[i] = qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 3: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: - b = qBound((float)MIN_CHANNEL_L, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_L); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueL * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueL); break; case a_pos: case b_pos: - b = qBound((float)MIN_CHANNEL_AB, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_AB); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueAB * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueAB); break; case 3: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoLabColorSpaceMathsTraits::min, + (float)KoLabColorSpaceMathsTraits::unitValue * values[i], + (float)KoLabColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif struct KoLabF32Traits : public KoLabTraits { - static constexpr float MIN_CHANNEL_L = 0; - static constexpr float MAX_CHANNEL_L = 100; - static constexpr float MIN_CHANNEL_AB = -128; - static constexpr float MAX_CHANNEL_AB = +127; - // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case L_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL)); + case a_pos: + case b_pos: + return QString().setNum(100.0 * qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB)); + case 3: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case L_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL); + break; + case a_pos: + case b_pos: + channels[i] = qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 3: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: - b = qBound((float)MIN_CHANNEL_L, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_L); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueL * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueL); break; case a_pos: case b_pos: - b = qBound((float)MIN_CHANNEL_AB, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_AB); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueAB * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueAB); break; case 3: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoLabColorSpaceMathsTraits::min, + (float)KoLabColorSpaceMathsTraits::unitValue * values[i], + (float)KoLabColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; struct KoLabF64Traits : public KoLabTraits { - static constexpr double MIN_CHANNEL_L = 0; - static constexpr double MAX_CHANNEL_L = 100; - static constexpr double MIN_CHANNEL_AB = -128; - static constexpr double MAX_CHANNEL_AB = +127; - // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { - return channelValueText(pixel, channelIndex); + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case L_pos: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL)); + case a_pos: + case b_pos: + return QString().setNum(100.0 * qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB)); + case 3: + return QString().setNum(100.0 * qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue)); + default: + return QString("Error"); + } } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { - c = parent::nativeArray(pixel)[i]; - channels[i] = (qreal)c; + c = nativeArray(pixel)[i]; + switch (i) { + case L_pos: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValueL, + (qreal)KoLabColorSpaceMathsTraits::unitValueL); + break; + case a_pos: + case b_pos: + channels[i] = qBound((qreal)0, + (((qreal)c) - KoLabColorSpaceMathsTraits::halfValueAB) / KoLabColorSpaceMathsTraits::unitValueAB, + (qreal)KoLabColorSpaceMathsTraits::unitValueAB); + break; + // As per KoChannelInfo alpha channels are [0..1] + case 3: + default: + channels[i] = qBound((qreal)0, + ((qreal)c) / KoLabColorSpaceMathsTraits::unitValue, + (qreal)KoLabColorSpaceMathsTraits::unitValue); + break; + } } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: - b = qBound((float)MIN_CHANNEL_L, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_L); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueL * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueL); break; case a_pos: case b_pos: - b = qBound((float)MIN_CHANNEL_AB, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)MAX_CHANNEL_AB); + b = qBound((float)0, + (float)KoLabColorSpaceMathsTraits::unitValueAB * values[i], + (float)KoLabColorSpaceMathsTraits::unitValueAB); break; case 3: - b = qBound((float)KoColorSpaceMathsTraits::min, - (float)KoColorSpaceMathsTraits::unitValue * values[i], - (float)KoColorSpaceMathsTraits::max); + b = qBound((float)KoLabColorSpaceMathsTraits::min, + (float)KoLabColorSpaceMathsTraits::unitValue * values[i], + (float)KoLabColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif diff --git a/libs/widgets/kis_spinbox_color_selector.cpp b/libs/widgets/kis_spinbox_color_selector.cpp index 9ca41e3080..250c5cd591 100644 --- a/libs/widgets/kis_spinbox_color_selector.cpp +++ b/libs/widgets/kis_spinbox_color_selector.cpp @@ -1,267 +1,268 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 + * Copyright (C) 2020 L. E. Segovia * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spinbox_color_selector.h" #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" #include "kis_signal_compressor.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include struct KisSpinboxColorSelector::Private { QList labels; QList spinBoxList; QList doubleSpinBoxList; KoColor color; const KoColorSpace *cs {0}; bool chooseAlpha {false}; QFormLayout *layout {0}; }; KisSpinboxColorSelector::KisSpinboxColorSelector(QWidget *parent) : QWidget(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); m_d->layout = new QFormLayout(this); } KisSpinboxColorSelector::~KisSpinboxColorSelector() { } void KisSpinboxColorSelector::slotSetColor(KoColor color) { m_d->color = color; slotSetColorSpace(m_d->color.colorSpace()); updateSpinboxesWithNewValues(); } void KisSpinboxColorSelector::slotSetColorSpace(const KoColorSpace *cs) { if (cs == m_d->cs) { return; } m_d->cs = cs; - //remake spinboxes delete m_d->layout; m_d->layout = new QFormLayout(this); Q_FOREACH(QObject *o, m_d->labels) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->spinBoxList) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->doubleSpinBoxList) { o->deleteLater(); } m_d->labels.clear(); m_d->spinBoxList.clear(); m_d->doubleSpinBoxList.clear(); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); Q_FOREACH (KoChannelInfo* channel, channels) { QString inputLabel = channel->name(); QLabel *inlb = new QLabel(this); m_d->labels << inlb; inlb->setText(inputLabel); switch (channel->channelValueType()) { case KoChannelInfo::UINT8: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb, input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT16: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT32: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFFFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); - input->setMinimum(0); - input->setMaximum(KoColorSpaceMathsTraits::max); + input->setMinimum(channel->getUIMin()); + input->setMaximum(channel->getUIMax()); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #endif case KoChannelInfo::FLOAT32: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); - input->setMinimum(0); - input->setMaximum(KoColorSpaceMathsTraits::max); + input->setMinimum(channel->getUIMin()); + input->setMaximum(channel->getUIMax()); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; default: Q_ASSERT(false); } } this->setLayout(m_d->layout); } void KisSpinboxColorSelector::createColorFromSpinboxValues() { KoColor newColor(m_d->cs); int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); for (int i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType()==KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)){ int value = m_d->spinBoxList.at(i)->value(); channelValues[channelposition] = KoColorSpaceMaths::scaleToA(value); } else if (channels.at(i)->channelValueType()==KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)){ channelValues[channelposition] = KoColorSpaceMaths::scaleToA(m_d->spinBoxList.at(i)->value()); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { channelValues[channelposition] = m_d->doubleSpinBoxList.at(i)->value(); } } m_d->cs->fromNormalisedChannelsValue(newColor.data(), channelValues); newColor.setOpacity(m_d->color.opacityU8()); m_d->color = newColor; } void KisSpinboxColorSelector::slotUpdateFromSpinBoxes() { createColorFromSpinboxValues(); emit sigNewColor(m_d->color); } void KisSpinboxColorSelector::updateSpinboxesWithNewValues() { int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); m_d->cs->normalisedChannelsValue(m_d->color.data(), channelValues); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); int i; /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(true); }*/ for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(true); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(true); } for (i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType() == KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)) { int value = KoColorSpaceMaths::scaleToA(channelValues[channelposition]); m_d->spinBoxList.at(i)->setValue(value); } else if (channels.at(i)->channelValueType() == KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)) { m_d->spinBoxList.at(i)->setValue(KoColorSpaceMaths::scaleToA(channelValues[channelposition])); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { - m_d->doubleSpinBoxList.at(i)->setValue(channelValues[channelposition]); + float value = channels.at(i)->getUIMin() + channelValues[channelposition] * channels.at(i)->getUIUnitValue(); + m_d->doubleSpinBoxList.at(i)->setValue(value); } } for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(false); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(false); } /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(false); }*/ } diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp index f9c6e3bd68..54c67d9942 100644 --- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp +++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp @@ -1,406 +1,407 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "IccColorProfile.h" #include #include #include #include #include "QDebug" #include "LcmsColorProfileContainer.h" #include "lcms2.h" #include "kis_assert.h" struct IccColorProfile::Data::Private { QByteArray rawData; }; IccColorProfile::Data::Data() : d(new Private) { } IccColorProfile::Data::Data(const QByteArray &rawData) : d(new Private) { d->rawData = rawData; } IccColorProfile::Data::~Data() { } QByteArray IccColorProfile::Data::rawData() { return d->rawData; } void IccColorProfile::Data::setRawData(const QByteArray &rawData) { d->rawData = rawData; } IccColorProfile::Container::Container() { } IccColorProfile::Container::~Container() { } struct IccColorProfile::Private { struct Shared { QScopedPointer data; QScopedPointer lcmsProfile; QVector uiMinMaxes; bool canCreateCyclicTransform = false; }; QSharedPointer shared; }; IccColorProfile::IccColorProfile(const QString &fileName) : KoColorProfile(fileName), d(new Private) { // QSharedPointer lacks a reset in Qt 4.x d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); } IccColorProfile::IccColorProfile(const QByteArray &rawData) : KoColorProfile(QString()), d(new Private) { d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); setRawData(rawData); init(); } IccColorProfile::IccColorProfile(const IccColorProfile &rhs) : KoColorProfile(rhs) , d(new Private(*rhs.d)) { Q_ASSERT(d->shared); } IccColorProfile::~IccColorProfile() { Q_ASSERT(d->shared); } KoColorProfile *IccColorProfile::clone() const { return new IccColorProfile(*this); } QByteArray IccColorProfile::rawData() const { return d->shared->data->rawData(); } void IccColorProfile::setRawData(const QByteArray &rawData) { d->shared->data->setRawData(rawData); } bool IccColorProfile::valid() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->valid(); } return false; } float IccColorProfile::version() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->version(); } return 0.0; } bool IccColorProfile::isSuitableForOutput() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForOutput() && d->shared->canCreateCyclicTransform; } return false; } bool IccColorProfile::isSuitableForPrinting() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForPrinting(); } return false; } bool IccColorProfile::isSuitableForDisplay() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForDisplay(); } return false; } bool IccColorProfile::supportsPerceptual() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsPerceptual(); } return false; } bool IccColorProfile::supportsSaturation() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsSaturation(); } return false; } bool IccColorProfile::supportsAbsolute() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsAbsolute(); } return false; } bool IccColorProfile::supportsRelative() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsRelative(); } return false; } bool IccColorProfile::hasColorants() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->hasColorants(); } return false; } bool IccColorProfile::hasTRC() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->hasTRC(); return false; } bool IccColorProfile::isLinear() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->isLinear(); return false; } QVector IccColorProfile::getColorantsXYZ() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsXYZ(); } return QVector(9); } QVector IccColorProfile::getColorantsxyY() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsxyY(); } return QVector(9); } QVector IccColorProfile::getWhitePointXYZ() const { QVector d50Dummy(3); d50Dummy << 0.9642 << 1.0000 << 0.8249; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointXYZ(); } return d50Dummy; } QVector IccColorProfile::getWhitePointxyY() const { QVector d50Dummy(3); d50Dummy << 0.34773 << 0.35952 << 1.0; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointxyY(); } return d50Dummy; } QVector IccColorProfile::getEstimatedTRC() const { QVector dummy(3); dummy.fill(2.2);//estimated sRGB trc. if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getEstimatedTRC(); } return dummy; } void IccColorProfile::linearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValue(Value); } void IccColorProfile::delinearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValue(Value); } void IccColorProfile::linearizeFloatValueFast(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValueFast(Value); } void IccColorProfile::delinearizeFloatValueFast(QVector &Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValueFast(Value); } QByteArray IccColorProfile::uniqueId() const { QByteArray dummy; if (d->shared->lcmsProfile) { dummy = d->shared->lcmsProfile->getProfileUniqueId(); } return dummy; } bool IccColorProfile::load() { QFile file(fileName()); file.open(QIODevice::ReadOnly); QByteArray rawData = file.readAll(); setRawData(rawData); file.close(); if (init()) { return true; } qWarning() << "Failed to load profile from " << fileName(); return false; } bool IccColorProfile::save() { return false; } bool IccColorProfile::init() { if (!d->shared->lcmsProfile) { d->shared->lcmsProfile.reset(new LcmsColorProfileContainer(d->shared->data.data())); } if (d->shared->lcmsProfile->init()) { setName(d->shared->lcmsProfile->name()); setInfo(d->shared->lcmsProfile->info()); setManufacturer(d->shared->lcmsProfile->manufacturer()); setCopyright(d->shared->lcmsProfile->copyright()); if (d->shared->lcmsProfile->valid()) { calculateFloatUIMinMax(); } return true; } else { return false; } } LcmsColorProfileContainer *IccColorProfile::asLcms() const { Q_ASSERT(d->shared->lcmsProfile); return d->shared->lcmsProfile.data(); } bool IccColorProfile::operator==(const KoColorProfile &rhs) const { const IccColorProfile *rhsIcc = dynamic_cast(&rhs); if (rhsIcc) { return d->shared == rhsIcc->d->shared; } return false; } const QVector &IccColorProfile::getFloatUIMinMax(void) const { Q_ASSERT(!d->shared->uiMinMaxes.isEmpty()); return d->shared->uiMinMaxes; } void IccColorProfile::calculateFloatUIMinMax(void) { QVector &ret = d->shared->uiMinMaxes; cmsHPROFILE cprofile = d->shared->lcmsProfile->lcmsProfile(); Q_ASSERT(cprofile); cmsColorSpaceSignature color_space_sig = cmsGetColorSpace(cprofile); unsigned int num_channels = cmsChannelsOf(color_space_sig); unsigned int color_space_mask = _cmsLCMScolorSpace(color_space_sig); Q_ASSERT(num_channels >= 1 && num_channels <= 4); // num_channels==1 is for grayscale, we need to handle it Q_ASSERT(color_space_mask); // to try to find the max range of float/doubles for this profile, // pass in min/max int and make the profile convert that // this is far from perfect, we need a better way, if possible to get the "bounds" of a profile uint16_t in_min_pixel[4] = {0, 0, 0, 0}; uint16_t in_max_pixel[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; qreal out_min_pixel[4] = {0, 0, 0, 0}; qreal out_max_pixel[4] = {0, 0, 0, 0}; cmsHTRANSFORM trans = cmsCreateTransform( cprofile, (COLORSPACE_SH(color_space_mask) | CHANNELS_SH(num_channels) | BYTES_SH(2)), cprofile, (COLORSPACE_SH(color_space_mask) | FLOAT_SH(1) | CHANNELS_SH(num_channels) | BYTES_SH(0)), //NOTE THAT 'BYTES' FIELD IS SET TO ZERO ON DLB because 8 bytes overflows the bitfield - INTENT_PERCEPTUAL, 0); // does the intent matter in this case? + INTENT_ABSOLUTE_COLORIMETRIC, 0); // does the intent matter in this case? + // absolute colorimetric gives bigger bounds with cmyk's Chemical Proof if (trans) { cmsDoTransform(trans, in_min_pixel, out_min_pixel, 1); cmsDoTransform(trans, in_max_pixel, out_max_pixel, 1); cmsDeleteTransform(trans); }//else, we'll just default to [0..1] below // Some (calibration) proifles may have a weird RGB->XYZ transformation matrix, // which is not invertible. Therefore, such profile cannot be used as // a workspace color profile and we should convert the image to sRGB // right on image loading // LCMS doesn't have a separate method for checking if conversion matrix // is invertible, therefore we just try to create a simple transformation, // where the profile is both, input and output. If the transformation // is created successfully, then this profile is probably suitable for // usage as a working color space. d->shared->canCreateCyclicTransform = bool(trans); ret.resize(num_channels); for (unsigned int i = 0; i < num_channels; ++i) { if (out_min_pixel[i] < out_max_pixel[i]) { ret[i].minVal = out_min_pixel[i]; ret[i].maxVal = out_max_pixel[i]; } else { // apparently we can't even guarantee that converted_to_double(0x0000) < converted_to_double(0xFFFF) // assume [0..1] in such cases // we need to find a really solid way of determining the bounds of a profile, if possible ret[i].minVal = 0; ret[i].maxVal = 1; } } } diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp index 9516a313ed..6a55982afe 100644 --- a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp @@ -1,134 +1,144 @@ /* * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "CmykF32ColorSpace.h" #include #include #include #include "compositeops/KoCompositeOps.h" #include #include CmykF32ColorSpace::CmykF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_CMYKA_FLT, cmsSigCmykData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 4); addChannel(new KoChannelInfo(i18n("Cyan"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::cyan, uiRanges[0])); addChannel(new KoChannelInfo(i18n("Magenta"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::magenta, uiRanges[1])); addChannel(new KoChannelInfo(i18n("Yellow"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::yellow, uiRanges[2])); addChannel(new KoChannelInfo(i18n("Black"), 3 * sizeof(float), 3, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::black, uiRanges[3])); addChannel(new KoChannelInfo(i18n("Alpha"), 4 * sizeof(float), 4, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, sizeof(float))); init(); + dbgPlugins << "CMYK (float) profile bounds for: " << icc_p->name(); + dbgPlugins << "C: " << uiRanges[0].minVal << uiRanges[0].maxVal; + dbgPlugins << "M: " << uiRanges[1].minVal << uiRanges[1].maxVal; + dbgPlugins << "Y: " << uiRanges[2].minVal << uiRanges[2].maxVal; + dbgPlugins << "K: " << uiRanges[3].minVal << uiRanges[3].maxVal; + addStandardCompositeOps(this); } bool CmykF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *CmykF32ColorSpace::clone() const { return new CmykF32ColorSpace(name(), profile()->clone()); } void CmykF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoCmykF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("CMYK"); - labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(p->cyan))); - labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(p->magenta))); - labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(p->yellow))); - labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(p->black))); + + // XML expects 0-1, we need 0-100 + // Get the bounds from the channels and adjust the calculations + labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[0]->getUIUnitValue() * (p->cyan - this->channels()[0]->getUIMin())))); + labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[1]->getUIUnitValue() * (p->magenta - this->channels()[1]->getUIMin())))); + labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[2]->getUIUnitValue() * (p->yellow - this->channels()[2]->getUIMin())))); + labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[3]->getUIUnitValue() * (p->black - this->channels()[3]->getUIMin())))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void CmykF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoCmykF32Traits::Pixel *p = reinterpret_cast(pixel); - p->cyan = KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c"))); - p->magenta = KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m"))); - p->yellow = KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y"))); - p->black = KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k"))); + p->cyan = this->channels()[0]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c"))) * this->channels()[0]->getUIUnitValue(); + p->magenta = this->channels()[1]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m"))) * this->channels()[1]->getUIUnitValue(); + p->yellow = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y"))) * this->channels()[2]->getUIUnitValue(); + p->black = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k"))) * this->channels()[2]->getUIUnitValue(); p->alpha = 1.0; } void CmykF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { qreal c0 = channelValues[0]; qreal c1 = channelValues[1]; qreal c2 = channelValues[2]; qreal c3 = channelValues[3]; //we use HSI here because we can't linearise CMYK, and HSY doesn't work right with... CMYKToCMY(&c0, &c1, &c2, &c3); c0 = 1.0 - c0; c1 = 1.0 - c1; c2 = 1.0 - c2; RGBToHSI(c0, c1, c2, hue, sat, luma); } QVector CmykF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(5); channelValues.fill(1.0); HSIToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[0] = qBound(0.0,1.0-channelValues[0],1.0); channelValues[1] = qBound(0.0,1.0-channelValues[1],1.0); channelValues[2] = qBound(0.0,1.0-channelValues[2],1.0); CMYToCMYK(&channelValues[0],&channelValues[1],&channelValues[2],&channelValues[3]); return channelValues; } void CmykF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { qreal c0 = channelValues[0]; qreal c1 = channelValues[1]; qreal c2 = channelValues[2]; qreal c3 = channelValues[3]; CMYKToCMY(&c0, &c1, &c2, &c3); c0 = 1.0 - c0; c1 = 1.0 - c1; c2 = 1.0 - c2; RGBToYUV(c0, c1, c2, y, u, v, (1.0 - 0.299),(1.0 - 0.587), (1.0 - 0.114)); } QVector CmykF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(5); channelValues.fill(1.0); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], 0.33, 0.33, 0.33); channelValues[0] = qBound(0.0,1.0-channelValues[0],1.0); channelValues[1] = qBound(0.0,1.0-channelValues[1],1.0); channelValues[2] = qBound(0.0,1.0-channelValues[2],1.0); CMYToCMYK(&channelValues[0],&channelValues[1],&channelValues[2],&channelValues[3]); return channelValues; } diff --git a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp index e682e303b9..a11749a3d0 100644 --- a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp @@ -1,109 +1,118 @@ /* * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "LabF32ColorSpace.h" #include #include #include "../compositeops/KoCompositeOps.h" #include #include LabF32ColorSpace::LabF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_LabA_FLT, cmsSigLabData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 3); addChannel(new KoChannelInfo(i18n("Lightness"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(100, 100, 100), uiRanges[0])); addChannel(new KoChannelInfo(i18n("a*"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(150, 150, 150), uiRanges[1])); addChannel(new KoChannelInfo(i18n("b*"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), QColor(200, 200, 200), uiRanges[2])); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(float), 3, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, sizeof(float))); init(); addStandardCompositeOps(this); + + dbgPlugins << "La*b* (float) channel bounds for: " << icc_p->name(); + dbgPlugins << "L: " << uiRanges[0].minVal << uiRanges[0].maxVal; + dbgPlugins << "a: " << uiRanges[1].minVal << uiRanges[1].maxVal; + dbgPlugins << "b: " << uiRanges[2].minVal << uiRanges[2].maxVal; } bool LabF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *LabF32ColorSpace::clone() const { return new LabF32ColorSpace(name(), profile()->clone()); } void LabF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoLabF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("Lab"); - labElt.setAttribute("L", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(p->L))); - labElt.setAttribute("a", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(p->a))); - labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(p->b))); + + // XML expects 0-1, we need 0-100, -128-+127 + // Get the bounds from the channels and adjust the calculations + labElt.setAttribute("L", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[0]->getUIUnitValue() * (p->L - this->channels()[0]->getUIMin())))); + labElt.setAttribute("a", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[1]->getUIUnitValue() * (p->a - this->channels()[1]->getUIMin())))); + labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoLabF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[2]->getUIUnitValue() * (p->b - this->channels()[2]->getUIMin())))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void LabF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoLabF32Traits::Pixel *p = reinterpret_cast(pixel); - p->L = KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("L"))); - p->a = KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("a"))); - p->b = KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); + p->L = this->channels()[0]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("L"))) * this->channels()[0]->getUIUnitValue(); + p->a = this->channels()[1]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("a"))) * this->channels()[1]->getUIUnitValue(); + p->b = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoLabF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))) * this->channels()[2]->getUIUnitValue(); p->alpha = 1.0; } void LabF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); } QVector LabF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void LabF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { *y =channelValues[0]; *u=channelValues[1]; *v=channelValues[2]; } QVector LabF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); channelValues[0]=*y; channelValues[1]=*u; channelValues[2]=*v; channelValues[3]=1.0; return channelValues; }