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`.
This commit is contained in:
Dustin L. Howett (MSFT) 2020-03-09 17:17:24 -07:00 committed by GitHub
parent 71f7d0311e
commit bf48ce5b51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 230 additions and 0 deletions

View file

@ -86,7 +86,9 @@
#include <wrl.h>
// 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)

View file

@ -4,6 +4,7 @@
#pragma once
#include "til/at.h"
#include "til/color.h"
#include "til/some.h"
#include "til/u8u16convert.h"

117
src/inc/til/color.h Normal file
View file

@ -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<uint8_t>(c & 0xFF) },
g{ static_cast<uint8_t>((c & 0xFF00) >> 8) },
b{ static_cast<uint8_t>((c & 0xFF0000) >> 16) },
a{ 255 }
{
}
operator COLORREF() const noexcept
{
return static_cast<COLORREF>(r) | (static_cast<COLORREF>(g) << 8) | (static_cast<COLORREF>(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 <error-type> 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<typename TOther>
constexpr color(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().R)> && std::is_integral_v<decltype(std::declval<TOther>().A)>, int> /*sentinel*/ = 0) :
r{ static_cast<uint8_t>(other.R) },
g{ static_cast<uint8_t>(other.G) },
b{ static_cast<uint8_t>(other.B) },
a{ static_cast<uint8_t>(other.A) }
{
}
// Method Description:
// - Converting constructor for any other color structure type containing integral r, g, b, a (case sensitive.)
template<typename TOther>
constexpr color(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().r)> && std::is_integral_v<decltype(std::declval<TOther>().a)>, int> /*sentinel*/ = 0) :
r{ static_cast<uint8_t>(other.r) },
g{ static_cast<uint8_t>(other.g) },
b{ static_cast<uint8_t>(other.b) },
a{ static_cast<uint8_t>(other.a) }
{
}
// Method Description:
// - Converting constructor for any other color structure type containing floating-point R, G, B, A (case sensitive.)
template<typename TOther>
constexpr color(const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().R)> && std::is_floating_point_v<decltype(std::declval<TOther>().A)>, float> /*sentinel*/ = 1.0f) :
r{ static_cast<uint8_t>(other.R * 255.0f) },
g{ static_cast<uint8_t>(other.G * 255.0f) },
b{ static_cast<uint8_t>(other.B * 255.0f) },
a{ static_cast<uint8_t>(other.A * 255.0f) }
{
}
// Method Description:
// - Converting constructor for any other color structure type containing floating-point r, g, b, a (case sensitive.)
template<typename TOther>
constexpr color(const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().r)> && std::is_floating_point_v<decltype(std::declval<TOther>().a)>, float> /*sentinel*/ = 1.0f) :
r{ static_cast<uint8_t>(other.r * 255.0f) },
g{ static_cast<uint8_t>(other.g * 255.0f) },
b{ static_cast<uint8_t>(other.b * 255.0f) },
a{ static_cast<uint8_t>(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)
}

View file

@ -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<til::color>
{
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<COLORREF>(rgb)); // alpha is dropped, COLOREREF is 0BGR
}
template<typename T>
struct Quad_rgba
{
T r, g, b, a;
};
template<typename T>
struct Quad_RGBA
{
T R, G, B, A;
};
TEST_METHOD(ConvertFromIntColorStructs)
{
Quad_rgba<int> q1{ 0xca, 0xfe, 0xf0, 0x0d };
til::color t1{ 0xca, 0xfe, 0xf0, 0x0d };
VERIFY_ARE_EQUAL(t1, static_cast<til::color>(q1));
Quad_RGBA<int> q2{ 0xfa, 0xce, 0xb0, 0x17 };
til::color t2{ 0xfa, 0xce, 0xb0, 0x17 };
VERIFY_ARE_EQUAL(t2, static_cast<til::color>(q2));
}
TEST_METHOD(ConvertFromFloatColorStructs)
{
Quad_rgba<float> q1{ 0.730f, 0.867f, 0.793f, 0.997f };
til::color t1{ 0xba, 0xdd, 0xca, 0xfe };
VERIFY_ARE_EQUAL(t1, static_cast<til::color>(q1));
Quad_RGBA<float> q2{ 0.871f, 0.679f, 0.981f, 0.067f };
til::color t2{ 0xde, 0xad, 0xfa, 0x11 };
VERIFY_ARE_EQUAL(t2, static_cast<til::color>(q2));
}
};

View file

@ -14,6 +14,7 @@ DLLDEF =
SOURCES = \
$(SOURCES) \
ColorTests.cpp \
SomeTests.cpp \
DefaultResource.rc \

View file

@ -10,6 +10,7 @@
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="ColorTests.cpp" />
<ClCompile Include="SomeTests.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>

View file

@ -80,4 +80,8 @@
<DisplayString Condition="_keyDown">{{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
<DisplayString Condition="!_keyDown">{{↑ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
</Type>
<Type Name="til::color">
<DisplayString>{{RGB: {(int)r,d}, {(int)g,d}, {(int)b,d}; α: {(int)a,d}}}</DisplayString>
</Type>
</AutoVisualizer>