1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/image_util.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "content/public/common/color_parser.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 "ui/gfx/codec/png_codec.h"
namespace extensions::image_util {
bool IsIconSufficientlyVisible(const SkBitmap& bitmap) {
// TODO(crbug.com/40559794): Currently, we only consider if there are enough
// visible pixels that it won't be difficult for the user to see. Future
// revisions will consider the background color of the display context.
// If the alpha value of any pixel is greater than kAlphaThreshold, the
// pixmap is not transparent. These values will likely be adjusted, based
// on stats and research into visibility thresholds.
constexpr unsigned int kAlphaThreshold = 10;
// The minimum "percent" of pixels that must be visible for the icon to be
// considered OK.
constexpr double kMinPercentVisiblePixels = 0.03;
const int total_pixels = bitmap.height() * bitmap.width();
// Pre-calculate the minimum number of visible pixels so we can exit early.
// Since we expect most icons to be visible, this will perform better for
// the common case.
const int minimum_visible_pixels =
std::max(kMinPercentVisiblePixels * total_pixels, 1.0);
int visible_pixels = 0;
for (int y = 0; y < bitmap.height(); ++y) {
for (int x = 0; x < bitmap.width(); ++x) {
if (SkColorGetA(bitmap.getColor(x, y)) >= kAlphaThreshold) {
if (++visible_pixels == minimum_visible_pixels) {
return true;
}
}
}
}
return false;
}
bool IsIconAtPathSufficientlyVisible(const base::FilePath& path) {
SkBitmap icon = LoadPngFromFile(path);
if (icon.isNull()) {
return false;
}
return IsIconSufficientlyVisible(icon);
}
const SkColor kDefaultToolbarColor = SK_ColorWHITE;
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 = 7;
// The minimum "percent" of pixels that must be visible for the icon to be
// considered OK.
constexpr double kMinPercentVisiblePixels = 0.03;
const int total_pixels = icon.height() * icon.width();
// Pre-calculate the minimum number of visible pixels so we can exit early.
// Since we expect most icons to be visible, this will perform better for
// the common case.
const int minimum_visible_pixels =
std::max(kMinPercentVisiblePixels * total_pixels, 1.0);
// 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. If analysis fails, don't render the icon.
SkBitmap bitmap;
if (!RenderIconForVisibilityAnalysis(icon, background_color, &bitmap)) {
return false;
}
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) {
if (++visible_pixels == minimum_visible_pixels) {
return true;
}
}
}
}
return false;
}
bool RenderIconForVisibilityAnalysis(const SkBitmap& icon,
SkColor background_color,
SkBitmap* rendered_icon) {
DCHECK(rendered_icon);
DCHECK(rendered_icon->empty());
if (icon.width() * icon.height() > kMaxAllowedPixels) {
return false;
}
if (!rendered_icon->tryAllocN32Pixels(icon.width(), icon.height())) {
LOG(ERROR) << "Unable to allocate pixels for a " << icon.width() << "x"
<< icon.height() << "icon.";
return false;
}
rendered_icon->eraseColor(background_color);
SkCanvas offscreen(*rendered_icon, SkSurfaceProps{});
offscreen.drawImage(SkImages::RasterFromBitmap(icon), 0, 0);
offscreen.drawColor(background_color, SkBlendMode::kDifference);
return true;
}
bool IsRenderedIconAtPathSufficientlyVisible(const base::FilePath& path,
SkColor background_color) {
SkBitmap icon = LoadPngFromFile(path);
if (icon.isNull()) {
return false;
}
return IsRenderedIconSufficientlyVisible(icon, background_color);
}
SkBitmap LoadPngFromFile(const base::FilePath& path) {
std::optional<std::vector<uint8_t>> png_bytes = base::ReadFileToBytes(path);
if (!png_bytes) {
return SkBitmap();
}
return gfx::PNGCodec::Decode(png_bytes.value());
}
} // namespace extensions::image_util
|