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 }