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