diff --git a/extensions/common/image_util.cc b/extensions/common/image_util.cc index 4c441622bc917b..9cd7bd69528336 100644 --- a/extensions/common/image_util.cc +++ b/extensions/common/image_util.cc @@ -15,7 +15,9 @@ #include "base/strings/stringprintf.h" #include "third_party/re2/src/re2/re2.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/utils/SkParse.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/color_utils.h" @@ -194,9 +196,51 @@ bool IsIconAtPathSufficientlyVisible(const base::FilePath& path) { SkBitmap icon; if (!LoadPngFromFile(path, &icon)) { return false; - } else { - return image_util::IsIconSufficientlyVisible(icon); } + return IsIconSufficientlyVisible(icon); +} + +bool IsRenderedIconSufficientlyVisible(const SkBitmap& icon, + SkColor background_color) { + // If any of a pixel's RGB values is greater than this number, the pixel is + // considered visible. + constexpr unsigned int kThreshold = 15; + // The minimum "percent" of pixels that must be visible for the icon to be + // considered OK. + constexpr double kMinPercentVisiblePixels = 0.05; + const int total_pixels = icon.height() * icon.width(); + + // Draw the icon onto a canvas, then draw the background color onto the + // resulting bitmap, using SkBlendMode::kDifference. Then, check the RGB + // values against the threshold. Any pixel with a value greater than the + // threshold is considered visible. + SkBitmap bitmap; + bitmap.allocN32Pixels(icon.width(), icon.height()); + bitmap.eraseColor(background_color); + SkCanvas offscreen(bitmap); + offscreen.drawImage(SkImage::MakeFromBitmap(icon), 0, 0); + offscreen.drawColor(background_color, SkBlendMode::kDifference); + int visible_pixels = 0; + for (int x = 0; x < icon.width(); ++x) { + for (int y = 0; y < icon.height(); ++y) { + SkColor pixel = bitmap.getColor(x, y); + if (SkColorGetR(pixel) > kThreshold || SkColorGetB(pixel) > kThreshold || + SkColorGetG(pixel) > kThreshold) { + ++visible_pixels; + } + } + } + return static_cast(visible_pixels) / total_pixels >= + kMinPercentVisiblePixels; +} + +bool IsRenderedIconAtPathSufficientlyVisible(const base::FilePath& path, + SkColor background_color) { + SkBitmap icon; + if (!LoadPngFromFile(path, &icon)) { + return false; + } + return IsRenderedIconSufficientlyVisible(icon, background_color); } bool LoadPngFromFile(const base::FilePath& path, SkBitmap* dst) { diff --git a/extensions/common/image_util.h b/extensions/common/image_util.h index 727977e1da1933..b90a3e18e9be1d 100644 --- a/extensions/common/image_util.h +++ b/extensions/common/image_util.h @@ -44,6 +44,17 @@ bool IsIconSufficientlyVisible(const SkBitmap& bitmap); // context. bool IsIconAtPathSufficientlyVisible(const base::FilePath& path); +// Renders the icon bitmap onto another bitmap, combining it with the specified +// background color, then determines whether the rendered icon is sufficiently +// visible against the background. +bool IsRenderedIconSufficientlyVisible(const SkBitmap& bitmap, + SkColor background_color); + +// Returns whether an icon image is considered to be visible in its display +// context, according to the previous function. +bool IsRenderedIconAtPathSufficientlyVisible(const base::FilePath& path, + SkColor background_color); + // Load a PNG image from a file into the destination bitmap. bool LoadPngFromFile(const base::FilePath& path, SkBitmap* dst); diff --git a/extensions/common/image_util_unittest.cc b/extensions/common/image_util_unittest.cc index 1acd3755e18479..c2bc598b1d8905 100644 --- a/extensions/common/image_util_unittest.cc +++ b/extensions/common/image_util_unittest.cc @@ -190,6 +190,8 @@ TEST(ImageUtilTest, IsIconSufficientlyVisible) { SkBitmap transparent_icon; ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &transparent_icon)); EXPECT_FALSE(image_util::IsIconSufficientlyVisible(transparent_icon)); + EXPECT_FALSE(image_util::IsRenderedIconSufficientlyVisible(transparent_icon, + SK_ColorWHITE)); } { // Test with an icon that has one opaque pixel. @@ -197,6 +199,8 @@ TEST(ImageUtilTest, IsIconSufficientlyVisible) { SkBitmap visible_icon; ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &visible_icon)); EXPECT_FALSE(image_util::IsIconSufficientlyVisible(visible_icon)); + EXPECT_FALSE(image_util::IsRenderedIconSufficientlyVisible(visible_icon, + SK_ColorWHITE)); } { // Test with an icon that has one transparent pixel. @@ -204,6 +208,8 @@ TEST(ImageUtilTest, IsIconSufficientlyVisible) { SkBitmap visible_icon; ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &visible_icon)); EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon)); + EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon, + SK_ColorWHITE)); } { // Test with an icon that is completely opaque. @@ -211,6 +217,8 @@ TEST(ImageUtilTest, IsIconSufficientlyVisible) { SkBitmap visible_icon; ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &visible_icon)); EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon)); + EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon, + SK_ColorWHITE)); } { // Test with an icon that is rectangular. @@ -218,6 +226,30 @@ TEST(ImageUtilTest, IsIconSufficientlyVisible) { SkBitmap visible_icon; ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &visible_icon)); EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon)); + EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon, + SK_ColorWHITE)); + } + { + // Test with a solid color icon that is completely opaque. Use the icon's + // color as the background color in the call to analyze its visibility. + // It should be invisible in this case. + icon_path = test_dir.AppendASCII("grey_21x21.png"); + SkBitmap solid_icon; + ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &solid_icon)); + const SkColor pixel_color = solid_icon.getColor(0, 0); + EXPECT_FALSE( + image_util::IsRenderedIconSufficientlyVisible(solid_icon, pixel_color)); + } + { + // Test with a two-color icon that is completely opaque. Use one of the + // icon's colors as the background color in the call to analyze its + // visibility. It should be visible in this case. + icon_path = test_dir.AppendASCII("two_color_21x21.png"); + SkBitmap two_color_icon; + ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &two_color_icon)); + const SkColor pixel_color = two_color_icon.getColor(0, 0); + EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(two_color_icon, + pixel_color)); } } diff --git a/extensions/test/data/grey_21x21.png b/extensions/test/data/grey_21x21.png new file mode 100644 index 00000000000000..a52ad3e2922ef9 Binary files /dev/null and b/extensions/test/data/grey_21x21.png differ diff --git a/extensions/test/data/two_color_21x21.png b/extensions/test/data/two_color_21x21.png new file mode 100644 index 00000000000000..ddb94aa5b3d94f Binary files /dev/null and b/extensions/test/data/two_color_21x21.png differ