1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/platform/graphics/dark_mode_image_classifier.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
9 #include "third_party/blink/renderer/platform/graphics/dark_mode_generic_classifier.h"
10 #include "third_party/blink/renderer/platform/graphics/image.h"
11 #include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
12 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
13 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
14 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
15
16 namespace blink {
17 namespace {
18
19 const float kEpsilon = 0.00001;
20
21 } // namespace
22
23 class FakeImageForCacheTest : public Image {
24 public:
Create()25 static scoped_refptr<FakeImageForCacheTest> Create() {
26 return base::AdoptRef(new FakeImageForCacheTest());
27 }
28
GetMapSize()29 int GetMapSize() { return dark_mode_classifications_.size(); }
30
GetClassification(const FloatRect & src_rect)31 DarkModeClassification GetClassification(const FloatRect& src_rect) {
32 return GetDarkModeClassification(src_rect);
33 }
34
AddClassification(const FloatRect & src_rect,const DarkModeClassification dark_mode_classification)35 void AddClassification(
36 const FloatRect& src_rect,
37 const DarkModeClassification dark_mode_classification) {
38 AddDarkModeClassification(src_rect, dark_mode_classification);
39 }
40
41 // Pure virtual functions that have to be overridden.
CurrentFrameKnownToBeOpaque()42 bool CurrentFrameKnownToBeOpaque() override { return false; }
Size() const43 IntSize Size() const override { return IntSize(0, 0); }
DestroyDecodedData()44 void DestroyDecodedData() override {}
PaintImageForCurrentFrame()45 PaintImage PaintImageForCurrentFrame() override { return PaintImage(); }
Draw(cc::PaintCanvas *,const cc::PaintFlags &,const FloatRect & dst_rect,const FloatRect & src_rect,RespectImageOrientationEnum,ImageClampingMode,ImageDecodingMode)46 void Draw(cc::PaintCanvas*,
47 const cc::PaintFlags&,
48 const FloatRect& dst_rect,
49 const FloatRect& src_rect,
50 RespectImageOrientationEnum,
51 ImageClampingMode,
52 ImageDecodingMode) override {}
53 };
54
55 class DarkModeImageClassifierTest : public testing::Test {
56 public:
57 // Loads the image from |file_name|.
GetImage(const String & file_name)58 scoped_refptr<BitmapImage> GetImage(const String& file_name) {
59 SCOPED_TRACE(file_name);
60 String file_path = test::BlinkWebTestsDir() + file_name;
61 scoped_refptr<SharedBuffer> image_data = test::ReadFromFile(file_path);
62 EXPECT_TRUE(image_data.get() && image_data.get()->size());
63
64 scoped_refptr<BitmapImage> image = BitmapImage::Create();
65 image->SetData(image_data, true);
66 return image;
67 }
68
69 // Computes features into |features|.
GetFeatures(scoped_refptr<BitmapImage> image,DarkModeImageClassifier::Features * features)70 void GetFeatures(scoped_refptr<BitmapImage> image,
71 DarkModeImageClassifier::Features* features) {
72 CHECK(features);
73 dark_mode_image_classifier_.SetImageType(
74 DarkModeImageClassifier::ImageType::kBitmap);
75 auto features_or_null = dark_mode_image_classifier_.GetFeatures(
76 image.get(), FloatRect(0, 0, image->width(), image->height()));
77 CHECK(features_or_null.has_value());
78 (*features) = features_or_null.value();
79 }
80
81 // Returns the classification result.
GetClassification(const DarkModeImageClassifier::Features features)82 bool GetClassification(const DarkModeImageClassifier::Features features) {
83 DarkModeClassification result =
84 dark_mode_generic_classifier_.ClassifyWithFeatures(features);
85 return result == DarkModeClassification::kApplyFilter;
86 }
87
image_classifier()88 DarkModeImageClassifier* image_classifier() {
89 return &dark_mode_image_classifier_;
90 }
91
generic_classifier()92 DarkModeGenericClassifier* generic_classifier() {
93 return &dark_mode_generic_classifier_;
94 }
95
96 protected:
97 ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
98 platform_;
99 DarkModeImageClassifier dark_mode_image_classifier_;
100 DarkModeGenericClassifier dark_mode_generic_classifier_;
101 };
102
TEST_F(DarkModeImageClassifierTest,FeaturesAndClassification)103 TEST_F(DarkModeImageClassifierTest, FeaturesAndClassification) {
104 DarkModeImageClassifier::Features features;
105
106 // Test Case 1:
107 // Grayscale
108 // Color Buckets Ratio: Low
109 // Decision Tree: Apply
110 // Neural Network: NA
111
112 // The data members of DarkModeImageClassifier have to be reset for every
113 // image as the same classifier object is used for all the tests.
114 image_classifier()->ResetDataMembersToDefaults();
115 GetFeatures(GetImage("/images/resources/grid-large.png"), &features);
116 EXPECT_TRUE(GetClassification(features));
117 EXPECT_EQ(generic_classifier()->ClassifyUsingDecisionTreeForTesting(features),
118 DarkModeClassification::kApplyFilter);
119 EXPECT_FALSE(features.is_colorful);
120 EXPECT_FALSE(features.is_svg);
121 EXPECT_NEAR(0.1875f, features.color_buckets_ratio, kEpsilon);
122 EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
123 EXPECT_NEAR(0.0f, features.background_ratio, kEpsilon);
124
125 // Test Case 2:
126 // Grayscale
127 // Color Buckets Ratio: Medium
128 // Decision Tree: Can't Decide
129 // Neural Network: Apply
130 image_classifier()->ResetDataMembersToDefaults();
131 GetFeatures(GetImage("/images/resources/apng08-ref.png"), &features);
132 EXPECT_FALSE(GetClassification(features));
133 EXPECT_EQ(generic_classifier()->ClassifyUsingDecisionTreeForTesting(features),
134 DarkModeClassification::kNotClassified);
135 EXPECT_FALSE(features.is_colorful);
136 EXPECT_FALSE(features.is_svg);
137 EXPECT_NEAR(0.8125f, features.color_buckets_ratio, kEpsilon);
138 EXPECT_NEAR(0.446667f, features.transparency_ratio, kEpsilon);
139 EXPECT_NEAR(0.03f, features.background_ratio, kEpsilon);
140
141 // Test Case 3:
142 // Color
143 // Color Buckets Ratio: Low
144 // Decision Tree: Apply
145 // Neural Network: NA.
146 image_classifier()->ResetDataMembersToDefaults();
147 GetFeatures(GetImage("/images/resources/twitter_favicon.ico"), &features);
148 EXPECT_TRUE(GetClassification(features));
149 EXPECT_EQ(generic_classifier()->ClassifyUsingDecisionTreeForTesting(features),
150 DarkModeClassification::kApplyFilter);
151 EXPECT_TRUE(features.is_colorful);
152 EXPECT_FALSE(features.is_svg);
153 EXPECT_NEAR(0.0002441f, features.color_buckets_ratio, kEpsilon);
154 EXPECT_NEAR(0.542092f, features.transparency_ratio, kEpsilon);
155 EXPECT_NEAR(0.1500000f, features.background_ratio, kEpsilon);
156
157 // Test Case 4:
158 // Color
159 // Color Buckets Ratio: High
160 // Decision Tree: Do Not Apply
161 // Neural Network: NA.
162 image_classifier()->ResetDataMembersToDefaults();
163 GetFeatures(GetImage("/images/resources/blue-wheel-srgb-color-profile.png"),
164 &features);
165 EXPECT_FALSE(GetClassification(features));
166 EXPECT_EQ(generic_classifier()->ClassifyUsingDecisionTreeForTesting(features),
167 DarkModeClassification::kDoNotApplyFilter);
168 EXPECT_TRUE(features.is_colorful);
169 EXPECT_FALSE(features.is_svg);
170 EXPECT_NEAR(0.032959f, features.color_buckets_ratio, kEpsilon);
171 EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
172 EXPECT_NEAR(0.0f, features.background_ratio, kEpsilon);
173
174 // Test Case 5:
175 // Color
176 // Color Buckets Ratio: Medium
177 // Decision Tree: Apply
178 // Neural Network: NA.
179 image_classifier()->ResetDataMembersToDefaults();
180 GetFeatures(GetImage("/images/resources/ycbcr-444-float.jpg"), &features);
181 EXPECT_TRUE(GetClassification(features));
182 EXPECT_EQ(generic_classifier()->ClassifyUsingDecisionTreeForTesting(features),
183 DarkModeClassification::kApplyFilter);
184 EXPECT_TRUE(features.is_colorful);
185 EXPECT_FALSE(features.is_svg);
186 EXPECT_NEAR(0.0151367f, features.color_buckets_ratio, kEpsilon);
187 EXPECT_NEAR(0.0f, features.transparency_ratio, kEpsilon);
188 EXPECT_NEAR(0.0f, features.background_ratio, kEpsilon);
189 }
190
TEST_F(DarkModeImageClassifierTest,Caching)191 TEST_F(DarkModeImageClassifierTest, Caching) {
192 scoped_refptr<FakeImageForCacheTest> image = FakeImageForCacheTest::Create();
193 FloatRect src_rect1(0, 0, 50, 50);
194 FloatRect src_rect2(5, 20, 100, 100);
195 FloatRect src_rect3(6, -9, 50, 50);
196
197 EXPECT_EQ(image->GetClassification(src_rect1),
198 DarkModeClassification::kNotClassified);
199 image->AddClassification(src_rect1, DarkModeClassification::kApplyFilter);
200 EXPECT_EQ(image->GetClassification(src_rect1),
201 DarkModeClassification::kApplyFilter);
202
203 EXPECT_EQ(image->GetClassification(src_rect2),
204 DarkModeClassification::kNotClassified);
205 image->AddClassification(src_rect2,
206 DarkModeClassification::kDoNotApplyFilter);
207 EXPECT_EQ(image->GetClassification(src_rect2),
208 DarkModeClassification::kDoNotApplyFilter);
209
210 EXPECT_EQ(image->GetClassification(src_rect3),
211 DarkModeClassification::kNotClassified);
212 image->AddClassification(src_rect3, DarkModeClassification::kApplyFilter);
213 EXPECT_EQ(image->GetClassification(src_rect3),
214 DarkModeClassification::kApplyFilter);
215
216 EXPECT_EQ(image->GetMapSize(), 3);
217 }
218
TEST_F(DarkModeImageClassifierTest,BlocksCount)219 TEST_F(DarkModeImageClassifierTest, BlocksCount) {
220 scoped_refptr<BitmapImage> image =
221 GetImage("/images/resources/grid-large.png");
222 DarkModeImageClassifier::Features features;
223 image_classifier()->ResetDataMembersToDefaults();
224
225 // When the horizontal and vertical blocks counts are lesser than the
226 // image dimensions, they should remain unaltered.
227 image_classifier()->SetHorizontalBlocksCount((int)(image->width() - 1));
228 image_classifier()->SetVerticalBlocksCount((int)(image->height() - 1));
229 GetFeatures(image, &features);
230 EXPECT_EQ(image_classifier()->HorizontalBlocksCount(),
231 (int)(image->width() - 1));
232 EXPECT_EQ(image_classifier()->VerticalBlocksCount(),
233 (int)(image->height() - 1));
234
235 // When the horizontal and vertical blocks counts are lesser than the
236 // image dimensions, they should remain unaltered.
237 image_classifier()->SetHorizontalBlocksCount((int)(image->width()));
238 image_classifier()->SetVerticalBlocksCount((int)(image->height()));
239 GetFeatures(image, &features);
240 EXPECT_EQ(image_classifier()->HorizontalBlocksCount(),
241 (int)(image->width()));
242 EXPECT_EQ(image_classifier()->VerticalBlocksCount(),
243 (int)(image->height()));
244
245 // When the horizontal and vertical blocks counts are greater than the
246 // image dimensions, they should be reduced.
247 image_classifier()->SetHorizontalBlocksCount((int)(image->width() + 1));
248 image_classifier()->SetVerticalBlocksCount((int)(image->height() + 1));
249 GetFeatures(image, &features);
250 EXPECT_EQ(image_classifier()->HorizontalBlocksCount(),
251 floor(image->width()));
252 EXPECT_EQ(image_classifier()->VerticalBlocksCount(),
253 floor(image->height()));
254 }
255
256 } // namespace blink
257