#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include <memory>
#include <utility>
#include "base/containers/span.h"
#include "base/debug/stack_trace.h"
#include "base/debug/task_trace.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_loader.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/grit/app_icon_resources.h"
#include "components/services/app_service/public/cpp/icon_effects.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "services/data_decoder/public/mojom/image_decoder.mojom.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#endif
namespace {
#if BUILDFLAG(IS_CHROMEOS_ASH)
float kAndroidAdaptiveIconPaddingPercentage = 1.0f / 8.0f;
bool IsConsistentPixelSize(const gfx::ImageSkiaRep& rep,
const gfx::ImageSkia& image_skia) {
return rep.pixel_width() == roundf(image_skia.width() * rep.scale()) &&
rep.pixel_height() == roundf(image_skia.height() * rep.scale());
}
bool ShouldExtractSubset(const gfx::ImageSkia& image_skia) {
DCHECK(!image_skia.image_reps().empty());
for (const auto& rep : image_skia.image_reps()) {
if (!IsConsistentPixelSize(rep, image_skia)) {
return true;
}
}
return false;
}
gfx::ImageSkia ExtractSubsetForArcImage(const gfx::ImageSkia& image_skia) {
gfx::ImageSkia subset_image;
for (const auto& rep : image_skia.image_reps()) {
if (IsConsistentPixelSize(rep, image_skia)) {
subset_image.AddRepresentation(rep);
continue;
}
int padding_width =
rep.pixel_width() * kAndroidAdaptiveIconPaddingPercentage;
int padding_height =
rep.pixel_height() * kAndroidAdaptiveIconPaddingPercentage;
SkBitmap dst;
bool success = rep.GetBitmap().extractSubset(
&dst,
RectToSkIRect(gfx::Rect(padding_width, padding_height,
rep.pixel_width() - 2 * padding_width,
rep.pixel_height() - 2 * padding_height)));
DCHECK(success);
const SkBitmap resized = skia::ImageOperations::Resize(
dst, skia::ImageOperations::RESIZE_LANCZOS3,
roundf(image_skia.width() * rep.scale()),
roundf(image_skia.height() * rep.scale()));
subset_image.AddRepresentation(gfx::ImageSkiaRep(resized, rep.scale()));
}
return subset_image;
}
#endif
SizeToImageSkiaRep;
ScaleToImageSkiaReps;
MaskImageSkiaReps;
MaskImageSkiaReps& GetMaskResourceIconCache() { … }
const SkBitmap& GetMaskBitmap() { … }
const gfx::ImageSkiaRep& GetMaskAsImageSkiaRep(float scale,
int size_hint_in_dip) { … }
void CompleteIconWithCompressed(apps::LoadIconCallback callback,
std::vector<uint8_t> data) { … }
}
namespace apps {
std::map<std::pair<int, int>, gfx::ImageSkia>& GetResourceIconCache() { … }
gfx::ImageSkia CreateResizedResourceImage(int icon_resource,
int32_t size_in_dip) { … }
apps::ScaleToSize GetScaleToSize(const gfx::ImageSkia& image_skia) { … }
void CompressedDataToSkBitmap(
base::span<const uint8_t> compressed_data,
base::OnceCallback<void(const SkBitmap&)> callback) { … }
void CompressedDataToImageSkia(
base::span<const uint8_t> compressed_data,
float icon_scale,
base::OnceCallback<void(gfx::ImageSkia)> callback) { … }
base::OnceCallback<void(std::vector<uint8_t> compressed_data)>
CompressedDataToImageSkiaCallback(
base::OnceCallback<void(gfx::ImageSkia)> callback,
float icon_scale) { … }
gfx::ImageSkia SkBitmapToImageSkia(const SkBitmap& bitmap, float icon_scale) { … }
std::vector<uint8_t> EncodeImageToPngBytes(const gfx::ImageSkia image,
float rep_icon_scale) { … }
gfx::ImageSkia LoadMaskImage(const ScaleToSize& scale_to_size) { … }
gfx::ImageSkia ApplyBackgroundAndMask(const gfx::ImageSkia& image) { … }
#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::ImageSkia CompositeImagesAndApplyMask(
const gfx::ImageSkia& foreground_image,
const gfx::ImageSkia& background_image) {
TRACE_EVENT0("ui", "apps::CompositeImagesAndApplyMask");
bool should_extract_subset_foreground = ShouldExtractSubset(foreground_image);
bool should_extract_subset_background = ShouldExtractSubset(background_image);
if (!should_extract_subset_foreground && !should_extract_subset_background) {
return gfx::ImageSkiaOperations::CreateMaskedImage(
gfx::ImageSkiaOperations::CreateSuperimposedImage(background_image,
foreground_image),
LoadMaskImage(GetScaleToSize(foreground_image)));
}
gfx::ImageSkia foreground = should_extract_subset_foreground
? ExtractSubsetForArcImage(foreground_image)
: foreground_image;
gfx::ImageSkia background = should_extract_subset_background
? ExtractSubsetForArcImage(background_image)
: background_image;
return gfx::ImageSkiaOperations::CreateMaskedImage(
gfx::ImageSkiaOperations::CreateSuperimposedImage(background, foreground),
LoadMaskImage(GetScaleToSize(foreground)));
}
void ArcRawIconPngDataToImageSkia(
arc::mojom::RawIconPngDataPtr icon,
int size_hint_in_dip,
base::OnceCallback<void(const gfx::ImageSkia& icon)> callback) {
TRACE_EVENT0("ui", "apps::ArcRawIconPngDataToImageSkia");
if (!icon) {
std::move(callback).Run(gfx::ImageSkia());
return;
}
if (!icon->is_adaptive_icon) {
if (!icon->icon_png_data.has_value()) {
std::move(callback).Run(gfx::ImageSkia());
return;
}
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(size_hint_in_dip,
std::move(callback));
icon_loader->LoadArcIconPngData(icon->icon_png_data.value());
return;
}
if (!icon->foreground_icon_png_data.has_value() ||
icon->foreground_icon_png_data.value().empty() ||
!icon->background_icon_png_data.has_value() ||
icon->background_icon_png_data.value().empty()) {
std::move(callback).Run(gfx::ImageSkia());
return;
}
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(size_hint_in_dip,
std::move(callback));
icon_loader->LoadCompositeImages(
std::move(icon->foreground_icon_png_data.value()),
std::move(icon->background_icon_png_data.value()));
}
void ArcActivityIconsToImageSkias(
const std::vector<arc::mojom::ActivityIconPtr>& icons,
base::OnceCallback<void(const std::vector<gfx::ImageSkia>& icons)>
callback) {
TRACE_EVENT0("ui", "apps::ArcActivityIconsToImageSkias");
if (icons.empty()) {
std::move(callback).Run(std::vector<gfx::ImageSkia>{});
return;
}
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(std::move(callback));
icon_loader->LoadArcActivityIcons(icons);
}
gfx::ImageSkia ConvertSquareBitmapsToImageSkia(
const std::map<web_app::SquareSizePx, SkBitmap>& icon_bitmaps,
IconEffects icon_effects,
int size_hint_in_dip) {
TRACE_EVENT0("ui", "apps::ConvertSquareBitmapsToImageSkia");
auto image_skia =
ConvertIconBitmapsToImageSkia(icon_bitmaps, size_hint_in_dip);
if (image_skia.isNull()) {
return gfx::ImageSkia{};
}
if ((icon_effects & IconEffects::kCrOsStandardMask) &&
(icon_effects & IconEffects::kCrOsStandardBackground)) {
image_skia = apps::ApplyBackgroundAndMask(image_skia);
}
return image_skia;
}
#endif
gfx::ImageSkia ConvertIconBitmapsToImageSkia(
const std::map<web_app::SquareSizePx, SkBitmap>& icon_bitmaps,
int size_hint_in_dip) { … }
void ApplyIconEffects(Profile* profile,
const std::optional<std::string>& app_id,
IconEffects icon_effects,
int size_hint_in_dip,
IconValuePtr iv,
LoadIconCallback callback) { … }
void ConvertUncompressedIconToCompressedIconWithScale(float rep_icon_scale,
LoadIconCallback callback,
IconValuePtr iv) { … }
void ConvertUncompressedIconToCompressedIcon(IconValuePtr iv,
LoadIconCallback callback) { … }
void LoadIconFromExtension(IconType icon_type,
int size_hint_in_dip,
Profile* profile,
const std::string& extension_id,
IconEffects icon_effects,
LoadIconCallback callback) { … }
void LoadIconFromWebApp(Profile* profile,
IconType icon_type,
int size_hint_in_dip,
const std::string& web_app_id,
IconEffects icon_effects,
LoadIconCallback callback) { … }
#if BUILDFLAG(IS_CHROMEOS)
void GetWebAppCompressedIconData(Profile* profile,
const std::string& web_app_id,
int size_in_dip,
ui::ResourceScaleFactor scale_factor,
LoadIconCallback callback) {
TRACE_EVENT0("ui", "apps::GetWebAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(profile);
web_app::WebAppProvider* web_app_provider =
web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
DCHECK(web_app_provider);
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(
profile, std::nullopt, IconType::kCompressed, size_in_dip,
false, IconEffects::kNone,
kInvalidIconResource, std::move(callback));
icon_loader->GetWebAppCompressedIconData(web_app_id, scale_factor,
web_app_provider->icon_manager());
}
void GetChromeAppCompressedIconData(Profile* profile,
const std::string& extension_id,
int size_in_dip,
ui::ResourceScaleFactor scale_factor,
LoadIconCallback callback) {
TRACE_EVENT0("ui", "apps::GetChromeAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(
profile, std::nullopt, IconType::kCompressed, size_in_dip,
false, IconEffects::kNone,
IDR_APP_DEFAULT_ICON, std::move(callback));
icon_loader->GetChromeAppCompressedIconData(
extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
extension_id),
scale_factor);
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
void GetArcAppCompressedIconData(Profile* profile,
const std::string& app_id,
int size_in_dip,
ui::ResourceScaleFactor scale_factor,
LoadIconCallback callback) {
TRACE_EVENT0("ui", "apps::GetArcAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(profile);
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile);
if (!prefs) {
std::move(callback).Run(std::make_unique<IconValue>());
return;
}
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(
profile, app_id, IconType::kCompressed, size_in_dip,
false, IconEffects::kNone,
kInvalidIconResource, std::move(callback));
icon_loader->GetArcAppCompressedIconData(app_id, prefs, scale_factor);
}
void GetGuestOSAppCompressedIconData(Profile* profile,
const std::string& app_id,
int size_in_dip,
ui::ResourceScaleFactor scale_factor,
LoadIconCallback callback) {
TRACE_EVENT0("ui", "apps::GetGuestOSAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(profile);
scoped_refptr<AppIconLoader> icon_loader =
base::MakeRefCounted<AppIconLoader>(
profile, app_id, IconType::kCompressed, size_in_dip,
false, IconEffects::kNone,
kInvalidIconResource, std::move(callback));
icon_loader->GetGuestOSAppCompressedIconData(app_id, scale_factor);
}
#endif
void LoadIconFromFileWithFallback(
IconType icon_type,
int size_hint_in_dip,
const base::FilePath& path,
IconEffects icon_effects,
LoadIconCallback callback,
base::OnceCallback<void(LoadIconCallback)> fallback) { … }
void LoadIconFromCompressedData(IconType icon_type,
int size_hint_in_dip,
IconEffects icon_effects,
const std::string& compressed_icon_data,
LoadIconCallback callback) { … }
void LoadIconFromResource(Profile* profile,
std::optional<std::string> app_id,
IconType icon_type,
int size_hint_in_dip,
int resource_id,
bool is_placeholder_icon,
IconEffects icon_effects,
LoadIconCallback callback) { … }
}