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