From bf48ce5b51edd1822badc3310d3ac605b5ca9129 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett (MSFT)" Date: Mon, 9 Mar 2020 17:17:24 -0700 Subject: [PATCH] add til::color, a universal-converting color type (#4108) til::color will help us move away from COLORREF internally. It supports conversion to/from COLORREF, and from all types of structs containing members named R, G, B and A (or r, g, b, and a). ## Validation Steps Performed Tests; run through profile/colorScheme deserialization with `til::color` instead of `uint32_t` or `COLORREF`. --- src/inc/LibraryIncludes.h | 2 + src/inc/til.h | 1 + src/inc/til/color.h | 117 ++++++++++++++++++++++++++ src/til/ut_til/ColorTests.cpp | 104 +++++++++++++++++++++++ src/til/ut_til/sources | 1 + src/til/ut_til/til.unit.tests.vcxproj | 1 + tools/ConsoleTypes.natvis | 4 + 7 files changed, 230 insertions(+) create mode 100644 src/inc/til/color.h create mode 100644 src/til/ut_til/ColorTests.cpp diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 61399cde0..2002fa48f 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -86,7 +86,9 @@ #include // TIL - Terminal Implementation Library +#ifndef BLOCK_TIL // Certain projects may want to include TIL manually to gain superpowers #include "til.h" +#endif #pragma warning(pop) diff --git a/src/inc/til.h b/src/inc/til.h index abd12c59e..fce071cf1 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -4,6 +4,7 @@ #pragma once #include "til/at.h" +#include "til/color.h" #include "til/some.h" #include "til/u8u16convert.h" diff --git a/src/inc/til/color.h b/src/inc/til/color.h new file mode 100644 index 000000000..59d35dd4f --- /dev/null +++ b/src/inc/til/color.h @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace til // Terminal Implementation Library. Also: "Today I Learned" +{ + // color is a universal integral 8bpp RGBA (0-255) color type implicitly convertible to/from + // a number of other color types. +#pragma warning(push) + // we can't depend on GSL here (some libraries use BLOCK_GSL), so we use static_cast for explicit narrowing +#pragma warning(disable : 26472) + struct color + { + uint8_t r, g, b, a; + + constexpr color() noexcept : + r{ 0 }, + g{ 0 }, + b{ 0 }, + a{ 0 } {} + + constexpr color(uint8_t _r, uint8_t _g, uint8_t _b) noexcept : + r{ _r }, + g{ _g }, + b{ _b }, + a{ 255 } {} + + constexpr color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) noexcept : + r{ _r }, + g{ _g }, + b{ _b }, + a{ _a } {} + + constexpr color(const color&) = default; + constexpr color(color&&) = default; + color& operator=(const color&) = default; + color& operator=(color&&) = default; + ~color() = default; + +#ifdef _WINDEF_ + constexpr color(COLORREF c) : + r{ static_cast(c & 0xFF) }, + g{ static_cast((c & 0xFF00) >> 8) }, + b{ static_cast((c & 0xFF0000) >> 16) }, + a{ 255 } + { + } + + operator COLORREF() const noexcept + { + return static_cast(r) | (static_cast(g) << 8) | (static_cast(b) << 16); + } +#endif + + // Method Description: + // - Converting constructor for any other color structure type containing integral R, G, B, A (case sensitive.) + // Notes: + // - This and all below conversions make use of std::enable_if and a default parameter to disambiguate themselves. + // enable_if will result in an if the constraint within it is not met, which will make this + // template ill-formed. Because SFINAE, ill-formed templated members "disappear" instead of causing an error. + template + constexpr color(const TOther& other, std::enable_if_t().R)> && std::is_integral_v().A)>, int> /*sentinel*/ = 0) : + r{ static_cast(other.R) }, + g{ static_cast(other.G) }, + b{ static_cast(other.B) }, + a{ static_cast(other.A) } + { + } + + // Method Description: + // - Converting constructor for any other color structure type containing integral r, g, b, a (case sensitive.) + template + constexpr color(const TOther& other, std::enable_if_t().r)> && std::is_integral_v().a)>, int> /*sentinel*/ = 0) : + r{ static_cast(other.r) }, + g{ static_cast(other.g) }, + b{ static_cast(other.b) }, + a{ static_cast(other.a) } + { + } + + // Method Description: + // - Converting constructor for any other color structure type containing floating-point R, G, B, A (case sensitive.) + template + constexpr color(const TOther& other, std::enable_if_t().R)> && std::is_floating_point_v().A)>, float> /*sentinel*/ = 1.0f) : + r{ static_cast(other.R * 255.0f) }, + g{ static_cast(other.G * 255.0f) }, + b{ static_cast(other.B * 255.0f) }, + a{ static_cast(other.A * 255.0f) } + { + } + + // Method Description: + // - Converting constructor for any other color structure type containing floating-point r, g, b, a (case sensitive.) + template + constexpr color(const TOther& other, std::enable_if_t().r)> && std::is_floating_point_v().a)>, float> /*sentinel*/ = 1.0f) : + r{ static_cast(other.r * 255.0f) }, + g{ static_cast(other.g * 255.0f) }, + b{ static_cast(other.b * 255.0f) }, + a{ static_cast(other.a * 255.0f) } + { + } + +#ifdef D3DCOLORVALUE_DEFINED + constexpr operator D3DCOLORVALUE() const + { + return D3DCOLORVALUE{ r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f }; + } +#endif + + constexpr bool operator==(const til::color& other) const + { + return r == other.r && g == other.g && b == other.b && a == other.a; + } + }; +#pragma warning(pop) +} diff --git a/src/til/ut_til/ColorTests.cpp b/src/til/ut_til/ColorTests.cpp new file mode 100644 index 000000000..a2381f809 --- /dev/null +++ b/src/til/ut_til/ColorTests.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "WexTestClass.h" + +namespace WEX::TestExecution +{ + template<> + class VerifyOutputTraits + { + public: + static WEX::Common::NoThrowString ToString(const til::color& c) + { + return WEX::Common::NoThrowString().Format(L"(RGBA: %2.02x%2.02x%2.02x%2.02x)", c.r, c.g, c.b, c.a); + } + }; +} + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +class ColorTests +{ + TEST_CLASS(ColorTests); + + TEST_METHOD(Construct) + { + til::color rgb{ 0xde, 0xad, 0xbe }; + + VERIFY_ARE_EQUAL(0xde, rgb.r); + VERIFY_ARE_EQUAL(0xad, rgb.g); + VERIFY_ARE_EQUAL(0xbe, rgb.b); + VERIFY_ARE_EQUAL(0xff, rgb.a); // auto-filled by constructor + + VERIFY_ARE_EQUAL(rgb, rgb); + + til::color rgba{ 0xde, 0xad, 0xbe, 0xef }; + + VERIFY_ARE_EQUAL(0xde, rgba.r); + VERIFY_ARE_EQUAL(0xad, rgba.g); + VERIFY_ARE_EQUAL(0xbe, rgba.b); + VERIFY_ARE_EQUAL(0xef, rgba.a); + + VERIFY_ARE_NOT_EQUAL(rgb, rgba); + } + + TEST_METHOD(ConvertFromColorRef) + { + COLORREF c = 0x00FEEDFAu; // remember, this one is in 0BGR + til::color fromColorRef{ c }; + + VERIFY_ARE_EQUAL(0xfa, fromColorRef.r); + VERIFY_ARE_EQUAL(0xed, fromColorRef.g); + VERIFY_ARE_EQUAL(0xfe, fromColorRef.b); + VERIFY_ARE_EQUAL(0xff, fromColorRef.a); // COLORREF do not have an alpha channel + } + + TEST_METHOD(ConvertToColorRef) + { + til::color rgb{ 0xf0, 0x0d, 0xca, 0xfe }; + + VERIFY_ARE_EQUAL(0x00CA0DF0u, static_cast(rgb)); // alpha is dropped, COLOREREF is 0BGR + } + + template + struct Quad_rgba + { + T r, g, b, a; + }; + + template + struct Quad_RGBA + { + T R, G, B, A; + }; + + TEST_METHOD(ConvertFromIntColorStructs) + { + Quad_rgba q1{ 0xca, 0xfe, 0xf0, 0x0d }; + til::color t1{ 0xca, 0xfe, 0xf0, 0x0d }; + + VERIFY_ARE_EQUAL(t1, static_cast(q1)); + + Quad_RGBA q2{ 0xfa, 0xce, 0xb0, 0x17 }; + til::color t2{ 0xfa, 0xce, 0xb0, 0x17 }; + + VERIFY_ARE_EQUAL(t2, static_cast(q2)); + } + + TEST_METHOD(ConvertFromFloatColorStructs) + { + Quad_rgba q1{ 0.730f, 0.867f, 0.793f, 0.997f }; + til::color t1{ 0xba, 0xdd, 0xca, 0xfe }; + + VERIFY_ARE_EQUAL(t1, static_cast(q1)); + + Quad_RGBA q2{ 0.871f, 0.679f, 0.981f, 0.067f }; + til::color t2{ 0xde, 0xad, 0xfa, 0x11 }; + + VERIFY_ARE_EQUAL(t2, static_cast(q2)); + } +}; diff --git a/src/til/ut_til/sources b/src/til/ut_til/sources index 43e4d13d7..2e7861cdf 100644 --- a/src/til/ut_til/sources +++ b/src/til/ut_til/sources @@ -14,6 +14,7 @@ DLLDEF = SOURCES = \ $(SOURCES) \ + ColorTests.cpp \ SomeTests.cpp \ DefaultResource.rc \ diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index 3245f172e..bfa70c72f 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -10,6 +10,7 @@ + Create diff --git a/tools/ConsoleTypes.natvis b/tools/ConsoleTypes.natvis index 4d793255b..31340c96d 100644 --- a/tools/ConsoleTypes.natvis +++ b/tools/ConsoleTypes.natvis @@ -80,4 +80,8 @@ {{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}} {{↑ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}} + + + {{RGB: {(int)r,d}, {(int)g,d}, {(int)b,d}; α: {(int)a,d}}} +