Commit 8cc46420 authored by Prashant Nevase's avatar Prashant Nevase Committed by Commit Bot

Make DarkModeImageClassifier apis to work with SkPixmap.

For RSDM (wip patch crrev.com/c/2295379), the ImageDecodeCache expects
DarkModeFilter work on SkPixmap. This patch makes
DarkModeImageClassifier use SkPixmap instead of PaintImage. Also as
RSDM requires the purging of the cache to be implemented in decoded
image cache, this patch moves dark mode image cache outside dark mode
module. Now blink implements caching in Image class and dark mode
filter helper is added to handle cache modifications.

To check the correctness of the dark mode classifier logic to work on
full image than portion of the image, few more tests related to image
sprite and block samples are added.

Bug: 1094005
Change-Id: I39dbd7638691da459b898045d4721069dc5dd21c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2382990Reviewed-by: default avatarKhushal <khushalsagar@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Commit-Queue: Prashant Nevase <prashant.n@samsung.com>
Cr-Commit-Position: refs/heads/master@{#809969}
parent c5e234cc
......@@ -879,10 +879,14 @@ component("platform") {
"graphics/dark_mode_color_filter.h",
"graphics/dark_mode_filter.cc",
"graphics/dark_mode_filter.h",
"graphics/dark_mode_filter_helper.cc",
"graphics/dark_mode_filter_helper.h",
"graphics/dark_mode_image_cache.h",
"graphics/dark_mode_image_classifier.cc",
"graphics/dark_mode_image_classifier.h",
"graphics/dark_mode_lab_color_space.h",
"graphics/dark_mode_settings.h",
"graphics/dark_mode_types.h",
"graphics/darkmode/darkmode_classifier.cc",
"graphics/darkmode/darkmode_classifier.h",
"graphics/dash_array.h",
......@@ -1949,6 +1953,7 @@ source_set("blink_platform_unittests_sources") {
"graphics/contiguous_container_test.cc",
"graphics/dark_mode_color_classifier_test.cc",
"graphics/dark_mode_filter_test.cc",
"graphics/dark_mode_image_cache_test.cc",
"graphics/dark_mode_image_classifier_test.cc",
"graphics/dark_mode_lab_color_space_test.cc",
"graphics/decoding_image_generator_test.cc",
......
......@@ -5,7 +5,6 @@
#include "third_party/blink/renderer/platform/graphics/dark_mode_color_classifier.h"
#include "base/check_op.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
namespace blink {
namespace {
......@@ -14,36 +13,34 @@ class SimpleColorClassifier : public DarkModeColorClassifier {
public:
static std::unique_ptr<SimpleColorClassifier> NeverInvert() {
return std::unique_ptr<SimpleColorClassifier>(
new SimpleColorClassifier(DarkModeClassification::kDoNotApplyFilter));
new SimpleColorClassifier(DarkModeResult::kDoNotApplyFilter));
}
static std::unique_ptr<SimpleColorClassifier> AlwaysInvert() {
return std::unique_ptr<SimpleColorClassifier>(
new SimpleColorClassifier(DarkModeClassification::kApplyFilter));
new SimpleColorClassifier(DarkModeResult::kApplyFilter));
}
DarkModeClassification ShouldInvertColor(SkColor color) override {
return value_;
}
DarkModeResult ShouldInvertColor(SkColor color) override { return value_; }
private:
SimpleColorClassifier(DarkModeClassification value) : value_(value) {}
explicit SimpleColorClassifier(DarkModeResult value) : value_(value) {}
DarkModeClassification value_;
DarkModeResult value_;
};
class InvertLowBrightnessColorsClassifier : public DarkModeColorClassifier {
public:
InvertLowBrightnessColorsClassifier(int brightness_threshold)
explicit InvertLowBrightnessColorsClassifier(int brightness_threshold)
: brightness_threshold_(brightness_threshold) {
DCHECK_GT(brightness_threshold_, 0);
DCHECK_LT(brightness_threshold_, 256);
}
DarkModeClassification ShouldInvertColor(SkColor color) override {
DarkModeResult ShouldInvertColor(SkColor color) override {
if (CalculateColorBrightness(color) < brightness_threshold_)
return DarkModeClassification::kApplyFilter;
return DarkModeClassification::kDoNotApplyFilter;
return DarkModeResult::kApplyFilter;
return DarkModeResult::kDoNotApplyFilter;
}
private:
......@@ -52,16 +49,16 @@ class InvertLowBrightnessColorsClassifier : public DarkModeColorClassifier {
class InvertHighBrightnessColorsClassifier : public DarkModeColorClassifier {
public:
InvertHighBrightnessColorsClassifier(int brightness_threshold)
explicit InvertHighBrightnessColorsClassifier(int brightness_threshold)
: brightness_threshold_(brightness_threshold) {
DCHECK_GT(brightness_threshold_, 0);
DCHECK_LT(brightness_threshold_, 256);
}
DarkModeClassification ShouldInvertColor(SkColor color) override {
DarkModeResult ShouldInvertColor(SkColor color) override {
if (CalculateColorBrightness(color) > brightness_threshold_)
return DarkModeClassification::kApplyFilter;
return DarkModeClassification::kDoNotApplyFilter;
return DarkModeResult::kApplyFilter;
return DarkModeResult::kDoNotApplyFilter;
}
private:
......
......@@ -8,8 +8,9 @@
#include <memory>
#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_types.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/skia/include/core/SkColor.h"
namespace blink {
......@@ -29,7 +30,7 @@ class PLATFORM_EXPORT DarkModeColorClassifier {
// whether to invert a color. The background is likely to be dark, so a lower
// opacity will usually decrease the effective brightness of both the original
// and the inverted colors.
virtual DarkModeClassification ShouldInvertColor(SkColor color) = 0;
virtual DarkModeResult ShouldInvertColor(SkColor color) = 0;
};
} // namespace blink
......
......@@ -7,7 +7,6 @@
#include "base/check_op.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
#include "third_party/skia/include/core/SkColor.h"
namespace blink {
......@@ -33,18 +32,18 @@ TEST(DarkModeColorClassifierTest, ApplyFilterToDarkTextOnly) {
// * white text
// * text brighter than the text brightness threshold
// * text at the brightness threshold
EXPECT_EQ(DarkModeClassification::kApplyFilter,
EXPECT_EQ(DarkModeResult::kApplyFilter,
classifier->ShouldInvertColor(GetColorWithBrightness(
settings.text_brightness_threshold - 5)));
EXPECT_EQ(DarkModeClassification::kApplyFilter,
EXPECT_EQ(DarkModeResult::kApplyFilter,
classifier->ShouldInvertColor(SK_ColorBLACK));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(SK_ColorWHITE));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(GetColorWithBrightness(
settings.text_brightness_threshold + 5)));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(
GetColorWithBrightness(settings.text_brightness_threshold)));
}
......@@ -56,18 +55,18 @@ TEST(DarkModeColorClassifierTest, ApplyFilterToLightBackgroundElementsOnly) {
auto classifier =
DarkModeColorClassifier::MakeBackgroundColorClassifier(settings);
EXPECT_EQ(DarkModeClassification::kApplyFilter,
EXPECT_EQ(DarkModeResult::kApplyFilter,
classifier->ShouldInvertColor(SK_ColorWHITE));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(SK_ColorBLACK));
EXPECT_EQ(DarkModeClassification::kApplyFilter,
EXPECT_EQ(DarkModeResult::kApplyFilter,
classifier->ShouldInvertColor(GetColorWithBrightness(
settings.background_brightness_threshold + 5)));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(GetColorWithBrightness(
settings.background_brightness_threshold)));
EXPECT_EQ(DarkModeClassification::kDoNotApplyFilter,
EXPECT_EQ(DarkModeResult::kDoNotApplyFilter,
classifier->ShouldInvertColor(GetColorWithBrightness(
settings.background_brightness_threshold - 5)));
}
......
......@@ -13,7 +13,6 @@
#include "third_party/blink/renderer/platform/graphics/dark_mode_color_filter.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_image_classifier.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "third_party/blink/renderer/platform/wtf/lru_cache.h"
#include "third_party/skia/include/core/SkColorFilter.h"
......@@ -148,42 +147,47 @@ SkColor DarkModeFilter::InvertColorIfNeeded(SkColor color, ElementRole role) {
return color;
}
bool DarkModeFilter::AnalyzeShouldApplyToImage(const SkRect& src,
const SkRect& dst) {
DarkModeResult DarkModeFilter::AnalyzeShouldApplyToImage(
const SkRect& src,
const SkRect& dst) const {
if (settings().image_policy == DarkModeImagePolicy::kFilterNone)
return false;
return DarkModeResult::kDoNotApplyFilter;
if (settings().image_policy == DarkModeImagePolicy::kFilterAll)
return true;
return DarkModeResult::kApplyFilter;
// Images being drawn from very smaller |src| rect, i.e. one of the dimensions
// is very small, can be used for the border around the content or showing
// separator. Consider these images irrespective of size of the rect being
// drawn to. Classifying them will not be too costly.
if (src.width() <= kMinImageLength || src.height() <= kMinImageLength)
return true;
return DarkModeResult::kNotClassified;
// Do not consider images being drawn into bigger rect as these images are not
// meant for icons or representing smaller widgets. These images are
// considered as photos which should be untouched.
return (dst.width() <= kMaxImageLength && dst.height() <= kMaxImageLength);
return (dst.width() <= kMaxImageLength && dst.height() <= kMaxImageLength)
? DarkModeResult::kNotClassified
: DarkModeResult::kDoNotApplyFilter;
}
void DarkModeFilter::ApplyToImageFlagsIfNeeded(const SkRect& src,
const SkRect& dst,
const PaintImage& paint_image,
cc::PaintFlags* flags) {
// The construction of |paint_image| is expensive, so ensure
// IsDarkModeActive() is checked prior to calling this function.
// See: https://crbug.com/1094781.
DCHECK(IsDarkModeActive());
if (!image_filter_ || !AnalyzeShouldApplyToImage(src, dst))
return;
sk_sp<SkColorFilter> DarkModeFilter::ApplyToImage(const SkPixmap& pixmap,
const SkRect& src,
const SkRect& dst) {
DCHECK(AnalyzeShouldApplyToImage(src, dst) == DarkModeResult::kNotClassified);
DCHECK(settings().image_policy == DarkModeImagePolicy::kFilterSmart);
DCHECK(image_filter_);
return (image_classifier_->Classify(pixmap, src) ==
DarkModeResult::kApplyFilter)
? image_filter_
: nullptr;
}
if (ClassifyImage(settings(), src, dst, paint_image) ==
DarkModeClassification::kApplyFilter)
flags->setColorFilter(image_filter_);
sk_sp<SkColorFilter> DarkModeFilter::GetImageFilter() {
DCHECK(settings().image_policy == DarkModeImagePolicy::kFilterAll);
DCHECK(image_filter_);
return image_filter_;
}
base::Optional<cc::PaintFlags> DarkModeFilter::ApplyToFlagsIfNeeded(
......@@ -219,18 +223,18 @@ bool DarkModeFilter::ShouldApplyToColor(SkColor color, ElementRole role) {
case ElementRole::kText:
DCHECK(text_classifier_);
return text_classifier_->ShouldInvertColor(color) ==
DarkModeClassification::kApplyFilter;
DarkModeResult::kApplyFilter;
case ElementRole::kListSymbol:
// TODO(prashant.n): Rename text_classifier_ to foreground_classifier_,
// so that same classifier can be used for all roles which are supposed
// to be at foreground.
DCHECK(text_classifier_);
return text_classifier_->ShouldInvertColor(color) ==
DarkModeClassification::kApplyFilter;
DarkModeResult::kApplyFilter;
case ElementRole::kBackground:
DCHECK(background_classifier_);
return background_classifier_->ShouldInvertColor(color) ==
DarkModeClassification::kApplyFilter;
DarkModeResult::kApplyFilter;
case ElementRole::kSVG:
// 1) Inline SVG images are considered as individual shapes and do not
// have an Image object associated with them. So they do not go through
......@@ -251,23 +255,6 @@ size_t DarkModeFilter::GetInvertedColorCacheSizeForTesting() {
return inverted_color_cache_->size();
}
DarkModeClassification DarkModeFilter::ClassifyImage(
const DarkModeSettings& settings,
const SkRect& src,
const SkRect& dst,
const PaintImage& paint_image) {
switch (settings.image_policy) {
case DarkModeImagePolicy::kFilterSmart:
return image_classifier_->Classify(paint_image, src, dst);
case DarkModeImagePolicy::kFilterNone:
return DarkModeClassification::kDoNotApplyFilter;
case DarkModeImagePolicy::kFilterAll:
return DarkModeClassification::kApplyFilter;
}
NOTREACHED();
}
ScopedDarkModeElementRoleOverride::ScopedDarkModeElementRoleOverride(
GraphicsContext* graphics_context,
DarkModeFilter::ElementRole role)
......
......@@ -8,12 +8,9 @@
#include <memory>
#include "cc/paint/paint_flags.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/graphics_types.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_types.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/skia/include/core/SkRefCnt.h"
class SkColorFilter;
......@@ -48,19 +45,31 @@ class PLATFORM_EXPORT DarkModeFilter {
const cc::PaintFlags& flags,
ElementRole element_role);
// Decides whether to apply dark mode or not based on |src| and |dst|. True
// means dark mode should be applied. For applying the dark mode color filter
// to the image call ApplyToImageFlagsIfNeeded().
bool AnalyzeShouldApplyToImage(const SkRect& src, const SkRect& dst);
// Decides whether to apply dark mode or not based on |src| and |dst|.
// DarkModeResult::kDoNotApplyFilter - Dark mode filter should not be applied.
// DarkModeResult::kApplyFilter - Dark mode filter should be applied and to
// get the color filter GetImageFilter() should be called.
// DarkModeResult::kNotClassified - Dark mode filter should be applied and to
// get the color filter ApplyToImage() should be called.
DarkModeResult AnalyzeShouldApplyToImage(const SkRect& src,
const SkRect& dst) const;
// Returns dark mode color filter based on the classification done on
// |pixmap|. The image cannot be classified if pixmap is empty or |src| is
// empty or |src| is larger than pixmap bounds. Before calling this function
// AnalyzeShouldApplyToImage() must be called for early out or deciding
// appropriate function call. This function should be called only if image
// policy is set to DarkModeImagePolicy::kFilterSmart.
sk_sp<SkColorFilter> ApplyToImage(const SkPixmap& pixmap,
const SkRect& src,
const SkRect& dst);
// Sets dark mode color filter on the flags based on the classification done
// on |paint_image|. |flags| must not be null.
void ApplyToImageFlagsIfNeeded(const SkRect& src,
const SkRect& dst,
const PaintImage& paint_image,
cc::PaintFlags* flags);
// Returns dark mode color filter for images. Before calling this function
// AnalyzeShouldApplyToImage() must be called for early out or deciding
// appropriate function call. This function should be called only if image
// policy is set to DarkModeImagePolicy::kFilterAll.
sk_sp<SkColorFilter> GetImageFilter();
SkColorFilter* GetImageFilterForTesting() { return image_filter_.get(); }
size_t GetInvertedColorCacheSizeForTesting();
private:
......@@ -69,10 +78,6 @@ class PLATFORM_EXPORT DarkModeFilter {
DarkModeSettings settings_;
bool ShouldApplyToColor(SkColor color, ElementRole role);
DarkModeClassification ClassifyImage(const DarkModeSettings& settings,
const SkRect& src,
const SkRect& dst,
const PaintImage& paint_image);
std::unique_ptr<DarkModeColorClassifier> text_classifier_;
std::unique_ptr<DarkModeColorClassifier> background_classifier_;
......
// Copyright 2020 The Chromium 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 "third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h"
#include "base/hash/hash.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_image_cache.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
namespace blink {
// static
void DarkModeFilterHelper::ApplyToImageIfNeeded(
DarkModeFilter* dark_mode_filter,
Image* image,
cc::PaintFlags* flags,
const SkRect& src,
const SkRect& dst) {
DCHECK(dark_mode_filter);
DCHECK(image);
DCHECK(flags);
// The Image::AsSkBitmapForCurrentFrame() is expensive due creation of paint
// image and bitmap, so ensure IsDarkModeActive() is checked prior to calling
// this function. See: https://crbug.com/1094781.
DCHECK(dark_mode_filter->IsDarkModeActive());
sk_sp<SkColorFilter> filter;
DarkModeResult result = dark_mode_filter->AnalyzeShouldApplyToImage(src, dst);
if (result == DarkModeResult::kApplyFilter) {
filter = dark_mode_filter->GetImageFilter();
} else if (result == DarkModeResult::kNotClassified) {
DarkModeImageCache* cache = image->GetDarkModeImageCache();
DCHECK(cache);
if (cache->Exists(src)) {
filter = cache->Get(src);
} else {
// Performance warning: Calling this function will synchronously decode
// image.
SkBitmap bitmap =
image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
SkPixmap pixmap;
bitmap.peekPixels(&pixmap);
filter = dark_mode_filter->ApplyToImage(pixmap, src, dst);
cache->Add(src, filter);
}
}
if (filter)
flags->setColorFilter(filter);
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_FILTER_HELPER_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_FILTER_HELPER_H_
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/skia/include/core/SkRect.h"
namespace blink {
class DarkModeFilter;
class Image;
class PLATFORM_EXPORT DarkModeFilterHelper {
public:
static void ApplyToImageIfNeeded(DarkModeFilter* dark_mode_filter,
Image* image,
cc::PaintFlags* flags,
const SkRect& src,
const SkRect& dst);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_FILTER_HELPER_H_
......@@ -30,8 +30,6 @@ TEST(DarkModeFilterTest, DoNotApplyFilterWhenDarkModeIsOff) {
EXPECT_EQ(base::nullopt,
filter.ApplyToFlagsIfNeeded(
cc::PaintFlags(), DarkModeFilter::ElementRole::kBackground));
EXPECT_EQ(nullptr, filter.GetImageFilterForTesting());
}
TEST(DarkModeFilterTest, ApplyDarkModeToColorsAndFlags) {
......@@ -61,8 +59,6 @@ TEST(DarkModeFilterTest, ApplyDarkModeToColorsAndFlags) {
flags, DarkModeFilter::ElementRole::kBackground);
ASSERT_NE(flags_or_nullopt, base::nullopt);
EXPECT_EQ(SK_ColorBLACK, flags_or_nullopt.value().getColor());
EXPECT_NE(nullptr, filter.GetImageFilterForTesting());
}
TEST(DarkModeFilterTest, InvertedColorCacheSize) {
......@@ -122,32 +118,39 @@ TEST(DarkModeFilterTest, AnalyzeShouldApplyToImage) {
filter.UpdateSettings(settings);
// |dst| is smaller than threshold size.
EXPECT_TRUE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(100, 100),
SkRect::MakeWH(100, 100)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(100, 100),
SkRect::MakeWH(100, 100)),
DarkModeResult::kNotClassified);
// |dst| is smaller than threshold size, even |src| is larger.
EXPECT_TRUE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(200, 200),
SkRect::MakeWH(100, 100)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(200, 200),
SkRect::MakeWH(100, 100)),
DarkModeResult::kNotClassified);
// |dst| is smaller than threshold size, |src| is smaller.
EXPECT_TRUE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 20),
SkRect::MakeWH(100, 100)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 20),
SkRect::MakeWH(100, 100)),
DarkModeResult::kNotClassified);
// |src| having very smaller width, even |dst| is larger than threshold size.
EXPECT_TRUE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(5, 200),
SkRect::MakeWH(5, 200)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(5, 200),
SkRect::MakeWH(5, 200)),
DarkModeResult::kNotClassified);
// |src| having very smaller height, even |dst| is larger than threshold size.
EXPECT_TRUE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(200, 5),
SkRect::MakeWH(200, 5)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(200, 5),
SkRect::MakeWH(200, 5)),
DarkModeResult::kNotClassified);
// |dst| is larger than threshold size.
EXPECT_FALSE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 20),
SkRect::MakeWH(200, 200)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 20),
SkRect::MakeWH(200, 200)),
DarkModeResult::kDoNotApplyFilter);
// |dst| is larger than threshold size.
EXPECT_FALSE(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 200),
SkRect::MakeWH(20, 200)));
EXPECT_EQ(filter.AnalyzeShouldApplyToImage(SkRect::MakeWH(20, 200),
SkRect::MakeWH(20, 200)),
DarkModeResult::kDoNotApplyFilter);
}
} // namespace
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_IMAGE_CACHE_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_IMAGE_CACHE_H_
#include <unordered_map>
#include "base/hash/hash.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/skia/include/core/SkColorFilter.h"
namespace blink {
// DarkModeImageCache - Implements dark mode filter cache for different |src|
// rects from the image.
class PLATFORM_EXPORT DarkModeImageCache {
public:
DarkModeImageCache() = default;
~DarkModeImageCache() = default;
bool Exists(const SkRect& src) {
return cache_.find(DarkModeKey(src)) != cache_.end();
}
sk_sp<SkColorFilter> Get(const SkRect& src) {
auto result = cache_.find(DarkModeKey(src));
return (result != cache_.end()) ? result->second : nullptr;
}
void Add(const SkRect& src, sk_sp<SkColorFilter> dark_mode_color_filter) {
DCHECK(!Exists(src));
cache_.emplace(DarkModeKey(src), std::move(dark_mode_color_filter));
}
size_t Size() { return cache_.size(); }
void Clear() { cache_.clear(); }
private:
struct DarkModeKeyHash;
struct DarkModeKey {
explicit DarkModeKey(SkRect src) : src_(src) {}
bool operator==(const DarkModeKey& other) const {
return src_ == other.src_;
}
private:
SkRect src_;
friend struct DarkModeImageCache::DarkModeKeyHash;
};
struct DarkModeKeyHash {
size_t operator()(const DarkModeKey& key) const {
return base::HashInts(
base::HashInts(base::HashInts(key.src_.x(), key.src_.y()),
key.src_.width()),
key.src_.height());
}
};
std::unordered_map<DarkModeKey, sk_sp<SkColorFilter>, DarkModeKeyHash> cache_;
DISALLOW_COPY_AND_ASSIGN(DarkModeImageCache);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_IMAGE_CACHE_H_
// Copyright 2020 The Chromium 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 "third_party/blink/renderer/platform/graphics/dark_mode_image_cache.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/effects/SkHighContrastFilter.h"
namespace blink {
class DarkModeImageCacheTest : public testing::Test {};
TEST_F(DarkModeImageCacheTest, Caching) {
DarkModeImageCache cache;
SkHighContrastConfig config;
config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
sk_sp<SkColorFilter> filter = SkHighContrastFilter::Make(config);
SkRect src1 = SkRect::MakeXYWH(0, 0, 50, 50);
SkRect src2 = SkRect::MakeXYWH(5, 20, 100, 100);
SkRect src3 = SkRect::MakeXYWH(6, -9, 50, 50);
EXPECT_FALSE(cache.Exists(src1));
EXPECT_EQ(cache.Get(src1), nullptr);
cache.Add(src1, filter);
EXPECT_TRUE(cache.Exists(src1));
EXPECT_EQ(cache.Get(src1), filter);
EXPECT_FALSE(cache.Exists(src2));
EXPECT_EQ(cache.Get(src2), nullptr);
cache.Add(src2, nullptr);
EXPECT_TRUE(cache.Exists(src2));
EXPECT_EQ(cache.Get(src2), nullptr);
EXPECT_EQ(cache.Size(), 2u);
cache.Clear();
EXPECT_EQ(cache.Size(), 0u);
EXPECT_FALSE(cache.Exists(src1));
EXPECT_EQ(cache.Get(src1), nullptr);
EXPECT_FALSE(cache.Exists(src2));
EXPECT_EQ(cache.Get(src2), nullptr);
EXPECT_FALSE(cache.Exists(src3));
EXPECT_EQ(cache.Get(src3), nullptr);
cache.Add(src3, filter);
EXPECT_TRUE(cache.Exists(src3));
EXPECT_EQ(cache.Get(src3), filter);
EXPECT_EQ(cache.Size(), 1u);
}
} // namespace blink
......@@ -10,7 +10,6 @@
#include "base/optional.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/graphics/darkmode/darkmode_classifier.h"
#include "third_party/skia/include/utils/SkNullCanvas.h"
namespace blink {
namespace {
......@@ -35,129 +34,35 @@ const int kMaxSampledPixels = 1000;
const int kMaxBlocks = 10;
const float kMinOpaquePixelPercentageForForeground = 0.2;
// DarkModeImageClassificationCache - Implements classification caches for
// different paint image ids. The classification result for the given |src|
// rect is added to cache identified by |image_id| and result for the same
// can be retrieved. Using Remove(), the cache identified by |image_id| can
// be deleted.
class DarkModeImageClassificationCache {
public:
static DarkModeImageClassificationCache* GetInstance() {
return base::Singleton<DarkModeImageClassificationCache>::get();
}
DarkModeClassification Get(PaintImage::Id image_id, const SkRect& src) {
auto map = cache_.find(image_id);
if (map == cache_.end())
return DarkModeClassification::kNotClassified;
Key key = std::pair<float, float>(src.x(), src.y());
auto result = map->second.find(key);
if (result == map->second.end())
return DarkModeClassification::kNotClassified;
return result->second;
}
void Add(PaintImage::Id image_id,
const SkRect& src,
const DarkModeClassification result) {
DCHECK(Get(image_id, src) == DarkModeClassification::kNotClassified);
auto map = cache_.find(image_id);
if (map == cache_.end())
map = cache_.emplace(image_id, ClassificationMap()).first;
// TODO(prashant.n): Check weather full |src| should be used or not for
// key, considering the scenario of same origin and different sizes in the
// given sprite. Here only location in the image is considered as of now.
Key key = std::pair<float, float>(src.x(), src.y());
map->second.emplace(key, result);
}
size_t GetSize(PaintImage::Id image_id) {
auto map = cache_.find(image_id);
if (map == cache_.end())
return 0;
return map->second.size();
}
void Remove(PaintImage::Id image_id) { cache_.erase(image_id); }
private:
typedef std::pair<float, float> Key;
typedef std::map<Key, DarkModeClassification> ClassificationMap;
std::map<PaintImage::Id, ClassificationMap> cache_;
DarkModeImageClassificationCache() = default;
~DarkModeImageClassificationCache() = default;
friend struct base::DefaultSingletonTraits<DarkModeImageClassificationCache>;
DISALLOW_COPY_AND_ASSIGN(DarkModeImageClassificationCache);
};
} // namespace
DarkModeImageClassifier::DarkModeImageClassifier() = default;
DarkModeImageClassifier::~DarkModeImageClassifier() = default;
DarkModeClassification DarkModeImageClassifier::Classify(
const PaintImage& paint_image,
const SkRect& src,
const SkRect& dst) {
// Empty paint image cannot be classified.
if (!paint_image)
return DarkModeClassification::kDoNotApplyFilter;
DarkModeImageClassificationCache* cache =
DarkModeImageClassificationCache::GetInstance();
PaintImage::Id image_id = paint_image.stable_id();
DarkModeClassification result = cache->Get(image_id, src);
if (result != DarkModeClassification::kNotClassified)
return result;
auto features_or_null = GetFeatures(paint_image, src);
if (!features_or_null) {
// Do not cache this classification.
return DarkModeClassification::kDoNotApplyFilter;
}
result = ClassifyWithFeatures(features_or_null.value());
cache->Add(image_id, src, result);
return result;
}
bool DarkModeImageClassifier::GetBitmap(const PaintImage& paint_image,
const SkRect& src,
SkBitmap* bitmap) {
DCHECK(paint_image);
if (!src.width() || !src.height())
return false;
SkRect dst = {0, 0, src.width(), src.height()};
DarkModeResult DarkModeImageClassifier::Classify(const SkPixmap& pixmap,
const SkRect& src) {
// Empty pixmap or |src| out of bounds cannot be classified.
SkIRect bounds = pixmap.bounds();
if (src.isEmpty() || bounds.isEmpty() || !bounds.contains(src) ||
!pixmap.addr())
return DarkModeResult::kDoNotApplyFilter;
if (!bitmap || !bitmap->tryAllocPixels(SkImageInfo::MakeN32(
static_cast<int>(src.width()),
static_cast<int>(src.height()), kPremul_SkAlphaType)))
return false;
auto features_or_null = GetFeatures(pixmap, src);
if (!features_or_null)
return DarkModeResult::kDoNotApplyFilter;
SkCanvas canvas(*bitmap);
canvas.clear(SK_ColorTRANSPARENT);
canvas.drawImageRect(paint_image.GetSwSkImage(), src, dst, nullptr);
return true;
return ClassifyWithFeatures(features_or_null.value());
}
base::Optional<DarkModeImageClassifier::Features>
DarkModeImageClassifier::GetFeatures(const PaintImage& paint_image,
DarkModeImageClassifier::GetFeatures(const SkPixmap& pixmap,
const SkRect& src) {
DCHECK(!pixmap.bounds().isEmpty());
float transparency_ratio;
float background_ratio;
std::vector<SkColor> sampled_pixels;
GetSamples(paint_image, src, &sampled_pixels, &transparency_ratio,
GetSamples(pixmap, src, &sampled_pixels, &transparency_ratio,
&background_ratio);
// TODO(https://crbug.com/945434): Investigate why an incorrect resource is
// loaded and how we can fetch the correct resource. This condition will
......@@ -171,19 +76,17 @@ DarkModeImageClassifier::GetFeatures(const PaintImage& paint_image,
// Extracts sample pixels from the image. The image is separated into uniformly
// distributed blocks through its width and height, each block is sampled, and
// checked to see if it seems to be background or foreground.
void DarkModeImageClassifier::GetSamples(const PaintImage& paint_image,
void DarkModeImageClassifier::GetSamples(const SkPixmap& pixmap,
const SkRect& src,
std::vector<SkColor>* sampled_pixels,
float* transparency_ratio,
float* background_ratio) {
SkBitmap bitmap;
if (!GetBitmap(paint_image, src, &bitmap))
return;
int num_sampled_pixels = kMaxSampledPixels;
int num_blocks_x = kMaxBlocks;
int num_blocks_y = kMaxBlocks;
// TODO(prashant.n): Make src as SkIRect for removing flooring and rounding.
// Let caller control the rounding.
// Crash reports indicate that the src can be less than 1, so make
// sure it goes to 1. We know it is not 0 because GetBitmap above
// will return false for zero-sized src.
......@@ -207,26 +110,29 @@ void DarkModeImageClassifier::GetSamples(const PaintImage& paint_image,
std::vector<int> vertical_grid(num_blocks_y + 1);
for (int block = 0; block <= num_blocks_x; block++) {
horizontal_grid[block] = static_cast<int>(
round(block * bitmap.width() / static_cast<float>(num_blocks_x)));
horizontal_grid[block] =
src.x() + static_cast<int>(round(block * src.width() /
static_cast<float>(num_blocks_x)));
}
for (int block = 0; block <= num_blocks_y; block++) {
vertical_grid[block] = static_cast<int>(
round(block * bitmap.height() / static_cast<float>(num_blocks_y)));
vertical_grid[block] =
src.y() + static_cast<int>(round(block * src.height() /
static_cast<float>(num_blocks_y)));
}
sampled_pixels->clear();
std::vector<gfx::Rect> foreground_blocks;
std::vector<SkIRect> foreground_blocks;
for (int y = 0; y < num_blocks_y; y++) {
for (int x = 0; x < num_blocks_x; x++) {
gfx::Rect block(horizontal_grid[x], vertical_grid[y],
SkIRect block =
SkIRect::MakeXYWH(horizontal_grid[x], vertical_grid[y],
horizontal_grid[x + 1] - horizontal_grid[x],
vertical_grid[y + 1] - vertical_grid[y]);
std::vector<SkColor> block_samples;
int block_transparent_pixels;
GetBlockSamples(bitmap, block, pixels_per_block, &block_samples,
GetBlockSamples(pixmap, block, pixels_per_block, &block_samples,
&block_transparent_pixels);
opaque_pixels += static_cast<int>(block_samples.size());
transparent_pixels += block_transparent_pixels;
......@@ -250,32 +156,25 @@ void DarkModeImageClassifier::GetSamples(const PaintImage& paint_image,
// Returns the opaque sampled pixels, and the number of transparent
// sampled pixels.
void DarkModeImageClassifier::GetBlockSamples(
const SkBitmap& bitmap,
const gfx::Rect& block,
const SkPixmap& pixmap,
const SkIRect& block,
const int required_samples_count,
std::vector<SkColor>* sampled_pixels,
int* transparent_pixels_count) {
*transparent_pixels_count = 0;
int x1 = block.x();
int y1 = block.y();
int x2 = block.right();
int y2 = block.bottom();
DCHECK(x1 < bitmap.width());
DCHECK(y1 < bitmap.height());
DCHECK(x2 <= bitmap.width());
DCHECK(y2 <= bitmap.height());
DCHECK(pixmap.bounds().contains(block));
sampled_pixels->clear();
int cx = static_cast<int>(
ceil(static_cast<float>(x2 - x1) / sqrt(required_samples_count)));
ceil(static_cast<float>(block.width()) / sqrt(required_samples_count)));
int cy = static_cast<int>(
ceil(static_cast<float>(y2 - y1) / sqrt(required_samples_count)));
ceil(static_cast<float>(block.height()) / sqrt(required_samples_count)));
for (int y = y1; y < y2; y += cy) {
for (int x = x1; x < x2; x += cx) {
SkColor new_sample = bitmap.getColor(x, y);
for (int y = block.y(); y < block.bottom(); y += cy) {
for (int x = block.x(); x < block.right(); x += cx) {
SkColor new_sample = pixmap.getColor(x, y);
if (IsColorTransparent(new_sample))
(*transparent_pixels_count)++;
else
......@@ -341,13 +240,13 @@ float DarkModeImageClassifier::ComputeColorBucketsRatio(
max_buckets[color_mode == ColorMode::kColor];
}
DarkModeClassification DarkModeImageClassifier::ClassifyWithFeatures(
DarkModeResult DarkModeImageClassifier::ClassifyWithFeatures(
const Features& features) {
DarkModeClassification result = ClassifyUsingDecisionTree(features);
DarkModeResult result = ClassifyUsingDecisionTree(features);
// If decision tree cannot decide, we use a neural network to decide whether
// to filter or not based on all the features.
if (result == DarkModeClassification::kNotClassified) {
if (result == DarkModeResult::kNotClassified) {
darkmode_tfnative_model::FixedAllocations nn_temp;
float nn_out;
......@@ -359,14 +258,14 @@ DarkModeClassification DarkModeImageClassifier::ClassifyWithFeatures(
features.background_ratio};
darkmode_tfnative_model::Inference(feature_list, &nn_out, &nn_temp);
result = nn_out > 0 ? DarkModeClassification::kApplyFilter
: DarkModeClassification::kDoNotApplyFilter;
result = nn_out > 0 ? DarkModeResult::kApplyFilter
: DarkModeResult::kDoNotApplyFilter;
}
return result;
}
DarkModeClassification DarkModeImageClassifier::ClassifyUsingDecisionTree(
DarkModeResult DarkModeImageClassifier::ClassifyUsingDecisionTree(
const DarkModeImageClassifier::Features& features) {
float low_color_count_threshold =
kLowColorCountThreshold[features.is_colorful];
......@@ -375,36 +274,14 @@ DarkModeClassification DarkModeImageClassifier::ClassifyUsingDecisionTree(
// Very few colors means it's not a photo, apply the filter.
if (features.color_buckets_ratio < low_color_count_threshold)
return DarkModeClassification::kApplyFilter;
return DarkModeResult::kApplyFilter;
// Too many colors means it's probably photorealistic, do not apply it.
if (features.color_buckets_ratio > high_color_count_threshold)
return DarkModeClassification::kDoNotApplyFilter;
return DarkModeResult::kDoNotApplyFilter;
// In-between, decision tree cannot give a precise result.
return DarkModeClassification::kNotClassified;
}
// static
void DarkModeImageClassifier::RemoveCache(PaintImage::Id image_id) {
DarkModeImageClassificationCache::GetInstance()->Remove(image_id);
}
DarkModeClassification DarkModeImageClassifier::GetCacheValue(
PaintImage::Id image_id,
const SkRect& src) {
return DarkModeImageClassificationCache::GetInstance()->Get(image_id, src);
}
void DarkModeImageClassifier::AddCacheValue(PaintImage::Id image_id,
const SkRect& src,
DarkModeClassification result) {
return DarkModeImageClassificationCache::GetInstance()->Add(image_id, src,
result);
}
size_t DarkModeImageClassifier::GetCacheSize(PaintImage::Id image_id) {
return DarkModeImageClassificationCache::GetInstance()->GetSize(image_id);
return DarkModeResult::kNotClassified;
}
} // namespace blink
......@@ -9,17 +9,15 @@
#include "base/gtest_prod_util.h"
#include "base/optional.h"
#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_types.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/gfx/geometry/rect.h"
namespace blink {
FORWARD_DECLARE_TEST(DarkModeImageClassifierTest, BlockSamples);
FORWARD_DECLARE_TEST(DarkModeImageClassifierTest, FeaturesAndClassification);
FORWARD_DECLARE_TEST(DarkModeImageClassifierTest, Caching);
// This class is not threadsafe as the cache used for storing classification
// results is not threadsafe. So it can be used only in blink main thread.
......@@ -43,38 +41,27 @@ class PLATFORM_EXPORT DarkModeImageClassifier {
float background_ratio;
};
// Performance warning: |paint_image| will be synchronously decoded if this
// function is called in blink main thread.
DarkModeClassification Classify(const PaintImage& paint_image,
const SkRect& src,
const SkRect& dst);
// Removes cache identified by given |image_id|.
static void RemoveCache(PaintImage::Id image_id);
DarkModeResult Classify(const SkPixmap& pixmap, const SkRect& src);
private:
DarkModeClassification ClassifyWithFeatures(const Features& features);
DarkModeClassification ClassifyUsingDecisionTree(const Features& features);
bool GetBitmap(const PaintImage& paint_image,
const SkRect& src,
SkBitmap* bitmap);
base::Optional<Features> GetFeatures(const PaintImage& paint_image,
const SkRect& src);
DarkModeResult ClassifyWithFeatures(const Features& features);
DarkModeResult ClassifyUsingDecisionTree(const Features& features);
enum class ColorMode { kColor = 0, kGrayscale = 1 };
base::Optional<Features> GetFeatures(const SkPixmap& pixmap,
const SkRect& src);
// Extracts a sample set of pixels (|sampled_pixels|), |transparency_ratio|,
// and |background_ratio|.
void GetSamples(const PaintImage& paint_image,
void GetSamples(const SkPixmap& pixmap,
const SkRect& src,
std::vector<SkColor>* sampled_pixels,
float* transparency_ratio,
float* background_ratio);
// Gets the |required_samples_count| for a specific |block| of the given
// SkBitmap, and returns |sampled_pixels| and |transparent_pixels_count|.
void GetBlockSamples(const SkBitmap& bitmap,
const gfx::Rect& block,
// pixmap, and returns |sampled_pixels| and |transparent_pixels_count|.
void GetBlockSamples(const SkPixmap& pixmap,
const SkIRect& block,
const int required_samples_count,
std::vector<SkColor>* sampled_pixels,
int* transparent_pixels_count);
......@@ -92,19 +79,9 @@ class PLATFORM_EXPORT DarkModeImageClassifier {
float ComputeColorBucketsRatio(const std::vector<SkColor>& sampled_pixels,
const ColorMode color_mode);
// Gets cached value from the given |image_id| cache.
DarkModeClassification GetCacheValue(PaintImage::Id image_id,
const SkRect& src);
// Adds cache value |result| to the given |image_id| cache.
void AddCacheValue(PaintImage::Id image_id,
const SkRect& src,
DarkModeClassification result);
// Returns the cache size for the given |image_id|.
size_t GetCacheSize(PaintImage::Id image_id);
FRIEND_TEST_ALL_PREFIXES(DarkModeImageClassifierTest, BlockSamples);
FRIEND_TEST_ALL_PREFIXES(DarkModeImageClassifierTest,
FeaturesAndClassification);
FRIEND_TEST_ALL_PREFIXES(DarkModeImageClassifierTest, Caching);
};
} // namespace blink
......
......@@ -47,9 +47,212 @@ class DarkModeImageClassifierTest : public testing::Test {
std::unique_ptr<DarkModeImageClassifier> dark_mode_image_classifier_;
};
TEST_F(DarkModeImageClassifierTest, ValidImage) {
scoped_refptr<BitmapImage> image;
SkBitmap bitmap;
SkPixmap pixmap;
image = GetImage("/images/resources/twitter_favicon.ico");
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
EXPECT_EQ(image_classifier()->Classify(
pixmap, SkRect::MakeWH(image->width(), image->height())),
DarkModeResult::kApplyFilter);
}
TEST_F(DarkModeImageClassifierTest, InvalidImage) {
scoped_refptr<BitmapImage> image;
SkBitmap bitmap;
SkPixmap pixmap;
// Empty pixmap.
SkRect src = SkRect::MakeWH(50, 50);
EXPECT_EQ(image_classifier()->Classify(pixmap, src),
DarkModeResult::kDoNotApplyFilter);
// |src| larger than image size.
image = GetImage("/images/resources/twitter_favicon.ico");
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
EXPECT_EQ(
image_classifier()->Classify(
pixmap, SkRect::MakeWH(image->width() + 10, image->height() + 10)),
DarkModeResult::kDoNotApplyFilter);
// Empty src rect.
EXPECT_EQ(image_classifier()->Classify(pixmap, SkRect()),
DarkModeResult::kDoNotApplyFilter);
}
TEST_F(DarkModeImageClassifierTest, ImageSpriteAllFragmentsSame) {
scoped_refptr<BitmapImage> image;
SkBitmap bitmap;
SkPixmap pixmap;
image = GetImage("/images/resources/sprite_all_fragments_same.png");
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 0, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 36, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 72, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 108, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 144, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 180, 95, 36)),
DarkModeResult::kApplyFilter);
}
TEST_F(DarkModeImageClassifierTest, ImageSpriteAlternateFragmentsSame) {
scoped_refptr<BitmapImage> image;
SkBitmap bitmap;
SkPixmap pixmap;
image = GetImage("/images/resources/sprite_alternate_fragments_same.png");
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 0, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 36, 95, 36)),
DarkModeResult::kDoNotApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 72, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 108, 95, 36)),
DarkModeResult::kDoNotApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 144, 95, 36)),
DarkModeResult::kApplyFilter);
EXPECT_EQ(
image_classifier()->Classify(pixmap, SkRect::MakeXYWH(0, 180, 95, 36)),
DarkModeResult::kDoNotApplyFilter);
}
TEST_F(DarkModeImageClassifierTest, BlockSamples) {
SkBitmap bitmap;
SkPixmap pixmap;
bitmap.allocPixels(SkImageInfo::MakeN32Premul(4, 4), 4 * 4);
SkCanvas canvas(bitmap);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
std::vector<SkColor> sampled_pixels;
int transparent_pixels_count = -1;
// All transparent.
// ┌──────┐
// │ AAAA │
// │ AAAA │
// │ AAAA │
// │ AAAA │
// └──────┘
bitmap.eraseColor(SK_AlphaTRANSPARENT);
bitmap.peekPixels(&pixmap);
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(0, 0, 4, 4), 16,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 0u);
EXPECT_EQ(transparent_pixels_count, 16);
// All pixels red.
// ┌──────┐
// │ RRRR │
// │ RRRR │
// │ RRRR │
// │ RRRR │
// └──────┘
bitmap.eraseColor(SK_AlphaTRANSPARENT);
paint.setColor(SK_ColorRED);
canvas.drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), paint);
bitmap.peekPixels(&pixmap);
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(0, 0, 4, 4), 16,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 16u);
EXPECT_EQ(transparent_pixels_count, 0);
for (auto color : sampled_pixels)
EXPECT_EQ(color, SK_ColorRED);
// Mixed.
// ┌──────┐
// │ RRGG │
// │ RRGG │
// │ BBAA │
// │ BBAA │
// └──────┘
bitmap.eraseColor(SK_AlphaTRANSPARENT);
paint.setColor(SK_ColorRED);
canvas.drawIRect(SkIRect::MakeXYWH(0, 0, 2, 2), paint);
paint.setColor(SK_ColorGREEN);
canvas.drawIRect(SkIRect::MakeXYWH(2, 0, 2, 2), paint);
paint.setColor(SK_ColorBLUE);
canvas.drawIRect(SkIRect::MakeXYWH(0, 2, 2, 2), paint);
bitmap.peekPixels(&pixmap);
// Full block.
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(0, 0, 4, 4), 16,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 12u);
EXPECT_EQ(transparent_pixels_count, 4);
// Red block.
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(0, 0, 2, 2), 4,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 4u);
EXPECT_EQ(transparent_pixels_count, 0);
for (auto color : sampled_pixels)
EXPECT_EQ(color, SK_ColorRED);
// Green block.
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(2, 0, 2, 2), 4,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 4u);
EXPECT_EQ(transparent_pixels_count, 0);
for (auto color : sampled_pixels)
EXPECT_EQ(color, SK_ColorGREEN);
// Blue block.
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(0, 2, 2, 2), 4,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 4u);
EXPECT_EQ(transparent_pixels_count, 0);
for (auto color : sampled_pixels)
EXPECT_EQ(color, SK_ColorBLUE);
// Alpha block.
image_classifier()->GetBlockSamples(pixmap, SkIRect::MakeXYWH(2, 2, 2, 2), 4,
&sampled_pixels,
&transparent_pixels_count);
EXPECT_EQ(sampled_pixels.size(), 0u);
EXPECT_EQ(transparent_pixels_count, 4);
}
TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
DarkModeImageClassifier::Features features;
scoped_refptr<BitmapImage> image;
SkBitmap bitmap;
SkPixmap pixmap;
// Test Case 1:
// Grayscale
......@@ -60,14 +263,16 @@ TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
// The data members of DarkModeImageClassifier have to be reset for every
// image as the same classifier object is used for all the tests.
image = GetImage("/images/resources/grid-large.png");
features = image_classifier()
->GetFeatures(image->PaintImageForCurrentFrame(),
SkRect::MakeWH(image->width(), image->height()))
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
features =
image_classifier()
->GetFeatures(pixmap, SkRect::MakeWH(image->width(), image->height()))
.value();
EXPECT_EQ(image_classifier()->ClassifyWithFeatures(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_EQ(image_classifier()->ClassifyUsingDecisionTree(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_FALSE(features.is_colorful);
EXPECT_NEAR(0.1875f, features.color_buckets_ratio, kEpsilon);
EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
......@@ -79,14 +284,16 @@ TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
// Decision Tree: Can't Decide
// Neural Network: Apply
image = GetImage("/images/resources/apng08-ref.png");
features = image_classifier()
->GetFeatures(image->PaintImageForCurrentFrame(),
SkRect::MakeWH(image->width(), image->height()))
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
features =
image_classifier()
->GetFeatures(pixmap, SkRect::MakeWH(image->width(), image->height()))
.value();
EXPECT_EQ(image_classifier()->ClassifyWithFeatures(features),
DarkModeClassification::kDoNotApplyFilter);
DarkModeResult::kDoNotApplyFilter);
EXPECT_EQ(image_classifier()->ClassifyUsingDecisionTree(features),
DarkModeClassification::kNotClassified);
DarkModeResult::kNotClassified);
EXPECT_FALSE(features.is_colorful);
EXPECT_NEAR(0.8125f, features.color_buckets_ratio, kEpsilon);
EXPECT_NEAR(0.446667f, features.transparency_ratio, kEpsilon);
......@@ -98,14 +305,16 @@ TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
// Decision Tree: Apply
// Neural Network: NA.
image = GetImage("/images/resources/twitter_favicon.ico");
features = image_classifier()
->GetFeatures(image->PaintImageForCurrentFrame(),
SkRect::MakeWH(image->width(), image->height()))
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
features =
image_classifier()
->GetFeatures(pixmap, SkRect::MakeWH(image->width(), image->height()))
.value();
EXPECT_EQ(image_classifier()->ClassifyWithFeatures(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_EQ(image_classifier()->ClassifyUsingDecisionTree(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_TRUE(features.is_colorful);
EXPECT_NEAR(0.0002441f, features.color_buckets_ratio, kEpsilon);
EXPECT_NEAR(0.542092f, features.transparency_ratio, kEpsilon);
......@@ -117,14 +326,16 @@ TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
// Decision Tree: Do Not Apply
// Neural Network: NA.
image = GetImage("/images/resources/blue-wheel-srgb-color-profile.png");
features = image_classifier()
->GetFeatures(image->PaintImageForCurrentFrame(),
SkRect::MakeWH(image->width(), image->height()))
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
features =
image_classifier()
->GetFeatures(pixmap, SkRect::MakeWH(image->width(), image->height()))
.value();
EXPECT_EQ(image_classifier()->ClassifyWithFeatures(features),
DarkModeClassification::kDoNotApplyFilter);
DarkModeResult::kDoNotApplyFilter);
EXPECT_EQ(image_classifier()->ClassifyUsingDecisionTree(features),
DarkModeClassification::kDoNotApplyFilter);
DarkModeResult::kDoNotApplyFilter);
EXPECT_TRUE(features.is_colorful);
EXPECT_NEAR(0.032959f, features.color_buckets_ratio, kEpsilon);
EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
......@@ -136,64 +347,20 @@ TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
// Decision Tree: Apply
// Neural Network: NA.
image = GetImage("/images/resources/ycbcr-444-float.jpg");
features = image_classifier()
->GetFeatures(image->PaintImageForCurrentFrame(),
SkRect::MakeWH(image->width(), image->height()))
bitmap = image->AsSkBitmapForCurrentFrame(kDoNotRespectImageOrientation);
bitmap.peekPixels(&pixmap);
features =
image_classifier()
->GetFeatures(pixmap, SkRect::MakeWH(image->width(), image->height()))
.value();
EXPECT_EQ(image_classifier()->ClassifyWithFeatures(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_EQ(image_classifier()->ClassifyUsingDecisionTree(features),
DarkModeClassification::kApplyFilter);
DarkModeResult::kApplyFilter);
EXPECT_TRUE(features.is_colorful);
EXPECT_NEAR(0.0151367f, features.color_buckets_ratio, kEpsilon);
EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
EXPECT_NEAR(0.0f, features.background_ratio, kEpsilon);
}
TEST_F(DarkModeImageClassifierTest, InvalidImage) {
PaintImage paint_image;
SkRect src = SkRect::MakeWH(50, 50);
SkRect dst = SkRect::MakeWH(50, 50);
EXPECT_EQ(image_classifier()->Classify(paint_image, src, dst),
DarkModeClassification::kDoNotApplyFilter);
}
TEST_F(DarkModeImageClassifierTest, Caching) {
PaintImage::Id image_id = PaintImage::GetNextId();
SkRect src1 = SkRect::MakeXYWH(0, 0, 50, 50);
SkRect src2 = SkRect::MakeXYWH(5, 20, 100, 100);
SkRect src3 = SkRect::MakeXYWH(6, -9, 50, 50);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src1),
DarkModeClassification::kNotClassified);
image_classifier()->AddCacheValue(image_id, src1,
DarkModeClassification::kApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src1),
DarkModeClassification::kApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src2),
DarkModeClassification::kNotClassified);
image_classifier()->AddCacheValue(image_id, src2,
DarkModeClassification::kDoNotApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src2),
DarkModeClassification::kDoNotApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheSize(image_id), 2u);
DarkModeImageClassifier::RemoveCache(image_id);
EXPECT_EQ(image_classifier()->GetCacheSize(image_id), 0u);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src1),
DarkModeClassification::kNotClassified);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src2),
DarkModeClassification::kNotClassified);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src3),
DarkModeClassification::kNotClassified);
image_classifier()->AddCacheValue(image_id, src3,
DarkModeClassification::kApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheValue(image_id, src3),
DarkModeClassification::kApplyFilter);
EXPECT_EQ(image_classifier()->GetCacheSize(image_id), 1u);
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_TYPES_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_TYPES_H_
namespace blink {
enum class DarkModeResult : uint8_t {
kDoNotApplyFilter,
kApplyFilter,
kNotClassified
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_DARK_MODE_TYPES_H_
......@@ -37,6 +37,7 @@
#include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_filter_helper.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/interpolation_space.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
......@@ -877,10 +878,9 @@ void GraphicsContext::DrawImage(
image_flags.setFilterQuality(ComputeFilterQuality(image, dest, src));
// Do not classify the image if the element has any CSS filters.
if (!has_filter_property && dark_mode_filter_.IsDarkModeActive() &&
dark_mode_filter_.AnalyzeShouldApplyToImage(src, dest)) {
dark_mode_filter_.ApplyToImageFlagsIfNeeded(
src, dest, image->PaintImageForCurrentFrame(), &image_flags);
if (!has_filter_property && dark_mode_filter_.IsDarkModeActive()) {
DarkModeFilterHelper::ApplyToImageIfNeeded(&dark_mode_filter_, image,
&image_flags, src, dest);
}
image->Draw(canvas_, image_flags, dest, src, should_respect_image_orientation,
......@@ -918,11 +918,9 @@ void GraphicsContext::DrawImageRRect(
image_flags.setFilterQuality(
ComputeFilterQuality(image, dest.Rect(), src_rect));
if (dark_mode_filter_.IsDarkModeActive() &&
dark_mode_filter_.AnalyzeShouldApplyToImage(src_rect, dest.Rect())) {
dark_mode_filter_.ApplyToImageFlagsIfNeeded(
src_rect, dest.Rect(), image->PaintImageForCurrentFrame(),
&image_flags);
if (dark_mode_filter_.IsDarkModeActive()) {
DarkModeFilterHelper::ApplyToImageIfNeeded(
&dark_mode_filter_, image, &image_flags, src_rect, dest.Rect());
}
bool use_shader = (visible_src == src_rect) &&
......
......@@ -134,12 +134,6 @@ enum MailboxSyncMode {
kOrderingBarrier,
};
enum class DarkModeClassification {
kNotClassified,
kApplyFilter,
kDoNotApplyFilter,
};
// TODO(junov): crbug.com/453113 Relocate ShadowMode to
// CanvasRenderingContext2DState.h once GraphicsContext no longer uses it.
enum ShadowMode {
......
......@@ -40,6 +40,7 @@
#include "third_party/blink/renderer/platform/geometry/float_size.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_image_cache.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_image_classifier.h"
#include "third_party/blink/renderer/platform/graphics/deferred_image_decoder.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
......@@ -65,12 +66,7 @@ Image::Image(ImageObserver* observer, bool is_multipart)
stable_image_id_(PaintImage::GetNextId()),
is_multipart_(is_multipart) {}
Image::~Image() {
// TODO(prashant.n): This logic is needed to purge cache for the same origin
// page navigations. Redesign this once dark mode filter module gets moved to
// compositor side.
DarkModeImageClassifier::RemoveCache(stable_image_id_);
}
Image::~Image() = default;
Image* Image::NullImage() {
DCHECK(IsMainThread());
......@@ -365,6 +361,13 @@ SkBitmap Image::AsSkBitmapForCurrentFrame(
return bitmap;
}
DarkModeImageCache* Image::GetDarkModeImageCache() {
if (!dark_mode_image_cache_)
dark_mode_image_cache_ = std::make_unique<DarkModeImageCache>();
return dark_mode_image_cache_.get();
}
FloatRect Image::CorrectSrcRectForImageOrientation(FloatSize image_size,
FloatRect src_rect) const {
ImageOrientation orientation = CurrentFrameOrientation();
......
......@@ -61,6 +61,7 @@ class GraphicsContext;
class Image;
class WebGraphicsContext3DProvider;
class WebGraphicsContext3DProviderWrapper;
class DarkModeImageCache;
class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
friend class GeneratedImage;
......@@ -252,6 +253,8 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
// Returns an SkBitmap that is a copy of the image's current frame.
SkBitmap AsSkBitmapForCurrentFrame(RespectImageOrientationEnum);
DarkModeImageCache* GetDarkModeImageCache();
protected:
Image(ImageObserver* = nullptr, bool is_multipart = false);
......@@ -284,6 +287,7 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
WeakPersistent<ImageObserver> image_observer_;
PaintImage::Id stable_image_id_;
const bool is_multipart_;
std::unique_ptr<DarkModeImageCache> dark_mode_image_cache_;
DISALLOW_COPY_AND_ASSIGN(Image);
};
......
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