Changes DlColor to support wide gamut colors (#54473) (flutter/engine#54648)

relands https://github.com/flutter/engine/pull/54473
issue: https://github.com/flutter/flutter/issues/127855
integration test: https://github.com/flutter/engine/pull/54415

This is the engine side changes required for wide gamut framework support.  It changes the internal representation of DlColor to be floats.  It will be married with https://github.com/flutter/engine/pull/54415 when it lands in https://github.com/flutter/engine/pull/54567.

## Difference from last attempt

1) The default color is now opaque black, not transparent black (not the issue for revert)
1) Updated a test to send in valid numbers when constructing a color and added asserts to avoid those problems in the future.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
gaaclarke 2024-08-20 14:44:24 -07:00 committed by GitHub
parent e256163e94
commit a6c6c50a25
22 changed files with 443 additions and 144 deletions

View File

@ -42320,6 +42320,7 @@ ORIGIN: ../../../flutter/display_list/dl_builder.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_builder.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_canvas.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_canvas.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_color.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_color.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_op_flags.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/display_list/dl_op_flags.h + ../../../flutter/LICENSE
@ -45200,6 +45201,7 @@ FILE: ../../../flutter/display_list/dl_builder.cc
FILE: ../../../flutter/display_list/dl_builder.h
FILE: ../../../flutter/display_list/dl_canvas.cc
FILE: ../../../flutter/display_list/dl_canvas.h
FILE: ../../../flutter/display_list/dl_color.cc
FILE: ../../../flutter/display_list/dl_color.h
FILE: ../../../flutter/display_list/dl_op_flags.cc
FILE: ../../../flutter/display_list/dl_op_flags.h

View File

@ -35,6 +35,7 @@ source_set("display_list") {
"dl_builder.h",
"dl_canvas.cc",
"dl_canvas.h",
"dl_color.cc",
"dl_color.h",
"dl_op_flags.cc",
"dl_op_flags.h",

View File

@ -1847,7 +1847,7 @@ TEST_F(DisplayListTest, FlutterSvgIssue661BoundsWereEmpty) {
// This is the more practical result. The bounds are "almost" 0,0,100x100
EXPECT_EQ(display_list->bounds().roundOut(), SkIRect::MakeWH(100, 100));
EXPECT_EQ(display_list->op_count(), 19u);
EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 408u);
EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 424u);
EXPECT_EQ(display_list->total_depth(), 3u);
}

View File

@ -805,7 +805,7 @@ class DisplayListBuilder final : public virtual DlCanvas,
// kAnyColor is a non-opaque and non-transparent color that will not
// trigger any short-circuit tests about the results of a blend.
static constexpr DlColor kAnyColor = DlColor::kMidGrey().withAlpha(0x80);
static constexpr DlColor kAnyColor = DlColor::kMidGrey().withAlphaF(0.5f);
static_assert(!kAnyColor.isOpaque());
static_assert(!kAnyColor.isTransparent());
static DlColor GetEffectiveColor(const DlPaint& paint,

View File

@ -0,0 +1,75 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/display_list/dl_color.h"
namespace flutter {
namespace {
const std::array<DlScalar, 12> kP3ToSrgb = {
1.306671048092539, -0.298061942172353,
0.213228303487995, -0.213580156254466, //
-0.117390025596251, 1.127722006101976,
0.109727644608938, -0.109450321455370, //
0.214813187718391, 0.054268702864647,
1.406898424029350, -0.364892765879631};
DlColor transform(const DlColor& color,
const std::array<DlScalar, 12>& matrix,
DlColorSpace color_space) {
return DlColor(color.getAlphaF(),
matrix[0] * color.getRedF() + //
matrix[1] * color.getGreenF() + //
matrix[2] * color.getBlueF() + //
matrix[3], //
matrix[4] * color.getRedF() + //
matrix[5] * color.getGreenF() + //
matrix[6] * color.getBlueF() + //
matrix[7], //
matrix[8] * color.getRedF() + //
matrix[9] * color.getGreenF() + //
matrix[10] * color.getBlueF() + //
matrix[11], //
color_space);
}
} // namespace
DlColor DlColor::withColorSpace(DlColorSpace color_space) const {
switch (color_space_) {
case DlColorSpace::kSRGB:
switch (color_space) {
case DlColorSpace::kSRGB:
return *this;
case DlColorSpace::kExtendedSRGB:
return DlColor(alpha_, red_, green_, blue_,
DlColorSpace::kExtendedSRGB);
case DlColorSpace::kDisplayP3:
FML_CHECK(false) << "not implemented";
return *this;
}
case DlColorSpace::kExtendedSRGB:
switch (color_space) {
case DlColorSpace::kSRGB:
FML_CHECK(false) << "not implemented";
return *this;
case DlColorSpace::kExtendedSRGB:
return *this;
case DlColorSpace::kDisplayP3:
FML_CHECK(false) << "not implemented";
return *this;
}
case DlColorSpace::kDisplayP3:
switch (color_space) {
case DlColorSpace::kSRGB:
FML_CHECK(false) << "not implemented";
return *this;
case DlColorSpace::kExtendedSRGB:
return transform(*this, kP3ToSrgb, DlColorSpace::kExtendedSRGB);
case DlColorSpace::kDisplayP3:
return *this;
}
}
}
} // namespace flutter

View File

@ -9,10 +9,39 @@
namespace flutter {
// These should match the enumerations defined in //lib/ui/painting.dart.
enum class DlColorSpace { kSRGB = 0, kExtendedSRGB = 1, kDisplayP3 = 2 };
/// A representation of a color.
///
/// The color belongs to a DlColorSpace. Using deprecated integer data accessors
/// on colors not in the kSRGB colorspace can lead to data loss. Using the
/// floating point accessors and constructors that were added for wide-gamut
/// support are preferred.
struct DlColor {
public:
constexpr DlColor() : argb_(0xFF000000) {}
constexpr explicit DlColor(uint32_t argb) : argb_(argb) {}
constexpr DlColor()
: alpha_(1.f),
red_(0.f),
green_(0.f),
blue_(0.f),
color_space_(DlColorSpace::kSRGB) {}
constexpr explicit DlColor(uint32_t argb)
: alpha_(toF((argb >> 24) & 0xff)),
red_(toF((argb >> 16) & 0xff)),
green_(toF((argb >> 8) & 0xff)),
blue_(toF((argb >> 0) & 0xff)),
color_space_(DlColorSpace::kSRGB) {}
constexpr DlColor(DlScalar alpha,
DlScalar red,
DlScalar green,
DlScalar blue,
DlColorSpace colorspace)
: alpha_(alpha),
red_(red),
green_(green),
blue_(blue),
color_space_(colorspace) {}
/// @brief Construct a 32 bit color from floating point R, G, B, and A color
/// channels.
@ -29,10 +58,7 @@ struct DlColor {
DlScalar r,
DlScalar g,
DlScalar b) {
return DlColor(toC(a) << 24 | //
toC(r) << 16 | //
toC(g) << 8 | //
toC(b));
return DlColor(a, r, g, b, DlColorSpace::kSRGB);
}
static constexpr uint8_t toAlpha(DlScalar opacity) { return toC(opacity); }
@ -66,42 +92,58 @@ struct DlColor {
static constexpr DlColor kOrangeRed() {return DlColor(0xFFFF4500);};
// clang-format on
constexpr bool isOpaque() const { return getAlpha() == 0xFF; }
constexpr bool isTransparent() const { return getAlpha() == 0; }
constexpr bool isOpaque() const { return alpha_ >= 1.f; }
constexpr bool isTransparent() const { return alpha_ <= 0.f; }
constexpr int getAlpha() const { return argb_ >> 24; }
constexpr int getRed() const { return (argb_ >> 16) & 0xFF; }
constexpr int getGreen() const { return (argb_ >> 8) & 0xFF; }
constexpr int getBlue() const { return argb_ & 0xFF; }
///\deprecated Use floating point accessors to avoid data loss when using wide
/// gamut colors.
constexpr int getAlpha() const { return toC(alpha_); }
///\deprecated Use floating point accessors to avoid data loss when using wide
/// gamut colors.
constexpr int getRed() const { return toC(red_); }
///\deprecated Use floating point accessors to avoid data loss when using wide
/// gamut colors.
constexpr int getGreen() const { return toC(green_); }
///\deprecated Use floating point accessors to avoid data loss when using wide
/// gamut colors.
constexpr int getBlue() const { return toC(blue_); }
constexpr DlScalar getAlphaF() const { return toF(getAlpha()); }
constexpr DlScalar getRedF() const { return toF(getRed()); }
constexpr DlScalar getGreenF() const { return toF(getGreen()); }
constexpr DlScalar getBlueF() const { return toF(getBlue()); }
constexpr DlScalar getAlphaF() const { return alpha_; }
constexpr DlScalar getRedF() const { return red_; }
constexpr DlScalar getGreenF() const { return green_; }
constexpr DlScalar getBlueF() const { return blue_; }
constexpr uint32_t premultipliedArgb() const {
if (isOpaque()) {
return argb_;
}
DlScalar f = getAlphaF();
return (argb_ & 0xFF000000) | //
toC(getRedF() * f) << 16 | //
toC(getGreenF() * f) << 8 | //
toC(getBlueF() * f);
}
constexpr DlColorSpace getColorSpace() const { return color_space_; }
constexpr DlColor withAlpha(uint8_t alpha) const { //
return DlColor((argb_ & 0x00FFFFFF) | (alpha << 24));
return DlColor((argb() & 0x00FFFFFF) | (alpha << 24));
}
constexpr DlColor withRed(uint8_t red) const { //
return DlColor((argb_ & 0xFF00FFFF) | (red << 16));
return DlColor((argb() & 0xFF00FFFF) | (red << 16));
}
constexpr DlColor withGreen(uint8_t green) const { //
return DlColor((argb_ & 0xFFFF00FF) | (green << 8));
return DlColor((argb() & 0xFFFF00FF) | (green << 8));
}
constexpr DlColor withBlue(uint8_t blue) const { //
return DlColor((argb_ & 0xFFFFFF00) | (blue << 0));
return DlColor((argb() & 0xFFFFFF00) | (blue << 0));
}
constexpr DlColor withAlphaF(float alpha) const { //
return DlColor(alpha, red_, green_, blue_, color_space_);
}
constexpr DlColor withRedF(float red) const { //
return DlColor(alpha_, red, green_, blue_, color_space_);
}
constexpr DlColor withGreenF(float green) const { //
return DlColor(alpha_, red_, green, blue_, color_space_);
}
constexpr DlColor withBlueF(float blue) const { //
return DlColor(alpha_, red_, green_, blue, color_space_);
}
/// Performs a colorspace transformation.
///
/// This isn't just a replacement of the color space field, the new color
/// components are calculated.
DlColor withColorSpace(DlColorSpace color_space) const;
constexpr DlColor modulateOpacity(DlScalar opacity) const {
return opacity <= 0 ? withAlpha(0)
@ -109,15 +151,47 @@ struct DlColor {
: withAlpha(round(getAlpha() * opacity));
}
constexpr uint32_t argb() const { return argb_; }
///\deprecated Use floating point accessors to avoid data loss when using wide
/// gamut colors.
constexpr uint32_t argb() const {
return toC(alpha_) << 24 | //
toC(red_) << 16 | //
toC(green_) << 8 | //
toC(blue_) << 0;
}
bool operator==(DlColor const& other) const { return argb_ == other.argb_; }
bool operator!=(DlColor const& other) const { return argb_ != other.argb_; }
bool operator==(uint32_t const& other) const { return argb_ == other; }
bool operator!=(uint32_t const& other) const { return argb_ != other; }
/// Checks that no difference in color components exceeds the delta.
///
/// This doesn't check against the actual distance between the colors in some
/// space.
bool isClose(DlColor const& other, DlScalar delta = 1.0f / 256.0f) {
return color_space_ == other.color_space_ &&
std::abs(alpha_ - other.alpha_) < delta &&
std::abs(red_ - other.red_) < delta &&
std::abs(green_ - other.green_) < delta &&
std::abs(blue_ - other.blue_) < delta;
}
bool operator==(DlColor const& other) const {
return alpha_ == other.alpha_ && red_ == other.red_ &&
green_ == other.green_ && blue_ == other.blue_ &&
color_space_ == other.color_space_;
}
bool operator!=(DlColor const& other) const {
return !this->operator==(other);
}
bool operator==(uint32_t const& other) const {
return argb() == other && color_space_ == DlColorSpace::kSRGB;
}
bool operator!=(uint32_t const& other) const {
return !this->operator==(other);
}
private:
uint32_t argb_;
DlScalar alpha_;
DlScalar red_;
DlScalar green_;
DlScalar blue_;
DlColorSpace color_space_;
static constexpr DlScalar toF(uint8_t comp) { return comp * (1.0f / 255); }
static constexpr uint8_t toC(DlScalar fComp) { return round(fComp * 255); }

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "flutter/display_list/dl_color.h"
#include "flutter/testing/display_list_testing.h"
#include "flutter/testing/testing.h"
#include "third_party/skia/include/core/SkColor.h"
@ -14,10 +15,19 @@ static void arraysEqual(const uint32_t* ints,
const DlColor* colors,
int count) {
for (int i = 0; i < count; i++) {
EXPECT_TRUE(ints[i] == colors[i].argb());
EXPECT_EQ(ints[i], colors[i].argb()) << " index:" << i;
}
}
TEST(DisplayListColor, DefaultValue) {
DlColor color;
EXPECT_EQ(color.getAlphaF(), 1.f);
EXPECT_EQ(color.getRedF(), 0.f);
EXPECT_EQ(color.getGreenF(), 0.f);
EXPECT_EQ(color.getBlueF(), 0.f);
EXPECT_EQ(color.getColorSpace(), DlColorSpace::kSRGB);
}
TEST(DisplayListColor, ArrayInterchangeableWithUint32) {
uint32_t ints[5] = {
0xFF000000, //
@ -34,8 +44,6 @@ TEST(DisplayListColor, ArrayInterchangeableWithUint32) {
DlColor(0xF1F2F3F4),
};
arraysEqual(ints, colors, 5);
arraysEqual(reinterpret_cast<const uint32_t*>(colors),
reinterpret_cast<const DlColor*>(ints), 5);
}
TEST(DisplayListColor, DlColorDirectlyComparesToSkColor) {
@ -48,13 +56,17 @@ TEST(DisplayListColor, DlColorDirectlyComparesToSkColor) {
TEST(DisplayListColor, DlColorFloatConstructor) {
EXPECT_EQ(DlColor::ARGB(1.0f, 1.0f, 1.0f, 1.0f), DlColor(0xFFFFFFFF));
EXPECT_EQ(DlColor::ARGB(0.0f, 0.0f, 0.0f, 0.0f), DlColor(0x00000000));
EXPECT_EQ(DlColor::ARGB(0.5f, 0.5f, 0.5f, 0.5f), DlColor(0x80808080));
EXPECT_EQ(DlColor::ARGB(1.0f, 0.0f, 0.5f, 1.0f), DlColor(0xFF0080FF));
EXPECT_TRUE(
DlColor::ARGB(0.5f, 0.5f, 0.5f, 0.5f).isClose(DlColor(0x80808080)));
EXPECT_TRUE(
DlColor::ARGB(1.0f, 0.0f, 0.5f, 1.0f).isClose(DlColor(0xFF0080FF)));
EXPECT_EQ(DlColor::RGBA(1.0f, 1.0f, 1.0f, 1.0f), DlColor(0xFFFFFFFF));
EXPECT_EQ(DlColor::RGBA(0.0f, 0.0f, 0.0f, 0.0f), DlColor(0x00000000));
EXPECT_EQ(DlColor::RGBA(0.5f, 0.5f, 0.5f, 0.5f), DlColor(0x80808080));
EXPECT_EQ(DlColor::RGBA(1.0f, 0.0f, 0.5f, 1.0f), DlColor(0xFFFF0080));
EXPECT_TRUE(
DlColor::RGBA(0.5f, 0.5f, 0.5f, 0.5f).isClose(DlColor(0x80808080)));
EXPECT_TRUE(
DlColor::RGBA(1.0f, 0.0f, 0.5f, 1.0f).isClose(DlColor(0xFFFF0080)));
}
TEST(DisplayListColor, DlColorComponentGetters) {
@ -124,10 +136,10 @@ TEST(DisplayListColor, DlColorComponentGetters) {
const DlScalar half = 127.0f * (1.0f / 255.0f);
EXPECT_EQ(test.getAlphaF(), half);
EXPECT_EQ(test.getRedF(), half);
EXPECT_EQ(test.getGreenF(), half);
EXPECT_EQ(test.getBlueF(), half);
EXPECT_NEAR(test.getAlphaF(), half, 0.00001);
EXPECT_NEAR(test.getRedF(), half, 0.00001);
EXPECT_NEAR(test.getGreenF(), half, 0.00001);
EXPECT_NEAR(test.getBlueF(), half, 0.00001);
}
{
@ -138,12 +150,10 @@ TEST(DisplayListColor, DlColorComponentGetters) {
EXPECT_EQ(test.getGreen(), 0x80);
EXPECT_EQ(test.getBlue(), 0x80);
const DlScalar half = 128.0f * (1.0f / 255.0f);
EXPECT_EQ(test.getAlphaF(), half);
EXPECT_EQ(test.getRedF(), half);
EXPECT_EQ(test.getGreenF(), half);
EXPECT_EQ(test.getBlueF(), half);
EXPECT_EQ(test.getAlphaF(), 0.5);
EXPECT_EQ(test.getRedF(), 0.5);
EXPECT_EQ(test.getGreenF(), 0.5);
EXPECT_EQ(test.getBlueF(), 0.5);
}
{
@ -154,10 +164,10 @@ TEST(DisplayListColor, DlColorComponentGetters) {
EXPECT_EQ(test.getGreen(), 0x3F);
EXPECT_EQ(test.getBlue(), 0x4F);
EXPECT_EQ(test.getAlphaF(), 0x1f * (1.0f / 255.0f));
EXPECT_EQ(test.getRedF(), 0x2f * (1.0f / 255.0f));
EXPECT_EQ(test.getGreenF(), 0x3f * (1.0f / 255.0f));
EXPECT_EQ(test.getBlueF(), 0x4f * (1.0f / 255.0f));
EXPECT_NEAR(test.getAlphaF(), 0x1f * (1.0f / 255.0f), 0.00001);
EXPECT_NEAR(test.getRedF(), 0x2f * (1.0f / 255.0f), 0.00001);
EXPECT_NEAR(test.getGreenF(), 0x3f * (1.0f / 255.0f), 0.00001);
EXPECT_NEAR(test.getBlueF(), 0x4f * (1.0f / 255.0f), 0.00001);
}
{
@ -168,11 +178,10 @@ TEST(DisplayListColor, DlColorComponentGetters) {
EXPECT_EQ(test.getGreen(), round(0.3f * 255));
EXPECT_EQ(test.getBlue(), round(0.4f * 255));
// Unfortunately conversion from float to 8-bit back to float is lossy
EXPECT_EQ(test.getAlphaF(), round(0.1f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getRedF(), round(0.2f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getGreenF(), round(0.3f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getBlueF(), round(0.4f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getAlphaF(), 0.1f);
EXPECT_EQ(test.getRedF(), 0.2f);
EXPECT_EQ(test.getGreenF(), 0.3f);
EXPECT_EQ(test.getBlueF(), 0.4f);
}
{
@ -183,11 +192,10 @@ TEST(DisplayListColor, DlColorComponentGetters) {
EXPECT_EQ(test.getGreen(), round(0.3f * 255));
EXPECT_EQ(test.getBlue(), round(0.4f * 255));
// Unfortunately conversion from float to 8-bit back to float is lossy
EXPECT_EQ(test.getAlphaF(), round(0.1f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getRedF(), round(0.2f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getGreenF(), round(0.3f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getBlueF(), round(0.4f * 255) * (1.0f / 255.0f));
EXPECT_EQ(test.getAlphaF(), 0.1f);
EXPECT_EQ(test.getRedF(), 0.2f);
EXPECT_EQ(test.getGreenF(), 0.3f);
EXPECT_EQ(test.getBlueF(), 0.4f);
}
}
@ -230,5 +238,73 @@ TEST(DisplayListColor, DlColorOpaqueTransparent) {
}
}
TEST(DisplayListColor, EqualityWithColorspace) {
EXPECT_TRUE(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB) ==
DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB));
EXPECT_FALSE(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB) ==
DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kExtendedSRGB));
EXPECT_FALSE(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB) !=
DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB));
EXPECT_TRUE(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB) !=
DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kExtendedSRGB));
}
TEST(DisplayListColor, EqualityWithExtendedSRGB) {
EXPECT_TRUE(DlColor(1.0, 1.1, -0.2, 0.1, DlColorSpace::kExtendedSRGB) ==
DlColor(1.0, 1.1, -0.2, 0.1, DlColorSpace::kExtendedSRGB));
EXPECT_FALSE(DlColor(1.0, 1.1, -0.2, 0.1, DlColorSpace::kExtendedSRGB) ==
DlColor(1.0, 1.0, 0.0, 0.0, DlColorSpace::kExtendedSRGB));
}
TEST(DisplayListColor, ColorSpaceSRGBtoSRGB) {
DlColor srgb(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB);
EXPECT_EQ(srgb, srgb.withColorSpace(DlColorSpace::kSRGB));
}
TEST(DisplayListColor, ColorSpaceSRGBtoExtendedSRGB) {
DlColor srgb(0.9, 0.8, 0.7, 0.6, DlColorSpace::kSRGB);
EXPECT_EQ(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kExtendedSRGB),
srgb.withColorSpace(DlColorSpace::kExtendedSRGB));
}
TEST(DisplayListColor, ColorSpaceExtendedSRGBtoExtendedSRGB) {
DlColor xsrgb(0.9, 0.8, 0.7, 0.6, DlColorSpace::kExtendedSRGB);
EXPECT_EQ(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kExtendedSRGB),
xsrgb.withColorSpace(DlColorSpace::kExtendedSRGB));
}
TEST(DisplayListColor, ColorSpaceP3ToP3) {
DlColor p3(0.9, 0.8, 0.7, 0.6, DlColorSpace::kDisplayP3);
EXPECT_EQ(DlColor(0.9, 0.8, 0.7, 0.6, DlColorSpace::kDisplayP3),
p3.withColorSpace(DlColorSpace::kDisplayP3));
}
TEST(DisplayListColor, ColorSpaceP3ToExtendedSRGB) {
DlColor red(0.9, 1.0, 0.0, 0.0, DlColorSpace::kDisplayP3);
EXPECT_TRUE(
DlColor(0.9, 1.0931, -0.2268, -0.1501, DlColorSpace::kExtendedSRGB)
.isClose(red.withColorSpace(DlColorSpace::kExtendedSRGB)))
<< red.withColorSpace(DlColorSpace::kExtendedSRGB);
DlColor green(0.9, 0.0, 1.0, 0.0, DlColorSpace::kDisplayP3);
EXPECT_TRUE(
DlColor(0.9, -0.5116, 1.0183, -0.3106, DlColorSpace::kExtendedSRGB)
.isClose(green.withColorSpace(DlColorSpace::kExtendedSRGB)))
<< green.withColorSpace(DlColorSpace::kExtendedSRGB);
DlColor blue(0.9, 0.0, 0.0, 1.0, DlColorSpace::kDisplayP3);
EXPECT_TRUE(DlColor(0.9, -0.0004, 0.0003, 1.0420, DlColorSpace::kExtendedSRGB)
.isClose(blue.withColorSpace(DlColorSpace::kExtendedSRGB)))
<< blue.withColorSpace(DlColorSpace::kExtendedSRGB);
}
TEST(DisplayListColor, isClose) {
EXPECT_TRUE(DlColor(0xffaabbcc).isClose(DlColor(0xffaabbcc)));
}
TEST(DisplayListColor, isNotClose) {
EXPECT_FALSE(DlColor(0xffaabbcc).isClose(DlColor(0xffaabbcd)));
}
} // namespace testing
} // namespace flutter

View File

@ -267,6 +267,18 @@ void DlVertices::Builder::store_colors(const DlColor colors[]) {
needs_colors_ = false;
}
void DlVertices::Builder::store_colors(const uint32_t colors[]) {
FML_CHECK(is_valid());
FML_CHECK(needs_colors_);
char* pod = reinterpret_cast<char*>(vertices_.get());
DlColor* dlcolors_ptr =
reinterpret_cast<DlColor*>(pod + vertices_->colors_offset_);
for (int i = 0; i < vertices_->vertex_count_; ++i) {
*dlcolors_ptr++ = DlColor(colors[i]);
}
needs_colors_ = false;
}
void DlVertices::Builder::store_indices(const uint16_t indices[]) {
FML_CHECK(is_valid());
FML_CHECK(needs_indices_);

View File

@ -147,9 +147,7 @@ class DlVertices {
///
/// fails if colors have already been supplied or if they were not
/// promised by the flags.has_colors.
void store_colors(const uint32_t colors[]) {
store_colors(reinterpret_cast<const DlColor*>(colors));
}
void store_colors(const uint32_t colors[]);
/// @brief Copies the indicated list of 16-bit indices as vertex indices.
///

View File

@ -28,7 +28,7 @@ std::shared_ptr<DlLinearGradientColorSource> DlColorSource::MakeLinear(
DlTileMode tile_mode,
const SkMatrix* matrix) {
size_t needed = sizeof(DlLinearGradientColorSource) +
(stop_count * (sizeof(uint32_t) + sizeof(float)));
(stop_count * (sizeof(DlColor) + sizeof(float)));
void* storage = ::operator new(needed);
@ -49,7 +49,7 @@ std::shared_ptr<DlRadialGradientColorSource> DlColorSource::MakeRadial(
DlTileMode tile_mode,
const SkMatrix* matrix) {
size_t needed = sizeof(DlRadialGradientColorSource) +
(stop_count * (sizeof(uint32_t) + sizeof(float)));
(stop_count * (sizeof(DlColor) + sizeof(float)));
void* storage = ::operator new(needed);
@ -71,7 +71,7 @@ std::shared_ptr<DlConicalGradientColorSource> DlColorSource::MakeConical(
DlTileMode tile_mode,
const SkMatrix* matrix) {
size_t needed = sizeof(DlConicalGradientColorSource) +
(stop_count * (sizeof(uint32_t) + sizeof(float)));
(stop_count * (sizeof(DlColor) + sizeof(float)));
void* storage = ::operator new(needed);
@ -93,7 +93,7 @@ std::shared_ptr<DlSweepGradientColorSource> DlColorSource::MakeSweep(
DlTileMode tile_mode,
const SkMatrix* matrix) {
size_t needed = sizeof(DlSweepGradientColorSource) +
(stop_count * (sizeof(uint32_t) + sizeof(float)));
(stop_count * (sizeof(DlColor) + sizeof(float)));
void* storage = ::operator new(needed);

View File

@ -327,11 +327,10 @@ class DlGradientColorSourceBase : public DlMatrixColorSourceBase {
stop_count_ != other_base->stop_count_) {
return false;
}
static_assert(sizeof(colors()[0]) == 4);
static_assert(sizeof(stops()[0]) == 4);
int num_bytes = stop_count_ * 4;
return (memcmp(colors(), other_base->colors(), num_bytes) == 0 &&
memcmp(stops(), other_base->stops(), num_bytes) == 0);
return (memcmp(colors(), other_base->colors(),
stop_count_ * sizeof(colors()[0])) == 0 &&
memcmp(stops(), other_base->stops(),
stop_count_ * sizeof(stops()[0])) == 0);
}
void store_color_stops(void* pod,

View File

@ -311,9 +311,13 @@ void DlSkCanvasAdapter::DrawAtlas(const sk_sp<DlImage>& atlas,
const DlPaint* paint) {
SkOptionalPaint sk_paint(paint);
sk_sp<SkImage> sk_image = atlas->skia_image();
const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors);
delegate_->drawAtlas(sk_image.get(), xform, tex, sk_colors, count, ToSk(mode),
ToSk(sampling), cullRect, sk_paint());
std::vector<SkColor> sk_colors;
sk_colors.reserve(count);
for (int i = 0; i < count; ++i) {
sk_colors.push_back(colors[i].argb());
}
delegate_->drawAtlas(sk_image.get(), xform, tex, sk_colors.data(), count,
ToSk(mode), ToSk(sampling), cullRect, sk_paint());
}
void DlSkCanvasAdapter::DrawDisplayList(const sk_sp<DisplayList> display_list,

View File

@ -69,8 +69,14 @@ sk_sp<SkShader> ToSk(const DlColorSource* source) {
if (!source) {
return nullptr;
}
static auto ToSkColors = [](const DlGradientColorSourceBase* gradient) {
return reinterpret_cast<const SkColor*>(gradient->colors());
static auto ToSkColors =
[](const DlGradientColorSourceBase* gradient) -> std::vector<SkColor> {
std::vector<SkColor> sk_colors;
sk_colors.reserve(gradient->stop_count());
for (int i = 0; i < gradient->stop_count(); ++i) {
sk_colors.push_back(gradient->colors()[i].argb());
}
return sk_colors;
};
switch (source->type()) {
case DlColorSourceType::kColor: {
@ -96,8 +102,9 @@ sk_sp<SkShader> ToSk(const DlColorSource* source) {
FML_DCHECK(linear_source != nullptr);
SkPoint pts[] = {linear_source->start_point(),
linear_source->end_point()};
std::vector<SkColor> skcolors = ToSkColors(linear_source);
return SkGradientShader::MakeLinear(
pts, ToSkColors(linear_source), linear_source->stops(),
pts, skcolors.data(), linear_source->stops(),
linear_source->stop_count(), ToSk(linear_source->tile_mode()), 0,
linear_source->matrix_ptr());
}
@ -107,7 +114,7 @@ sk_sp<SkShader> ToSk(const DlColorSource* source) {
FML_DCHECK(radial_source != nullptr);
return SkGradientShader::MakeRadial(
radial_source->center(), radial_source->radius(),
ToSkColors(radial_source), radial_source->stops(),
ToSkColors(radial_source).data(), radial_source->stops(),
radial_source->stop_count(), ToSk(radial_source->tile_mode()), 0,
radial_source->matrix_ptr());
}
@ -118,7 +125,7 @@ sk_sp<SkShader> ToSk(const DlColorSource* source) {
return SkGradientShader::MakeTwoPointConical(
conical_source->start_center(), conical_source->start_radius(),
conical_source->end_center(), conical_source->end_radius(),
ToSkColors(conical_source), conical_source->stops(),
ToSkColors(conical_source).data(), conical_source->stops(),
conical_source->stop_count(), ToSk(conical_source->tile_mode()), 0,
conical_source->matrix_ptr());
}
@ -128,7 +135,7 @@ sk_sp<SkShader> ToSk(const DlColorSource* source) {
FML_DCHECK(sweep_source != nullptr);
return SkGradientShader::MakeSweep(
sweep_source->center().x(), sweep_source->center().y(),
ToSkColors(sweep_source), sweep_source->stops(),
ToSkColors(sweep_source).data(), sweep_source->stops(),
sweep_source->stop_count(), ToSk(sweep_source->tile_mode()),
sweep_source->start(), sweep_source->end(), 0,
sweep_source->matrix_ptr());
@ -275,11 +282,18 @@ sk_sp<SkMaskFilter> ToSk(const DlMaskFilter* filter) {
}
sk_sp<SkVertices> ToSk(const std::shared_ptr<DlVertices>& vertices) {
const SkColor* sk_colors =
reinterpret_cast<const SkColor*>(vertices->colors());
std::vector<SkColor> sk_colors;
const SkColor* sk_colors_ptr = nullptr;
if (vertices->colors()) {
sk_colors.reserve(vertices->vertex_count());
for (int i = 0; i < vertices->vertex_count(); ++i) {
sk_colors.push_back(vertices->colors()[i].argb());
}
sk_colors_ptr = sk_colors.data();
}
return SkVertices::MakeCopy(ToSk(vertices->mode()), vertices->vertex_count(),
vertices->vertices(),
vertices->texture_coordinates(), sk_colors,
vertices->texture_coordinates(), sk_colors_ptr,
vertices->index_count(), vertices->indices());
}

View File

@ -19,9 +19,7 @@ inline SkBlendMode ToSk(DlBlendMode mode) {
}
inline SkColor ToSk(DlColor color) {
// This is safe because both SkColor and DlColor are backed by ARGB uint32_t.
// See dl_sk_conversions_unittests.cc.
return reinterpret_cast<SkColor&>(color);
return color.argb();
}
inline SkPaint::Style ToSk(DlDrawStyle style) {

View File

@ -250,9 +250,13 @@ void DlSkCanvasDispatcher::drawAtlas(const sk_sp<DlImage> atlas,
if (!skia_atlas) {
return;
}
const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors);
canvas_->drawAtlas(skia_atlas.get(), xform, tex, sk_colors, count, ToSk(mode),
ToSk(sampling), cullRect,
std::vector<SkColor> sk_colors;
sk_colors.reserve(count);
for (int i = 0; i < count; ++i) {
sk_colors.push_back(colors[i].argb());
}
canvas_->drawAtlas(skia_atlas.get(), xform, tex, sk_colors.data(), count,
ToSk(mode), ToSk(sampling), cullRect,
safe_paint(render_with_attributes));
}
void DlSkCanvasDispatcher::drawDisplayList(

View File

@ -156,6 +156,23 @@ const int kHorizontalMiterDiamondPointCount =
(sizeof(kHorizontalMiterDiamondPoints) /
sizeof(kHorizontalMiterDiamondPoints[0]));
namespace {
constexpr uint8_t toC(DlScalar fComp) {
return round(fComp * 255);
}
constexpr uint32_t PremultipliedArgb(const DlColor& color) {
if (color.isOpaque()) {
return color.argb();
}
DlScalar f = color.getAlphaF();
return (color.argb() & 0xFF000000) | //
toC(color.getRedF() * f) << 16 | //
toC(color.getGreenF() * f) << 8 | //
toC(color.getBlueF() * f);
}
} // namespace
class SkImageSampling {
public:
static constexpr SkSamplingOptions kNearestNeighbor =
@ -2526,7 +2543,7 @@ class CanvasCompareTester {
const SkRect ref_bounds,
const std::string& info,
const DlColor bg = DlColor::kTransparent()) {
uint32_t untouched = bg.premultipliedArgb();
uint32_t untouched = PremultipliedArgb(bg);
int pixels_touched = 0;
int pixels_oob = 0;
SkIRect i_bounds = ref_bounds.roundOut();
@ -2611,7 +2628,7 @@ class CanvasCompareTester {
int width = kTestWidth,
int height = kTestHeight,
bool printMismatches = false) {
uint32_t untouched = bg.premultipliedArgb();
uint32_t untouched = PremultipliedArgb(bg);
ASSERT_EQ(test_result->width(), width) << info;
ASSERT_EQ(test_result->height(), height) << info;
SkIRect i_bounds =

View File

@ -114,9 +114,9 @@ std::vector<DisplayListInvocationGroup> CreateAllAttributesOps() {
}},
{"SetColor",
{
{0, 8, 0,
{0, 4 + sizeof(DlColor), 0,
[](DlOpReceiver& r) { r.setColor(DlColor(SK_ColorGREEN)); }},
{0, 8, 0,
{0, 4 + sizeof(DlColor), 0,
[](DlOpReceiver& r) { r.setColor(DlColor(SK_ColorBLUE)); }},
// Reset attribute to default as last entry
@ -137,14 +137,13 @@ std::vector<DisplayListInvocationGroup> CreateAllAttributesOps() {
{"SetColorSource",
{
{0, 96, 0, [](DlOpReceiver& r) { r.setColorSource(&kTestSource1); }},
// stop_count * (sizeof(float) + sizeof(uint32_t)) = 80
{0, 80 + 6 * 4, 0,
{0, 152, 0,
[](DlOpReceiver& r) { r.setColorSource(kTestSource2.get()); }},
{0, 80 + 6 * 4, 0,
{0, 152, 0,
[](DlOpReceiver& r) { r.setColorSource(kTestSource3.get()); }},
{0, 88 + 6 * 4, 0,
{0, 160, 0,
[](DlOpReceiver& r) { r.setColorSource(kTestSource4.get()); }},
{0, 80 + 6 * 4, 0,
{0, 152, 0,
[](DlOpReceiver& r) { r.setColorSource(kTestSource5.get()); }},
// Reset attribute to default as last entry
@ -219,11 +218,11 @@ std::vector<DisplayListInvocationGroup> CreateAllAttributesOps() {
}},
{"SetColorFilter",
{
{0, 24, 0,
{0, 40, 0,
[](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter1); }},
{0, 24, 0,
{0, 40, 0,
[](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter2); }},
{0, 24, 0,
{0, 40, 0,
[](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter3); }},
{0, 96, 0,
[](DlOpReceiver& r) {
@ -551,15 +550,15 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
}},
{"DrawColor",
{
{1, 16, 1,
{1, 32, 1,
[](DlOpReceiver& r) {
r.drawColor(DlColor(SK_ColorBLUE), DlBlendMode::kSrcIn);
}},
{1, 16, 1,
{1, 32, 1,
[](DlOpReceiver& r) {
r.drawColor(DlColor(SK_ColorBLUE), DlBlendMode::kDstOut);
}},
{1, 16, 1,
{1, 32, 1,
[](DlOpReceiver& r) {
r.drawColor(DlColor(SK_ColorCYAN), DlBlendMode::kSrcIn);
}},
@ -930,7 +929,7 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
DlBlendMode::kSrcIn, kNearestSampling, &cull_rect,
false);
}},
{1, 48 + 32 + 8 + 8, 1,
{1, 128, 1,
[](DlOpReceiver& r) {
static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
@ -939,7 +938,7 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
DlBlendMode::kSrcIn, kNearestSampling, nullptr,
false);
}},
{1, 64 + 32 + 8 + 8, 1,
{1, 144, 1,
[](DlOpReceiver& r) {
static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
@ -997,27 +996,27 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
}},
{"DrawShadow",
{
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath1, DlColor(SK_ColorGREEN), 1.0, false, 1.0);
}},
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath2, DlColor(SK_ColorGREEN), 1.0, false, 1.0);
}},
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath1, DlColor(SK_ColorBLUE), 1.0, false, 1.0);
}},
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath1, DlColor(SK_ColorGREEN), 2.0, false, 1.0);
}},
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath1, DlColor(SK_ColorGREEN), 1.0, true, 1.0);
}},
{1, 48, 1,
{1, 64, 1,
[](DlOpReceiver& r) {
r.drawShadow(kTestPath1, DlColor(SK_ColorGREEN), 1.0, false, 2.5);
}},

View File

@ -392,7 +392,7 @@ static sk_sp<DisplayList> BlendModeTest(Vector2 content_scale,
// Draw grid behind the images.
{
DlPaint paint;
paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 255));
paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 1));
builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 400), paint);
}

View File

@ -194,12 +194,7 @@ void DlDispatcherBase::setDrawStyle(flutter::DlDrawStyle style) {
// |flutter::DlOpReceiver|
void DlDispatcherBase::setColor(flutter::DlColor color) {
paint_.color = {
color.getRedF(),
color.getGreenF(),
color.getBlueF(),
color.getAlphaF(),
};
paint_.color = skia_conversions::ToColor(color);
}
// |flutter::DlOpReceiver|
@ -1369,12 +1364,7 @@ void TextFrameDispatcher::setDrawStyle(flutter::DlDrawStyle style) {
// |flutter::DlOpReceiver|
void TextFrameDispatcher::setColor(flutter::DlColor color) {
paint_.color = {
color.getRedF(),
color.getGreenF(),
color.getBlueF(),
color.getAlphaF(),
};
paint_.color = skia_conversions::ToColor(color);
}
// |flutter::DlOpReceiver|

View File

@ -164,6 +164,8 @@ Size ToSize(const SkPoint& point) {
}
Color ToColor(const flutter::DlColor& color) {
FML_DCHECK(color.getColorSpace() == flutter::DlColorSpace::kExtendedSRGB ||
color.getColorSpace() == flutter::DlColorSpace::kSRGB);
return {
static_cast<Scalar>(color.getRedF()), //
static_cast<Scalar>(color.getGreenF()), //

View File

@ -45,10 +45,15 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points,
SkPoint p0 = SkPoint::Make(end_points[0], end_points[1]);
SkPoint p1 = SkPoint::Make(end_points[2], end_points[3]);
const DlColor* colors_array = reinterpret_cast<const DlColor*>(colors.data());
std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
/// TODO(gaaclarke): Make this preserve wide gamut colors.
dl_colors.emplace_back(DlColor(colors[i]));
}
dl_shader_ = DlColorSource::MakeLinear(
p0, p1, colors.num_elements(), colors_array, color_stops.data(),
p0, p1, colors.num_elements(), dl_colors.data(), color_stops.data(),
tile_mode, has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
@ -73,11 +78,15 @@ void CanvasGradient::initRadial(double center_x,
sk_matrix = ToSkMatrix(matrix4);
}
const DlColor* colors_array = reinterpret_cast<const DlColor*>(colors.data());
std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
}
dl_shader_ = DlColorSource::MakeRadial(
SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)),
SafeNarrow(radius), colors.num_elements(), colors_array,
SafeNarrow(radius), colors.num_elements(), dl_colors.data(),
color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
@ -103,13 +112,17 @@ void CanvasGradient::initSweep(double center_x,
sk_matrix = ToSkMatrix(matrix4);
}
const DlColor* colors_array = reinterpret_cast<const DlColor*>(colors.data());
std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
}
dl_shader_ = DlColorSource::MakeSweep(
SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)),
SafeNarrow(start_angle) * 180.0f / static_cast<float>(M_PI),
SafeNarrow(end_angle) * 180.0f / static_cast<float>(M_PI),
colors.num_elements(), colors_array, color_stops.data(), tile_mode,
colors.num_elements(), dl_colors.data(), color_stops.data(), tile_mode,
has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
@ -137,13 +150,17 @@ void CanvasGradient::initTwoPointConical(double start_x,
sk_matrix = ToSkMatrix(matrix4);
}
const DlColor* colors_array = reinterpret_cast<const DlColor*>(colors.data());
std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
}
dl_shader_ = DlColorSource::MakeConical(
SkPoint::Make(SafeNarrow(start_x), SafeNarrow(start_y)),
SafeNarrow(start_radius),
SkPoint::Make(SafeNarrow(end_x), SafeNarrow(end_y)),
SafeNarrow(end_radius), colors.num_elements(), colors_array,
SafeNarrow(end_radius), colors.num_elements(), dl_colors.data(),
color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());

View File

@ -326,7 +326,24 @@ std::ostream& operator<<(std::ostream& os, const DlFilterMode& mode) {
}
std::ostream& operator<<(std::ostream& os, const DlColor& color) {
return os << "DlColor(" << std::hex << color.argb() << std::dec << ")";
const char* color_space;
switch(color.getColorSpace()) {
case flutter::DlColorSpace::kSRGB:
color_space = "srgb";
break;
case flutter::DlColorSpace::kExtendedSRGB:
color_space = "srgb_xr";
break;
case flutter::DlColorSpace::kDisplayP3:
color_space = "p3";
break;
}
return os << "DlColor(" << //
color.getAlphaF() << ", " << //
color.getRedF() << ", " << //
color.getGreenF() << ", " << //
color.getBlueF() << ", " << //
color_space << ")";
}
std::ostream& operator<<(std::ostream& os, DlImageSampling sampling) {