Commit 623b7070 authored by Alexey Baskakov's avatar Alexey Baskakov Committed by Commit Bot

WebApp: Extract BookmarkAppHelper's gfx stuff into a separate file.

Extract all the low-level skia/gfx/color_util stuff as
ResizeIconsAndGenerateMissing function.

This is a cut-and-paste CL, no behavior changes.

Notes:
- WebApplicationInfo::IconInfo becomes IconInfo. TODO for next CL:
Merge IconInfo and BitmapAndSource - they are essentially the same.

- BookmarkAppHelperExtensionServiceTest.LinkedAppIconsAreNotChanged test
was always broken (a bug in ValidateAllIconsWithURLsArePresent helper)
https://codereview.chromium.org/1066623008/patch/80001/90005

This is a re-land of:
https://chromium-review.googlesource.com/c/chromium/src/+/1166751
MSan issue solved, ouput parameters initialized everywhere:
SkColor generated_icon_color = SK_ColorTRANSPARENT;

TBR=ortuno@chromium.org

Bug: 860581
Change-Id: Ib26c765083e67e3a1c938a49994a5f5d5ecc8265
Reviewed-on: https://chromium-review.googlesource.com/1170665
Commit-Queue: Alexey Baskakov <loyso@chromium.org>
Reviewed-by: default avatarGiovanni Ortuño Urquidi <ortuno@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582491}
parent f708c466
......@@ -6,7 +6,6 @@
#include <stddef.h>
#include <cctype>
#include <string>
#include <utility>
......@@ -46,7 +45,6 @@
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/origin_trials/chrome_origin_trial_policy.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/platform_locale_settings.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/notification_service.h"
......@@ -61,21 +59,9 @@
#include "extensions/common/extension.h"
#include "extensions/common/url_pattern.h"
#include "net/base/load_flags.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/url_request/url_request.h"
#include "skia/ext/image_operations.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/blink/public/common/manifest/web_display_mode.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
#if defined(OS_MACOSX)
#include "chrome/browser/web_applications/extensions/web_app_extension_shortcut_mac.h"
......@@ -92,62 +78,6 @@ namespace extensions {
namespace {
// Overlays a shortcut icon over the bottom left corner of a given image.
class GeneratedIconImageSource : public gfx::CanvasImageSource {
public:
explicit GeneratedIconImageSource(char letter, SkColor color, int output_size)
: gfx::CanvasImageSource(gfx::Size(output_size, output_size), false),
letter_(letter),
color_(color),
output_size_(output_size) {}
~GeneratedIconImageSource() override {}
private:
// gfx::CanvasImageSource overrides:
void Draw(gfx::Canvas* canvas) override {
const uint8_t kLumaThreshold = 190;
const int icon_size = output_size_ * 3 / 4;
const int icon_inset = output_size_ / 8;
const size_t border_radius = output_size_ / 16;
const size_t font_size = output_size_ * 7 / 16;
std::string font_name =
l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY);
#if defined(OS_CHROMEOS)
const std::string kChromeOSFontFamily = "Noto Sans";
font_name = kChromeOSFontFamily;
#endif
// Draw a rounded rect of the given |color|.
cc::PaintFlags background_flags;
background_flags.setAntiAlias(true);
background_flags.setColor(color_);
gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size);
canvas->DrawRoundRect(icon_rect, border_radius, background_flags);
// The text rect's size needs to be odd to center the text correctly.
gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1);
// Draw the letter onto the rounded rect. The letter's color depends on the
// luma of |color|.
const uint8_t luma = color_utils::GetLuma(color_);
canvas->DrawStringRectWithFlags(
base::string16(1, std::toupper(letter_)),
gfx::FontList(gfx::Font(font_name, font_size)),
(luma > kLumaThreshold) ? SK_ColorBLACK : SK_ColorWHITE,
text_rect,
gfx::Canvas::TEXT_ALIGN_CENTER);
}
char letter_;
SkColor color_;
int output_size_;
DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource);
};
std::set<int> SizesToGenerate() {
// Generate container icons from smaller icons.
const int kIconSizesToGenerate[] = {
......@@ -162,49 +92,8 @@ std::set<int> SizesToGenerate() {
kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
}
void GenerateIcons(
std::set<int> generate_sizes,
const GURL& app_url,
SkColor generated_icon_color,
std::map<int, BookmarkAppHelper::BitmapAndSource>* bitmap_map) {
// The letter that will be painted on the generated icon.
char icon_letter = ' ';
std::string domain_and_registry(
net::registry_controlled_domains::GetDomainAndRegistry(
app_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
if (!domain_and_registry.empty()) {
icon_letter = domain_and_registry[0];
} else if (app_url.has_host()) {
icon_letter = app_url.host_piece()[0];
}
// If no color has been specified, use a dark gray so it will stand out on the
// black shelf.
if (generated_icon_color == SK_ColorTRANSPARENT)
generated_icon_color = SK_ColorDKGRAY;
for (int size : generate_sizes) {
BookmarkAppHelper::GenerateIcon(bitmap_map, size, generated_icon_color,
icon_letter);
}
}
SkBitmap GenerateBitmap(int output_size, SkColor color, char letter) {
gfx::ImageSkia icon_image(
std::make_unique<GeneratedIconImageSource>(letter, color, output_size),
gfx::Size(output_size, output_size));
SkBitmap dst;
if (dst.tryAllocPixels(icon_image.bitmap()->info())) {
icon_image.bitmap()->readPixels(dst.info(), dst.getPixels(), dst.rowBytes(),
0, 0);
}
return dst;
}
void ReplaceWebAppIcons(
std::map<int, BookmarkAppHelper::BitmapAndSource> bitmap_map,
WebApplicationInfo* web_app_info) {
void ReplaceWebAppIcons(std::map<int, web_app::BitmapAndSource> bitmap_map,
WebApplicationInfo* web_app_info) {
web_app_info->icons.clear();
// Populate the icon data into the WebApplicationInfo we are using to
......@@ -297,7 +186,7 @@ class BookmarkAppInstaller : public base::RefCounted<BookmarkAppInstaller>,
continue;
downloaded_bitmaps_.push_back(
BookmarkAppHelper::BitmapAndSource(url_bitmaps.first, bitmap));
web_app::BitmapAndSource(url_bitmaps.first, bitmap));
}
}
}
......@@ -319,7 +208,7 @@ class BookmarkAppInstaller : public base::RefCounted<BookmarkAppInstaller>,
for (const auto& icon : web_app_info_.icons)
sizes_to_generate.insert(icon.width);
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
std::map<int, web_app::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded_bitmaps_, sizes_to_generate, &web_app_info_);
BookmarkAppHelper::UpdateWebAppIconsWithoutChangingLinks(size_map,
......@@ -335,7 +224,7 @@ class BookmarkAppInstaller : public base::RefCounted<BookmarkAppInstaller>,
std::unique_ptr<content::WebContents> web_contents_;
std::unique_ptr<WebAppIconDownloader> web_app_icon_downloader_;
std::vector<GURL> urls_to_download_;
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded_bitmaps_;
std::vector<web_app::BitmapAndSource> downloaded_bitmaps_;
};
} // namespace
......@@ -382,54 +271,6 @@ void BookmarkAppHelper::UpdateWebAppInfoFromManifest(
}
}
// static
std::map<int, BookmarkAppHelper::BitmapAndSource>
BookmarkAppHelper::ConstrainBitmapsToSizes(
const std::vector<BookmarkAppHelper::BitmapAndSource>& bitmaps,
const std::set<int>& sizes) {
std::map<int, BitmapAndSource> output_bitmaps;
std::map<int, BitmapAndSource> ordered_bitmaps;
for (const BitmapAndSource& bitmap_and_source : bitmaps) {
const SkBitmap& bitmap = bitmap_and_source.bitmap;
DCHECK(bitmap.width() == bitmap.height());
ordered_bitmaps[bitmap.width()] = bitmap_and_source;
}
if (ordered_bitmaps.size() > 0) {
for (const auto& size : sizes) {
// Find the closest not-smaller bitmap, or failing that use the largest
// icon available.
auto bitmaps_it = ordered_bitmaps.lower_bound(size);
if (bitmaps_it != ordered_bitmaps.end())
output_bitmaps[size] = bitmaps_it->second;
else
output_bitmaps[size] = ordered_bitmaps.rbegin()->second;
// Resize the bitmap if it does not exactly match the desired size.
if (output_bitmaps[size].bitmap.width() != size) {
output_bitmaps[size].bitmap = skia::ImageOperations::Resize(
output_bitmaps[size].bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
size, size);
}
}
}
return output_bitmaps;
}
// static
void BookmarkAppHelper::GenerateIcon(
std::map<int, BookmarkAppHelper::BitmapAndSource>* bitmaps,
int output_size,
SkColor color,
char letter) {
// Do nothing if there is already an icon of |output_size|.
if (bitmaps->count(output_size))
return;
(*bitmaps)[output_size].bitmap = GenerateBitmap(output_size, color, letter);
}
// static
WebApplicationInfo::IconInfo BookmarkAppHelper::GenerateIconInfo(
int output_size,
......@@ -438,7 +279,7 @@ WebApplicationInfo::IconInfo BookmarkAppHelper::GenerateIconInfo(
WebApplicationInfo::IconInfo icon_info;
icon_info.width = output_size;
icon_info.height = output_size;
icon_info.data = GenerateBitmap(output_size, color, letter);
icon_info.data = web_app::GenerateBitmap(output_size, color, letter);
return icon_info;
}
......@@ -464,47 +305,25 @@ bool BookmarkAppHelper::BookmarkOrHostedAppInstalled(
}
// static
std::map<int, BookmarkAppHelper::BitmapAndSource>
std::map<int, web_app::BitmapAndSource>
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
std::vector<BookmarkAppHelper::BitmapAndSource> icons,
std::vector<web_app::BitmapAndSource> icons,
std::set<int> sizes_to_generate,
WebApplicationInfo* web_app_info) {
// Resize provided icons to make sure we have versions for each size in
// |sizes_to_generate|.
std::map<int, BitmapAndSource> resized_bitmaps(
ConstrainBitmapsToSizes(icons, sizes_to_generate));
// Also add all provided icon sizes.
for (const BitmapAndSource& icon : icons) {
if (resized_bitmaps.find(icon.bitmap.width()) == resized_bitmaps.end())
resized_bitmaps.insert(std::make_pair(icon.bitmap.width(), icon));
}
// Determine the color that will be used for the icon's background. For this
// the dominant color of the first icon found is used.
if (resized_bitmaps.size()) {
color_utils::GridSampler sampler;
web_app_info->generated_icon_color =
color_utils::CalculateKMeanColorOfBitmap(
resized_bitmaps.begin()->second.bitmap);
}
SkColor generated_icon_color = SK_ColorTRANSPARENT;
// Work out what icons we need to generate here. Icons are only generated if
// there is no icon in the required size.
std::set<int> generate_sizes;
for (int size : sizes_to_generate) {
if (resized_bitmaps.find(size) == resized_bitmaps.end())
generate_sizes.insert(size);
}
GenerateIcons(generate_sizes, web_app_info->app_url,
web_app_info->generated_icon_color, &resized_bitmaps);
std::map<int, web_app::BitmapAndSource> resized_bitmaps =
web_app::ResizeIconsAndGenerateMissing(icons, sizes_to_generate,
web_app_info->app_url,
&generated_icon_color);
web_app_info->generated_icon_color = generated_icon_color;
return resized_bitmaps;
}
// static
void BookmarkAppHelper::UpdateWebAppIconsWithoutChangingLinks(
std::map<int, BookmarkAppHelper::BitmapAndSource> bitmap_map,
std::map<int, web_app::BitmapAndSource> bitmap_map,
WebApplicationInfo* web_app_info) {
// First add in the icon data that have urls with the url / size data from the
// original web app info, and the data from the new icons (if any).
......@@ -528,17 +347,6 @@ void BookmarkAppHelper::UpdateWebAppIconsWithoutChangingLinks(
}
}
BookmarkAppHelper::BitmapAndSource::BitmapAndSource() {
}
BookmarkAppHelper::BitmapAndSource::BitmapAndSource(const GURL& source_url_p,
const SkBitmap& bitmap_p)
: source_url(source_url_p),
bitmap(bitmap_p) {
}
BookmarkAppHelper::BitmapAndSource::~BitmapAndSource() {
}
BookmarkAppHelper::BookmarkAppHelper(Profile* profile,
WebApplicationInfo web_app_info,
......@@ -676,13 +484,14 @@ void BookmarkAppHelper::OnIconsDownloaded(
return;
}
std::vector<BitmapAndSource> downloaded_icons;
std::vector<web_app::BitmapAndSource> downloaded_icons;
for (const std::pair<GURL, std::vector<SkBitmap>>& url_bitmap : bitmaps) {
for (const SkBitmap& bitmap : url_bitmap.second) {
if (bitmap.empty() || bitmap.width() != bitmap.height())
continue;
downloaded_icons.push_back(BitmapAndSource(url_bitmap.first, bitmap));
downloaded_icons.push_back(
web_app::BitmapAndSource(url_bitmap.first, bitmap));
}
}
......@@ -690,7 +499,7 @@ void BookmarkAppHelper::OnIconsDownloaded(
for (const WebApplicationInfo::IconInfo& icon_info : web_app_info_.icons) {
const SkBitmap& icon = icon_info.data;
if (!icon.drawsNothing() && icon.width() == icon.height()) {
downloaded_icons.push_back(BitmapAndSource(icon_info.url, icon));
downloaded_icons.push_back(web_app::BitmapAndSource(icon_info.url, icon));
}
}
......@@ -698,8 +507,9 @@ void BookmarkAppHelper::OnIconsDownloaded(
// icons down to smaller sizes, and generating icons for sizes where resizing
// is not possible.
web_app_info_.generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, BitmapAndSource> size_to_icons = ResizeIconsAndGenerateMissing(
downloaded_icons, SizesToGenerate(), &web_app_info_);
std::map<int, web_app::BitmapAndSource> size_to_icons =
ResizeIconsAndGenerateMissing(downloaded_icons, SizesToGenerate(),
&web_app_info_);
ReplaceWebAppIcons(size_to_icons, &web_app_info_);
web_app_icon_downloader_.reset();
......
......@@ -15,6 +15,7 @@
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "chrome/browser/web_applications/components/web_app_icon_generator.h"
#include "chrome/common/web_application_info.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
......@@ -45,15 +46,6 @@ class BookmarkAppHelper : public content::NotificationObserver {
kUnknown,
};
struct BitmapAndSource {
BitmapAndSource();
BitmapAndSource(const GURL& source_url_p, const SkBitmap& bitmap_p);
~BitmapAndSource();
GURL source_url;
SkBitmap bitmap;
};
typedef base::Callback<void(const Extension*, const WebApplicationInfo&)>
CreateBookmarkAppCallback;
......@@ -75,24 +67,10 @@ class BookmarkAppHelper : public content::NotificationObserver {
WebApplicationInfo* web_app_info,
ForInstallableSite installable_site);
// This finds the closest not-smaller bitmap in |bitmaps| for each size in
// |sizes| and resizes it to that size. This returns a map of sizes to bitmaps
// which contains only bitmaps of a size in |sizes| and at most one bitmap of
// each size.
static std::map<int, BitmapAndSource> ConstrainBitmapsToSizes(
const std::vector<BitmapAndSource>& bitmaps,
const std::set<int>& sizes);
// Adds a square container icon of |output_size| and 2 * |output_size| pixels
// to |bitmaps| by drawing the given |letter| into a rounded background of
// |color|. For each size, if an icon of the requested size already exists in
// |bitmaps|, nothing will happen.
static void GenerateIcon(std::map<int, BitmapAndSource>* bitmaps,
int output_size,
SkColor color,
char letter);
// Same as above, but the generated icon is returned in a
// |bitmaps|, nothing will happen. The generated icon is returned in a
// `WebApplicationInfo::IconInfo`.
static WebApplicationInfo::IconInfo GenerateIconInfo(int output_size,
SkColor color,
......@@ -105,8 +83,8 @@ class BookmarkAppHelper : public content::NotificationObserver {
// Resize icons to the accepted sizes, and generate any that are missing. Does
// not update |web_app_info| except to update |generated_icon_color|.
static std::map<int, BitmapAndSource> ResizeIconsAndGenerateMissing(
std::vector<BitmapAndSource> icons,
static std::map<int, web_app::BitmapAndSource> ResizeIconsAndGenerateMissing(
std::vector<web_app::BitmapAndSource> icons,
std::set<int> sizes_to_generate,
WebApplicationInfo* web_app_info);
......@@ -118,7 +96,7 @@ class BookmarkAppHelper : public content::NotificationObserver {
// |bitmap_map| that has a URL and size matching that in |web_app_info|, as
// well as adding any new images from |bitmap_map| that have no URL.
static void UpdateWebAppIconsWithoutChangingLinks(
std::map<int, BookmarkAppHelper::BitmapAndSource> bitmap_map,
std::map<int, web_app::BitmapAndSource> bitmap_map,
WebApplicationInfo* web_app_info);
// Begins the asynchronous bookmark app creation.
......
......@@ -53,18 +53,11 @@ const char kAppIcon2[] = "fav2.png";
const char kAppIcon3[] = "fav3.png";
const char kAppIconURL1[] = "http://foo.com/1.png";
const char kAppIconURL2[] = "http://foo.com/2.png";
const char kAppIconURL3[] = "http://foo.com/3.png";
const char kAppIconURL4[] = "http://foo.com/4.png";
const int kIconSizeTiny = extension_misc::EXTENSION_ICON_BITTY;
const int kIconSizeSmall = extension_misc::EXTENSION_ICON_SMALL;
const int kIconSizeMedium = extension_misc::EXTENSION_ICON_MEDIUM;
const int kIconSizeLarge = extension_misc::EXTENSION_ICON_LARGE;
const int kIconSizeGigantor = extension_misc::EXTENSION_ICON_GIGANTOR;
const int kIconSizeUnsupported = 123;
const int kIconSizeSmallBetweenMediumAndLarge = 63;
const int kIconSizeLargeBetweenMediumAndLarge = 96;
class BookmarkAppHelperTest : public testing::Test {
public:
......@@ -112,20 +105,6 @@ SkBitmap CreateSquareBitmapWithColor(int size, SkColor color) {
return bitmap;
}
BookmarkAppHelper::BitmapAndSource CreateSquareBitmapAndSourceWithColor(
int size,
SkColor color) {
return BookmarkAppHelper::BitmapAndSource(
GURL(), CreateSquareBitmapWithColor(size, color));
}
void ValidateBitmapSizeAndColor(SkBitmap bitmap, int size, SkColor color) {
// Obtain pixel lock to access pixels.
EXPECT_EQ(color, bitmap.getColor(0, 0));
EXPECT_EQ(size, bitmap.width());
EXPECT_EQ(size, bitmap.height());
}
WebApplicationInfo::IconInfo CreateIconInfoWithBitmap(int size, SkColor color) {
WebApplicationInfo::IconInfo icon_info;
icon_info.width = size;
......@@ -134,152 +113,6 @@ WebApplicationInfo::IconInfo CreateIconInfoWithBitmap(int size, SkColor color) {
return icon_info;
}
std::set<int> TestSizesToGenerate() {
const int kIconSizesToGenerate[] = {
extension_misc::EXTENSION_ICON_SMALL,
extension_misc::EXTENSION_ICON_MEDIUM,
extension_misc::EXTENSION_ICON_LARGE,
};
return std::set<int>(kIconSizesToGenerate,
kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
}
void ValidateAllIconsWithURLsArePresent(const WebApplicationInfo& info_to_check,
const WebApplicationInfo& other_info) {
for (const auto& icon : info_to_check.icons) {
if (!icon.url.is_empty()) {
bool found = false;
for (const auto& other_icon : info_to_check.icons) {
if (other_icon.url == icon.url && other_icon.width == icon.width) {
found = true;
break;
}
}
EXPECT_TRUE(found);
}
}
}
std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator
FindLargestBitmapAndSourceVector(
const std::vector<BookmarkAppHelper::BitmapAndSource>& bitmap_vector) {
auto result = bitmap_vector.end();
int largest = -1;
for (std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() > largest) {
result = it;
}
}
return result;
}
std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator
FindMatchingBitmapAndSourceVector(
const std::vector<BookmarkAppHelper::BitmapAndSource>& bitmap_vector,
int size) {
for (std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() == size) {
return it;
}
}
return bitmap_vector.end();
}
std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator
FindEqualOrLargerBitmapAndSourceVector(
const std::vector<BookmarkAppHelper::BitmapAndSource>& bitmap_vector,
int size) {
for (std::vector<BookmarkAppHelper::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() >= size) {
return it;
}
}
return bitmap_vector.end();
}
void ValidateIconsGeneratedAndResizedCorrectly(
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded,
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map,
std::set<int> sizes_to_generate,
int expected_generated,
int expected_resized) {
GURL empty_url("");
int number_generated = 0;
int number_resized = 0;
auto icon_largest = FindLargestBitmapAndSourceVector(downloaded);
for (const auto& size : sizes_to_generate) {
auto icon_downloaded = FindMatchingBitmapAndSourceVector(downloaded, size);
auto icon_larger = FindEqualOrLargerBitmapAndSourceVector(downloaded, size);
if (icon_downloaded == downloaded.end()) {
auto icon_resized = size_map.find(size);
if (icon_largest == downloaded.end()) {
// There are no downloaded icons. Expect an icon to be generated.
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(empty_url, icon_resized->second.source_url);
++number_generated;
} else {
// If there is a larger downloaded icon, it should be resized. Otherwise
// the largest downloaded icon should be resized.
auto icon_to_resize = icon_largest;
if (icon_larger != downloaded.end())
icon_to_resize = icon_larger;
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(icon_to_resize->source_url, icon_resized->second.source_url);
++number_resized;
}
} else {
// There is an icon of exactly this size downloaded. Expect no icon to be
// generated, and the existing downloaded icon to be used.
auto icon_resized = size_map.find(size);
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_downloaded->bitmap.width());
EXPECT_EQ(size, icon_downloaded->bitmap.height());
EXPECT_EQ(icon_downloaded->source_url, icon_resized->second.source_url);
}
}
EXPECT_EQ(expected_generated, number_generated);
EXPECT_EQ(expected_resized, number_resized);
}
void TestIconGeneration(int icon_size,
int expected_generated,
int expected_resized) {
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
// Add an icon with a URL and bitmap. 'Download' it.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(icon_size, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
// Now run the resizing/generation and validation.
WebApplicationInfo web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &web_app_info);
ValidateIconsGeneratedAndResizedCorrectly(downloaded, size_map,
TestSizesToGenerate(),
expected_generated,
expected_resized);
}
class TestBookmarkAppHelper : public BookmarkAppHelper {
public:
TestBookmarkAppHelper(ExtensionService* service,
......@@ -691,53 +524,6 @@ TEST_F(BookmarkAppHelperExtensionServiceTest, CreateAndUpdateBookmarkApp) {
}
}
TEST_F(BookmarkAppHelperExtensionServiceTest, LinkedAppIconsAreNotChanged) {
WebApplicationInfo web_app_info;
// Add two icons with a URL and bitmap, two icons with just a bitmap, an
// icon with just URL and an icon in an unsupported size with just a URL.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
web_app_info.icons.push_back(icon_info);
icon_info = CreateIconInfoWithBitmap(kIconSizeMedium, SK_ColorRED);
icon_info.url = GURL(kAppIconURL2);
web_app_info.icons.push_back(icon_info);
icon_info.data = SkBitmap();
icon_info.url = GURL(kAppIconURL3);
icon_info.width = 0;
icon_info.height = 0;
web_app_info.icons.push_back(icon_info);
icon_info.url = GURL(kAppIconURL4);
web_app_info.icons.push_back(icon_info);
icon_info = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorRED);
web_app_info.icons.push_back(icon_info);
icon_info = CreateIconInfoWithBitmap(kIconSizeUnsupported, SK_ColorRED);
web_app_info.icons.push_back(icon_info);
// 'Download' one of the icons without a size or bitmap.
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
GURL(kAppIconURL3),
CreateSquareBitmapWithColor(kIconSizeLarge, SK_ColorBLACK)));
// Now run the resizing and generation into a new web app info.
WebApplicationInfo new_web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &new_web_app_info);
// Now check that the linked app icons (i.e. those with URLs) are matching in
// both lists.
ValidateAllIconsWithURLsArePresent(web_app_info, new_web_app_info);
ValidateAllIconsWithURLsArePresent(new_web_app_info, web_app_info);
}
TEST_F(BookmarkAppHelperTest, UpdateWebAppInfoFromManifest) {
WebApplicationInfo web_app_info;
web_app_info.title = base::UTF8ToUTF16(kAlternativeAppTitle);
......@@ -812,53 +598,6 @@ TEST_F(BookmarkAppHelperTest, UpdateWebAppInfoFromManifestInstallableSite) {
}
}
TEST_F(BookmarkAppHelperTest, ConstrainBitmapsToSizes) {
std::set<int> desired_sizes;
desired_sizes.insert(16);
desired_sizes.insert(32);
desired_sizes.insert(48);
desired_sizes.insert(96);
desired_sizes.insert(128);
desired_sizes.insert(256);
{
std::vector<BookmarkAppHelper::BitmapAndSource> bitmaps;
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(16, SK_ColorRED));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(32, SK_ColorGREEN));
bitmaps.push_back(
CreateSquareBitmapAndSourceWithColor(144, SK_ColorYELLOW));
std::map<int, BookmarkAppHelper::BitmapAndSource> results(
BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes));
EXPECT_EQ(6u, results.size());
ValidateBitmapSizeAndColor(results[16].bitmap, 16, SK_ColorRED);
ValidateBitmapSizeAndColor(results[32].bitmap, 32, SK_ColorGREEN);
ValidateBitmapSizeAndColor(results[48].bitmap, 48, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[96].bitmap, 96, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[128].bitmap, 128, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[256].bitmap, 256, SK_ColorYELLOW);
}
{
std::vector<BookmarkAppHelper::BitmapAndSource> bitmaps;
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(512, SK_ColorRED));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(18, SK_ColorGREEN));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(33, SK_ColorBLUE));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(17, SK_ColorYELLOW));
std::map<int, BookmarkAppHelper::BitmapAndSource> results(
BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes));
EXPECT_EQ(6u, results.size());
ValidateBitmapSizeAndColor(results[16].bitmap, 16, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[32].bitmap, 32, SK_ColorBLUE);
ValidateBitmapSizeAndColor(results[48].bitmap, 48, SK_ColorRED);
ValidateBitmapSizeAndColor(results[96].bitmap, 96, SK_ColorRED);
ValidateBitmapSizeAndColor(results[128].bitmap, 128, SK_ColorRED);
ValidateBitmapSizeAndColor(results[256].bitmap, 256, SK_ColorRED);
}
}
TEST_F(BookmarkAppHelperTest, IsValidBookmarkAppUrl) {
EXPECT_TRUE(IsValidBookmarkAppUrl(GURL("https://chromium.org")));
EXPECT_TRUE(IsValidBookmarkAppUrl(GURL("https://www.chromium.org")));
......@@ -879,140 +618,4 @@ TEST_F(BookmarkAppHelperTest, IsValidBookmarkAppUrl) {
EXPECT_FALSE(IsValidBookmarkAppUrl(GURL("chrome://extensions")));
}
TEST_F(BookmarkAppHelperTest, IconsResizedFromOddSizes) {
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' each of them.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeSmallBetweenMediumAndLarge,
SK_ColorRED);
icon_info.url = GURL(kAppIconURL2);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeLargeBetweenMediumAndLarge,
SK_ColorRED);
icon_info.url = GURL(kAppIconURL3);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
// Now run the resizing and generation.
WebApplicationInfo web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &web_app_info);
// No icons should be generated. The LARGE and MEDIUM sizes should be resized.
ValidateIconsGeneratedAndResizedCorrectly(
downloaded, size_map, TestSizesToGenerate(), 0, 2);
}
TEST_F(BookmarkAppHelperTest, IconsResizedFromLarger) {
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' two of them and pretend
// the third failed to download.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
// Now run the resizing and generation.
WebApplicationInfo web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &web_app_info);
// Expect icon for MEDIUM and LARGE to be resized from the gigantor icon
// as it was not downloaded.
ValidateIconsGeneratedAndResizedCorrectly(
downloaded, size_map, TestSizesToGenerate(), 0, 2);
}
TEST_F(BookmarkAppHelperTest, AllIconsGeneratedWhenNotDownloaded) {
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' none of them.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
icon_info = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
// Now run the resizing and generation.
WebApplicationInfo web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &web_app_info);
// Expect all icons to be generated.
ValidateIconsGeneratedAndResizedCorrectly(
downloaded, size_map, TestSizesToGenerate(), 3, 0);
}
TEST_F(BookmarkAppHelperTest, IconResizedFromLargerAndSmaller) {
std::vector<BookmarkAppHelper::BitmapAndSource> downloaded;
// Pretend the huge icon wasn't downloaded but two smaller ones were.
WebApplicationInfo::IconInfo icon_info =
CreateIconInfoWithBitmap(kIconSizeTiny, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeMedium, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
downloaded.push_back(BookmarkAppHelper::BitmapAndSource(
icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
// Now run the resizing and generation.
WebApplicationInfo web_app_info;
std::map<int, BookmarkAppHelper::BitmapAndSource> size_map =
BookmarkAppHelper::ResizeIconsAndGenerateMissing(
downloaded, TestSizesToGenerate(), &web_app_info);
// Expect no icons to be generated, but the LARGE and SMALL icons to be
// resized from the MEDIUM icon.
ValidateIconsGeneratedAndResizedCorrectly(
downloaded, size_map, TestSizesToGenerate(), 0, 2);
// Verify specifically that the LARGE icons was resized from the medium icon.
const auto it = size_map.find(kIconSizeLarge);
EXPECT_NE(size_map.end(), it);
EXPECT_EQ(GURL(kAppIconURL2), it->second.source_url);
}
TEST_F(BookmarkAppHelperTest, IconsResizedWhenOnlyATinyOneIsProvided) {
// When only a tiny icon is downloaded (smaller than the three desired
// sizes), 3 icons should be resized.
TestIconGeneration(kIconSizeTiny, 0, 3);
}
TEST_F(BookmarkAppHelperTest, IconsResizedWhenOnlyAGigantorOneIsProvided) {
// When an enormous icon is provided, each desired icon size should be resized
// from it, and no icons should be generated.
TestIconGeneration(kIconSizeGigantor, 0, 3);
}
} // namespace extensions
......@@ -8,6 +8,8 @@ source_set("components") {
"pending_app_manager.h",
"web_app_helpers.cc",
"web_app_helpers.h",
"web_app_icon_generator.cc",
"web_app_icon_generator.h",
"web_app_shortcut.cc",
"web_app_shortcut.h",
"web_app_shortcut_chromeos.cc",
......@@ -49,6 +51,7 @@ source_set("unit_tests") {
sources = [
"web_app_helpers_unittest.cc",
"web_app_icon_downloader_unittest.cc",
"web_app_icon_generator_unittest.cc",
"web_app_shortcut_mac_unittest.mm",
"web_app_shortcut_unittest.cc",
]
......
// Copyright 2018 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 "chrome/browser/web_applications/components/web_app_icon_generator.h"
#include <cctype>
#include <string>
#include <utility>
#include "base/macros.h"
#include "build/build_config.h"
#include "chrome/grit/platform_locale_settings.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "skia/ext/image_operations.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
namespace web_app {
namespace {
// Generates a square container icon of |output_size| by drawing the given
// |letter| into a rounded background of |color|.
class GeneratedIconImageSource : public gfx::CanvasImageSource {
public:
explicit GeneratedIconImageSource(char letter, SkColor color, int output_size)
: gfx::CanvasImageSource(gfx::Size(output_size, output_size), false),
letter_(letter),
color_(color),
output_size_(output_size) {}
~GeneratedIconImageSource() override {}
private:
// gfx::CanvasImageSource overrides:
void Draw(gfx::Canvas* canvas) override {
const uint8_t kLumaThreshold = 190;
const int icon_size = output_size_ * 3 / 4;
const int icon_inset = output_size_ / 8;
const size_t border_radius = output_size_ / 16;
const size_t font_size = output_size_ * 7 / 16;
std::string font_name =
l10n_util::GetStringUTF8(IDS_SANS_SERIF_FONT_FAMILY);
#if defined(OS_CHROMEOS)
const std::string kChromeOSFontFamily = "Noto Sans";
font_name = kChromeOSFontFamily;
#endif
// Draw a rounded rect of the given |color|.
cc::PaintFlags background_flags;
background_flags.setAntiAlias(true);
background_flags.setColor(color_);
gfx::Rect icon_rect(icon_inset, icon_inset, icon_size, icon_size);
canvas->DrawRoundRect(icon_rect, border_radius, background_flags);
// The text rect's size needs to be odd to center the text correctly.
gfx::Rect text_rect(icon_inset, icon_inset, icon_size + 1, icon_size + 1);
// Draw the letter onto the rounded rect. The letter's color depends on the
// luma of |color|.
const uint8_t luma = color_utils::GetLuma(color_);
canvas->DrawStringRectWithFlags(
base::string16(1, std::toupper(letter_)),
gfx::FontList(gfx::Font(font_name, font_size)),
(luma > kLumaThreshold) ? SK_ColorBLACK : SK_ColorWHITE, text_rect,
gfx::Canvas::TEXT_ALIGN_CENTER);
}
char letter_;
SkColor color_;
int output_size_;
DISALLOW_COPY_AND_ASSIGN(GeneratedIconImageSource);
};
// Adds a square container icon of |output_size| and 2 * |output_size| pixels
// to |bitmaps| by drawing the given |letter| into a rounded background of
// |color|. For each size, if an icon of the requested size already exists in
// |bitmaps|, nothing will happen.
void GenerateIcon(std::map<int, BitmapAndSource>* bitmaps,
int output_size,
SkColor color,
char letter) {
// Do nothing if there is already an icon of |output_size|.
if (bitmaps->count(output_size))
return;
(*bitmaps)[output_size].bitmap = GenerateBitmap(output_size, color, letter);
}
void GenerateIcons(std::set<int> generate_sizes,
const GURL& app_url,
SkColor generated_icon_color,
std::map<int, BitmapAndSource>* bitmap_map) {
// The letter that will be painted on the generated icon.
char icon_letter = ' ';
std::string domain_and_registry(
net::registry_controlled_domains::GetDomainAndRegistry(
app_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
if (!domain_and_registry.empty()) {
icon_letter = domain_and_registry[0];
} else if (app_url.has_host()) {
icon_letter = app_url.host_piece()[0];
}
// If no color has been specified, use a dark gray so it will stand out on the
// black shelf.
if (generated_icon_color == SK_ColorTRANSPARENT)
generated_icon_color = SK_ColorDKGRAY;
for (int size : generate_sizes) {
GenerateIcon(bitmap_map, size, generated_icon_color, icon_letter);
}
}
} // namespace
BitmapAndSource::BitmapAndSource() {}
BitmapAndSource::BitmapAndSource(const GURL& source_url_p,
const SkBitmap& bitmap_p)
: source_url(source_url_p), bitmap(bitmap_p) {}
BitmapAndSource::~BitmapAndSource() {}
std::map<int, BitmapAndSource> ConstrainBitmapsToSizes(
const std::vector<BitmapAndSource>& bitmaps,
const std::set<int>& sizes) {
std::map<int, BitmapAndSource> output_bitmaps;
std::map<int, BitmapAndSource> ordered_bitmaps;
for (const BitmapAndSource& bitmap_and_source : bitmaps) {
const SkBitmap& bitmap = bitmap_and_source.bitmap;
DCHECK(bitmap.width() == bitmap.height());
ordered_bitmaps[bitmap.width()] = bitmap_and_source;
}
if (ordered_bitmaps.size() > 0) {
for (const auto& size : sizes) {
// Find the closest not-smaller bitmap, or failing that use the largest
// icon available.
auto bitmaps_it = ordered_bitmaps.lower_bound(size);
if (bitmaps_it != ordered_bitmaps.end())
output_bitmaps[size] = bitmaps_it->second;
else
output_bitmaps[size] = ordered_bitmaps.rbegin()->second;
// Resize the bitmap if it does not exactly match the desired size.
if (output_bitmaps[size].bitmap.width() != size) {
output_bitmaps[size].bitmap = skia::ImageOperations::Resize(
output_bitmaps[size].bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
size, size);
}
}
}
return output_bitmaps;
}
SkBitmap GenerateBitmap(int output_size, SkColor color, char letter) {
gfx::ImageSkia icon_image(
std::make_unique<GeneratedIconImageSource>(letter, color, output_size),
gfx::Size(output_size, output_size));
SkBitmap dst;
if (dst.tryAllocPixels(icon_image.bitmap()->info())) {
icon_image.bitmap()->readPixels(dst.info(), dst.getPixels(), dst.rowBytes(),
0, 0);
}
return dst;
}
std::map<int, BitmapAndSource> ResizeIconsAndGenerateMissing(
const std::vector<BitmapAndSource>& icons,
const std::set<int>& sizes_to_generate,
const GURL& app_url,
SkColor* generated_icon_color) {
DCHECK(generated_icon_color);
// Resize provided icons to make sure we have versions for each size in
// |sizes_to_generate|.
std::map<int, BitmapAndSource> resized_bitmaps(
ConstrainBitmapsToSizes(icons, sizes_to_generate));
// Also add all provided icon sizes.
for (const BitmapAndSource& icon : icons) {
if (resized_bitmaps.find(icon.bitmap.width()) == resized_bitmaps.end())
resized_bitmaps.insert(std::make_pair(icon.bitmap.width(), icon));
}
// Determine the color that will be used for the icon's background. For this
// the dominant color of the first icon found is used.
if (resized_bitmaps.size()) {
color_utils::GridSampler sampler;
*generated_icon_color = color_utils::CalculateKMeanColorOfBitmap(
resized_bitmaps.begin()->second.bitmap);
}
// Work out what icons we need to generate here. Icons are only generated if
// there is no icon in the required size.
std::set<int> generate_sizes;
for (int size : sizes_to_generate) {
if (resized_bitmaps.find(size) == resized_bitmaps.end())
generate_sizes.insert(size);
}
GenerateIcons(generate_sizes, app_url, *generated_icon_color,
&resized_bitmaps);
return resized_bitmaps;
}
} // namespace web_app
// Copyright 2018 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.
#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_ICON_GENERATOR_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_ICON_GENERATOR_H_
#include <map>
#include <set>
#include <vector>
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/gurl.h"
namespace web_app {
struct BitmapAndSource {
BitmapAndSource();
BitmapAndSource(const GURL& source_url_p, const SkBitmap& bitmap_p);
~BitmapAndSource();
GURL source_url;
SkBitmap bitmap;
};
// This finds the closest not-smaller bitmap in |bitmaps| for each size in
// |sizes| and resizes it to that size. This returns a map of sizes to bitmaps
// which contains only bitmaps of a size in |sizes| and at most one bitmap of
// each size.
std::map<int, BitmapAndSource> ConstrainBitmapsToSizes(
const std::vector<BitmapAndSource>& bitmaps,
const std::set<int>& sizes);
// Generates a square container icon of |output_size| by drawing the given
// |letter| into a rounded background of |color|.
SkBitmap GenerateBitmap(int output_size, SkColor color, char letter);
// Resize icons to the accepted sizes, and generate any that are missing.
// Note that |app_url| is the launch URL for the app.
// Output: |generated_icon_color| is the color to use if an icon needs to be
// generated for the web app.
std::map<int, BitmapAndSource> ResizeIconsAndGenerateMissing(
const std::vector<BitmapAndSource>& icons,
const std::set<int>& sizes_to_generate,
const GURL& app_url,
SkColor* generated_icon_color);
} // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_ICON_GENERATOR_H_
// Copyright 2018 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 "chrome/browser/web_applications/components/web_app_icon_generator.h"
#include <vector>
#include "base/stl_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace web_app {
namespace {
const char kAppIconURL1[] = "http://foo.com/1.png";
const char kAppIconURL2[] = "http://foo.com/2.png";
const char kAppIconURL3[] = "http://foo.com/3.png";
// These sizes match extension_misc::ExtensionIcons enum.
const int kIconSizeTiny = 16;
const int kIconSizeSmall = 32;
const int kIconSizeMedium = 48;
const int kIconSizeLarge = 128;
const int kIconSizeGigantor = 512;
const int kIconSizeSmallBetweenMediumAndLarge = 63;
const int kIconSizeLargeBetweenMediumAndLarge = 96;
SkBitmap CreateSquareBitmapWithColor(int size, SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(size, size);
bitmap.eraseColor(color);
return bitmap;
}
web_app::BitmapAndSource CreateSquareBitmapAndSourceWithColor(int size,
SkColor color) {
return web_app::BitmapAndSource(GURL(),
CreateSquareBitmapWithColor(size, color));
}
struct IconInfo {
IconInfo() : width(0), height(0) {}
~IconInfo() = default;
GURL url;
int width;
int height;
SkBitmap data;
};
IconInfo CreateIconInfoWithBitmap(int size, SkColor color) {
IconInfo icon_info;
icon_info.width = size;
icon_info.height = size;
icon_info.data = CreateSquareBitmapWithColor(size, color);
return icon_info;
}
std::set<int> TestSizesToGenerate() {
const int kIconSizesToGenerate[] = {
kIconSizeSmall, kIconSizeMedium, kIconSizeLarge,
};
return std::set<int>(kIconSizesToGenerate,
kIconSizesToGenerate + base::size(kIconSizesToGenerate));
}
void ValidateAllIconsWithURLsArePresent(
const std::vector<IconInfo>& icons_to_check,
const std::map<int, BitmapAndSource>& size_map) {
EXPECT_EQ(icons_to_check.size(), size_map.size());
// Check that every icon with URL has a mapped icon.
for (const auto& icon : icons_to_check) {
if (!icon.url.is_empty()) {
bool found = false;
if (base::ContainsKey(size_map, icon.width)) {
const BitmapAndSource& mapped_icon = size_map.at(icon.width);
if (mapped_icon.source_url == icon.url &&
mapped_icon.bitmap.width() == icon.width) {
found = true;
}
}
EXPECT_TRUE(found);
}
}
}
std::vector<web_app::BitmapAndSource>::const_iterator
FindLargestBitmapAndSourceVector(
const std::vector<web_app::BitmapAndSource>& bitmap_vector) {
auto result = bitmap_vector.end();
int largest = -1;
for (std::vector<web_app::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() > largest) {
result = it;
}
}
return result;
}
std::vector<web_app::BitmapAndSource>::const_iterator
FindMatchingBitmapAndSourceVector(
const std::vector<web_app::BitmapAndSource>& bitmap_vector,
int size) {
for (std::vector<web_app::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() == size) {
return it;
}
}
return bitmap_vector.end();
}
std::vector<web_app::BitmapAndSource>::const_iterator
FindEqualOrLargerBitmapAndSourceVector(
const std::vector<web_app::BitmapAndSource>& bitmap_vector,
int size) {
for (std::vector<web_app::BitmapAndSource>::const_iterator it =
bitmap_vector.begin();
it != bitmap_vector.end(); ++it) {
if (it->bitmap.width() >= size) {
return it;
}
}
return bitmap_vector.end();
}
void ValidateIconsGeneratedAndResizedCorrectly(
std::vector<web_app::BitmapAndSource> downloaded,
std::map<int, web_app::BitmapAndSource> size_map,
std::set<int> sizes_to_generate,
int expected_generated,
int expected_resized) {
GURL empty_url("");
int number_generated = 0;
int number_resized = 0;
auto icon_largest = FindLargestBitmapAndSourceVector(downloaded);
for (const auto& size : sizes_to_generate) {
auto icon_downloaded = FindMatchingBitmapAndSourceVector(downloaded, size);
auto icon_larger = FindEqualOrLargerBitmapAndSourceVector(downloaded, size);
if (icon_downloaded == downloaded.end()) {
auto icon_resized = size_map.find(size);
if (icon_largest == downloaded.end()) {
// There are no downloaded icons. Expect an icon to be generated.
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(empty_url, icon_resized->second.source_url);
++number_generated;
} else {
// If there is a larger downloaded icon, it should be resized. Otherwise
// the largest downloaded icon should be resized.
auto icon_to_resize = icon_largest;
if (icon_larger != downloaded.end())
icon_to_resize = icon_larger;
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(icon_to_resize->source_url, icon_resized->second.source_url);
++number_resized;
}
} else {
// There is an icon of exactly this size downloaded. Expect no icon to be
// generated, and the existing downloaded icon to be used.
auto icon_resized = size_map.find(size);
EXPECT_NE(size_map.end(), icon_resized);
EXPECT_EQ(size, icon_resized->second.bitmap.width());
EXPECT_EQ(size, icon_resized->second.bitmap.height());
EXPECT_EQ(size, icon_downloaded->bitmap.width());
EXPECT_EQ(size, icon_downloaded->bitmap.height());
EXPECT_EQ(icon_downloaded->source_url, icon_resized->second.source_url);
}
}
EXPECT_EQ(expected_generated, number_generated);
EXPECT_EQ(expected_resized, number_resized);
}
void ValidateBitmapSizeAndColor(SkBitmap bitmap, int size, SkColor color) {
// Obtain pixel lock to access pixels.
EXPECT_EQ(color, bitmap.getColor(0, 0));
EXPECT_EQ(size, bitmap.width());
EXPECT_EQ(size, bitmap.height());
}
void TestIconGeneration(int icon_size,
int expected_generated,
int expected_resized) {
std::vector<BitmapAndSource> downloaded;
// Add an icon with a URL and bitmap. 'Download' it.
IconInfo icon_info = CreateIconInfoWithBitmap(icon_size, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(BitmapAndSource(icon_info.url, icon_info.data));
// Now run the resizing/generation and validation.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, TestSizesToGenerate(), GURL(),
&generated_icon_color);
ValidateIconsGeneratedAndResizedCorrectly(
downloaded, size_map, TestSizesToGenerate(), expected_generated,
expected_resized);
}
} // namespace
TEST(WebAppIconGeneratorTest, ConstrainBitmapsToSizes) {
std::set<int> desired_sizes;
desired_sizes.insert(16);
desired_sizes.insert(32);
desired_sizes.insert(48);
desired_sizes.insert(96);
desired_sizes.insert(128);
desired_sizes.insert(256);
{
std::vector<web_app::BitmapAndSource> bitmaps;
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(16, SK_ColorRED));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(32, SK_ColorGREEN));
bitmaps.push_back(
CreateSquareBitmapAndSourceWithColor(144, SK_ColorYELLOW));
std::map<int, web_app::BitmapAndSource> results(
ConstrainBitmapsToSizes(bitmaps, desired_sizes));
EXPECT_EQ(6u, results.size());
ValidateBitmapSizeAndColor(results[16].bitmap, 16, SK_ColorRED);
ValidateBitmapSizeAndColor(results[32].bitmap, 32, SK_ColorGREEN);
ValidateBitmapSizeAndColor(results[48].bitmap, 48, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[96].bitmap, 96, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[128].bitmap, 128, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[256].bitmap, 256, SK_ColorYELLOW);
}
{
std::vector<web_app::BitmapAndSource> bitmaps;
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(512, SK_ColorRED));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(18, SK_ColorGREEN));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(33, SK_ColorBLUE));
bitmaps.push_back(CreateSquareBitmapAndSourceWithColor(17, SK_ColorYELLOW));
std::map<int, web_app::BitmapAndSource> results(
ConstrainBitmapsToSizes(bitmaps, desired_sizes));
EXPECT_EQ(6u, results.size());
ValidateBitmapSizeAndColor(results[16].bitmap, 16, SK_ColorYELLOW);
ValidateBitmapSizeAndColor(results[32].bitmap, 32, SK_ColorBLUE);
ValidateBitmapSizeAndColor(results[48].bitmap, 48, SK_ColorRED);
ValidateBitmapSizeAndColor(results[96].bitmap, 96, SK_ColorRED);
ValidateBitmapSizeAndColor(results[128].bitmap, 128, SK_ColorRED);
ValidateBitmapSizeAndColor(results[256].bitmap, 256, SK_ColorRED);
}
}
TEST(WebAppIconGeneratorTest, LinkedAppIconsAreNotChanged) {
std::vector<IconInfo> icons_info;
IconInfo icon_info;
icon_info.url = GURL(kAppIconURL3);
icon_info.width = kIconSizeMedium;
icons_info.push_back(icon_info);
icon_info.width = kIconSizeSmall;
icons_info.push_back(icon_info);
icon_info.width = kIconSizeLarge;
icons_info.push_back(icon_info);
// 'Download' one of the icons without a size or bitmap.
std::vector<BitmapAndSource> downloaded;
downloaded.push_back(BitmapAndSource(
GURL(kAppIconURL3),
CreateSquareBitmapWithColor(kIconSizeLarge, SK_ColorBLACK)));
const auto& sizes = TestSizesToGenerate();
// Now run the resizing and generation into a new web icons info.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, sizes, GURL(),
&generated_icon_color);
EXPECT_EQ(sizes.size(), size_map.size());
// Now check that the linked app icons (i.e. those with URLs) are matching.
ValidateAllIconsWithURLsArePresent(icons_info, size_map);
}
TEST(WebAppIconGeneratorTest, IconsResizedFromOddSizes) {
std::vector<BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' each of them.
IconInfo icon_info = CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeSmallBetweenMediumAndLarge,
SK_ColorRED);
icon_info.url = GURL(kAppIconURL2);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeLargeBetweenMediumAndLarge,
SK_ColorRED);
icon_info.url = GURL(kAppIconURL3);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
// Now run the resizing and generation.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, TestSizesToGenerate(), GURL(),
&generated_icon_color);
// No icons should be generated. The LARGE and MEDIUM sizes should be resized.
ValidateIconsGeneratedAndResizedCorrectly(downloaded, size_map,
TestSizesToGenerate(), 0, 2);
}
TEST(WebAppIconGeneratorTest, IconsResizedFromLarger) {
std::vector<web_app::BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' two of them and pretend
// the third failed to download.
IconInfo icon_info = CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
// Now run the resizing and generation.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, TestSizesToGenerate(), GURL(),
&generated_icon_color);
// Expect icon for MEDIUM and LARGE to be resized from the gigantor icon
// as it was not downloaded.
ValidateIconsGeneratedAndResizedCorrectly(downloaded, size_map,
TestSizesToGenerate(), 0, 2);
}
TEST(WebAppIconGeneratorTest, AllIconsGeneratedWhenNotDownloaded) {
std::vector<web_app::BitmapAndSource> downloaded;
// Add three icons with a URL and bitmap. 'Download' none of them.
IconInfo icon_info = CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
icon_info = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
// Now run the resizing and generation.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, TestSizesToGenerate(), GURL(),
&generated_icon_color);
// Expect all icons to be generated.
ValidateIconsGeneratedAndResizedCorrectly(downloaded, size_map,
TestSizesToGenerate(), 3, 0);
}
TEST(WebAppIconGeneratorTest, IconResizedFromLargerAndSmaller) {
std::vector<web_app::BitmapAndSource> downloaded;
// Pretend the huge icon wasn't downloaded but two smaller ones were.
IconInfo icon_info = CreateIconInfoWithBitmap(kIconSizeTiny, SK_ColorRED);
icon_info.url = GURL(kAppIconURL1);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeMedium, SK_ColorBLUE);
icon_info.url = GURL(kAppIconURL2);
downloaded.push_back(web_app::BitmapAndSource(icon_info.url, icon_info.data));
icon_info = CreateIconInfoWithBitmap(kIconSizeGigantor, SK_ColorBLACK);
icon_info.url = GURL(kAppIconURL3);
// Now run the resizing and generation.
SkColor generated_icon_color = SK_ColorTRANSPARENT;
std::map<int, web_app::BitmapAndSource> size_map =
ResizeIconsAndGenerateMissing(downloaded, TestSizesToGenerate(), GURL(),
&generated_icon_color);
// Expect no icons to be generated, but the LARGE and SMALL icons to be
// resized from the MEDIUM icon.
ValidateIconsGeneratedAndResizedCorrectly(downloaded, size_map,
TestSizesToGenerate(), 0, 2);
// Verify specifically that the LARGE icons was resized from the medium icon.
const auto it = size_map.find(kIconSizeLarge);
EXPECT_NE(size_map.end(), it);
EXPECT_EQ(GURL(kAppIconURL2), it->second.source_url);
}
TEST(WebAppIconGeneratorTest, IconsResizedWhenOnlyATinyOneIsProvided) {
// When only a tiny icon is downloaded (smaller than the three desired
// sizes), 3 icons should be resized.
TestIconGeneration(kIconSizeTiny, 0, 3);
}
TEST(WebAppIconGeneratorTest, IconsResizedWhenOnlyAGigantorOneIsProvided) {
// When an enormous icon is provided, each desired icon size should be resized
// from it, and no icons should be generated.
TestIconGeneration(kIconSizeGigantor, 0, 3);
}
} // namespace web_app
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment