Commit ca80dfd9 authored by Anna Malova's avatar Anna Malova Committed by Commit Bot

[Dark Web Contents]: Implement color inversion using CIELAB.

To invert color:
1. Tranform color to CIELab.
2. Invert the L and add 10.
3. Transform back to RGB.

Bug: 939312
Change-Id: Iadeec2232afbe35cb189f0a36722392514db4781
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1547830
Commit-Queue: Anna Malova <amalova@chromium.org>
Reviewed-by: default avatarTobias Sargeant <tobiasjs@chromium.org>
Reviewed-by: default avatarStephen Chenney <schenney@chromium.org>
Cr-Commit-Position: refs/heads/master@{#656195}
parent 1defdd65
...@@ -109,7 +109,7 @@ void Settings::SetForceDarkModeEnabled(bool enabled) { ...@@ -109,7 +109,7 @@ void Settings::SetForceDarkModeEnabled(bool enabled) {
force_dark_mode_ = enabled; force_dark_mode_ = enabled;
if (force_dark_mode_) { if (force_dark_mode_) {
SetDarkMode(DarkMode::kInvertLightness); SetDarkMode(DarkMode::kInvertLightnessLAB);
SetDarkModeImagePolicy(DarkModeImagePolicy::kFilterSmart); SetDarkModeImagePolicy(DarkModeImagePolicy::kFilterSmart);
} else { } else {
SetDarkMode(DarkMode::kOff); SetDarkMode(DarkMode::kOff);
......
...@@ -1026,6 +1026,7 @@ jumbo_component("platform") { ...@@ -1026,6 +1026,7 @@ jumbo_component("platform") {
"graphics/intercepting_canvas.h", "graphics/intercepting_canvas.h",
"graphics/interpolation_space.cc", "graphics/interpolation_space.cc",
"graphics/interpolation_space.h", "graphics/interpolation_space.h",
"graphics/lab_color_space.h",
"graphics/link_highlight.h", "graphics/link_highlight.h",
"graphics/logging_canvas.cc", "graphics/logging_canvas.cc",
"graphics/logging_canvas.h", "graphics/logging_canvas.h",
...@@ -1743,6 +1744,7 @@ jumbo_source_set("blink_platform_unittests_sources") { ...@@ -1743,6 +1744,7 @@ jumbo_source_set("blink_platform_unittests_sources") {
"graphics/gpu/webgl_image_conversion_test.cc", "graphics/gpu/webgl_image_conversion_test.cc",
"graphics/gpu/webgpu_swap_buffer_provider_test.cc", "graphics/gpu/webgpu_swap_buffer_provider_test.cc",
"graphics/graphics_context_test.cc", "graphics/graphics_context_test.cc",
"graphics/lab_color_space_test.cc",
"graphics/paint/cull_rect_test.cc", "graphics/paint/cull_rect_test.cc",
"graphics/paint/display_item_client_test.cc", "graphics/paint/display_item_client_test.cc",
"graphics/paint/display_item_raster_invalidator_test.cc", "graphics/paint/display_item_raster_invalidator_test.cc",
......
...@@ -35,6 +35,7 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) { ...@@ -35,6 +35,7 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) {
settings_ = new_settings; settings_ = new_settings;
SkHighContrastConfig config; SkHighContrastConfig config;
transformer_ = base::nullopt;
switch (settings_.mode) { switch (settings_.mode) {
case DarkMode::kOff: case DarkMode::kOff:
default_filter_.reset(nullptr); default_filter_.reset(nullptr);
...@@ -58,6 +59,10 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) { ...@@ -58,6 +59,10 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) {
case DarkMode::kInvertLightness: case DarkMode::kInvertLightness:
config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness; config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
break; break;
case DarkMode::kInvertLightnessLAB:
transformer_ = LabColorSpace::RGBLABTransformer();
config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
break;
} }
config.fGrayscale = settings_.grayscale; config.fGrayscale = settings_.grayscale;
...@@ -75,7 +80,10 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) { ...@@ -75,7 +80,10 @@ void DarkModeFilter::UpdateSettings(const DarkModeSettings& new_settings) {
Color DarkModeFilter::ApplyIfNeeded(const Color& color) { Color DarkModeFilter::ApplyIfNeeded(const Color& color) {
if (!default_filter_) if (!default_filter_)
return color; return color;
return Color(default_filter_->filterColor(color.Rgb())); if (!transformer_)
return Color(default_filter_->filterColor(color.Rgb()));
return InvertColor(color);
} }
// TODO(gilmanmh): Investigate making |image| a const reference. This code // TODO(gilmanmh): Investigate making |image| a const reference. This code
...@@ -102,10 +110,25 @@ base::Optional<cc::PaintFlags> DarkModeFilter::ApplyToFlagsIfNeeded( ...@@ -102,10 +110,25 @@ base::Optional<cc::PaintFlags> DarkModeFilter::ApplyToFlagsIfNeeded(
if (flags.HasShader()) { if (flags.HasShader()) {
dark_mode_flags.setColorFilter(default_filter_); dark_mode_flags.setColorFilter(default_filter_);
} else { } else {
dark_mode_flags.setColor(default_filter_->filterColor(flags.getColor())); auto invertedColor = ApplyIfNeeded(flags.getColor());
dark_mode_flags.setColor(
SkColorSetRGB(invertedColor.Red(), invertedColor.Green(), invertedColor.Blue()));
} }
return base::make_optional<cc::PaintFlags>(std::move(dark_mode_flags)); return base::make_optional<cc::PaintFlags>(std::move(dark_mode_flags));
} }
Color DarkModeFilter::InvertColor(const Color& color) const {
blink::FloatPoint3D rgb = {color.Red() / 255.0f, color.Green() / 255.0f,
color.Blue() / 255.0f};
blink::FloatPoint3D lab = transformer_->sRGBToLab(rgb);
float invertedL = std::min(110.0f - lab.X(), 100.0f);
lab.SetX(invertedL);
rgb = transformer_->LabToSRGB(lab);
return Color(static_cast<unsigned int>(rgb.X() * 255 + 0.5),
static_cast<unsigned int>(rgb.Y() * 255 + 0.5),
static_cast<unsigned int>(rgb.Z() * 255 + 0.5), color.Alpha());
}
} // namespace blink } // namespace blink
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "third_party/blink/renderer/platform/graphics/color.h" #include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h" #include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
#include "third_party/blink/renderer/platform/graphics/image.h" #include "third_party/blink/renderer/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/graphics/lab_color_space.h"
#include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkRefCnt.h"
class SkColorFilter; class SkColorFilter;
...@@ -30,11 +31,14 @@ class DarkModeFilter { ...@@ -30,11 +31,14 @@ class DarkModeFilter {
base::Optional<cc::PaintFlags> ApplyToFlagsIfNeeded( base::Optional<cc::PaintFlags> ApplyToFlagsIfNeeded(
const cc::PaintFlags& flags); const cc::PaintFlags& flags);
Color InvertColor(const Color& color) const;
private: private:
DarkModeSettings settings_; DarkModeSettings settings_;
sk_sp<SkColorFilter> default_filter_; sk_sp<SkColorFilter> default_filter_;
sk_sp<SkColorFilter> image_filter_; sk_sp<SkColorFilter> image_filter_;
base::Optional<LabColorSpace::RGBLABTransformer> transformer_;
}; };
} // namespace blink } // namespace blink
......
...@@ -14,6 +14,7 @@ enum class DarkMode { ...@@ -14,6 +14,7 @@ enum class DarkMode {
kSimpleInvertForTesting, kSimpleInvertForTesting,
kInvertBrightness, kInvertBrightness,
kInvertLightness, kInvertLightness,
kInvertLightnessLAB,
}; };
enum class DarkModeImagePolicy { enum class DarkModeImagePolicy {
......
...@@ -79,7 +79,6 @@ class GraphicsContext::DarkModeFlags final { ...@@ -79,7 +79,6 @@ class GraphicsContext::DarkModeFlags final {
} }
operator const PaintFlags&() const { return *flags_; } operator const PaintFlags&() const { return *flags_; }
private: private:
const PaintFlags* flags_; const PaintFlags* flags_;
base::Optional<PaintFlags> dark_mode_flags_; base::Optional<PaintFlags> dark_mode_flags_;
......
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
#include <algorithm>
#include <array>
#include <cmath>
#include <initializer_list>
#include <iterator>
#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
// Class to handle color transformation between RGB and CIE L*a*b* color spaces.
namespace LabColorSpace {
using blink::FloatPoint3D;
using blink::TransformationMatrix;
static constexpr FloatPoint3D kIlluminantD50 =
FloatPoint3D(0.964212f, 1.0f, 0.825188f);
static constexpr FloatPoint3D kIlluminantD65 =
FloatPoint3D(0.95042855f, 1.0f, 1.0889004f);
// All matrices here are 3x3 matrices.
// They are stored in blink::TransformationMatrix which is 4x4 matrix in the
// following form.
// |a b c 0|
// |d e f 0|
// |g h i 0|
// |0 0 0 1|
inline TransformationMatrix mul3x3Diag(const FloatPoint3D& lhs,
const TransformationMatrix& rhs) {
return TransformationMatrix(
lhs.X() * rhs.M11(), lhs.Y() * rhs.M12(), lhs.Z() * rhs.M13(), 0.0f,
lhs.X() * rhs.M21(), lhs.Y() * rhs.M22(), lhs.Z() * rhs.M23(), 0.0f,
lhs.X() * rhs.M31(), lhs.Y() * rhs.M32(), lhs.Z() * rhs.M33(), 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
}
template <typename T>
inline constexpr T clamp(T x, T min, T max) {
return x < min ? min : x > max ? max : x;
}
// See https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform.
inline TransformationMatrix chromaticAdaptation(
const TransformationMatrix& matrix,
const FloatPoint3D& srcWhitePoint,
const FloatPoint3D& dstWhitePoint) {
FloatPoint3D srcLMS = matrix.MapPoint(srcWhitePoint);
FloatPoint3D dstLMS = matrix.MapPoint(dstWhitePoint);
// LMS is a diagonal matrix stored as a float[3]
FloatPoint3D LMS = {dstLMS.X() / srcLMS.X(), dstLMS.Y() / srcLMS.Y(),
dstLMS.Z() / srcLMS.Z()};
return matrix.Inverse() * mul3x3Diag(LMS, matrix);
}
class sRGBColorSpace {
public:
FloatPoint3D toLinear(const FloatPoint3D& v) const {
auto EOTF = [](float u) {
return u < 0.04045f
? clamp(u / 12.92f, .0f, 1.0f)
: clamp(std::pow((u + 0.055f) / 1.055f, 2.4f), .0f, 1.0f);
};
return {EOTF(v.X()), EOTF(v.Y()), EOTF(v.Z())};
}
FloatPoint3D fromLinear(const FloatPoint3D& v) const {
auto OETF = [](float u) {
return (u < 0.0031308f
? clamp(12.92 * u, .0, 1.0)
: clamp(1.055 * std::pow(u, 1.0 / 2.4) - 0.055, .0, 1.0));
};
return {OETF(v.X()), OETF(v.Y()), OETF(v.Z())};
}
// See https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation.
FloatPoint3D toXYZ(const FloatPoint3D& rgb) const {
return transform_.MapPoint(toLinear(rgb));
}
// See
// https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB).
FloatPoint3D fromXYZ(const FloatPoint3D& xyz) const {
return fromLinear(inverseTransform_.MapPoint(xyz));
}
private:
TransformationMatrix kBradford = TransformationMatrix(
0.8951f, -0.7502f, 0.0389f, 0.0f,
0.2664f, 1.7135f, -0.0685f, 0.0f,
-0.1614f, 0.0367f, 1.0296f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
TransformationMatrix xyzTransform = TransformationMatrix(
0.41238642f, 0.21263677f, 0.019330615f, 0.0f,
0.3575915f, 0.715183f, 0.11919712f, 0.0f,
0.18045056f, 0.07218022f, 0.95037293f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
TransformationMatrix transform_ =
chromaticAdaptation(kBradford, kIlluminantD65, kIlluminantD50) *
xyzTransform;
TransformationMatrix inverseTransform_ = transform_.Inverse();
};
class LABColorSpace {
public:
// See
// https://en.wikipedia.org/wiki/CIELAB_color_space#Reverse_transformation.
FloatPoint3D fromXYZ(const FloatPoint3D& v) const {
auto f = [](float x) {
return x > kSigma3 ? pow(x, 1.0f / 3.0f)
: x / (3 * kSigma2) + 4.0f / 29.0f;
};
float fx = f(v.X() / kIlluminantD50.X());
float fy = f(v.Y() / kIlluminantD50.Y());
float fz = f(v.Z() / kIlluminantD50.Z());
float L = 116.0f * fy - 16.0f;
float a = 500.0f * (fx - fy);
float b = 200.0f * (fy - fz);
return {clamp(L, 0.0f, 100.0f), clamp(a, -128.0f, 128.0f),
clamp(b, -128.0f, 128.0f)};
}
// See
// https://en.wikipedia.org/wiki/CIELAB_color_space#Forward_transformation.
FloatPoint3D toXYZ(const FloatPoint3D& lab) const {
auto invf = [](float x) {
return x > kSigma ? pow(x, 3) : 3 * kSigma2 * (x - 4.0f / 29.0f);
};
FloatPoint3D v = {clamp(lab.X(), 0.0f, 100.0f),
clamp(lab.Y(), -128.0f, 128.0f),
clamp(lab.Z(), -128.0f, 128.0f)};
return {
invf((v.X() + 16.0f) / 116.0f + (v.Y() * 0.002f)) * kIlluminantD50.X(),
invf((v.X() + 16.0f) / 116.0f) * kIlluminantD50.Y(),
invf((v.X() + 16.0f) / 116.0f - (v.Z() * 0.005f)) * kIlluminantD50.Z()};
}
private:
static constexpr float kSigma = 6.0f / 29.0f;
static constexpr float kSigma2 = 36.0f / 841.0f;
static constexpr float kSigma3 = 216.0f / 24389.0f;
};
class RGBLABTransformer {
public:
FloatPoint3D sRGBToLab(const FloatPoint3D& rgb) const {
FloatPoint3D xyz = rcs.toXYZ(rgb);
return lcs.fromXYZ(xyz);
}
FloatPoint3D LabToSRGB(const FloatPoint3D& lab) const {
FloatPoint3D xyz = lcs.toXYZ(lab);
return rcs.fromXYZ(xyz);
}
private:
sRGBColorSpace rcs = sRGBColorSpace();
LABColorSpace lcs = LABColorSpace();
};
} // namespace LabColorSpace
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
#include "third_party/blink/renderer/platform/graphics/lab_color_space.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace LabColorSpace {
using blink::FloatPoint3D;
static constexpr FloatPoint3D rgbReferenceWhite =
FloatPoint3D(1.0f, 1.0f, 1.0f);
static constexpr FloatPoint3D labReferenceWhite =
FloatPoint3D(100.0f, 0.0f, 0.0f);
static constexpr float epsilon = 0.0001;
class LabColorSpaceTest : public testing::Test {
public:
void AssertColorsEqual(const FloatPoint3D& color1,
const FloatPoint3D& color2) {
EXPECT_NEAR(color1.X(), color2.X(), epsilon);
EXPECT_NEAR(color1.Y(), color2.Y(), epsilon);
EXPECT_NEAR(color1.Z(), color2.Z(), epsilon);
}
};
TEST_F(LabColorSpaceTest, XYZTranslation) {
sRGBColorSpace colorSpace = sRGBColorSpace();
// Check whether white transformation is correct
FloatPoint3D xyzWhite = colorSpace.toXYZ(rgbReferenceWhite);
AssertColorsEqual(xyzWhite, kIlluminantD50);
FloatPoint3D rgbWhite = colorSpace.fromXYZ(kIlluminantD50);
AssertColorsEqual(rgbWhite, rgbReferenceWhite);
// Check whether transforming sRGB to XYZ and back gives the same RGB values
// for some random colors with different r, g, b components.
for (unsigned r = 0; r <= 255; r += 40) {
for (unsigned g = 0; r <= 255; r += 50) {
for (unsigned b = 0; r <= 255; r += 60) {
FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
FloatPoint3D xyz = colorSpace.toXYZ(rgb);
AssertColorsEqual(rgb, colorSpace.fromXYZ(xyz));
}
}
}
}
TEST_F(LabColorSpaceTest, LabTranslation) {
RGBLABTransformer transformer = RGBLABTransformer();
// Check whether white transformation is correct
FloatPoint3D labWhite = transformer.sRGBToLab(rgbReferenceWhite);
AssertColorsEqual(labWhite, labReferenceWhite);
FloatPoint3D rgbWhite = transformer.LabToSRGB(labReferenceWhite);
AssertColorsEqual(rgbWhite, rgbReferenceWhite);
// Check whether transforming sRGB to Lab and back gives the same RGB values
// for some random colors with different r, g, b components.
for (unsigned r = 0; r <= 255; r += 40) {
for (unsigned g = 0; r <= 255; r += 50) {
for (unsigned b = 0; r <= 255; r += 60) {
FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
FloatPoint3D lab = transformer.sRGBToLab(rgb);
AssertColorsEqual(rgb, transformer.LabToSRGB(lab));
}
}
}
}
} // namespace LabColorSpace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment