910e62b5创建于 1月15日历史提交
// 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