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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/shortcuts/icon_badging.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/enum_set.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/version_info/channel.h"
#include "build/branding_buildflags.h"
#include "build/buildflag.h"
#include "chrome/common/channel_info.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_family.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace shortcuts {
namespace {
enum class ShortcutSize {
k16,
k32,
k48,
k128,
k256,
k512,
kMaxValue = k512
};
enum class BadgeSize {
k8,
k16,
k24,
k64,
k128,
kMaxValue = k128
};
// Returns the icon sizes needed for shortcut creation on the desktop. k32 is
// needed for the icon in the Create Shortcut dialog.
#if BUILDFLAG(IS_MAC)
constexpr ShortcutSize kSizesNeededForShortcutCreation[] = {
ShortcutSize::k16, ShortcutSize::k32, ShortcutSize::k128,
ShortcutSize::k256, ShortcutSize::k512};
#elif BUILDFLAG(IS_LINUX)
constexpr ShortcutSize kSizesNeededForShortcutCreation[] = {ShortcutSize::k32,
ShortcutSize::k128};
#elif BUILDFLAG(IS_WIN)
constexpr ShortcutSize kSizesNeededForShortcutCreation[] = {
ShortcutSize::k16, ShortcutSize::k32, ShortcutSize::k48,
ShortcutSize::k256};
#endif
int ToInt(ShortcutSize size) {
switch (size) {
case ShortcutSize::k16:
return 16;
case ShortcutSize::k32:
return 32;
case ShortcutSize::k48:
return 48;
case ShortcutSize::k128:
return 128;
case ShortcutSize::k256:
return 256;
case ShortcutSize::k512:
return 512;
}
}
int ToInt(BadgeSize size) {
switch (size) {
case BadgeSize::k8:
return 8;
case BadgeSize::k16:
return 16;
case BadgeSize::k24:
return 24;
case BadgeSize::k64:
return 64;
case BadgeSize::k128:
return 128;
}
}
// For icon sizes > 128, use a badge size that is 1/4, else use a badge size
// that is 1/2.
BadgeSize GetBadgeSizeFromShortcutSize(ShortcutSize icon_size) {
switch (icon_size) {
case ShortcutSize::k16:
return BadgeSize::k8;
case ShortcutSize::k32:
return BadgeSize::k16;
case ShortcutSize::k48:
return BadgeSize::k24;
case ShortcutSize::k128:
return BadgeSize::k64;
case ShortcutSize::k256:
return BadgeSize::k64;
case ShortcutSize::k512:
return BadgeSize::k128;
}
}
constexpr int resource_map_size = static_cast<int>(BadgeSize::kMaxValue) + 1;
using SizeToResourceMap = base::fixed_flat_map<BadgeSize, int, 5>;
// At very low pixels < 16, there is probably no need to recreate the
// resource files necessary for these icons. Resizing them instead would
// work just as better, without the need to increase the chrome binary
// size.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr SizeToResourceMap kStableResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_16_STABLE_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_16_STABLE_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_24_STABLE_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_64_STABLE_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_128_STABLE_SHORTCUTS}});
constexpr SizeToResourceMap kCanaryResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_CANARY_16_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_CANARY_16_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_CANARY_24_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_CANARY_64_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_CANARY_128_SHORTCUTS}});
constexpr SizeToResourceMap kBetaResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_BETA_16_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_BETA_16_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_BETA_24_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_BETA_64_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_BETA_128_SHORTCUTS}});
constexpr SizeToResourceMap kDevResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_DEV_16_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_DEV_16_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_DEV_24_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_DEV_64_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_DEV_128_SHORTCUTS}});
static_assert(static_cast<int>(kStableResourceMap.size()) == resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for stable resource map");
static_assert(static_cast<int>(kCanaryResourceMap.size()) == resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for canary resource map");
static_assert(static_cast<int>(kBetaResourceMap.size()) == resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for beta resource map");
static_assert(static_cast<int>(kDevResourceMap.size()) == resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for dev resource map");
#elif BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING)
constexpr SizeToResourceMap kChromeForTestingBrandedResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_16_CFT_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_16_CFT_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_24_CFT_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_64_CFT_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_128_CFT_SHORTCUTS}});
static_assert(static_cast<int>(kChromeForTestingBrandedResourceMap.size()) ==
resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for Chrome for testing resource map");
#else
constexpr SizeToResourceMap kChromiumResourceMap =
base::MakeFixedFlatMap<BadgeSize, int>(
{{BadgeSize::k8, IDR_PRODUCT_LOGO_16_SHORTCUTS},
{BadgeSize::k16, IDR_PRODUCT_LOGO_16_SHORTCUTS},
{BadgeSize::k24, IDR_PRODUCT_LOGO_24_SHORTCUTS},
{BadgeSize::k64, IDR_PRODUCT_LOGO_64_SHORTCUTS},
{BadgeSize::k128, IDR_PRODUCT_LOGO_128_SHORTCUTS}});
static_assert(static_cast<int>(kChromiumResourceMap.size()) ==
resource_map_size,
"Ensure icon resources are filled for all entries in BadgeSize "
"for chromium resource map");
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
SizeToResourceMap GetResourceMapForCurrentChannel() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
switch (chrome::GetChannel()) {
// |version_info::Channel::DEFAULT| is seen on local builds with
// is_chrome_branded = true.
case version_info::Channel::DEFAULT:
case version_info::Channel::STABLE:
return kStableResourceMap;
case version_info::Channel::CANARY:
return kCanaryResourceMap;
case version_info::Channel::DEV:
return kDevResourceMap;
case version_info::Channel::BETA:
return kBetaResourceMap;
}
#elif BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING)
return kChromeForTestingBrandedResourceMap;
#else
return kChromiumResourceMap;
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}
gfx::ImageSkia GetMaskForBadging(ShortcutSize icon_size) {
BadgeSize badge_size = GetBadgeSizeFromShortcutSize(icon_size);
gfx::ImageSkia* image =
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
GetResourceMapForCurrentChannel().at(badge_size));
CHECK(image);
// TODO(crbug.com/337998776): Update if assets are sent by UX.
const float kWhiteMaskFactor = 1.75f;
// Set white mask for the loaded icon.
gfx::ImageSkia masked_badge =
gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
image->width() / kWhiteMaskFactor, SK_ColorWHITE, *image);
int badge_size_int = ToInt(badge_size);
// Maintain the badging ratio of 1/4th for sizes < 128, and 1/8th
// for sizes greater. Adding the white background after loading the resource
// will likely throw that ratio off.
gfx::ImageSkia resized_post_masking =
gfx::ImageSkiaOperations::CreateResizedImage(
masked_badge, skia::ImageOperations::ResizeMethod::RESIZE_LANCZOS3,
gfx::Size(badge_size_int, badge_size_int));
return resized_post_masking;
}
int FindClosestIconSizeToUse(const std::vector<int>& sorted_icons,
int size_to_use) {
auto closest_size_iter =
std::lower_bound(sorted_icons.begin(), sorted_icons.end(), size_to_use);
// Iterator out of bounds, can happen if the input icon size is too large,
// like say 1000.
if (closest_size_iter == sorted_icons.end()) {
closest_size_iter = sorted_icons.end() - 1;
} else if (closest_size_iter != sorted_icons.begin()) {
// Compare with the iterator found and the one just before, and choose the
// size that is closer. This helps reduce the pixelation when the icon gets
// resized ultimately.
int size_before = *(closest_size_iter - 1);
int size_after = *(closest_size_iter);
if (std::fabs(size_to_use - size_before) <
std::fabs(size_to_use - size_after)) {
--closest_size_iter;
}
}
return *closest_size_iter;
}
using ShortcutSizes =
base::EnumSet<ShortcutSize, ShortcutSize::k16, ShortcutSize::k512>;
} // namespace
gfx::ImageFamily ApplyProductLogoBadgeToIcons(std::vector<SkBitmap> icons) {
gfx::ImageFamily badged_icons;
CHECK(!icons.empty());
base::flat_map<int, SkBitmap> sorted_icons;
std::vector<int> icon_sizes;
for (const auto& icon : icons) {
sorted_icons.insert_or_assign(icon.width(), icon);
icon_sizes.push_back(icon.width());
}
std::sort(icon_sizes.begin(), icon_sizes.end());
for (const ShortcutSize needed_size : kSizesNeededForShortcutCreation) {
int icon_size = ToInt(needed_size);
int size_to_use = FindClosestIconSizeToUse(icon_sizes, icon_size);
SkBitmap bitmap_to_use = sorted_icons.at(size_to_use);
// Resize the current bitmap to the needed_size, so that the properly
// selected product logo does not get pixellated.
gfx::ImageSkia resized_to_fit =
gfx::ImageSkiaOperations::CreateResizedImage(
gfx::ImageSkia::CreateFrom1xBitmap(bitmap_to_use),
skia::ImageOperations::ResizeMethod::RESIZE_BEST,
gfx::Size(icon_size, icon_size));
gfx::ImageSkia masked_badge = GetMaskForBadging(needed_size);
// Apply the masked product logo to the bitmaps.
gfx::ImageSkia badged_icon = gfx::ImageSkiaOperations::CreateIconWithBadge(
resized_to_fit, masked_badge);
// Doing this allows the returned gfx::ImageFamily to be passed across
// multiple sequences, like when this is passed to the ThreadPool during
// shortcut creation at the OS level.
badged_icon.MakeThreadSafe();
badged_icons.Add(badged_icon);
}
return badged_icons;
}
} // namespace shortcuts
|