blob: 7d56fca853b64cf282db41f20dcf92ec1e1a155e [file] [log] [blame]
// Copyright 2017 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 "components/safe_browsing/core/common/visual_utils.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColorPriv.h"
namespace safe_browsing {
namespace visual_utils {
namespace {
const SkPMColor kSkPMRed = SkPackARGB32(255, 255, 0, 0);
const SkPMColor kSkPMGreen = SkPackARGB32(255, 0, 255, 0);
const SkPMColor kSkPMBlue = SkPackARGB32(255, 0, 0, 255);
// Use to add noise to the 5 lower bits of each color component. Use to
// introduce values directly into an N32 bitmap's memory. Use to diversify input
// in tests that cover function that will only look at the most significant
// three bits of each component. |i| is used to get different noise that changes
// in sequence. Any number can be provided. Noise applied can be identical for
// two different values of |i|.
SkPMColor AddNoiseToLowerBits(SkColor color, unsigned int i) {
// Get a mask between 00000 and 11111 from index.
unsigned int mask = i % 0x1f;
// Apply noise to each color component separately.
color = SkColorSetARGB(SkColorGetA(color) | mask, SkColorGetR(color) | mask,
SkColorGetG(color) | mask, SkColorGetB(color) | mask);
return color;
}
} // namespace
using ::testing::FloatEq;
class VisualUtilsTest : public testing::Test {
protected:
void SetUp() override {
base::DiscardableMemoryAllocator::SetInstance(&test_allocator_);
sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(
{2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
SkNamedGamut::kRec2020);
SkImageInfo bitmap_info = SkImageInfo::MakeN32(
1000, 1000, SkAlphaType::kUnpremul_SkAlphaType, rec2020);
ASSERT_TRUE(bitmap_.tryAllocPixels(bitmap_info));
}
void TearDown() override {
base::DiscardableMemoryAllocator::SetInstance(nullptr);
}
std::string GetBlurredBitmapHash() {
VisualFeatures::BlurredImage blurred_image;
EXPECT_TRUE(visual_utils::GetBlurredImage(bitmap_, &blurred_image));
std::string blurred_image_hash =
visual_utils::GetHashFromBlurredImage(blurred_image);
return blurred_image_hash;
}
VisualFeatures::ColorHistogram GetBitmapHistogram() {
VisualFeatures::ColorHistogram histogram;
EXPECT_TRUE(GetHistogramForImage(bitmap_, &histogram));
return histogram;
}
// A test bitmap to work with. Initialized to be 1000x1000 in the Rec 2020
// color space.
SkBitmap bitmap_;
private:
// A DiscardableMemoryAllocator is needed for certain Skia operations.
base::TestDiscardableMemoryAllocator test_allocator_;
};
TEST_F(VisualUtilsTest, TestkColorToQuantizedColor) {
// Test quantization
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 31)), 0u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 32)), 1u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 31, 0)), 0u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 32, 0)), 8u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(31, 0, 0)), 0u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(32, 0, 0)), 64u);
// Test composition of RGB quantized values
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 0)), 0u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 255)), 7u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 255, 255)), 63u);
EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(255, 255, 255)), 511u);
}
TEST_F(VisualUtilsTest, GetQuantizedR) {
EXPECT_EQ(GetQuantizedR(0), 0);
EXPECT_EQ(GetQuantizedR(64), 1);
EXPECT_EQ(GetQuantizedR(448), 7);
}
TEST_F(VisualUtilsTest, GetQuantizedG) {
EXPECT_EQ(GetQuantizedG(0), 0);
EXPECT_EQ(GetQuantizedG(8), 1);
EXPECT_EQ(GetQuantizedG(56), 7);
}
TEST_F(VisualUtilsTest, GetQuantizedB) {
EXPECT_EQ(GetQuantizedB(0), 0);
EXPECT_EQ(GetQuantizedB(1), 1);
EXPECT_EQ(GetQuantizedB(7), 7);
}
TEST_F(VisualUtilsTest, GetHistogramForImageWhite) {
VisualFeatures::ColorHistogram histogram;
// Draw white over half the image
for (int x = 0; x < 1000; x++)
for (int y = 0; y < 1000; y++)
// NOTE: getAddr32 used since byte ordering does not matter for white and
// repeated erase() calls might make tests time out.
*bitmap_.getAddr32(x, y) = AddNoiseToLowerBits(SK_ColorWHITE, x + y);
ASSERT_TRUE(GetHistogramForImage(bitmap_, &histogram));
ASSERT_EQ(histogram.bins_size(), 1);
EXPECT_THAT(histogram.bins(0).centroid_x(),
FloatEq(0.4995)); // All pixels are the same color, so centroid_x
// is (0+1+...+999)/1000/1000 = 0.4995
EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.4995));
EXPECT_EQ(histogram.bins(0).quantized_r(), 7);
EXPECT_EQ(histogram.bins(0).quantized_g(), 7);
EXPECT_EQ(histogram.bins(0).quantized_b(), 7);
EXPECT_THAT(histogram.bins(0).weight(), FloatEq(1.0));
}
TEST_F(VisualUtilsTest, GetHistogramForImageHalfWhiteHalfBlack) {
VisualFeatures::ColorHistogram histogram;
// Draw white over half the image
for (int x = 0; x < 1000; x++)
for (int y = 0; y < 500; y++)
// NOTE: getAddr32 used since byte ordering does not matter for white and
// repeated erase() calls might make tests time out.
*bitmap_.getAddr32(x, y) = AddNoiseToLowerBits(SK_ColorWHITE, x + y);
// Draw black over half the image.
for (int x = 0; x < 1000; x++)
for (int y = 500; y < 1000; y++)
// NOTE: getAddr32 used since byte ordering does not matter for black and
// repeated erase() calls might make tests time out.
*bitmap_.getAddr32(x, y) = AddNoiseToLowerBits(SK_ColorBLACK, x + y);
ASSERT_TRUE(GetHistogramForImage(bitmap_, &histogram));
ASSERT_EQ(histogram.bins_size(), 2);
EXPECT_THAT(histogram.bins(0).centroid_x(), FloatEq(0.4995));
EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.7495));
EXPECT_EQ(histogram.bins(0).quantized_r(), 0);
EXPECT_EQ(histogram.bins(0).quantized_g(), 0);
EXPECT_EQ(histogram.bins(0).quantized_b(), 0);
EXPECT_THAT(histogram.bins(0).weight(), FloatEq(0.5));
EXPECT_THAT(histogram.bins(1).centroid_x(), FloatEq(0.4995));
EXPECT_THAT(histogram.bins(1).centroid_y(), FloatEq(0.2495));
EXPECT_EQ(histogram.bins(1).quantized_r(), 7);
EXPECT_EQ(histogram.bins(1).quantized_g(), 7);
EXPECT_EQ(histogram.bins(1).quantized_b(), 7);
EXPECT_THAT(histogram.bins(1).weight(), FloatEq(0.5));
}
TEST_F(VisualUtilsTest, BlurImageWhite) {
VisualFeatures::BlurredImage blurred;
// Draw white over the image
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1000, 1000));
ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
ASSERT_EQ(48, blurred.width());
ASSERT_EQ(48, blurred.height());
ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
for (size_t i = 0; i < 48u * 48u; i++) {
EXPECT_EQ('\xff', blurred.data()[3 * i]);
EXPECT_EQ('\xff', blurred.data()[3 * i + 1]);
EXPECT_EQ('\xff', blurred.data()[3 * i + 2]);
}
}
TEST_F(VisualUtilsTest, BlurImageRed) {
VisualFeatures::BlurredImage blurred;
// Draw red over the image.
for (int x = 0; x < 1000; x++)
for (int y = 0; y < 1000; y++)
*bitmap_.getAddr32(x, y) = kSkPMRed;
ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
ASSERT_EQ(48, blurred.width());
ASSERT_EQ(48, blurred.height());
ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
for (size_t i = 0; i < 48u * 48u; i++) {
EXPECT_EQ('\xff', blurred.data()[3 * i]);
EXPECT_EQ('\x00', blurred.data()[3 * i + 1]);
EXPECT_EQ('\x00', blurred.data()[3 * i + 2]);
}
}
TEST_F(VisualUtilsTest, BlurImageHalfWhiteHalfBlack) {
VisualFeatures::BlurredImage blurred;
// Draw black over half the image.
bitmap_.erase(SK_ColorBLACK, SkIRect::MakeXYWH(0, 0, 1000, 500));
// Draw white over half the image
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 500, 1000, 1000));
ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
ASSERT_EQ(48, blurred.width());
ASSERT_EQ(48, blurred.height());
ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
// The middle blocks may have been blurred to something between white and
// black, so only verify the first 22 and last 22 rows.
for (size_t i = 0; i < 22u * 48u; i++) {
EXPECT_EQ('\x00', blurred.data()[3 * i]);
EXPECT_EQ('\x00', blurred.data()[3 * i + 1]);
EXPECT_EQ('\x00', blurred.data()[3 * i + 2]);
}
for (size_t i = 26u * 48u; i < 48u * 48u; i++) {
EXPECT_EQ('\xff', blurred.data()[3 * i]);
EXPECT_EQ('\xff', blurred.data()[3 * i + 1]);
EXPECT_EQ('\xff', blurred.data()[3 * i + 2]);
}
}
TEST_F(VisualUtilsTest, BlockMeanAverageOneBlock) {
// Draw black over half the image.
bitmap_.erase(SK_ColorBLACK, SkIRect::MakeXYWH(0, 0, 1000, 500));
// Draw white over half the image
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 500, 1000, 1000));
std::unique_ptr<SkBitmap> blocks = BlockMeanAverage(bitmap_, 1000);
ASSERT_EQ(1, blocks->width());
ASSERT_EQ(1, blocks->height());
EXPECT_EQ(blocks->getColor(0, 0), SkColorSetRGB(127, 127, 127));
}
TEST_F(VisualUtilsTest, BlockMeanAveragePartialBlocks) {
// Draw a white, red, green, and blue box with the expected block sizes.
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 600, 600));
for (int x = 600; x < 1000; x++)
for (int y = 0; y < 600; y++)
*bitmap_.getAddr32(x, y) = kSkPMRed;
for (int x = 0; x < 600; x++)
for (int y = 600; y < 1000; y++)
*bitmap_.getAddr32(x, y) = kSkPMGreen;
for (int x = 600; x < 1000; x++)
for (int y = 600; y < 1000; y++)
*bitmap_.getAddr32(x, y) = kSkPMBlue;
std::unique_ptr<SkBitmap> blocks = BlockMeanAverage(bitmap_, 600);
ASSERT_EQ(2, blocks->width());
ASSERT_EQ(2, blocks->height());
EXPECT_EQ(blocks->getColor(0, 0), SK_ColorWHITE);
EXPECT_EQ(*blocks->getAddr32(1, 0), kSkPMRed);
EXPECT_EQ(*blocks->getAddr32(0, 1), kSkPMGreen);
EXPECT_EQ(*blocks->getAddr32(1, 1), kSkPMBlue);
}
TEST_F(VisualUtilsTest, IsVisualMatchHash) {
{
// An all-white image should hash to all 1-bits.
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1000, 1000));
std::vector<unsigned char> target_hash;
target_hash.push_back('\x30');
for (int i = 0; i < 288; i++)
target_hash.push_back('\xff');
VisualTarget target;
target.set_hash(target_hash.data(), target_hash.size());
target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
{
// Make the top quarter black, and the corresponding bits of the hash should
// be 0.
bitmap_.erase(SK_ColorBLACK, SkIRect::MakeXYWH(0, 0, 1000, 250));
std::vector<unsigned char> target_hash;
target_hash.push_back('\x30');
for (int i = 0; i < 72; i++)
target_hash.push_back('\x00');
for (int i = 0; i < 216; i++)
target_hash.push_back('\xff');
VisualTarget target;
target.set_hash(target_hash.data(), target_hash.size());
target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
}
TEST_F(VisualUtilsTest, IsVisualMatchHashPartialMatch) {
// Make the top quarter black, and the corresponding bits of the hash should
// be 0.
bitmap_.erase(SK_ColorBLACK, SkIRect::MakeXYWH(0, 0, 1000, 250));
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 250, 1000, 1000));
std::vector<unsigned char> target_hash;
target_hash.push_back('\x30');
for (int i = 0; i < 75; i++)
target_hash.push_back('\x00');
for (int i = 0; i < 213; i++)
target_hash.push_back('\xff');
VisualTarget target;
target.set_hash(target_hash.data(), target_hash.size());
target.mutable_match_config()->add_match_rule()->set_hash_distance(23.0);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
target.mutable_match_config()->add_match_rule()->set_hash_distance(24.0);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchHashStrideComparison) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1000, 1000));
std::vector<unsigned char> target_hash;
target_hash.push_back('\x30');
for (int i = 0; i < 288; i++)
target_hash.push_back('\xff');
VisualTarget target;
target.set_hash(target_hash.data(), target_hash.size());
target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
target_hash[0] = '\x00';
target.set_hash(target_hash.data(), target_hash.size());
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchHistogramOnly) {
for (int x = 0; x < 1000; x++)
for (int y = 0; y < 1000; y++)
// NOTE: getAddr32 used since byte ordering does not matter for white and
// repeated erase calls might make tests time out.
*bitmap_.getAddr32(x, y) = AddNoiseToLowerBits(SK_ColorWHITE, x + y);
{
VisualTarget target;
VisualFeatures::ColorHistogramBin* bin = target.add_bins();
// Our coordinates range from 0 to 999, giving an average of 0.4995 instead
// of 0.5.
bin->set_centroid_x(0.4995);
bin->set_centroid_y(0.4995);
bin->set_quantized_r(7);
bin->set_quantized_g(7);
bin->set_quantized_b(7);
bin->set_weight(1.0);
target.mutable_match_config()->add_match_rule()->set_color_distance(0.0);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
{
// Move the target histogram to have centroid 0,0. This leads to a distance
// just under 0.5 between the actual and target histograms.
VisualTarget target;
VisualFeatures::ColorHistogramBin* bin = target.add_bins();
bin->set_centroid_x(0);
bin->set_centroid_y(0);
bin->set_quantized_r(7);
bin->set_quantized_g(7);
bin->set_quantized_b(7);
bin->set_weight(1.0);
MatchRule* match_rule = target.mutable_match_config()->add_match_rule();
match_rule->set_color_distance(0.5);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
match_rule->set_color_distance(0.4);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
{
// Change the target histogram color slightly. This leads to a distance
// of roughly 0.12 between the actual and target histogram.
VisualTarget target;
VisualFeatures::ColorHistogramBin* bin = target.add_bins();
bin->set_centroid_x(0.4995);
bin->set_centroid_y(0.4995);
bin->set_quantized_r(6);
bin->set_quantized_g(6);
bin->set_quantized_b(6);
bin->set_weight(1.0);
MatchRule* match_rule = target.mutable_match_config()->add_match_rule();
match_rule->set_color_distance(0.2);
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
match_rule->set_color_distance(0.1);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
}
TEST_F(VisualUtilsTest, IsVisualMatchColorRange) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1000, 1000));
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
SkScalar hsv[3];
SkColorToHSV(bitmap_.getColor(0, 0), hsv);
SkScalar target_hue = hsv[0];
VisualTarget target;
MatchRule::ColorRange* color_range =
target.mutable_match_config()->add_match_rule()->add_color_range();
color_range->set_low(target_hue);
color_range->set_high(target_hue);
// Blue hue present
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Color range too high
color_range->set_low(target_hue + 1);
color_range->set_high(target_hue + 1);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Color range too low
color_range->set_low(target_hue - 1);
color_range->set_high(target_hue - 1);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No blue hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchMultipleColorRanges) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1000, 1000));
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
*bitmap_.getAddr32(1, 0) = kSkPMGreen;
SkScalar hsv[3];
SkColorToHSV(bitmap_.getColor(0, 0), hsv);
SkScalar blue_hue = hsv[0];
VisualTarget target;
MatchRule* match_rule = target.mutable_match_config()->add_match_rule();
MatchRule::ColorRange* color_range = match_rule->add_color_range();
color_range->set_low(blue_hue);
color_range->set_high(blue_hue);
SkColorToHSV(bitmap_.getColor(1, 0), hsv);
SkScalar green_hue = hsv[0];
color_range = match_rule->add_color_range();
color_range->set_low(green_hue);
color_range->set_high(green_hue);
// Both hues present
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No blue hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No green hue present
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(1, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Neither hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchMultipleMatchRules) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
*bitmap_.getAddr32(1, 0) = kSkPMGreen;
// Create a target with two match rules, one matching blue pixels and one
// matching green.
VisualTarget target;
MatchRule* match_rule_blue = target.mutable_match_config()->add_match_rule();
MatchRule::ColorRange* color_range = match_rule_blue->add_color_range();
SkScalar hsv[3];
SkColorToHSV(bitmap_.getColor(0, 0), hsv);
SkScalar blue_hue = hsv[0];
color_range->set_low(blue_hue);
color_range->set_high(blue_hue);
MatchRule* match_rule_green = target.mutable_match_config()->add_match_rule();
color_range = match_rule_green->add_color_range();
SkColorToHSV(bitmap_.getColor(1, 0), hsv);
SkScalar green_hue = hsv[0];
color_range->set_low(green_hue);
color_range->set_high(green_hue);
// Both hues present
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No blue hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No green hue present
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(1, 0, 1, 1));
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Neither hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchFloatColorRange) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
// Adding a little noise in the red component makes the target_hue not an
// integer. Testing for this color requires float ranges.
*bitmap_.getAddr32(0, 0) = kSkPMBlue | 0x0f;
SkScalar hsv[3];
SkColorToHSV(bitmap_.getColor(0, 0), hsv);
SkScalar target_hue = hsv[0];
VisualTarget target;
MatchRule::FloatColorRange* color_range =
target.mutable_match_config()->add_match_rule()->add_float_color_range();
color_range->set_low(target_hue);
color_range->set_high(target_hue);
// Blue hue present
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Color range too high
color_range->set_low(target_hue + 0.1);
color_range->set_high(target_hue + 0.1);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Color range too low
color_range->set_low(target_hue - 0.1);
color_range->set_high(target_hue - 0.1);
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No blue hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
TEST_F(VisualUtilsTest, IsVisualMatchMultipleFloatColorRanges) {
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
*bitmap_.getAddr32(1, 0) = kSkPMGreen;
SkScalar hsv[3];
SkColorToHSV(bitmap_.getColor(0, 0), hsv);
SkScalar blue_hue = hsv[0];
VisualTarget target;
MatchRule* match_rule = target.mutable_match_config()->add_match_rule();
MatchRule::FloatColorRange* color_range = match_rule->add_float_color_range();
color_range->set_low(blue_hue);
color_range->set_high(blue_hue);
SkColorToHSV(bitmap_.getColor(1, 0), hsv);
SkScalar green_hue = hsv[0];
color_range = match_rule->add_float_color_range();
color_range->set_low(green_hue);
color_range->set_high(green_hue);
// Both hues present
EXPECT_TRUE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No blue hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// No green hue present
*bitmap_.getAddr32(0, 0) = kSkPMBlue;
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(1, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
// Neither hue present
bitmap_.erase(SK_ColorWHITE, SkIRect::MakeXYWH(0, 0, 1, 1));
EXPECT_FALSE(IsVisualMatch(bitmap_, GetBlurredBitmapHash(),
GetBitmapHistogram(), target)
.has_value());
}
} // namespace visual_utils
} // namespace safe_browsing