File indexing completed on 2024-05-12 04:44:34
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // Own header 0005 #include "rgbcolor.h" 0006 0007 #include "helperqttypes.h" 0008 #include <qglobal.h> 0009 #include <type_traits> 0010 0011 namespace PerceptualColor 0012 { 0013 0014 // TODO xxx Decide on the commented-out static_assert statements. Ideally 0015 // get them working! 0016 0017 // static_assert(std::is_trivially_copyable_v<RgbColor>); 0018 // static_assert(std::is_trivial_v<RgbColor>); 0019 0020 static_assert(std::is_standard_layout_v<RgbColor>); 0021 0022 static_assert(std::is_default_constructible_v<RgbColor>); 0023 // static_assert(std::is_trivially_default_constructible_v<RgbColor>); 0024 // static_assert(std::is_nothrow_default_constructible_v<RgbColor>); 0025 0026 static_assert(std::is_copy_constructible_v<RgbColor>); 0027 // static_assert(std::is_trivially_copy_constructible_v<RgbColor>); 0028 // static_assert(std::is_nothrow_copy_constructible_v<RgbColor>); 0029 0030 // static_assert(std::is_move_constructible_v<RgbColor>); 0031 // static_assert(std::is_trivially_move_constructible_v<RgbColor>); 0032 // static_assert(std::is_nothrow_move_constructible_v<RgbColor>); 0033 0034 RgbColor::RgbColor() 0035 { 0036 } 0037 0038 /** @brief Set all member variables. 0039 * 0040 * @param color The new color as <tt>QColor</tt> object. Might be of any 0041 * <tt>QColor::Spec</tt>. 0042 * @param hue When empty, the hue is calculated automatically. Otherwise, 0043 * this value is used instead. Valid range: [0, 360[ 0044 * 0045 * @post @ref hsl, @ref hsv, @ref hwb, @ref rgb255 and @ref rgbQColor are 0046 * set. */ 0047 void RgbColor::fillAll(QColor color, std::optional<double> hue) 0048 { 0049 rgb255 = GenericColor(static_cast<double>(color.redF() * 255), // 0050 static_cast<double>(color.greenF() * 255), // 0051 static_cast<double>(color.blueF() * 255)); 0052 0053 rgbQColor = color.toRgb(); 0054 0055 // The hue is identical for HSL, HSV and HWB. 0056 const double hueDegree = hue.value_or( // 0057 qBound(0., rgbQColor.hueF() * 360, 360.)); 0058 0059 // HSL 0060 const double hslSaturationPercentage = // 0061 qBound(0., static_cast<double>(color.hslSaturationF()) * 100, 100.); 0062 const double hslLightnessPercentage = // 0063 qBound(0., static_cast<double>(color.lightnessF()) * 100, 100.); 0064 hsl = GenericColor(hueDegree, // 0065 hslSaturationPercentage, // 0066 hslLightnessPercentage); 0067 0068 // HSV 0069 const double hsvSaturationPercentage = // 0070 qBound(0.0, static_cast<double>(color.hsvSaturationF()) * 100, 100.0); 0071 const double hsvValuePercentage = // 0072 qBound<double>(0.0, static_cast<double>(color.valueF()) * 100, 100.0); 0073 hsv = GenericColor(hueDegree, // 0074 hsvSaturationPercentage, // 0075 hsvValuePercentage); 0076 0077 const double hwbWhitenessPercentage = // 0078 qBound(0.0, (1 - color.hsvSaturationF()) * color.valueF() * 100, 100.0); 0079 const double hwbBlacknessPercentage = // 0080 qBound(0.0, (1 - color.valueF()) * 100, 100.0); 0081 hwb = GenericColor(hueDegree, // 0082 hwbWhitenessPercentage, // 0083 hwbBlacknessPercentage); 0084 } 0085 0086 /** @brief Static convenience function that returns a @ref RgbColor 0087 * constructed from the given color. 0088 * 0089 * @param color Original color. Valid range: [0, 255] 0090 * @param hue If not empty, this value is used instead of the actually 0091 * calculated hue value. Valid range: [0, 360[ 0092 * @returns A @ref RgbColor object representing this color. */ 0093 RgbColor RgbColor::fromRgb255(const GenericColor &color, std::optional<double> hue) 0094 { 0095 RgbColor result; 0096 const auto red = static_cast<QColorFloatType>(color.first / 255.0); 0097 const auto green = static_cast<QColorFloatType>(color.second / 255.0); 0098 const auto blue = static_cast<QColorFloatType>(color.third / 255.0); 0099 constexpr auto zero = static_cast<QColorFloatType>(0); 0100 constexpr auto one = static_cast<QColorFloatType>(1); 0101 const QColor newRgbQColor = QColor::fromRgbF(qBound(zero, red, one), // 0102 qBound(zero, green, one), // 0103 qBound(zero, blue, one)); 0104 result.fillAll(newRgbQColor, hue); 0105 result.rgb255 = color; 0106 0107 return result; 0108 } 0109 0110 /** @brief Static convenience function that returns a @ref RgbColor 0111 * constructed from the given color. 0112 * 0113 * @param color Original color. 0114 * @returns A @ref RgbColor object representing this color. */ 0115 RgbColor RgbColor::fromRgbQColor(const QColor &color) 0116 { 0117 RgbColor result; 0118 result.fillAll(color, std::optional<double>()); 0119 0120 return result; 0121 } 0122 0123 /** @brief Static convenience function that returns a @ref RgbColor 0124 * constructed from the given color. 0125 * 0126 * @param color Original color. 0127 * @returns A @ref RgbColor object representing this color. */ 0128 RgbColor RgbColor::fromHsl(const GenericColor &color) 0129 { 0130 RgbColor result; 0131 0132 constexpr auto zero = static_cast<QColorFloatType>(0); 0133 constexpr auto one = static_cast<QColorFloatType>(1); 0134 const auto hslHue = // 0135 qBound(zero, static_cast<QColorFloatType>(color.first / 360.0), one); 0136 const auto hslSaturation = // 0137 qBound(zero, static_cast<QColorFloatType>(color.second / 100.0), one); 0138 const auto hslLightness = // 0139 qBound(zero, static_cast<QColorFloatType>(color.third / 100.0), one); 0140 const QColor newRgbQColor = // 0141 QColor::fromHslF(hslHue, hslSaturation, hslLightness).toRgb(); 0142 result.fillAll(newRgbQColor, color.first); 0143 // Override again with the original value: 0144 result.hsl = color; 0145 if (result.hsl.third == 0) { 0146 // Color is black. So neither changing HSV-saturation or changing 0147 // HSL-saturation will change the color itself. To give a better 0148 // user experience, we synchronize both values. 0149 result.hsv.second = result.hsl.second; 0150 } 0151 0152 return result; 0153 } 0154 0155 /** @brief Static convenience function that returns a @ref RgbColor 0156 * constructed from the given color. 0157 * 0158 * @param color Original color. 0159 * @returns A @ref RgbColor object representing this color. */ 0160 RgbColor RgbColor::fromHsv(const GenericColor &color) 0161 { 0162 RgbColor result; 0163 constexpr auto zero = static_cast<QColorFloatType>(0); 0164 constexpr auto one = static_cast<QColorFloatType>(1); 0165 const auto hsvHue = // 0166 qBound(zero, static_cast<QColorFloatType>(color.first / 360.0), one); 0167 const auto hsvSaturation = // 0168 qBound(zero, static_cast<QColorFloatType>(color.second / 100.0), one); 0169 const auto hsvValue = // 0170 qBound(zero, static_cast<QColorFloatType>(color.third / 100.0), one); 0171 const QColor newRgbQColor = // 0172 QColor::fromHsvF(hsvHue, hsvSaturation, hsvValue); 0173 result.fillAll(newRgbQColor, color.first); 0174 // Override again with the original value: 0175 result.hsv = color; 0176 if (result.hsv.third == 0) { 0177 // Color is black. So neither changing HSV-saturation or changing 0178 // HSL-saturation will change the color itself. To give a better 0179 // user experience, we synchronize both values. 0180 result.hsl.second = result.hsv.second; 0181 } 0182 0183 return result; 0184 } 0185 0186 /** @brief Static convenience function that returns a @ref RgbColor 0187 * constructed from the given color. 0188 * 0189 * @param color Original color. 0190 * @returns A @ref RgbColor object representing this color. */ 0191 RgbColor RgbColor::fromHwb(const GenericColor &color) 0192 { 0193 RgbColor result; 0194 GenericColor normalizedHwb = color; 0195 const auto whitenessBlacknessSum = // 0196 normalizedHwb.second + normalizedHwb.third; 0197 if (whitenessBlacknessSum > 100) { 0198 normalizedHwb.second *= 100 / whitenessBlacknessSum; 0199 normalizedHwb.third *= 100 / whitenessBlacknessSum; 0200 } 0201 0202 const double quotient = (100 - normalizedHwb.third); 0203 const auto newHsvSaturation = // 0204 (quotient == 0) // This is only the case for pure black. 0205 ? 0 // Avoid division by 0 in the formula below. Instead, set 0206 // an arbitrary (in-range) value, because the HSV saturation 0207 // is meaningless when value/brightness is 0, which is the case 0208 // for black. 0209 : qBound<double>(0, 100 - normalizedHwb.second / quotient * 100, 100); 0210 const auto newHsvValue = qBound<double>(0, 100 - normalizedHwb.third, 100); 0211 const GenericColor newHsv = GenericColor(normalizedHwb.first, // 0212 newHsvSaturation, // 0213 newHsvValue); 0214 const QColor newRgbQColor = // 0215 QColor::fromHsvF( // 0216 static_cast<QColorFloatType>(newHsv.first / 360), // 0217 static_cast<QColorFloatType>(newHsv.second / 100), // 0218 static_cast<QColorFloatType>(newHsv.third / 100)); 0219 result.fillAll(newRgbQColor, normalizedHwb.first); 0220 // Override again with the original value: 0221 result.hsv = newHsv; 0222 result.hwb = color; // Intentionally not normalized, but original value. 0223 0224 return result; 0225 } 0226 0227 /** @brief Equal operator 0228 * 0229 * @param other The object to compare with. 0230 * 0231 * @returns <tt>true</tt> if all data members have exactly the same 0232 * coordinates. <tt>false</tt> otherwise. */ 0233 bool RgbColor::operator==(const RgbColor &other) const 0234 { 0235 // Test equality for all data members 0236 return (hsl == other.hsl) // 0237 && (hsv == other.hsv) // 0238 && (hwb == other.hwb) // 0239 && (rgb255 == other.rgb255) // 0240 && (rgbQColor == other.rgbQColor); 0241 } 0242 0243 /** @internal 0244 * 0245 * @brief Adds QDebug() support for data type 0246 * @ref PerceptualColor::RgbColor 0247 * 0248 * @param dbg Existing debug object 0249 * @param value Value to stream into the debug object 0250 * @returns Debug object with value streamed in */ 0251 QDebug operator<<(QDebug dbg, const PerceptualColor::RgbColor &value) 0252 { 0253 dbg.nospace() // 0254 << "RgbColor(\n" 0255 << " - hsl: " << value.hsl << "\n" 0256 << " - hsv: " << value.hsv << "\n" 0257 << " - hwb: " << value.hwb << "\n" 0258 << " - rgb: " << value.rgb255 << "\n" 0259 << " - rgbQColor: " << value.rgbQColor << "\n" 0260 << ")"; 0261 return dbg.maybeSpace(); 0262 } 0263 0264 static_assert(std::is_standard_layout_v<RgbColor>); 0265 0266 } // namespace PerceptualColor