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:
parent
71f7d0311e
commit
bf48ce5b51
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
117
src/inc/til/color.h
Normal 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)
|
||||
}
|
104
src/til/ut_til/ColorTests.cpp
Normal file
104
src/til/ut_til/ColorTests.cpp
Normal 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));
|
||||
}
|
||||
};
|
|
@ -14,6 +14,7 @@ DLLDEF =
|
|||
|
||||
SOURCES = \
|
||||
$(SOURCES) \
|
||||
ColorTests.cpp \
|
||||
SomeTests.cpp \
|
||||
DefaultResource.rc \
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue