File indexing completed on 2024-05-12 15:59:34
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Peter Schatz <voronwe13@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 0008 #ifndef KOCOLORSPACEPRESERVELIGHTNESSUTILS_H 0009 #define KOCOLORSPACEPRESERVELIGHTNESSUTILS_H 0010 0011 #include <KoColorSpaceMaths.h> 0012 #include "kis_global.h" 0013 0014 template<typename CSTraits> 0015 inline static void fillGrayBrushWithColorPreserveLightnessRGB(quint8 *pixels, const QRgb *brush, quint8 *brushColor, qreal strength, qint32 nPixels) { 0016 using RGBPixel = typename CSTraits::Pixel; 0017 using channels_type = typename CSTraits::channels_type; 0018 static const quint32 pixelSize = CSTraits::pixelSize; 0019 0020 const RGBPixel *srcColorRGB = reinterpret_cast<const RGBPixel*>(brushColor); 0021 0022 const float srcColorR = KoColorSpaceMaths<channels_type, float>::scaleToA(srcColorRGB->red); 0023 const float srcColorG = KoColorSpaceMaths<channels_type, float>::scaleToA(srcColorRGB->green); 0024 const float srcColorB = KoColorSpaceMaths<channels_type, float>::scaleToA(srcColorRGB->blue); 0025 const float srcColorA = KoColorSpaceMaths<channels_type, float>::scaleToA(srcColorRGB->alpha); 0026 0027 /** 0028 * Lightness mixing algorithm is developed by Peter Schatz <voronwe13@gmail.com> 0029 * 0030 * We use a formula f(x) where f(0) = 0, f(1) = 1, and f(.5) = z, 0031 * where z is the lightness of the brush color. This can’t be linear unless 0032 * the color chosen is also .5. So we use a quadratic equation: 0033 * 0034 * f(x) = ax^2 + b^x +c 0035 * 0,0 -> 0 = a0^2 + b0 + c -> c = 0 0036 * 1,1 -> 1 = a1^2 +b1 + c -> 1 = a + b + 0 -> a = 1 - b 0037 * .5,z -> z = a*.5^2 + b*.5 + c -> z = 0038 * = a/4 + b/2 + 0 -> z = 0039 * = 1/4 - b/4 + b/2 -> z = 1/4 + b/4 -> b = 4z - 1 0040 * 0041 * f(x) = (1 - (4z - 1)) * x^2 + (4z - 1) * x 0042 */ 0043 0044 const float srcColorL = getLightness<HSLType, float>(srcColorR, srcColorG, srcColorB); 0045 const float lightnessB = 4 * srcColorL - 1; 0046 const float lightnessA = 1 - lightnessB; 0047 0048 for (; nPixels > 0; --nPixels, pixels += pixelSize, ++brush) { 0049 float brushMaskL = qRed(*brush) / 255.0f; 0050 brushMaskL = (brushMaskL - 0.5) * strength + 0.5; 0051 const float finalAlpha = qMin(qAlpha(*brush) / 255.0f, srcColorA); 0052 float finalLightness = lightnessA * pow2(brushMaskL) + lightnessB * brushMaskL; 0053 finalLightness = qBound(0.0f, finalLightness, 1.0f); 0054 0055 float pixelR = srcColorR; 0056 float pixelG = srcColorG; 0057 float pixelB = srcColorB; 0058 0059 setLightness<HSLType, float>(pixelR, pixelG, pixelB, finalLightness); 0060 0061 RGBPixel *pixelRGB = reinterpret_cast<RGBPixel*>(pixels); 0062 pixelRGB->red = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelR); 0063 pixelRGB->green = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelG); 0064 pixelRGB->blue = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelB); 0065 pixelRGB->alpha = KoColorSpaceMaths<quint8, channels_type>::scaleToA(quint8(finalAlpha * 255)); 0066 } 0067 } 0068 0069 template<typename CSTraits> 0070 inline static void modulateLightnessByGrayBrushRGB(quint8 *pixels, const QRgb *brush, qreal strength, qint32 nPixels) { 0071 using RGBPixel = typename CSTraits::Pixel; 0072 using channels_type = typename CSTraits::channels_type; 0073 static const quint32 pixelSize = CSTraits::pixelSize; 0074 0075 0076 /** 0077 * Lightness mixing algorithm is developed by Peter Schatz <voronwe13@gmail.com> 0078 * 0079 * We use a formula f(x) where f(0) = 0, f(1) = 1, and f(.5) = z, 0080 * where z is the lightness of the brush color. This can’t be linear unless 0081 * the color chosen is also .5. So we use a quadratic equation: 0082 * 0083 * f(x) = ax^2 + b^x +c 0084 * 0,0 -> 0 = a0^2 + b0 + c -> c = 0 0085 * 1,1 -> 1 = a1^2 +b1 + c -> 1 = a + b + 0 -> a = 1 - b 0086 * .5,z -> z = a*.5^2 + b*.5 + c -> z = 0087 * = a/4 + b/2 + 0 -> z = 0088 * = 1/4 - b/4 + b/2 -> z = 1/4 + b/4 -> b = 4z - 1 0089 * 0090 * f(x) = (1 - (4z - 1)) * x^2 + (4z - 1) * x 0091 */ 0092 0093 for (; nPixels > 0; --nPixels, pixels += pixelSize, ++brush) { 0094 0095 RGBPixel *pixelRGB = reinterpret_cast<RGBPixel*>(pixels); 0096 0097 const float srcColorR = KoColorSpaceMaths<channels_type, float>::scaleToA(pixelRGB->red); 0098 const float srcColorG = KoColorSpaceMaths<channels_type, float>::scaleToA(pixelRGB->green); 0099 const float srcColorB = KoColorSpaceMaths<channels_type, float>::scaleToA(pixelRGB->blue); 0100 //const float srcColorA = KoColorSpaceMaths<channels_type, float>::scaleToA(pixelRGB->alpha); 0101 0102 const float srcColorL = getLightness<HSLType, float>(srcColorR, srcColorG, srcColorB); 0103 float brushMaskL = qRed(*brush) / 255.0f; 0104 brushMaskL = (brushMaskL - 0.5) * strength * qAlpha(*brush) / 255.0 + 0.5; 0105 0106 const float lightnessB = 4 * srcColorL - 1; 0107 const float lightnessA = 1 - lightnessB; 0108 0109 float finalLightness = lightnessA * pow2(brushMaskL) + lightnessB * brushMaskL; 0110 finalLightness = qBound(0.0f, finalLightness, 1.0f); 0111 0112 float pixelR = srcColorR; 0113 float pixelG = srcColorG; 0114 float pixelB = srcColorB; 0115 0116 setLightness<HSLType, float>(pixelR, pixelG, pixelB, finalLightness); 0117 0118 pixelRGB->red = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelR); 0119 pixelRGB->green = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelG); 0120 pixelRGB->blue = KoColorSpaceMaths<float, channels_type>::scaleToA(pixelB); 0121 //pixelRGB->alpha = KoColorSpaceMaths<quint8, channels_type>::scaleToA(quint8(finalAlpha * 255)); 0122 } 0123 } 0124 0125 0126 #endif // KOCOLORSPACEPRESERVELIGHTNESSUTILS_H