chromium/chrome/browser/apps/app_service/app_icon/app_icon_decoder.cc

// Copyright 2022 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/apps/app_service/app_icon/app_icon_decoder.h"

#include <functional>

#include "base/functional/bind.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h"
#include "chrome/browser/image_decoder/image_decoder.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "services/data_decoder/public/mojom/image_decoder.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"

namespace apps {

AppIconDecoder::ImageSource::ImageSource(int32_t size_in_dip)
    : size_in_dip_(size_in_dip) {}

AppIconDecoder::ImageSource::~ImageSource() = default;

gfx::ImageSkiaRep AppIconDecoder::ImageSource::GetImageForScale(float scale) {
  TRACE_EVENT0("ui", "AppIconDecoder::ImageSource::GetImageForScale");
  // Host loads icon asynchronously, so use default icon so far.

  // Get the ImageSkia for the resource IDR_APP_DEFAULT_ICON and the size
  // `size_in_dip_`.
  return CreateResizedResourceImage(IDR_APP_DEFAULT_ICON, size_in_dip_)
      .GetRepresentation(scale);
}

AppIconDecoder::AppIconDecoder(
    const base::FilePath& base_path,
    const std::string& app_id,
    int32_t size_in_dip,
    base::OnceCallback<void(AppIconDecoder* decoder, IconValuePtr iv)> callback)
    : base_path_(base_path),
      app_id_(app_id),
      size_in_dip_(size_in_dip),
      callback_(std::move(callback)) {}

AppIconDecoder::~AppIconDecoder() = default;

void AppIconDecoder::Start() {
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(&ReadIconFilesOnBackgroundThread, base_path_, app_id_,
                     size_in_dip_),
      base::BindOnce(&AppIconDecoder::OnIconRead,
                     weak_ptr_factory_.GetWeakPtr()));
}

bool AppIconDecoder::SetScaleFactors(
    const std::map<ui::ResourceScaleFactor, IconValuePtr>& icon_datas) {
  TRACE_EVENT0("ui", "AppIconDecoder::SetScaleFactors");
  for (const auto& [scale_factor, iv] : icon_datas) {
    if (!iv || iv->icon_type != IconType::kCompressed) {
      return false;
    }

    if (HasAdaptiveIconData(iv)) {
      is_adaptive_icon_ = true;
      foreground_incomplete_scale_factors_.insert(scale_factor);
      background_incomplete_scale_factors_.insert(scale_factor);
    } else if (iv->compressed.empty()) {
      return false;
    } else {
      incomplete_scale_factors_.insert(scale_factor);
    }
  }

  if (is_adaptive_icon_ && !incomplete_scale_factors_.empty()) {
    // Some scales have non-adaptive icons. Then we can't generate the adaptive
    // icon for all scales. Set `is_adaptive_icon_` as false, and decode the
    // foreground images only for scales with adaptive icon data.
    is_adaptive_icon_ = false;
    for (const auto& [scale_factor, iv] : icon_datas) {
      incomplete_scale_factors_.insert(scale_factor);
    }
  }

  // Initialize the ImageSkia with placeholder bitmaps, and the correct icon
  // size to generate the adaptive icon using CompositeImagesAndApplyMask, which
  // checks the ImageSkia's size to chop for paddings and resize the image_reps.
  gfx::Size image_size(size_in_dip_, size_in_dip_);
  if (is_adaptive_icon_) {
    foreground_image_skia_ =
        gfx::ImageSkia(std::make_unique<ImageSource>(size_in_dip_), image_size);
    background_image_skia_ =
        gfx::ImageSkia(std::make_unique<ImageSource>(size_in_dip_), image_size);
  } else {
    image_skia_ =
        gfx::ImageSkia(std::make_unique<ImageSource>(size_in_dip_), image_size);
  }
  return true;
}

void AppIconDecoder::OnIconRead(
    std::map<ui::ResourceScaleFactor, IconValuePtr> icon_datas) {
  TRACE_EVENT0("ui", "AppIconDecoder::OnIconRead");
  // Check `icon_datas` to set scale factors.
  if (!SetScaleFactors(icon_datas)) {
    DiscardDecodeRequest();
    return;
  }

  // Create DecodeRequest to decode images safely in a sandboxed service per
  // security requests.
  for (auto& [scale_factor, iv] : icon_datas) {
    if (HasAdaptiveIconData(iv)) {
      if (!is_adaptive_icon_) {
        // If we can't generate the adaptive icon for all scales, decode the
        // foreground images only to fill in `image_skia_`.
        DecodeImage(scale_factor, std::move(iv->foreground_icon_png_data),
                    image_skia_, incomplete_scale_factors_);
        continue;
      }

      // Decode for the foreground and background image.
      DecodeImage(scale_factor, std::move(iv->foreground_icon_png_data),
                  foreground_image_skia_, foreground_incomplete_scale_factors_);
      DecodeImage(scale_factor, std::move(iv->background_icon_png_data),
                  background_image_skia_, background_incomplete_scale_factors_);
      continue;
    }

    is_maskable_icon_ = iv->is_maskable_icon;
    DecodeImage(scale_factor, std::move(iv->compressed), image_skia_,
                incomplete_scale_factors_);
  }
}

void AppIconDecoder::DecodeImage(
    ui::ResourceScaleFactor scale_factor,
    std::vector<uint8_t> icon_data,
    gfx::ImageSkia& image_skia,
    std::set<ui::ResourceScaleFactor>& incomplete_scale_factors) {
  data_decoder::DecodeImage(
      &GetIconDataDecoder(), std::move(icon_data),
      data_decoder::mojom::ImageCodec::kDefault,
      /*shrink_to_fit=*/false, data_decoder::kDefaultMaxSizeInBytes,
      /*desired_image_frame_size=*/gfx::Size(),
      base::BindOnce(&AppIconDecoder::UpdateImageSkia,
                     weak_ptr_factory_.GetWeakPtr(), scale_factor,
                     std::ref(image_skia), std::ref(incomplete_scale_factors)));
}

void AppIconDecoder::UpdateImageSkia(
    ui::ResourceScaleFactor scale_factor,
    gfx::ImageSkia& image_skia,
    std::set<ui::ResourceScaleFactor>& incomplete_scale_factors,
    const SkBitmap& bitmap) {
  TRACE_EVENT0("ui", "AppIconDecoder::UpdateImageSkia");
  // If decoding any scale factor fails, discard the entire decode request.
  if (bitmap.drawsNothing()) {
    DiscardDecodeRequest();
    return;
  }

  CHECK(ui::IsScaleFactorSupported(scale_factor));
  gfx::ImageSkiaRep image_rep(bitmap,
                              ui::GetScaleForResourceScaleFactor(scale_factor));
  image_skia.RemoveRepresentation(image_rep.scale());
  image_skia.AddRepresentation(image_rep);
  image_skia.RemoveUnsupportedRepresentationsForScale(image_rep.scale());

  incomplete_scale_factors.erase(scale_factor);

  // For the adaptive icon, generate the adaptive icon with the foreground and
  // background icon images.
  if (is_adaptive_icon_) {
    if (foreground_incomplete_scale_factors_.empty() &&
        background_incomplete_scale_factors_.empty()) {
      auto image = apps::CompositeImagesAndApplyMask(foreground_image_skia_,
                                                     background_image_skia_);
      image.MakeThreadSafe();
      CompleteWithImageSkia(image);
    }
    return;
  }

  if (incomplete_scale_factors_.empty()) {
    CompleteWithImageSkia(image_skia_);
  }
}

void AppIconDecoder::DiscardDecodeRequest() {
  // `callback_` is responsible for removing this AppIconDecoder object, which
  // will cause all pending decode results to be dropped.
  //
  // Return an empty icon value, because the callers assume the icon value
  // should never be nullptr.
  std::move(callback_).Run(this, std::make_unique<apps::IconValue>());
}

void AppIconDecoder::CompleteWithImageSkia(const gfx::ImageSkia& image_skia) {
  TRACE_EVENT0("ui", "AppIconDecoder::CompleteWithImageSkia");
  // `callback_` is responsible for removing this AppIconDecoder object.
  auto iv = std::make_unique<apps::IconValue>();
  iv->icon_type = IconType::kUncompressed;
  iv->uncompressed = image_skia;
  iv->is_maskable_icon = is_maskable_icon_;
  std::move(callback_).Run(this, std::move(iv));
}

}  // namespace apps