File indexing completed on 2024-12-22 04:11:38
0001 /* 0002 * SPDX-FileCopyrightText: 2004-2009 Boudewijn Rempt <boud@valdyas.org> 0003 * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net> 0004 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 #include "KoLabColorSpace.h" 0009 0010 #include <limits.h> 0011 #include <stdlib.h> 0012 #include <math.h> 0013 0014 #include <QImage> 0015 #include <QBitArray> 0016 0017 #include <klocalizedstring.h> 0018 0019 #include "KoChannelInfo.h" 0020 #include "KoID.h" 0021 #include "KoIntegerMaths.h" 0022 #include "KoColorConversions.h" 0023 0024 #include "../compositeops/KoCompositeOps.h" 0025 #include "dithering/KisLabDitherOpFactory.h" 0026 0027 KoLabColorSpace::KoLabColorSpace() : 0028 KoSimpleColorSpace<KoLabU16Traits>(colorSpaceId(), 0029 i18n("L*a*b* (16-bit integer/channel, unmanaged)"), 0030 LABAColorModelID, 0031 Integer16BitsColorDepthID) 0032 { 0033 addChannel(new KoChannelInfo(i18nc("Lightness value in Lab color model", "Lightness"), CHANNEL_L * sizeof(quint16), CHANNEL_L, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(100, 100, 100))); 0034 addChannel(new KoChannelInfo(i18n("a*"), CHANNEL_A * sizeof(quint16), CHANNEL_A, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(150, 150, 150))); 0035 addChannel(new KoChannelInfo(i18n("b*"), CHANNEL_B * sizeof(quint16), CHANNEL_B, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), QColor(200, 200, 200))); 0036 addChannel(new KoChannelInfo(i18n("Alpha"), CHANNEL_ALPHA * sizeof(quint16), CHANNEL_ALPHA, KoChannelInfo::ALPHA, KoChannelInfo::UINT16, sizeof(quint16))); 0037 0038 // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT 0039 addStandardCompositeOps<KoLabU16Traits>(this); 0040 addStandardDitherOps<KoLabU16Traits>(this); 0041 } 0042 0043 KoLabColorSpace::~KoLabColorSpace() 0044 { 0045 } 0046 0047 0048 QString KoLabColorSpace::colorSpaceId() 0049 { 0050 return QStringLiteral("LABA"); 0051 } 0052 0053 0054 KoColorSpace* KoLabColorSpace::clone() const 0055 { 0056 return new KoLabColorSpace(); 0057 } 0058 0059 void KoLabColorSpace::fromQColor(const QColor& c, quint8 *dst) const 0060 { 0061 // Convert between RGB and CIE-Lab color spaces 0062 // Uses ITU-R recommendation BT.709 with D65 as reference white. 0063 // algorithm contributed by "Mark A. Ruzon" <ruzon@CS.Stanford.EDU> 0064 0065 int R, G, B, A; 0066 c.getRgb(&R, &G, &B, &A); 0067 0068 double X, Y, Z, fX, fY, fZ; 0069 0070 X = 0.412453 * R + 0.357580 * G + 0.180423 * B; 0071 Y = 0.212671 * R + 0.715160 * G + 0.072169 * B; 0072 Z = 0.019334 * R + 0.119193 * G + 0.950227 * B; 0073 0074 X /= (255 * 0.950456); 0075 Y /= 255; 0076 Z /= (255 * 1.088754); 0077 0078 quint8 L, a, b; 0079 0080 if (Y > 0.008856) { 0081 fY = pow(Y, 1.0 / 3.0); 0082 L = static_cast<int>(116.0 * fY - 16.0 + 0.5); 0083 } else { 0084 fY = 7.787 * Y + 16.0 / 116.0; 0085 L = static_cast<int>(903.3 * Y + 0.5); 0086 } 0087 0088 if (X > 0.008856) 0089 fX = pow(X, 1.0 / 3.0); 0090 else 0091 fX = 7.787 * X + 16.0 / 116.0; 0092 0093 if (Z > 0.008856) 0094 fZ = pow(Z, 1.0 / 3.0); 0095 else 0096 fZ = 7.787 * Z + 16.0 / 116.0; 0097 0098 a = static_cast<int>(500.0 * (fX - fY) + 0.5); 0099 b = static_cast<int>(200.0 * (fY - fZ) + 0.5); 0100 0101 dst[CHANNEL_L] = UINT8_TO_UINT16(L); 0102 dst[CHANNEL_A] = UINT8_TO_UINT16(a); 0103 dst[CHANNEL_B] = UINT8_TO_UINT16(b); 0104 dst[CHANNEL_ALPHA] = UINT8_TO_UINT16(A); 0105 } 0106 0107 void KoLabColorSpace::toQColor(const quint8 * src, QColor *c) const 0108 { 0109 // Convert between RGB and CIE-Lab color spaces 0110 // Uses ITU-R recommendation BT.709 with D65 as reference white. 0111 // algorithm contributed by "Mark A. Ruzon" <ruzon@CS.Stanford.EDU> 0112 quint8 L, a, b, A; 0113 L = UINT16_TO_UINT8(src[CHANNEL_L]); 0114 a = UINT16_TO_UINT8(src[CHANNEL_A]); 0115 b = UINT16_TO_UINT8(src[CHANNEL_B]); 0116 A = UINT16_TO_UINT8(src[CHANNEL_ALPHA]); 0117 0118 double X, Y, Z, fX, fY, fZ; 0119 int RR, GG, BB; 0120 0121 fY = pow((L + 16.0) / 116.0, 3.0); 0122 if (fY < 0.008856) 0123 fY = L / 903.3; 0124 Y = fY; 0125 0126 if (fY > 0.008856) 0127 fY = pow(fY, 1.0 / 3.0); 0128 else 0129 fY = 7.787 * fY + 16.0 / 116.0; 0130 0131 fX = a / 500.0 + fY; 0132 if (fX > 0.206893) 0133 X = pow(fX, 3.0); 0134 else 0135 X = (fX - 16.0 / 116.0) / 7.787; 0136 0137 fZ = fY - b / 200.0; 0138 if (fZ > 0.206893) 0139 Z = pow(fZ, 3.0); 0140 else 0141 Z = (fZ - 16.0 / 116.0) / 7.787; 0142 0143 X *= 0.950456 * 255; 0144 Y *= 255; 0145 Z *= 1.088754 * 255; 0146 0147 RR = static_cast<int>(3.240479 * X - 1.537150 * Y - 0.498535 * Z + 0.5); 0148 GG = static_cast<int>(-0.969256 * X + 1.875992 * Y + 0.041556 * Z + 0.5); 0149 BB = static_cast<int>(0.055648 * X - 0.204043 * Y + 1.057311 * Z + 0.5); 0150 0151 quint8 R = RR < 0 ? 0 : RR > 255 ? 255 : RR; 0152 quint8 G = GG < 0 ? 0 : GG > 255 ? 255 : GG; 0153 quint8 B = BB < 0 ? 0 : BB > 255 ? 255 : BB; 0154 0155 c->setRgba(qRgba(R, G, B, A)); 0156 } 0157 0158 void KoLabColorSpace::toHSY(const QVector<double> &channelValues, qreal *hue, qreal *sat, qreal *luma) const 0159 { 0160 LabToLCH(channelValues[0],channelValues[1],channelValues[2], luma, sat, hue); 0161 } 0162 0163 QVector <double> KoLabColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const 0164 { 0165 QVector <double> channelValues(4); 0166 LCHToLab(*luma, *sat, *hue, &channelValues[0],&channelValues[1],&channelValues[2]); 0167 channelValues[3]=1.0; 0168 return channelValues; 0169 } 0170 0171 void KoLabColorSpace::toYUV(const QVector<double> &channelValues, qreal *y, qreal *u, qreal *v) const 0172 { 0173 *y =channelValues[0]; 0174 *v=channelValues[1]; 0175 *u=channelValues[2]; 0176 } 0177 0178 QVector <double> KoLabColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const 0179 { 0180 QVector <double> channelValues(4); 0181 channelValues[0]=*y; 0182 channelValues[1]=*v; 0183 channelValues[2]=*u; 0184 channelValues[3]=1.0; 0185 return channelValues; 0186 } 0187 0188 quint8 KoLabColorSpace::scaleToU8(const quint8 *srcPixel, qint32 channelIndex) const 0189 { 0190 typename ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray(srcPixel)[channelIndex]; 0191 qreal b = 0; 0192 switch (channelIndex) { 0193 case ColorSpaceTraits::L_pos: 0194 b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValueL; 0195 break; 0196 case ColorSpaceTraits::a_pos: 0197 case ColorSpaceTraits::b_pos: 0198 if (c <= ColorSpaceTraits::math_trait::halfValueAB) { 0199 b = ((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB)); 0200 } else { 0201 b = 0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB)); 0202 } 0203 break; 0204 default: 0205 b = ((qreal)c) / ColorSpaceTraits::math_trait::unitValue; 0206 break; 0207 } 0208 0209 return KoColorSpaceMaths<qreal, quint8>::scaleToA(b); 0210 } 0211 0212 void KoLabColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const 0213 { 0214 for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { 0215 for (uint channelIndex = 0; channelIndex < ColorSpaceTraits::channels_nb; ++channelIndex) { 0216 if (channelIndex != ColorSpaceTraits::alpha_pos) { 0217 if (channelIndex == ColorSpaceTraits::L_pos) { 0218 ColorSpaceTraits::channels_type c = ColorSpaceTraits::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[selectedChannelIndex]; 0219 switch (selectedChannelIndex) { 0220 case ColorSpaceTraits::L_pos: 0221 break; 0222 case ColorSpaceTraits::a_pos: 0223 case ColorSpaceTraits::b_pos: 0224 if (c <= ColorSpaceTraits::math_trait::halfValueAB) { 0225 c = ColorSpaceTraits::math_trait::unitValueL * (((qreal)c - ColorSpaceTraits::math_trait::zeroValueAB) / (2.0 * (ColorSpaceTraits::math_trait::halfValueAB - ColorSpaceTraits::math_trait::zeroValueAB))); 0226 } else { 0227 c = ColorSpaceTraits::math_trait::unitValueL * (0.5 + ((qreal)c - ColorSpaceTraits::math_trait::halfValueAB) / (2.0 * (ColorSpaceTraits::math_trait::unitValueAB - ColorSpaceTraits::math_trait::halfValueAB))); 0228 } 0229 break; 0230 // As per KoChannelInfo alpha channels are [0..1] 0231 default: 0232 c = ColorSpaceTraits::math_trait::unitValueL * (qreal)c / ColorSpaceTraits::math_trait::unitValue; 0233 break; 0234 } 0235 ColorSpaceTraits::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = c; 0236 } else { 0237 ColorSpaceTraits::nativeArray(dst + (pixelIndex * ColorSpaceTraits::pixelSize))[channelIndex] = ColorSpaceTraits::math_trait::halfValueAB; 0238 } 0239 } else { 0240 ColorSpaceTraits::nativeArray((dst + (pixelIndex * ColorSpaceTraits::pixelSize)))[channelIndex] = 0241 ColorSpaceTraits::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[channelIndex]; 0242 } 0243 } 0244 } 0245 } 0246 0247 void KoLabColorSpace::convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const 0248 { 0249 for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { 0250 for (uint channelIndex = 0; channelIndex < ColorSpaceTraits::channels_nb; ++channelIndex) { 0251 0252 if (selectedChannels.testBit(channelIndex)) { 0253 ColorSpaceTraits::nativeArray((dst + (pixelIndex * ColorSpaceTraits::pixelSize)))[channelIndex] = 0254 ColorSpaceTraits::nativeArray((src + (pixelIndex * ColorSpaceTraits::pixelSize)))[channelIndex]; 0255 } else { 0256 ColorSpaceTraits::channels_type v; 0257 switch (channelIndex) { 0258 case ColorSpaceTraits::L_pos: 0259 v = ColorSpaceTraits::math_trait::halfValueL; 0260 break; 0261 case ColorSpaceTraits::a_pos: 0262 case ColorSpaceTraits::b_pos: 0263 v = ColorSpaceTraits::math_trait::halfValueAB; 0264 break; 0265 default: 0266 v = ColorSpaceTraits::math_trait::zeroValue; 0267 break; 0268 } 0269 ColorSpaceTraits::nativeArray((dst + (pixelIndex * ColorSpaceTraits::pixelSize)))[channelIndex] = v; 0270 } 0271 } 0272 } 0273 }