| // Copyright 2012 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/themes/browser_theme_pack.h" |
| |
| #include <limits.h> |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/heap_array.h" |
| #include "base/containers/span.h" |
| #include "base/files/file.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/frame/window_frame_util.h" |
| #include "chrome/browser/ui/tabs/tab_group_theme.h" |
| #include "chrome/common/extensions/manifest_handlers/theme_handler.h" |
| #include "chrome/common/themes/autogenerated_theme_util.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "components/crx_file/id_util.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/resource/data_pack.h" |
| #include "ui/color/color_mixer.h" |
| #include "ui/color/color_provider.h" |
| #include "ui/color/color_recipe.h" |
| #include "ui/color/color_transform.h" |
| #include "ui/color/dynamic_color/palette_factory.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/color_analysis.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/gfx/image/canvas_image_source.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" |
| |
| using content::BrowserThread; |
| using extensions::Extension; |
| using TP = ThemeProperties; |
| |
| // Persistent constants for the main images that we need. These have the same |
| // names as their IDR_* counterparts but these values will always stay the |
| // same. |
| enum BrowserThemePack::PersistentID : int { |
| kInvalid = -1, |
| kFrame = 1, |
| kFrameInactive = 2, |
| kFrameIncognito = 3, |
| kFrameIncognitoInactive = 4, |
| kToolbar = 5, |
| kTabBackground = 6, |
| kTabBackgroundIncognito = 7, |
| // kTabBackgroundV = 8, Deprecated and unused. Previously supported windows |
| // vista aero glass theme. |
| kNtpBackground = 9, |
| kFrameOverlay = 10, |
| kFrameOverlayInactive = 11, |
| kButtonBackground = 12, |
| kNtpAttribution = 13, |
| kWindowControlBackground = 14, |
| kTabBackgroundInactive = 15, |
| kTabBackgroundIncognitoInactive = 16, |
| kMaxValue = kTabBackgroundIncognitoInactive, |
| }; |
| |
| namespace { |
| |
| using PRS = BrowserThemePack::PersistentID; |
| |
| // The tallest tab height in any mode. |
| constexpr int kTallestTabHeight = 41; |
| |
| // The tallest height above the tabs in any mode is 19 DIP. |
| constexpr int kTallestFrameHeight = kTallestTabHeight + 19; |
| |
| // Version number of the current theme pack. We just throw out and rebuild |
| // theme packs that aren't int-equal to this. Increment this number if you |
| // changed default theme assets, if you need themes to recreate their generated |
| // images (which are cached), if you changed how missing values are |
| // generated, or if you changed any constants. |
| const int kThemePackVersion = 104; |
| |
| // IDs that are in the DataPack won't clash with the positive integer |
| // uint16_t. kHeaderID should always have the maximum value because we want the |
| // "header" to be written last. That way we can detect whether the pack was |
| // successfully written and ignore and regenerate if it was only partially |
| // written (i.e. chrome crashed on a different thread while writing the pack). |
| const int kMaxID = 0x0000FFFF; // Max unsigned 16-bit int. |
| const int kHeaderID = kMaxID - 1; |
| const int kTintsID = kMaxID - 2; |
| const int kColorsID = kMaxID - 3; |
| const int kDisplayPropertiesID = kMaxID - 4; |
| const int kSourceImagesID = kMaxID - 5; |
| const int kScaleFactorsID = kMaxID - 6; |
| const int kTabGroupColorPaletteShadesID = kMaxID - 7; |
| |
| struct PersistingImagesTable { |
| // A non-changing integer ID meant to be saved in theme packs. This ID must |
| // not change between versions of chrome. |
| BrowserThemePack::PersistentID persistent_id; |
| |
| // The IDR that depends on the whims of GRIT and therefore changes whenever |
| // someone adds a new resource. |
| int idr_id; |
| |
| // String to check for when parsing theme manifests. |
| const char* const key; |
| }; |
| |
| // IDR_* resource names change whenever new resources are added; use persistent |
| // IDs when storing to a cached pack. |
| constexpr PersistingImagesTable kPersistingImages[] = { |
| {PRS::kFrame, IDR_THEME_FRAME, "theme_frame"}, |
| {PRS::kFrameInactive, IDR_THEME_FRAME_INACTIVE, "theme_frame_inactive"}, |
| {PRS::kFrameIncognito, IDR_THEME_FRAME_INCOGNITO, "theme_frame_incognito"}, |
| {PRS::kFrameIncognitoInactive, IDR_THEME_FRAME_INCOGNITO_INACTIVE, |
| "theme_frame_incognito_inactive"}, |
| {PRS::kToolbar, IDR_THEME_TOOLBAR, "theme_toolbar"}, |
| {PRS::kTabBackground, IDR_THEME_TAB_BACKGROUND, "theme_tab_background"}, |
| {PRS::kTabBackgroundInactive, IDR_THEME_TAB_BACKGROUND_INACTIVE, |
| "theme_tab_background_inactive"}, |
| {PRS::kTabBackgroundIncognito, IDR_THEME_TAB_BACKGROUND_INCOGNITO, |
| "theme_tab_background_incognito"}, |
| {PRS::kTabBackgroundIncognitoInactive, |
| IDR_THEME_TAB_BACKGROUND_INCOGNITO_INACTIVE, |
| "theme_tab_background_incognito_inactive"}, |
| {PRS::kNtpBackground, IDR_THEME_NTP_BACKGROUND, "theme_ntp_background"}, |
| {PRS::kFrameOverlay, IDR_THEME_FRAME_OVERLAY, "theme_frame_overlay"}, |
| {PRS::kFrameOverlayInactive, IDR_THEME_FRAME_OVERLAY_INACTIVE, |
| "theme_frame_overlay_inactive"}, |
| {PRS::kButtonBackground, IDR_THEME_BUTTON_BACKGROUND, |
| "theme_button_background"}, |
| {PRS::kNtpAttribution, IDR_THEME_NTP_ATTRIBUTION, "theme_ntp_attribution"}, |
| {PRS::kWindowControlBackground, IDR_THEME_WINDOW_CONTROL_BACKGROUND, |
| "theme_window_control_background"}, |
| |
| // /!\ If you make any changes here, you must also increment |
| // kThemePackVersion above, or else themes will display incorrectly. |
| }; |
| |
| BrowserThemePack::PersistentID GetPersistentIDByName(const std::string& key) { |
| auto* it = std::ranges::find_if(kPersistingImages, [&](const auto& image) { |
| return base::EqualsCaseInsensitiveASCII(key, image.key); |
| }); |
| return it == std::end(kPersistingImages) ? PRS::kInvalid : it->persistent_id; |
| } |
| |
| BrowserThemePack::PersistentID GetPersistentIDByIDR(int idr) { |
| auto* it = |
| std::ranges::find(kPersistingImages, idr, &PersistingImagesTable::idr_id); |
| return it == std::end(kPersistingImages) ? PRS::kInvalid : it->persistent_id; |
| } |
| |
| // Returns true if the scales in |input| match those in |expected|. |
| // The order must match as the index is used in determining the raw id. |
| bool InputScalesValid(std::string_view input, |
| base::span<ui::ResourceScaleFactor> expected) { |
| if (input.size() != expected.size() * sizeof(float)) { |
| return false; |
| } |
| auto scales = base::HeapArray<float>::WithSize(expected.size()); |
| base::as_writable_byte_span(base::allow_nonunique_obj, scales) |
| .copy_from(base::as_byte_span(input)); |
| for (size_t index = 0; index < expected.size(); ++index) { |
| if (scales[index] != ui::GetScaleForResourceScaleFactor(expected[index])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Returns |scale_factors| as a string to be written to disk. |
| std::string GetResourceScaleFactorsAsString( |
| base::span<const ui::ResourceScaleFactor> scale_factors) { |
| auto scales = base::HeapArray<float>::WithSize(scale_factors.size()); |
| for (size_t i = 0; i < scale_factors.size(); ++i) { |
| scales[i] = ui::GetScaleForResourceScaleFactor(scale_factors[i]); |
| } |
| return std::string(base::as_string_view( |
| base::as_byte_span(base::allow_nonunique_obj, scales))); |
| } |
| |
| // Stores the string |key| which is used by themes and its corresponding |
| // enumeration |id|. `T` currently represents |
| // `TP::OverwritableByUserThemeProperty` and `tab_groups::TabGroupColorId` |
| template <typename T> |
| struct StringToIdTable { |
| const char* const key; |
| T id; |
| }; |
| |
| // Strings used by themes to identify tints in the JSON. |
| const StringToIdTable<TP::OverwritableByUserThemeProperty> kTintTable[] = { |
| {"background_tab", TP::TINT_BACKGROUND_TAB}, |
| {"buttons", TP::TINT_BUTTONS}, |
| {"frame", TP::TINT_FRAME}, |
| {"frame_inactive", TP::TINT_FRAME_INACTIVE}, |
| {"frame_incognito", TP::TINT_FRAME_INCOGNITO}, |
| {"frame_incognito_inactive", TP::TINT_FRAME_INCOGNITO_INACTIVE}, |
| |
| // /!\ If you make any changes here, you must also increment |
| // kThemePackVersion above, or else themes will display incorrectly. |
| }; |
| const size_t kTintTableLength = std::size(kTintTable); |
| |
| // Strings used by themes to identify colors in the JSON. |
| constexpr StringToIdTable<TP::OverwritableByUserThemeProperty> |
| kOverwritableColorTable[] = { |
| {"background_tab", TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE}, |
| {"background_tab_inactive", |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE}, |
| {"background_tab_incognito", |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO}, |
| {"background_tab_incognito_inactive", |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO}, |
| {"bookmark_text", TP::COLOR_BOOKMARK_TEXT}, |
| {"button_background", TP::COLOR_CONTROL_BUTTON_BACKGROUND}, |
| {"frame", TP::COLOR_FRAME_ACTIVE}, |
| {"frame_inactive", TP::COLOR_FRAME_INACTIVE}, |
| {"frame_incognito", TP::COLOR_FRAME_ACTIVE_INCOGNITO}, |
| {"frame_incognito_inactive", TP::COLOR_FRAME_INACTIVE_INCOGNITO}, |
| {"ntp_background", TP::COLOR_NTP_BACKGROUND}, |
| {"ntp_header", TP::COLOR_NTP_HEADER}, |
| {"ntp_link", TP::COLOR_NTP_LINK}, |
| {"ntp_text", TP::COLOR_NTP_TEXT}, |
| {"omnibox_background", TP::COLOR_OMNIBOX_BACKGROUND}, |
| {"omnibox_text", TP::COLOR_OMNIBOX_TEXT}, |
| {"tab_background_text", TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE}, |
| {"tab_background_text_inactive", |
| TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE}, |
| {"tab_background_text_incognito", |
| TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO}, |
| {"tab_background_text_incognito_inactive", |
| TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO}, |
| {"tab_text", TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE}, |
| {"toolbar", TP::COLOR_TOOLBAR}, |
| {"toolbar_button_icon", TP::COLOR_TOOLBAR_BUTTON_ICON}, |
| {"toolbar_text", TP::COLOR_TOOLBAR_TEXT}, |
| |
| // /!\ If you make any changes here, you must also increment |
| // kThemePackVersion above, or else themes will display incorrectly. |
| }; |
| constexpr size_t kOverwritableColorTableLength = |
| std::size(kOverwritableColorTable); |
| |
| // Colors generated based on the theme, but not overwritable in the theme file. |
| constexpr int kNonOverwritableColorTable[] = { |
| TP::COLOR_NTP_LOGO, |
| TP::COLOR_NTP_SECTION_BORDER, |
| TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE, |
| TP::COLOR_TAB_THROBBER_SPINNING, |
| TP::COLOR_TAB_THROBBER_WAITING, |
| TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, |
| TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, |
| TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE, |
| TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE, |
| TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE, |
| TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE |
| |
| // /!\ If you make any changes here, you must also increment |
| // kThemePackVersion above, or else themes will display incorrectly. |
| }; |
| constexpr size_t kNonOverwritableColorTableLength = |
| std::size(kNonOverwritableColorTable); |
| |
| // The maximum number of colors we may need to store (includes ones that can be |
| // specified by the theme, and ones that we calculate but can't be specified). |
| constexpr size_t kColorsArrayLength = |
| kOverwritableColorTableLength + kNonOverwritableColorTableLength; |
| |
| // Strings used by themes to identify display properties keys in JSON. |
| const StringToIdTable<TP::OverwritableByUserThemeProperty> |
| kDisplayProperties[] = { |
| {"ntp_background_alignment", TP::NTP_BACKGROUND_ALIGNMENT}, |
| {"ntp_background_repeat", TP::NTP_BACKGROUND_TILING}, |
| {"ntp_logo_alternate", TP::NTP_LOGO_ALTERNATE}, |
| |
| // /!\ If you make any changes here, you must also increment |
| // kThemePackVersion above, or else themes will display incorrectly. |
| }; |
| const size_t kDisplayPropertiesSize = std::size(kDisplayProperties); |
| |
| const StringToIdTable<tab_groups::TabGroupColorId> kTabGroupColorIdTable[] = { |
| {"grey_override", tab_groups::TabGroupColorId::kGrey}, |
| {"blue_override", tab_groups::TabGroupColorId::kBlue}, |
| {"red_override", tab_groups::TabGroupColorId::kRed}, |
| {"yellow_override", tab_groups::TabGroupColorId::kYellow}, |
| {"green_override", tab_groups::TabGroupColorId::kGreen}, |
| {"pink_override", tab_groups::TabGroupColorId::kPink}, |
| {"purple_override", tab_groups::TabGroupColorId::kPurple}, |
| {"cyan_override", tab_groups::TabGroupColorId::kCyan}, |
| {"orange_override", tab_groups::TabGroupColorId::kOrange}, |
| }; |
| const size_t kTabGroupColorIdTableSize = std::size(kTabGroupColorIdTable); |
| |
| template <typename T> |
| int GetIdForString(const std::string& key, |
| base::span<const StringToIdTable<T>> table, |
| size_t spanification_suspected_redundant_table_length) { |
| // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be |
| // redundant in M143. |
| CHECK(spanification_suspected_redundant_table_length == table.size(), |
| base::NotFatalUntil::M143); |
| for (size_t i = 0; i < spanification_suspected_redundant_table_length; ++i) { |
| if (base::EqualsCaseInsensitiveASCII(key, table[i].key)) { |
| return static_cast<int>(table[i].id); |
| } |
| } |
| |
| return -1; |
| } |
| |
| struct CropEntry { |
| BrowserThemePack::PersistentID prs_id; |
| |
| // The maximum useful height of the image at |prs_id|. |
| int max_height; |
| }; |
| |
| // The images which should be cropped before being saved to the data pack. The |
| // maximum heights are meant to be conservative as to give room for the UI to |
| // change without the maximum heights having to be modified. |
| // |kThemePackVersion| must be incremented if any of the maximum heights below |
| // are modified. |
| const struct CropEntry kImagesToCrop[] = { |
| {PRS::kFrame, kTallestFrameHeight}, |
| {PRS::kFrameInactive, kTallestFrameHeight}, |
| {PRS::kFrameIncognito, kTallestFrameHeight}, |
| {PRS::kFrameIncognitoInactive, kTallestFrameHeight}, |
| {PRS::kFrameOverlay, kTallestFrameHeight}, |
| {PRS::kFrameOverlayInactive, kTallestFrameHeight}, |
| {PRS::kToolbar, 200}, |
| {PRS::kButtonBackground, 60}, |
| {PRS::kWindowControlBackground, 50}, |
| }; |
| |
| // A list of images that don't need tinting or any other modification and can |
| // be byte-copied directly into the finished DataPack. This should contain the |
| // persistent IDs for all themeable image IDs that aren't in kFrameValues, |
| // kTabBackgroundMap or kImagesToCrop. |
| constexpr auto kPreloadIDs = std::to_array<BrowserThemePack::PersistentID>({ |
| PRS::kNtpBackground, |
| PRS::kNtpAttribution, |
| }); |
| |
| // Returns a piece of memory with the contents of the file |path|. |
| scoped_refptr<base::RefCountedMemory> ReadFileData(const base::FilePath& path) { |
| if (!path.empty()) { |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (file.IsValid()) { |
| int64_t length = file.GetLength(); |
| if (length > 0 && length < INT_MAX) { |
| int size = static_cast<int>(length); |
| std::vector<unsigned char> raw_data; |
| raw_data.resize(size); |
| char* data = reinterpret_cast<char*>(&(raw_data.front())); |
| if (UNSAFE_TODO(file.ReadAtCurrentPos(data, size)) == length) { |
| return base::MakeRefCounted<base::RefCountedBytes>( |
| std::move(raw_data)); |
| } |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| // Computes a bitmap at one scale from a bitmap at a different scale. |
| SkBitmap CreateLowQualityResizedBitmap( |
| const SkBitmap& source_bitmap, |
| ui::ResourceScaleFactor source_scale_factor, |
| ui::ResourceScaleFactor desired_scale_factor) { |
| gfx::Size scaled_size = gfx::ScaleToCeiledSize( |
| gfx::Size(source_bitmap.width(), source_bitmap.height()), |
| ui::GetScaleForResourceScaleFactor(desired_scale_factor) / |
| ui::GetScaleForResourceScaleFactor(source_scale_factor)); |
| SkBitmap scaled_bitmap; |
| scaled_bitmap.allocN32Pixels(scaled_size.width(), scaled_size.height()); |
| scaled_bitmap.eraseARGB(0, 0, 0, 0); |
| SkCanvas canvas(scaled_bitmap, SkSurfaceProps{}); |
| SkRect scaled_bounds = RectToSkRect(gfx::Rect(scaled_size)); |
| // Note(oshima): The following scaling code doesn't work with |
| // a mask image. |
| canvas.drawImageRect(source_bitmap.asImage(), scaled_bounds, |
| SkSamplingOptions()); |
| return scaled_bitmap; |
| } |
| |
| // A ImageSkiaSource that scales 100P image to the target scale factor |
| // if the ImageSkiaRep for the target scale factor isn't available. |
| class ThemeImageSource : public gfx::ImageSkiaSource { |
| public: |
| explicit ThemeImageSource(const gfx::ImageSkia& source) : source_(source) {} |
| |
| ThemeImageSource(const ThemeImageSource&) = delete; |
| ThemeImageSource& operator=(const ThemeImageSource&) = delete; |
| |
| ~ThemeImageSource() override = default; |
| |
| gfx::ImageSkiaRep GetImageForScale(float scale) override { |
| if (source_.HasRepresentation(scale)) { |
| return source_.GetRepresentation(scale); |
| } |
| const gfx::ImageSkiaRep& rep_100p = source_.GetRepresentation(1.0f); |
| SkBitmap scaled_bitmap = CreateLowQualityResizedBitmap( |
| rep_100p.GetBitmap(), ui::k100Percent, |
| ui::GetSupportedResourceScaleFactor(scale)); |
| return gfx::ImageSkiaRep(scaled_bitmap, scale); |
| } |
| |
| private: |
| const gfx::ImageSkia source_; |
| }; |
| |
| // An ImageSkiaSource that delays decoding PNG data into bitmaps until |
| // needed. Missing data for a scale factor is computed by scaling data for an |
| // available scale factor. Computed bitmaps are stored for future look up. |
| class ThemeImagePngSource : public gfx::ImageSkiaSource { |
| public: |
| typedef std::map<ui::ResourceScaleFactor, |
| scoped_refptr<base::RefCountedMemory>> |
| PngMap; |
| |
| explicit ThemeImagePngSource(const PngMap& png_map) : png_map_(png_map) {} |
| |
| ThemeImagePngSource(const ThemeImagePngSource&) = delete; |
| ThemeImagePngSource& operator=(const ThemeImagePngSource&) = delete; |
| |
| ~ThemeImagePngSource() override = default; |
| |
| private: |
| gfx::ImageSkiaRep GetImageForScale(float scale) override { |
| ui::ResourceScaleFactor scale_factor = |
| ui::GetSupportedResourceScaleFactor(scale); |
| // Look up the bitmap for |scale factor| in the bitmap map. If found |
| // return it. |
| BitmapMap::const_iterator exact_bitmap_it = bitmap_map_.find(scale_factor); |
| if (exact_bitmap_it != bitmap_map_.end()) { |
| return gfx::ImageSkiaRep(exact_bitmap_it->second, scale); |
| } |
| |
| // Look up the raw PNG data for |scale_factor| in the png map. If found, |
| // decode it, store the result in the bitmap map and return it. |
| PngMap::const_iterator exact_png_it = png_map_.find(scale_factor); |
| if (exact_png_it != png_map_.end()) { |
| SkBitmap bitmap = gfx::PNGCodec::Decode(*exact_png_it->second); |
| if (bitmap.isNull()) { |
| // The image is either broken or a different format. |
| return gfx::ImageSkiaRep(); |
| } |
| bitmap_map_[scale_factor] = bitmap; |
| return gfx::ImageSkiaRep(bitmap, scale); |
| } |
| |
| // Find an available PNG for another scale factor. We want to use the |
| // highest available scale factor. |
| PngMap::const_iterator available_png_it = png_map_.end(); |
| for (PngMap::const_iterator png_it = png_map_.begin(); |
| png_it != png_map_.end(); ++png_it) { |
| if (available_png_it == png_map_.end() || |
| ui::GetScaleForResourceScaleFactor(png_it->first) > |
| ui::GetScaleForResourceScaleFactor(available_png_it->first)) { |
| available_png_it = png_it; |
| } |
| } |
| if (available_png_it == png_map_.end()) { |
| return gfx::ImageSkiaRep(); |
| } |
| ui::ResourceScaleFactor available_scale_factor = available_png_it->first; |
| |
| // Look up the bitmap for |available_scale_factor| in the bitmap map. |
| // If not found, decode the corresponging png data, store the result |
| // in the bitmap map. |
| BitmapMap::const_iterator available_bitmap_it = |
| bitmap_map_.find(available_scale_factor); |
| if (available_bitmap_it == bitmap_map_.end()) { |
| SkBitmap available_bitmap = |
| gfx::PNGCodec::Decode(*available_png_it->second); |
| if (available_bitmap.isNull()) { |
| NOTREACHED(); |
| } |
| bitmap_map_[available_scale_factor] = available_bitmap; |
| available_bitmap_it = bitmap_map_.find(available_scale_factor); |
| } |
| |
| // Scale the available bitmap to the desired scale factor, store the result |
| // in the bitmap map and return it. |
| SkBitmap scaled_bitmap = CreateLowQualityResizedBitmap( |
| available_bitmap_it->second, available_scale_factor, scale_factor); |
| bitmap_map_[scale_factor] = scaled_bitmap; |
| return gfx::ImageSkiaRep(scaled_bitmap, scale); |
| } |
| |
| PngMap png_map_; |
| |
| typedef std::map<ui::ResourceScaleFactor, SkBitmap> BitmapMap; |
| BitmapMap bitmap_map_; |
| }; |
| |
| class TabBackgroundImageSource : public gfx::CanvasImageSource { |
| public: |
| TabBackgroundImageSource(SkColor background_color, |
| const gfx::ImageSkia& image_to_tint, |
| const gfx::ImageSkia& overlay, |
| const color_utils::HSL& hsl_shift, |
| int vertical_offset) |
| : gfx::CanvasImageSource(image_to_tint.isNull() ? overlay.size() |
| : image_to_tint.size()), |
| background_color_(background_color), |
| image_to_tint_(image_to_tint), |
| overlay_(overlay), |
| hsl_shift_(hsl_shift), |
| vertical_offset_(vertical_offset) {} |
| |
| TabBackgroundImageSource(const TabBackgroundImageSource&) = delete; |
| TabBackgroundImageSource& operator=(const TabBackgroundImageSource&) = delete; |
| |
| ~TabBackgroundImageSource() override = default; |
| |
| // Overridden from CanvasImageSource: |
| void Draw(gfx::Canvas* canvas) override { |
| canvas->DrawColor(background_color_); |
| |
| // Begin with the frame background image, if any. Since the frame and tabs |
| // have grown taller and changed alignment over time, not all themes have a |
| // sufficiently tall image; tiling by vertically mirroring in this case is |
| // the least-glitchy-looking option. Note that the behavior here needs to |
| // stay in sync with how the browser frame will actually be drawn. |
| if (!image_to_tint_.isNull()) { |
| gfx::ImageSkia bg_tint = gfx::ImageSkiaOperations::CreateHSLShiftedImage( |
| image_to_tint_, hsl_shift_); |
| canvas->TileImageInt(bg_tint, 0, vertical_offset_, 0, 0, size().width(), |
| size().height(), 1.0f, SkTileMode::kRepeat, |
| SkTileMode::kMirror); |
| } |
| |
| // If the theme has a custom tab background image, overlay it. Vertical |
| // mirroring is used for the same reason as above. This behavior needs to |
| // stay in sync with how tabs are drawn. |
| if (!overlay_.isNull()) { |
| canvas->TileImageInt(overlay_, 0, 0, 0, 0, size().width(), |
| size().height(), 1.0f, SkTileMode::kRepeat, |
| SkTileMode::kMirror); |
| } |
| } |
| |
| private: |
| const SkColor background_color_; |
| const gfx::ImageSkia image_to_tint_; |
| const gfx::ImageSkia overlay_; |
| const color_utils::HSL hsl_shift_; |
| const int vertical_offset_; |
| }; |
| |
| class ControlButtonBackgroundImageSource : public gfx::CanvasImageSource { |
| public: |
| ControlButtonBackgroundImageSource(SkColor background_color, |
| const gfx::ImageSkia& bg_image, |
| const gfx::Size& dest_size) |
| : gfx::CanvasImageSource(dest_size), |
| background_color_(background_color), |
| bg_image_(bg_image) { |
| DCHECK(!bg_image.isNull()); |
| } |
| |
| ControlButtonBackgroundImageSource( |
| const ControlButtonBackgroundImageSource&) = delete; |
| ControlButtonBackgroundImageSource& operator=( |
| const ControlButtonBackgroundImageSource&) = delete; |
| |
| ~ControlButtonBackgroundImageSource() override = default; |
| |
| void Draw(gfx::Canvas* canvas) override { |
| canvas->DrawColor(background_color_); |
| |
| if (!bg_image_.isNull()) { |
| canvas->DrawImageInt(bg_image_, 0, 0); |
| } |
| } |
| |
| private: |
| const SkColor background_color_; |
| const gfx::ImageSkia bg_image_; |
| }; |
| |
| // Returns whether the color is grayscale. |
| bool IsColorGrayscale(SkColor color) { |
| constexpr int kChannelTolerance = 9; |
| auto channels = {SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)}; |
| const int range = std::max(channels) - std::min(channels); |
| return range < kChannelTolerance; |
| } |
| |
| // The minimum contrast the omnibox background must have against the toolbar. |
| constexpr float kMinOmniboxToolbarContrast = 1.3f; |
| |
| ui::ColorTransform ChooseOmniboxBgBlendTarget() { |
| return base::BindRepeating( |
| [](SkColor input_color, const ui::ColorMixer& mixer) { |
| const SkColor toolbar_color = mixer.GetResultColor(kColorToolbar); |
| const SkColor endpoint_color = |
| color_utils::GetEndpointColorWithMinContrast(toolbar_color); |
| return (color_utils::GetContrastRatio(toolbar_color, endpoint_color) >= |
| kMinOmniboxToolbarContrast) |
| ? endpoint_color |
| : color_utils::GetColorWithMaxContrast(endpoint_color); |
| }); |
| } |
| |
| } // namespace |
| |
| BrowserThemePack::~BrowserThemePack() { |
| DCHECK(!data_pack_ || using_data_pack_); |
| |
| if (using_data_pack_) { |
| if (data_pack_) { |
| auto task_runner = base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); |
| DCHECK(task_runner); |
| task_runner->DeleteSoon(FROM_HERE, std::move(data_pack_)); |
| } |
| } else { |
| // When not using a data_pack, the memory is allocated and must be freed. |
| delete header_; |
| delete[] tints_; |
| delete[] colors_; |
| delete[] display_properties_; |
| delete[] source_images_; |
| } |
| } |
| |
| void BrowserThemePack::SetColor(int id, SkColor color) { |
| DCHECK(colors_); |
| |
| int first_available_color = -1; |
| for (size_t i = 0; i < kColorsArrayLength; ++i) { |
| if (UNSAFE_TODO(colors_[i]).id == id) { |
| UNSAFE_TODO(colors_[i]).color = color; |
| return; |
| } |
| if (UNSAFE_TODO(colors_[i]).id == -1 && first_available_color == -1) { |
| first_available_color = i; |
| } |
| } |
| |
| DCHECK_NE(-1, first_available_color); |
| UNSAFE_TODO(colors_[first_available_color]).id = id; |
| UNSAFE_TODO(colors_[first_available_color]).color = color; |
| } |
| |
| void BrowserThemePack::SetColorIfUnspecified(int id, SkColor color) { |
| SkColor temp_color; |
| if (!GetColor(id, &temp_color)) { |
| SetColor(id, color); |
| } |
| } |
| |
| void BrowserThemePack::SetTint(int id, color_utils::HSL tint) { |
| DCHECK(tints_); |
| |
| int first_available_index = -1; |
| for (size_t i = 0; i < kTintTableLength; ++i) { |
| if (UNSAFE_TODO(tints_[i]).id == id) { |
| UNSAFE_TODO(tints_[i]).h = tint.h; |
| UNSAFE_TODO(tints_[i]).s = tint.s; |
| UNSAFE_TODO(tints_[i]).l = tint.l; |
| return; |
| } |
| if (UNSAFE_TODO(tints_[i]).id == -1 && first_available_index == -1) { |
| first_available_index = i; |
| } |
| } |
| |
| DCHECK_NE(-1, first_available_index); |
| UNSAFE_TODO(tints_[first_available_index]).id = id; |
| UNSAFE_TODO(tints_[first_available_index]).h = tint.h; |
| UNSAFE_TODO(tints_[first_available_index]).s = tint.s; |
| UNSAFE_TODO(tints_[first_available_index]).l = tint.l; |
| } |
| |
| void BrowserThemePack::SetDisplayProperty(int id, int value) { |
| DCHECK(display_properties_); |
| |
| int first_available_index = -1; |
| for (size_t i = 0; i < kDisplayPropertiesSize; ++i) { |
| if (UNSAFE_TODO(display_properties_[i]).id == id) { |
| UNSAFE_TODO(display_properties_[i]).property = value; |
| return; |
| } |
| if (UNSAFE_TODO(display_properties_[i]).id == -1 && |
| first_available_index == -1) { |
| first_available_index = i; |
| } |
| } |
| |
| DCHECK_NE(-1, first_available_index); |
| UNSAFE_TODO(display_properties_[first_available_index]).id = id; |
| UNSAFE_TODO(display_properties_[first_available_index]).property = value; |
| } |
| |
| SkColor BrowserThemePack::ComputeImageColor(const gfx::Image& image, |
| int height) { |
| // Include all colors in the analysis. |
| constexpr color_utils::HSL kNoBounds = {-1, -1, -1}; |
| const SkColor color = color_utils::CalculateKMeanColorOfBitmap( |
| *image.ToSkBitmap(), height, kNoBounds, kNoBounds, false); |
| |
| return color; |
| } |
| |
| // static |
| void BrowserThemePack::BuildFromExtension( |
| const extensions::Extension* extension, |
| BrowserThemePack* pack) { |
| DCHECK(extension); |
| DCHECK(extension->is_theme()); |
| DCHECK(!pack->is_valid()); |
| |
| // NOTE! If you make any changes here, please update kThemePackVersion. |
| |
| pack->InitEmptyPack(); |
| pack->set_extension_id(extension->id()); |
| pack->SetHeaderId(extension); |
| pack->SetTintsFromJSON(extensions::ThemeInfo::GetTints(extension)); |
| pack->SetColorsFromJSON(extensions::ThemeInfo::GetColors(extension)); |
| pack->SetDisplayPropertiesFromJSON( |
| extensions::ThemeInfo::GetDisplayProperties(extension)); |
| pack->SetTabGroupColorPaletteShadesFromJSON( |
| extensions::ThemeInfo::GetTabGroupColorPalette(extension)); |
| |
| // Builds the images. (Image building is dependent on tints). |
| FilePathMap file_paths; |
| pack->ParseImageNamesFromJSON(extensions::ThemeInfo::GetImages(extension), |
| extension->path(), &file_paths); |
| pack->BuildSourceImagesArray(file_paths); |
| |
| if (!pack->LoadRawBitmapsTo(file_paths, &pack->images_)) { |
| return; |
| } |
| |
| pack->AdjustThemePack(); |
| |
| // The BrowserThemePack is now in a consistent state. |
| pack->is_valid_ = true; |
| } |
| |
| // static |
| scoped_refptr<BrowserThemePack> BrowserThemePack::BuildFromDataPack( |
| const base::FilePath& path, |
| const std::string& expected_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Allow IO on UI thread due to deep-seated theme design issues. |
| // (see http://crbug.com/80206) |
| base::ScopedAllowBlocking scoped_allow_blocking; |
| |
| // For now data pack can only have extension type. |
| scoped_refptr<BrowserThemePack> pack( |
| new BrowserThemePack(ThemeType::kExtension)); |
| pack->set_extension_id(expected_id); |
| |
| // The data pack is loaded in a local variable to be released synchronously |
| // in case of failures (see https://crbug.com/1292632). |
| // Scale factor parameter is moot as data pack has image resources for all |
| // supported scale factors. |
| std::unique_ptr<ui::DataPack> data_pack = |
| std::make_unique<ui::DataPack>(ui::kScaleFactorNone); |
| |
| // The browser theme pack is using a data pack. |
| pack->using_data_pack_ = true; |
| |
| if (!data_pack->LoadFromPath(path)) { |
| LOG(ERROR) << "Failed to load theme data pack."; |
| return nullptr; |
| } |
| |
| std::optional<std::string_view> pointer = data_pack->GetStringView(kHeaderID); |
| if (!pointer) { |
| return nullptr; |
| } |
| pack->header_ = reinterpret_cast<BrowserThemePackHeader*>( |
| const_cast<char*>(pointer->data())); |
| |
| if (pack->header_->version != kThemePackVersion) { |
| DLOG(ERROR) << "BuildFromDataPack failure! Version mismatch!"; |
| return nullptr; |
| } |
| // TODO(erg): Check endianess once DataPack works on the other endian. |
| std::string theme_id(reinterpret_cast<char*>(pack->header_->theme_id), |
| crx_file::id_util::kIdSize); |
| std::string truncated_id = expected_id.substr(0, crx_file::id_util::kIdSize); |
| if (theme_id != truncated_id) { |
| DLOG(ERROR) << "Wrong id: " << theme_id << " vs " << expected_id; |
| return nullptr; |
| } |
| |
| pointer = data_pack->GetStringView(kTintsID); |
| if (!pointer) { |
| return nullptr; |
| } |
| pack->tints_ = |
| reinterpret_cast<TintEntry*>(const_cast<char*>(pointer->data())); |
| |
| pointer = data_pack->GetStringView(kColorsID); |
| if (!pointer) { |
| return nullptr; |
| } |
| pack->colors_ = |
| reinterpret_cast<ColorPair*>(const_cast<char*>(pointer->data())); |
| |
| pointer = data_pack->GetStringView(kDisplayPropertiesID); |
| if (!pointer) { |
| return nullptr; |
| } |
| pack->display_properties_ = reinterpret_cast<DisplayPropertyPair*>( |
| const_cast<char*>(pointer->data())); |
| |
| pointer = data_pack->GetStringView(kTabGroupColorPaletteShadesID); |
| if (!pointer) { |
| return nullptr; |
| } |
| UNSAFE_TODO(std::memcpy(pack->tab_group_color_palette_shades_.data(), |
| pointer->data(), |
| sizeof(pack->tab_group_color_palette_shades_))); |
| |
| pointer = data_pack->GetStringView(kSourceImagesID); |
| if (!pointer) { |
| return nullptr; |
| } |
| pack->source_images_ = |
| reinterpret_cast<SourceImage*>(const_cast<char*>(pointer->data())); |
| |
| pointer = data_pack->GetStringView(kScaleFactorsID); |
| if (!pointer) { |
| return nullptr; |
| } |
| |
| if (!InputScalesValid(pointer.value(), pack->scale_factors_)) { |
| DLOG(ERROR) << "BuildFromDataPack failure! The pack scale factors differ " |
| << "from those supported by platform."; |
| return nullptr; |
| } |
| |
| // The loaded pack is valid and can be used as a theme pack. |
| pack->data_pack_ = std::move(data_pack); |
| pack->is_valid_ = true; |
| |
| return pack; |
| } |
| |
| // static |
| bool BrowserThemePack::IsPersistentImageID(int id) { |
| return GetPersistentIDByIDR(id) != PersistentID::kInvalid; |
| } |
| |
| // static |
| void BrowserThemePack::BuildFromColor(SkColor color, BrowserThemePack* pack) { |
| BuildFromColors(GetAutogeneratedThemeColors(color), pack); |
| } |
| |
| // static |
| void BrowserThemePack::BuildFromColors(AutogeneratedThemeColors colors, |
| BrowserThemePack* pack) { |
| pack->InitEmptyPackFromColors(); |
| |
| // NOTE! If you make any changes here, please update kThemePackVersion. |
| |
| // Frame. |
| pack->SetColor(TP::COLOR_FRAME_ACTIVE, colors.frame_color); |
| |
| // Inactive tab uses frame color. |
| pack->SetColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE, |
| colors.frame_color); |
| pack->SetColor(TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE, |
| colors.frame_text_color); |
| |
| // Toolbar and active tab (set in SetFrameAndToolbarRelatedColors) use active |
| // tab color. |
| pack->SetColor(TP::COLOR_TOOLBAR, colors.active_tab_color); |
| pack->SetColor(TP::COLOR_TOOLBAR_TEXT, colors.active_tab_text_color); |
| pack->SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, colors.active_tab_text_color); |
| |
| // NTP. |
| pack->SetColor(TP::COLOR_NTP_BACKGROUND, colors.ntp_color); |
| pack->SetColor(TP::COLOR_NTP_TEXT, |
| color_utils::GetColorWithMaxContrast(colors.ntp_color)); |
| |
| // Always use alternate logo (not colorful one) for all backgrounds except |
| // white. |
| if (colors.active_tab_color != SK_ColorWHITE) { |
| pack->SetDisplayProperty(TP::NTP_LOGO_ALTERNATE, 1); |
| } |
| |
| // Don't change frame color for inactive window. |
| pack->SetTint(TP::TINT_FRAME_INACTIVE, {-1, -1, -1}); |
| pack->SetTint(TP::TINT_FRAME_INCOGNITO_INACTIVE, {-1, -1, -1}); |
| |
| pack->FinalizePackFromColors(); |
| } |
| |
| void BrowserThemePack::BuildFromWebAppColors(SkColor theme_color, |
| SkColor background_color, |
| BrowserThemePack* pack) { |
| pack->InitEmptyPackFromColors(); |
| |
| // NOTE! If you make any changes here, please update kThemePackVersion. |
| |
| // Frame and inactive tab use the theme color. |
| pack->SetColor(TP::COLOR_FRAME_ACTIVE, theme_color); |
| pack->SetColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE, theme_color); |
| SkColor theme_text_color = color_utils::GetColorWithMaxContrast(theme_color); |
| pack->SetColor(TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE, |
| theme_text_color); |
| |
| // Toolbar also uses theme color. |
| pack->SetColor(TP::COLOR_TOOLBAR, theme_color); |
| pack->SetColor(TP::COLOR_TOOLBAR_TEXT, theme_text_color); |
| pack->SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, theme_text_color); |
| |
| // Active tab however uses background color. |
| pack->SetColor(TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE, |
| background_color); |
| pack->SetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE, |
| color_utils::GetColorWithMaxContrast(background_color)); |
| |
| // Don't change frame color for inactive window. |
| pack->SetTint(TP::TINT_FRAME_INACTIVE, {-1, -1, -1}); |
| pack->SetTint(TP::TINT_FRAME_INCOGNITO_INACTIVE, {-1, -1, -1}); |
| |
| pack->FinalizePackFromColors(); |
| } |
| |
| BrowserThemePack::BrowserThemePack(ThemeType theme_type) |
| : CustomThemeSupplier(theme_type) { |
| scale_factors_ = ui::GetSupportedResourceScaleFactors(); |
| } |
| |
| bool BrowserThemePack::WriteToDisk(const base::FilePath& path) const { |
| // Add resources for each of the property arrays. |
| RawDataForWriting resources; |
| resources[kHeaderID] = |
| std::string_view(reinterpret_cast<const char*>(header_.get()), |
| sizeof(BrowserThemePackHeader)); |
| resources[kTintsID] = |
| std::string_view(reinterpret_cast<const char*>(tints_.get()), |
| sizeof(TintEntry[kTintTableLength])); |
| resources[kColorsID] = |
| std::string_view(reinterpret_cast<const char*>(colors_.get()), |
| sizeof(ColorPair[kColorsArrayLength])); |
| resources[kDisplayPropertiesID] = |
| std::string_view(reinterpret_cast<const char*>(display_properties_.get()), |
| sizeof(DisplayPropertyPair[kDisplayPropertiesSize])); |
| resources[kTabGroupColorPaletteShadesID] = std::string_view( |
| reinterpret_cast<const char*>(tab_group_color_palette_shades_.data()), |
| sizeof(tab_group_color_palette_shades_)); |
| |
| int source_count = 1; |
| SourceImage* end = source_images_; |
| for (; end->id != -1; UNSAFE_TODO(end++)) { |
| source_count++; |
| } |
| resources[kSourceImagesID] = |
| std::string_view(reinterpret_cast<const char*>(source_images_.get()), |
| source_count * sizeof(*source_images_)); |
| |
| // Store results of GetResourceScaleFactorsAsString() in std::string as |
| // std::string_view does not copy data in constructor. |
| std::string scale_factors_string = |
| GetResourceScaleFactorsAsString(scale_factors_); |
| resources[kScaleFactorsID] = scale_factors_string; |
| |
| AddRawImagesTo(image_memory_, &resources); |
| |
| RawImages reencoded_images; |
| RepackImages(images_on_file_thread_, &reencoded_images); |
| AddRawImagesTo(reencoded_images, &resources); |
| |
| return ui::DataPack::WritePack(path, resources, ui::DataPack::BINARY); |
| } |
| |
| bool BrowserThemePack::GetTint(int id, color_utils::HSL* hsl) const { |
| if (tints_) { |
| for (size_t i = 0; i < kTintTableLength; ++i) { |
| if (UNSAFE_TODO(tints_[i]).id == id) { |
| hsl->h = UNSAFE_TODO(tints_[i]).h; |
| hsl->s = UNSAFE_TODO(tints_[i]).s; |
| hsl->l = UNSAFE_TODO(tints_[i]).l; |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool BrowserThemePack::GetColor(int id, SkColor* color) const { |
| static constexpr auto kOpaqueColors = |
| base::MakeFixedFlatSet<TP::OverwritableByUserThemeProperty>({ |
| // Background tabs must be opaque since the tabstrip expects to be |
| // able to render text opaquely atop them. |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO, |
| // The frame colors will be used for background tabs when not |
| // otherwise overridden and thus must be opaque as well. |
| TP::COLOR_FRAME_ACTIVE, |
| TP::COLOR_FRAME_INACTIVE, |
| TP::COLOR_FRAME_ACTIVE_INCOGNITO, |
| TP::COLOR_FRAME_INACTIVE_INCOGNITO, |
| // The toolbar is used as the foreground tab color, so it must be |
| // opaque just like background tabs. |
| TP::COLOR_TOOLBAR, |
| }); |
| |
| if (colors_) { |
| for (size_t i = 0; i < kColorsArrayLength; ++i) { |
| if (UNSAFE_TODO(colors_[i]).id == id) { |
| *color = UNSAFE_TODO(colors_[i]).color; |
| if (base::Contains(kOpaqueColors, id)) { |
| *color = SkColorSetA(*color, SK_AlphaOPAQUE); |
| } |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool BrowserThemePack::GetDisplayProperty(int id, int* result) const { |
| if (display_properties_) { |
| for (size_t i = 0; i < kDisplayPropertiesSize; ++i) { |
| if (UNSAFE_TODO(display_properties_[i]).id == id) { |
| *result = UNSAFE_TODO(display_properties_[i]).property; |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| gfx::Image BrowserThemePack::GetImageNamed(int idr_id) const { |
| PersistentID prs_id = GetPersistentIDByIDR(idr_id); |
| if (prs_id == PersistentID::kInvalid) { |
| return gfx::Image(); |
| } |
| |
| // Check if the image is cached. |
| ImageCache::const_iterator image_iter = images_.find(prs_id); |
| if (image_iter != images_.end()) { |
| return image_iter->second; |
| } |
| |
| ThemeImagePngSource::PngMap png_map; |
| for (auto scale_factor : scale_factors_) { |
| scoped_refptr<base::RefCountedMemory> memory = |
| GetRawData(idr_id, scale_factor); |
| if (memory.get()) { |
| png_map[scale_factor] = memory; |
| } |
| } |
| if (!png_map.empty()) { |
| gfx::ImageSkia image_skia(std::make_unique<ThemeImagePngSource>(png_map), |
| 1.0f); |
| gfx::Image ret = gfx::Image(image_skia); |
| images_[prs_id] = ret; |
| return ret; |
| } |
| |
| return gfx::Image(); |
| } |
| |
| base::RefCountedMemory* BrowserThemePack::GetRawData( |
| int idr_id, |
| ui::ResourceScaleFactor scale_factor) const { |
| base::RefCountedMemory* memory = nullptr; |
| PersistentID prs_id = GetPersistentIDByIDR(idr_id); |
| int raw_id = GetRawIDByPersistentID(prs_id, scale_factor); |
| |
| if (raw_id != -1) { |
| if (data_pack_.get()) { |
| memory = data_pack_->GetStaticMemory(raw_id); |
| } else { |
| auto it = image_memory_.find(raw_id); |
| if (it != image_memory_.end()) { |
| memory = it->second.get(); |
| } |
| } |
| } |
| |
| return memory; |
| } |
| |
| bool BrowserThemePack::HasCustomImage(int idr_id) const { |
| PersistentID prs_id = GetPersistentIDByIDR(idr_id); |
| if (prs_id == PersistentID::kInvalid) { |
| return false; |
| } |
| |
| SourceImage* img = source_images_; |
| for (; img->id != -1; UNSAFE_TODO(++img)) { |
| if (img->id == prs_id) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void BrowserThemePack::AddColorMixers(ui::ColorProvider* provider, |
| const ui::ColorProviderKey& key) const { |
| ui::ColorMixer& mixer = provider->AddMixer(); |
| |
| // TODO(http://crbug.com/878664): Enable for all cases. |
| mixer[kColorToolbarBackgroundSubtleEmphasis] = ui::BlendForMinContrast( |
| kColorToolbar, kColorToolbar, ChooseOmniboxBgBlendTarget(), |
| kMinOmniboxToolbarContrast); |
| |
| // A map from theme property IDs to color IDs for use in color mixers. |
| constexpr struct { |
| int property_id; |
| int color_id; |
| } kThemePropertiesMap[] = { |
| {TP::COLOR_BOOKMARK_TEXT, kColorBookmarkBarForeground}, |
| {TP::COLOR_CONTROL_BUTTON_BACKGROUND, kColorCaptionButtonBackground}, |
| {TP::COLOR_FRAME_ACTIVE, ui::kColorFrameActive}, |
| {TP::COLOR_FRAME_INACTIVE, ui::kColorFrameInactive}, |
| {TP::COLOR_NTP_BACKGROUND, kColorNewTabPageBackground}, |
| {TP::COLOR_NTP_HEADER, kColorNewTabPageHeader}, |
| {TP::COLOR_NTP_LINK, kColorNewTabPageLink}, |
| {TP::COLOR_NTP_LOGO, kColorNewTabPageLogo}, |
| {TP::COLOR_NTP_SECTION_BORDER, kColorNewTabPageSectionBorder}, |
| {TP::COLOR_NTP_TEXT, kColorNewTabPageText}, |
| {TP::COLOR_OMNIBOX_TEXT, kColorOmniboxText}, |
| {TP::COLOR_OMNIBOX_BACKGROUND, kColorToolbarBackgroundSubtleEmphasis}, |
| {TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE, |
| kColorTabBackgroundInactiveFrameActive}, |
| {TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE, |
| kColorTabBackgroundInactiveFrameInactive}, |
| {TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE, |
| kColorTabForegroundActiveFrameActive}, |
| {TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE, |
| kColorTabForegroundActiveFrameInactive}, |
| {TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE, |
| kColorTabForegroundInactiveFrameActive}, |
| {TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE, |
| kColorTabForegroundInactiveFrameInactive}, |
| {TP::COLOR_TAB_THROBBER_SPINNING, kColorTabThrobber}, |
| {TP::COLOR_TAB_THROBBER_WAITING, kColorTabThrobberPreconnect}, |
| {TP::COLOR_TOOLBAR, kColorToolbar}, |
| {TP::COLOR_TOOLBAR_BUTTON_ICON, kColorToolbarButtonIcon}, |
| {TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, kColorToolbarButtonIconHovered}, |
| {TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, kColorToolbarButtonIconPressed}, |
| {TP::COLOR_TOOLBAR_TEXT, kColorToolbarText}, |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE, |
| kColorWindowControlButtonBackgroundActive}, |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE, |
| kColorWindowControlButtonBackgroundInactive}}; |
| |
| SkColor color; |
| for (const auto& entry : kThemePropertiesMap) { |
| if (GetColor(entry.property_id, &color)) { |
| mixer[entry.color_id] = {color}; |
| } |
| } |
| |
| if (GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &color)) { |
| mixer[kColorBookmarkFavicon] = {kColorToolbarButtonIcon}; |
| } |
| |
| if (GetColor(TP::COLOR_BOOKMARK_TEXT, &color)) { |
| mixer[kColorBookmarkFolderIcon] = |
| ui::DeriveDefaultIconColor(kColorBookmarkBarForeground); |
| } |
| |
| int result = 0; |
| if (GetDisplayProperty(TP::SHOULD_FILL_BACKGROUND_TAB_COLOR, &result) && |
| result == 0) { |
| mixer[kColorNewTabButtonBackgroundFrameActive] = {SK_ColorTRANSPARENT}; |
| mixer[kColorNewTabButtonBackgroundFrameInactive] = {SK_ColorTRANSPARENT}; |
| } |
| |
| for (const auto& [id, shades] : tab_group_color_palette_shades_) { |
| // The array |tab_group_color_palette_shades_| is populated from left to |
| // right, and unused entries are marked with id == -1. Stop iteration once |
| // an unused entry is encountered. |
| if (id == -1) { |
| break; |
| } |
| |
| tab_groups::TabGroupColorId tab_group_color_id = |
| static_cast<tab_groups::TabGroupColorId>(id); |
| |
| enum ShadeIndex { |
| k50, |
| k100, |
| k200, |
| k300, |
| k400, |
| k500, |
| k600, |
| k700, |
| k800, |
| k900, |
| k1000 |
| }; |
| |
| mixer[GetTabGroupTabStripColorId(tab_group_color_id, true)] = |
| ui::SelectBasedOnDarkInput(kColorTabBackgroundInactiveFrameActive, |
| shades[k300], shades[k600]); |
| |
| mixer[GetTabGroupTabStripColorId(tab_group_color_id, false)] = |
| ui::SelectBasedOnDarkInput(kColorTabBackgroundInactiveFrameInactive, |
| shades[k300], shades[k600]); |
| |
| mixer[GetTabGroupDialogColorId(tab_group_color_id)] = { |
| GetTabGroupContextMenuColorId(tab_group_color_id)}; |
| |
| mixer[GetTabGroupContextMenuColorId(tab_group_color_id)] = |
| ui::SelectBasedOnDarkInput(ui::kColorMenuBackground, shades[k300], |
| shades[k600]); |
| |
| mixer[GetSavedTabGroupForegroundColorId(tab_group_color_id)] = |
| ui::SelectBasedOnDarkInput(kColorBookmarkBarBackground, shades[k100], |
| shades[k800]); |
| |
| mixer[GetSavedTabGroupOutlineColorId(tab_group_color_id)] = |
| ui::SelectBasedOnDarkInput(kColorBookmarkBarBackground, shades[k300], |
| shades[k700]); |
| |
| mixer[GetTabGroupBookmarkColorId(tab_group_color_id)] = |
| ui::SelectBasedOnDarkInput(kColorBookmarkBarBackground, shades[k1000], |
| shades[k50]); |
| |
| mixer[GetThumbnailTabStripTabGroupColorId(tab_group_color_id, true)] = |
| ui::SelectBasedOnDarkInput(kColorThumbnailTabStripBackgroundActive, |
| shades[k300], shades[k600]); |
| |
| mixer[GetThumbnailTabStripTabGroupColorId(tab_group_color_id, false)] = |
| ui::SelectBasedOnDarkInput(kColorThumbnailTabStripBackgroundInactive, |
| shades[k300], shades[k600]); |
| } |
| } |
| |
| // private: |
| |
| void BrowserThemePack::AdjustThemePack() { |
| CropImages(&images_); |
| |
| // Create toolbar image, and generate toolbar color from image where relevant. |
| // This must be done after reading colors from JSON (so they can be used for |
| // compositing the image). |
| CreateToolbarImageAndColors(&images_); |
| |
| // Create frame images, and generate frame colors from images where relevant. |
| // This must be done after reading colors from JSON (so they can be used for |
| // compositing the image). |
| CreateFrameImagesAndColors(&images_); |
| |
| // Generate any missing frame colors from tints. This must be done after |
| // generating colors from the frame images, so only colors with no matching |
| // images are generated. |
| GenerateFrameColorsFromTints(); |
| |
| // Set frame and toolbar related elements' colors (e.g. status bubble, |
| // info bar, download shelf) to frame or toolbar color. |
| SetFrameAndToolbarRelatedColors(); |
| |
| // Generate background color information for window control buttons. This |
| // must be done after frame colors are set, since they are used when |
| // determining window control button colors. |
| GenerateWindowControlButtonColor(&images_); |
| |
| // Create the tab background images, and generate colors where relevant. This |
| // must be done after all frame colors are set, since they are used when |
| // creating these. |
| CreateTabBackgroundImagesAndColors(&images_); |
| |
| // Make sure the |images_on_file_thread_| has bitmaps for supported |
| // scale factors before passing to FILE thread. |
| images_on_file_thread_ = images_; |
| for (auto& image : images_on_file_thread_) { |
| gfx::ImageSkia* image_skia = |
| const_cast<gfx::ImageSkia*>(image.second.ToImageSkia()); |
| image_skia->MakeThreadSafe(); |
| } |
| |
| // Set ThemeImageSource on |images_| to resample the source |
| // image if a caller of BrowserThemePack::GetImageNamed() requests an |
| // ImageSkiaRep for a scale factor not specified by the theme author. |
| // Callers of BrowserThemePack::GetImageNamed() to be able to retrieve |
| // ImageSkiaReps for all supported scale factors. |
| for (auto& image : images_) { |
| const gfx::ImageSkia source_image_skia = image.second.AsImageSkia(); |
| auto source = std::make_unique<ThemeImageSource>(source_image_skia); |
| gfx::ImageSkia image_skia(std::move(source), source_image_skia.size()); |
| image.second = gfx::Image(image_skia); |
| } |
| |
| // Generate raw images (for new-tab-page attribution and background) for |
| // any missing scale from an available scale image. |
| for (auto kPreloadID : kPreloadIDs) { |
| GenerateRawImageForAllSupportedScales(kPreloadID); |
| } |
| |
| // Generates missing NTP related colors. Should be called after theme images |
| // are prepared. |
| GenerateMissingNtpColors(); |
| } |
| |
| void BrowserThemePack::InitEmptyPack() { |
| InitHeader(); |
| |
| InitTints(); |
| |
| InitColors(); |
| |
| InitDisplayProperties(); |
| } |
| |
| void BrowserThemePack::InitEmptyPackFromColors() { |
| DCHECK(!is_valid()); |
| InitEmptyPack(); |
| |
| // Init `source_images_` only here as other code paths initialize it |
| // differently. |
| InitSourceImages(); |
| } |
| |
| void BrowserThemePack::InitHeader() { |
| header_ = new BrowserThemePackHeader; |
| header_->version = kThemePackVersion; |
| |
| // TODO(erg): Need to make this endian safe on other computers. Prerequisite |
| // is that ui::DataPack removes this same check. |
| #if defined(__BYTE_ORDER) |
| // Linux check |
| static_assert(__BYTE_ORDER == __LITTLE_ENDIAN, |
| "datapack assumes little endian"); |
| #elif defined(__BIG_ENDIAN__) |
| // Mac check |
| #error DataPack assumes little endian |
| #endif |
| header_->little_endian = 1; |
| } |
| |
| void BrowserThemePack::InitTints() { |
| tints_ = new TintEntry[kTintTableLength]; |
| for (size_t i = 0; i < kTintTableLength; ++i) { |
| UNSAFE_TODO(tints_[i]).id = -1; |
| UNSAFE_TODO(tints_[i]).h = -1; |
| UNSAFE_TODO(tints_[i]).s = -1; |
| UNSAFE_TODO(tints_[i]).l = -1; |
| } |
| } |
| |
| void BrowserThemePack::InitColors() { |
| colors_ = new ColorPair[kColorsArrayLength]; |
| for (size_t i = 0; i < kColorsArrayLength; ++i) { |
| UNSAFE_TODO(colors_[i]).id = -1; |
| UNSAFE_TODO(colors_[i]).color = SkColorSetRGB(0, 0, 0); |
| } |
| } |
| |
| void BrowserThemePack::InitDisplayProperties() { |
| display_properties_ = new DisplayPropertyPair[kDisplayPropertiesSize]; |
| for (size_t i = 0; i < kDisplayPropertiesSize; ++i) { |
| UNSAFE_TODO(display_properties_[i]).id = -1; |
| UNSAFE_TODO(display_properties_[i]).property = 0; |
| } |
| } |
| |
| void BrowserThemePack::InitSourceImages() { |
| source_images_ = new SourceImage[1]; |
| UNSAFE_TODO(source_images_[0]).id = -1; |
| } |
| |
| void BrowserThemePack::FinalizePackFromColors() { |
| AdjustThemePack(); |
| |
| // The BrowserThemePack is now in a consistent state. |
| is_valid_ = true; |
| } |
| |
| void BrowserThemePack::SetHeaderId(const Extension* extension) { |
| DCHECK(header_); |
| const std::string& id = extension->id(); |
| UNSAFE_TODO( |
| memcpy(header_->theme_id, id.c_str(), crx_file::id_util::kIdSize)); |
| } |
| |
| void BrowserThemePack::SetTintsFromJSON(const base::Value::Dict* tints_value) { |
| DCHECK(tints_); |
| |
| if (!tints_value) { |
| return; |
| } |
| |
| // Parse the incoming data from |tints_value| into an intermediary structure. |
| std::map<int, color_utils::HSL> temp_tints; |
| for (const auto [key, value] : *tints_value) { |
| if (!value.is_list()) { |
| continue; |
| } |
| |
| const base::Value::List& tint_list = value.GetList(); |
| if (tint_list.size() != 3) { |
| continue; |
| } |
| |
| std::optional<double> h = tint_list[0].GetIfDouble(); |
| std::optional<double> s = tint_list[1].GetIfDouble(); |
| std::optional<double> l = tint_list[2].GetIfDouble(); |
| if (!h || !s || !l) { |
| continue; |
| } |
| |
| color_utils::HSL hsl = {*h, *s, *l}; |
| MakeHSLShiftValid(&hsl); |
| |
| int id = GetIdForString<TP::OverwritableByUserThemeProperty>( |
| key, kTintTable, kTintTableLength); |
| if (id != -1) { |
| temp_tints[id] = hsl; |
| } |
| } |
| |
| // Copy data from the intermediary data structure to the array. |
| size_t count = 0; |
| for (std::map<int, color_utils::HSL>::const_iterator it = temp_tints.begin(); |
| it != temp_tints.end() && count < kTintTableLength; ++it, ++count) { |
| UNSAFE_TODO(tints_[count]).id = it->first; |
| UNSAFE_TODO(tints_[count]).h = it->second.h; |
| UNSAFE_TODO(tints_[count]).s = it->second.s; |
| UNSAFE_TODO(tints_[count]).l = it->second.l; |
| } |
| } |
| |
| void BrowserThemePack::SetColorsFromJSON( |
| const base::Value::Dict* colors_value) { |
| DCHECK(colors_); |
| |
| std::map<int, SkColor> temp_colors; |
| if (colors_value) { |
| ReadColorsFromJSON(*colors_value, &temp_colors); |
| } |
| |
| // Copy data from the intermediary data structure to the array. |
| size_t count = 0; |
| for (std::map<int, SkColor>::const_iterator it = temp_colors.begin(); |
| it != temp_colors.end() && count < kOverwritableColorTableLength; |
| ++it, ++count) { |
| UNSAFE_TODO(colors_[count]).id = it->first; |
| UNSAFE_TODO(colors_[count]).color = it->second; |
| } |
| } |
| |
| void BrowserThemePack::ReadColorsFromJSON(const base::Value::Dict& colors_value, |
| std::map<int, SkColor>* temp_colors) { |
| // Parse the incoming data from |colors_value| into an intermediary structure. |
| for (const auto [key, value] : colors_value) { |
| if (!value.is_list()) { |
| continue; |
| } |
| const base::Value::List& color_list = value.GetList(); |
| if (!(color_list.size() == 3 || color_list.size() == 4)) { |
| continue; |
| } |
| |
| SkColor color = SK_ColorWHITE; |
| std::optional<int> r = color_list[0].GetIfInt(); |
| std::optional<int> g = color_list[1].GetIfInt(); |
| std::optional<int> b = color_list[2].GetIfInt(); |
| if (!(r.has_value() && r.value() >= 0 && r.value() <= 255 && |
| g.has_value() && g.value() >= 0 && g.value() <= 255 && |
| b.has_value() && b.value() >= 0 && b.value() <= 255)) { |
| continue; |
| } |
| |
| if (color_list.size() == 4) { |
| bool alpha_valid = false; |
| if (color_list[3].is_int()) { |
| int alpha_int = color_list[3].GetInt(); |
| if (alpha_int == 0 || alpha_int == 1) { |
| color = SkColorSetARGB(alpha_int ? 255 : 0, *r, *g, *b); |
| alpha_valid = true; |
| } |
| } else if (color_list[3].is_double()) { |
| double alpha = color_list[3].GetDouble(); |
| if (alpha >= 0 && alpha <= 1) { |
| color = |
| SkColorSetARGB(base::ClampRound<U8CPU>(alpha * 255), *r, *g, *b); |
| alpha_valid = true; |
| } |
| } |
| |
| if (!alpha_valid) { |
| continue; |
| } |
| } else { |
| color = SkColorSetRGB(*r, *g, *b); |
| } |
| |
| if (key == "ntp_section") { |
| // We no longer use ntp_section, but to support legacy |
| // themes we still need to use it as a fallback for |
| // ntp_header. |
| if (!temp_colors->count(TP::COLOR_NTP_HEADER)) { |
| (*temp_colors)[TP::COLOR_NTP_HEADER] = color; |
| } |
| } else { |
| int id = GetIdForString<TP::OverwritableByUserThemeProperty>( |
| key, kOverwritableColorTable, kOverwritableColorTableLength); |
| if (id != -1) { |
| (*temp_colors)[id] = color; |
| } |
| } |
| } |
| } |
| |
| void BrowserThemePack::SetDisplayPropertiesFromJSON( |
| const base::Value::Dict* display_properties_value) { |
| DCHECK(display_properties_); |
| |
| if (!display_properties_value) { |
| return; |
| } |
| |
| std::map<int, int> temp_properties; |
| for (const auto [key, value] : *display_properties_value) { |
| int property_id = GetIdForString<TP::OverwritableByUserThemeProperty>( |
| key, kDisplayProperties, kDisplayPropertiesSize); |
| switch (property_id) { |
| case TP::NTP_BACKGROUND_ALIGNMENT: { |
| if (value.is_string()) { |
| temp_properties[TP::NTP_BACKGROUND_ALIGNMENT] = |
| TP::StringToAlignment(value.GetString()); |
| } |
| break; |
| } |
| case TP::NTP_BACKGROUND_TILING: { |
| if (value.is_string()) { |
| temp_properties[TP::NTP_BACKGROUND_TILING] = |
| TP::StringToTiling(value.GetString()); |
| } |
| break; |
| } |
| case TP::NTP_LOGO_ALTERNATE: { |
| if (value.is_int()) { |
| temp_properties[TP::NTP_LOGO_ALTERNATE] = value.GetInt(); |
| } |
| break; |
| } |
| } |
| } |
| |
| // Copy data from the intermediary data structure to the array. |
| size_t count = 0; |
| for (std::map<int, int>::const_iterator it = temp_properties.begin(); |
| it != temp_properties.end() && count < kDisplayPropertiesSize; |
| ++it, ++count) { |
| UNSAFE_TODO(display_properties_[count]).id = it->first; |
| UNSAFE_TODO(display_properties_[count]).property = it->second; |
| } |
| } |
| |
| void BrowserThemePack::ParseImageNamesFromJSON( |
| const extensions::ThemeInfo::ThemeImages* theme_images, |
| const base::FilePath& images_path, |
| FilePathMap* file_paths) const { |
| if (!theme_images) { |
| return; |
| } |
| |
| for (const auto& [theme_image_name, theme_resources] : *theme_images) { |
| for (const auto& theme_resource : theme_resources) { |
| ui::ResourceScaleFactor scale_factor = ui::k100Percent; |
| if (!theme_resource.scale.empty() && |
| !GetScaleFactorFromManifestKey(theme_resource.scale, &scale_factor)) { |
| // Invalid scale factor; skip. |
| continue; |
| } |
| AddFileAtScaleToMap( |
| theme_image_name, scale_factor, |
| images_path.Append(theme_resource.resource.relative_path()), |
| file_paths); |
| } |
| } |
| } |
| |
| void BrowserThemePack::SetTabGroupColorPaletteShadesFromJSON( |
| const base::Value::Dict* tab_group_color_palette_value) { |
| size_t count = 0; |
| for (const auto [key, value] : *tab_group_color_palette_value) { |
| if (!value.is_int()) { |
| continue; |
| } |
| |
| int hue = value.GetInt(); |
| if (hue < -1 || hue > 360) { |
| continue; |
| } |
| |
| int id = GetIdForString<tab_groups::TabGroupColorId>( |
| key, kTabGroupColorIdTable, kTabGroupColorIdTableSize); |
| if (id != -1) { |
| tab_group_color_palette_shades_[count].id = id; |
| ui::GenerateStandardShadesFromHue( |
| hue == -1 ? std::nullopt : std::make_optional(hue), |
| tab_group_color_palette_shades_[count].shades); |
| ++count; |
| } |
| } |
| } |
| |
| void BrowserThemePack::AddFileAtScaleToMap(const std::string& image_name, |
| ui::ResourceScaleFactor scale_factor, |
| const base::FilePath& image_path, |
| FilePathMap* file_paths) const { |
| PersistentID id = GetPersistentIDByName(image_name); |
| if (id != PersistentID::kInvalid) { |
| (*file_paths)[id][scale_factor] = image_path; |
| } |
| } |
| |
| void BrowserThemePack::BuildSourceImagesArray(const FilePathMap& file_paths) { |
| source_images_ = new SourceImage[file_paths.size() + 1]; |
| std::ranges::transform( |
| file_paths, source_images_.get(), |
| [](const auto& pair) { return SourceImage{pair.first}; }); |
| UNSAFE_TODO(source_images_[file_paths.size()]).id = -1; |
| } |
| |
| bool BrowserThemePack::LoadRawBitmapsTo(const FilePathMap& file_paths, |
| ImageCache* image_cache) { |
| // Themes should be loaded on the file thread, not the UI thread. |
| // http://crbug.com/61838 |
| base::ScopedAllowBlocking scoped_allow_blocking; |
| |
| for (const auto& entry : file_paths) { |
| PersistentID prs_id = entry.first; |
| // Some images need to go directly into |image_memory_|. No modification is |
| // necessary or desirable. |
| const bool is_copyable = base::Contains(kPreloadIDs, prs_id); |
| gfx::ImageSkia image_skia; |
| for (int pass = 0; pass < 2; ++pass) { |
| // Two passes: In the first pass, we process only scale factor |
| // 100% and in the second pass all other scale factors. We |
| // process scale factor 100% first because the first image added |
| // in image_skia.AddRepresentation() determines the DIP size for |
| // all representations. |
| for (const auto& s2f : entry.second) { |
| ui::ResourceScaleFactor scale_factor = s2f.first; |
| if ((pass == 0 && scale_factor != ui::k100Percent) || |
| (pass == 1 && scale_factor == ui::k100Percent)) { |
| continue; |
| } |
| scoped_refptr<base::RefCountedMemory> raw_data( |
| ReadFileData(s2f.second)); |
| if (!raw_data.get() || !raw_data->size()) { |
| LOG(ERROR) << "Could not load theme image" << " prs_id=" << prs_id |
| << " scale_factor_enum=" << scale_factor |
| << " file=" << s2f.second.value() |
| << (raw_data.get() ? " (zero size)" : " (read error)"); |
| return false; |
| } |
| if (is_copyable) { |
| int raw_id = GetRawIDByPersistentID(prs_id, scale_factor); |
| image_memory_[raw_id] = raw_data; |
| } else { |
| SkBitmap bitmap = gfx::PNGCodec::Decode(*raw_data); |
| if (!bitmap.isNull()) { |
| image_skia.AddRepresentation(gfx::ImageSkiaRep( |
| bitmap, ui::GetScaleForResourceScaleFactor(scale_factor))); |
| } else { |
| // The image is either broken or a different format. |
| return false; |
| } |
| } |
| } |
| } |
| if (!is_copyable && !image_skia.isNull()) { |
| (*image_cache)[prs_id] = gfx::Image(image_skia); |
| } |
| } |
| |
| return true; |
| } |
| |
| void BrowserThemePack::CropImages(ImageCache* images) const { |
| for (const auto& image_to_crop : kImagesToCrop) { |
| auto it = images->find(image_to_crop.prs_id); |
| if (it == images->end()) { |
| continue; |
| } |
| |
| gfx::ImageSkia image_skia = it->second.AsImageSkia(); |
| (*images)[image_to_crop.prs_id] = |
| gfx::Image(gfx::ImageSkiaOperations::ExtractSubset( |
| image_skia, |
| gfx::Rect(0, 0, image_skia.width(), image_to_crop.max_height))); |
| } |
| } |
| |
| void BrowserThemePack::SetFrameAndToolbarRelatedColors() { |
| // Propagate the user-specified toolbar color to similar elements. |
| SkColor toolbar_color; |
| if (GetColor(TP::COLOR_TOOLBAR, &toolbar_color)) { |
| // If the toolbar color is set but the text color is not, ensure it has |
| // sufficient contrast. |
| SkColor toolbar_text_color = |
| TP::GetDefaultColor(TP::COLOR_TOOLBAR_TEXT, false); |
| toolbar_text_color = |
| color_utils::BlendForMinContrast(toolbar_text_color, toolbar_color) |
| .color; |
| SetColorIfUnspecified(TP::COLOR_TOOLBAR_TEXT, toolbar_text_color); |
| } else { |
| toolbar_color = TP::GetDefaultColor(TP::COLOR_TOOLBAR, false); |
| } |
| SkColor toolbar_button_icon_color; |
| color_utils::HSL button_tint; |
| if (GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &toolbar_button_icon_color)) { |
| SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, toolbar_button_icon_color); |
| SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, toolbar_button_icon_color); |
| SetColor(TP::COLOR_TAB_THROBBER_SPINNING, toolbar_button_icon_color); |
| SetColor(TP::COLOR_TAB_THROBBER_WAITING, toolbar_button_icon_color); |
| } else if (GetTint(TP::TINT_BUTTONS, &button_tint)) { |
| // Duplicate how COLOR_TOOLBAR_BUTTON_ICON will be computed. |
| // TODO(pkasting): Should this code be shared with |
| // ThemeHelper::GetDefaultColor() somehow? |
| const SkColor button_color = |
| color_utils::HSLShift(gfx::kGoogleGrey700, button_tint); |
| SetColor(TP::COLOR_TAB_THROBBER_SPINNING, button_color); |
| SetColor(TP::COLOR_TAB_THROBBER_WAITING, button_color); |
| } |
| SkColor toolbar_text_color; |
| if (GetColor(TP::COLOR_TOOLBAR_TEXT, &toolbar_text_color)) { |
| SetColorIfUnspecified(TP::COLOR_BOOKMARK_TEXT, toolbar_text_color); |
| SetColorIfUnspecified(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE, |
| toolbar_text_color); |
| } |
| SkColor tab_foreground_color; |
| if (GetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE, |
| &tab_foreground_color)) { |
| SetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE, |
| tab_foreground_color); |
| } |
| SkColor omnibox_background_color; |
| if (GetColor(TP::COLOR_OMNIBOX_BACKGROUND, &omnibox_background_color)) { |
| omnibox_background_color = color_utils::GetResultingPaintColor( |
| omnibox_background_color, toolbar_color); |
| SetColor(TP::COLOR_OMNIBOX_BACKGROUND, omnibox_background_color); |
| } else { |
| // TODO(pkasting): This should be shared with the omnibox color mixer. |
| omnibox_background_color = gfx::kGoogleGrey100; |
| } |
| SkColor omnibox_text_color; |
| if (GetColor(TP::COLOR_OMNIBOX_TEXT, &omnibox_text_color)) { |
| SetColor(TP::COLOR_OMNIBOX_TEXT, |
| color_utils::GetResultingPaintColor(omnibox_text_color, |
| omnibox_background_color)); |
| } |
| } |
| |
| void BrowserThemePack::CreateToolbarImageAndColors(ImageCache* images) { |
| ImageCache temp_output; |
| |
| constexpr PersistentID kSrcImageId = PRS::kToolbar; |
| |
| const auto image_it = images->find(kSrcImageId); |
| if (image_it == images->end()) { |
| return; |
| } |
| |
| auto image = image_it->second.AsImageSkia(); |
| |
| constexpr int kToolbarColorId = TP::COLOR_TOOLBAR; |
| SkColor toolbar_color; |
| if (!GetColor(kToolbarColorId, &toolbar_color)) { |
| toolbar_color = TP::GetDefaultColor(kToolbarColorId, false); |
| } |
| |
| // Generate a composite image by drawing the toolbar image on top of the |
| // specified toolbar color (if any). |
| color_utils::HSL hsl_shift{-1, -1, -1}; |
| gfx::ImageSkia overlay; |
| auto source = std::make_unique<TabBackgroundImageSource>( |
| toolbar_color, image, overlay, hsl_shift, 0); |
| gfx::Size dest_size = image.size(); |
| |
| const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size)); |
| temp_output[kSrcImageId] = dest_image; |
| |
| SetColorIfUnspecified(kToolbarColorId, |
| ComputeImageColor(dest_image, dest_size.height())); |
| |
| MergeImageCaches(temp_output, images); |
| } |
| |
| void BrowserThemePack::CreateFrameImagesAndColors(ImageCache* images) { |
| static constexpr struct FrameValues { |
| PersistentID prs_id; |
| int tint_id; |
| std::optional<int> color_id; |
| } kFrameValues[] = { |
| {PRS::kFrame, TP::TINT_FRAME, TP::COLOR_FRAME_ACTIVE}, |
| {PRS::kFrameInactive, TP::TINT_FRAME_INACTIVE, TP::COLOR_FRAME_INACTIVE}, |
| {PRS::kFrameOverlay, TP::TINT_FRAME, std::nullopt}, |
| {PRS::kFrameOverlayInactive, TP::TINT_FRAME_INACTIVE, std::nullopt}, |
| {PRS::kFrameIncognito, TP::TINT_FRAME_INCOGNITO, |
| TP::COLOR_FRAME_ACTIVE_INCOGNITO}, |
| {PRS::kFrameIncognitoInactive, TP::TINT_FRAME_INCOGNITO_INACTIVE, |
| TP::COLOR_FRAME_INACTIVE_INCOGNITO}, |
| }; |
| |
| // Create all the output images in a separate cache and move them back into |
| // the input images because there can be name collisions. |
| ImageCache temp_output; |
| |
| for (const auto& frame_values : kFrameValues) { |
| PersistentID src_id = frame_values.prs_id; |
| // If the theme doesn't provide an image, attempt to fall back to one it |
| // does. |
| if (!images->count(src_id)) { |
| // Fall back from inactive overlay to active overlay. |
| if (src_id == PRS::kFrameOverlayInactive) { |
| src_id = PRS::kFrameOverlay; |
| } |
| |
| // Fall back from inactive incognito to active incognito. |
| if (src_id == PRS::kFrameIncognitoInactive) { |
| src_id = PRS::kFrameIncognito; |
| } |
| |
| // For all non-overlay images, fall back to PRS_THEME_FRAME as a last |
| // resort. |
| if (!images->count(src_id) && src_id != PRS::kFrameOverlay) { |
| src_id = PRS::kFrame; |
| } |
| } |
| |
| // Note that if the original ID and all the fallbacks are absent, the caller |
| // will rely on the frame colors instead. |
| const auto image = images->find(src_id); |
| if (image != images->end()) { |
| const gfx::Image dest_image( |
| gfx::ImageSkiaOperations::CreateHSLShiftedImage( |
| *image->second.ToImageSkia(), |
| GetTintInternal(frame_values.tint_id))); |
| |
| temp_output[frame_values.prs_id] = dest_image; |
| |
| if (frame_values.color_id) { |
| SetColorIfUnspecified( |
| frame_values.color_id.value(), |
| ComputeImageColor(dest_image, kTallestFrameHeight)); |
| } |
| } |
| } |
| MergeImageCaches(temp_output, images); |
| } |
| |
| void BrowserThemePack::GenerateFrameColorsFromTints() { |
| SkColor frame; |
| if (!GetColor(TP::COLOR_FRAME_ACTIVE, &frame)) { |
| frame = TP::GetDefaultColor(TP::COLOR_FRAME_ACTIVE, false); |
| SetColor(TP::COLOR_FRAME_ACTIVE, |
| HSLShift(frame, GetTintInternal(TP::TINT_FRAME))); |
| } |
| |
| SetColorIfUnspecified( |
| TP::COLOR_FRAME_INACTIVE, |
| HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INACTIVE))); |
| |
| SetColorIfUnspecified( |
| TP::COLOR_FRAME_ACTIVE_INCOGNITO, |
| HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INCOGNITO))); |
| |
| SetColorIfUnspecified( |
| TP::COLOR_FRAME_INACTIVE_INCOGNITO, |
| HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INCOGNITO_INACTIVE))); |
| } |
| |
| void BrowserThemePack::GenerateWindowControlButtonColor(ImageCache* images) { |
| static constexpr struct ControlBGValue { |
| // The color to compute and store. |
| int color_id; |
| |
| // The frame color to use as the base of this button background. |
| int frame_color_id; |
| } kControlButtonBackgroundMap[] = { |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE, |
| TP::COLOR_FRAME_ACTIVE}, |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE, |
| TP::COLOR_FRAME_INACTIVE}, |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE, |
| TP::COLOR_FRAME_ACTIVE_INCOGNITO}, |
| {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE, |
| TP::COLOR_FRAME_INACTIVE_INCOGNITO}, |
| }; |
| |
| // Get data related to the control button background image and color first, |
| // since they are shared by all variants. |
| gfx::ImageSkia bg_image; |
| ImageCache::const_iterator bg_img_it = |
| images->find(PRS::kWindowControlBackground); |
| if (bg_img_it != images->end()) { |
| bg_image = bg_img_it->second.AsImageSkia(); |
| } |
| |
| SkColor button_bg_color = SK_ColorTRANSPARENT; |
| SkAlpha button_bg_alpha = SK_AlphaTRANSPARENT; |
| if (GetColor(TP::COLOR_CONTROL_BUTTON_BACKGROUND, &button_bg_color)) { |
| button_bg_alpha = SkColorGetA(button_bg_color); |
| } |
| |
| button_bg_alpha = |
| WindowFrameUtil::CalculateWindowsCaptionButtonBackgroundAlpha( |
| button_bg_alpha); |
| |
| // Determine what portion of the image to use in our calculations (we won't |
| // use more along the X-axis than the width of the caption buttons). This |
| // should theoretically be the maximum of the size of the caption button area |
| // on the glass frame and opaque frame, but it would be rather complicated to |
| // determine the size of the opaque frame's caption button area at pack |
| // processing time (as it is determined by the size of icons, which we don't |
| // have easy access to here), so we use the glass frame area as an |
| // approximation. |
| gfx::Size dest_size = WindowFrameUtil::GetWindowsCaptionButtonAreaSize(); |
| |
| // To get an accurate sampling, all we need to do is get a representative |
| // image that is at MOST the size of the caption button area. In the case of |
| // an image that is smaller - we only need to sample an area the size of the |
| // provided image (trying to take tiling into account would be overkill). |
| if (!bg_image.isNull()) { |
| dest_size.SetToMin(bg_image.size()); |
| } |
| |
| for (const ControlBGValue& bg_pair : kControlButtonBackgroundMap) { |
| SkColor frame_color; |
| GetColor(bg_pair.frame_color_id, &frame_color); |
| SkColor base_color = |
| color_utils::AlphaBlend(button_bg_color, frame_color, button_bg_alpha); |
| |
| if (bg_image.isNull()) { |
| SetColor(bg_pair.color_id, base_color); |
| continue; |
| } |
| |
| auto source = std::make_unique<ControlButtonBackgroundImageSource>( |
| base_color, bg_image, dest_size); |
| const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size)); |
| |
| SetColorIfUnspecified(bg_pair.color_id, |
| ComputeImageColor(dest_image, dest_size.height())); |
| } |
| } |
| |
| void BrowserThemePack::CreateTabBackgroundImagesAndColors(ImageCache* images) { |
| static constexpr struct TabValues { |
| // The background image to create/update. |
| PersistentID tab_id; |
| |
| // For inactive images, the corresponding active image. If the active |
| // images are customized and the inactive ones are not, the inactive ones |
| // will be based on the active ones. |
| std::optional<PersistentID> fallback_tab_id; |
| |
| // The frame image to use as the base of this tab background image. |
| PersistentID frame_id; |
| |
| // The frame color to use as the base of this tab background image. |
| int frame_color_id; |
| |
| // The color to compute and store for this image, if not present. |
| int color_id; |
| } kTabBackgroundMap[] = { |
| {PRS::kTabBackground, std::nullopt, PRS::kFrame, TP::COLOR_FRAME_ACTIVE, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE}, |
| {PRS::kTabBackgroundInactive, PRS::kTabBackground, PRS::kFrameInactive, |
| TP::COLOR_FRAME_INACTIVE, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE}, |
| {PRS::kTabBackgroundIncognito, std::nullopt, PRS::kFrameIncognito, |
| TP::COLOR_FRAME_ACTIVE_INCOGNITO, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO}, |
| {PRS::kTabBackgroundIncognitoInactive, PRS::kTabBackgroundIncognito, |
| PRS::kFrameIncognitoInactive, TP::COLOR_FRAME_INACTIVE_INCOGNITO, |
| TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO}, |
| }; |
| |
| ImageCache temp_output; |
| for (const auto& entry : kTabBackgroundMap) { |
| ImageCache::const_iterator tab_it = images->find(entry.tab_id); |
| |
| // Inactive images should be based on the active ones if the active ones |
| // were customized. |
| if (tab_it == images->end() && entry.fallback_tab_id) { |
| tab_it = images->find(*entry.fallback_tab_id); |
| } |
| |
| // Generate background tab images when provided with custom frame or |
| // background tab images; in the former case the theme author may want the |
| // background tabs to appear to tint the frame, and in the latter case the |
| // provided background tab image may have transparent regions, which must be |
| // made opaque by overlaying atop the original frame. |
| const ImageCache::const_iterator frame_it = images->find(entry.frame_id); |
| if (frame_it != images->end() || tab_it != images->end()) { |
| SkColor frame_color; |
| GetColor(entry.frame_color_id, &frame_color); |
| |
| gfx::ImageSkia image_to_tint; |
| if (frame_it != images->end()) { |
| image_to_tint = (frame_it->second).AsImageSkia(); |
| } |
| |
| gfx::ImageSkia overlay; |
| if (tab_it != images->end()) { |
| overlay = tab_it->second.AsImageSkia(); |
| } |
| |
| auto source = std::make_unique<TabBackgroundImageSource>( |
| frame_color, image_to_tint, overlay, |
| GetTintInternal(TP::TINT_BACKGROUND_TAB), TP::kFrameHeightAboveTabs); |
| gfx::Size dest_size = image_to_tint.size(); |
| dest_size.SetToMax(overlay.size()); |
| dest_size.set_height(kTallestTabHeight); |
| const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size)); |
| temp_output[entry.tab_id] = dest_image; |
| |
| SetColorIfUnspecified(entry.color_id, |
| ComputeImageColor(dest_image, kTallestTabHeight)); |
| } |
| } |
| MergeImageCaches(temp_output, images); |
| } |
| |
| void BrowserThemePack::GenerateMissingNtpColors() { |
| gfx::Image image = GetImageNamed(IDR_THEME_NTP_BACKGROUND); |
| bool has_background_image = !image.IsEmpty(); |
| |
| // Opaquify background color. |
| SkColor background_color; |
| bool has_background_color = |
| GetColor(TP::COLOR_NTP_BACKGROUND, &background_color); |
| if (has_background_color) { |
| background_color = color_utils::GetResultingPaintColor( |
| background_color, TP::GetDefaultColor(TP::COLOR_NTP_BACKGROUND, false)); |
| SetColor(TP::COLOR_NTP_BACKGROUND, background_color); |
| } |
| |
| // Calculate NTP text color based on NTP background. |
| SkColor text_color; |
| if (!GetColor(TP::COLOR_NTP_TEXT, &text_color)) { |
| if (has_background_image) { |
| background_color = ComputeImageColor(image, image.Height()); |
| } |
| |
| if (has_background_image || has_background_color) { |
| SetColor(TP::COLOR_NTP_TEXT, |
| color_utils::GetColorWithMaxContrast(background_color)); |
| } |
| } |
| |
| // Calculate logo alternate, if not specified. |
| int logo_alternate = 0; |
| if (!GetDisplayProperty(TP::NTP_LOGO_ALTERNATE, &logo_alternate)) { |
| logo_alternate = |
| has_background_image || |
| (has_background_color && !IsColorGrayscale(background_color)); |
| SetDisplayProperty(TP::NTP_LOGO_ALTERNATE, logo_alternate); |
| } |
| |
| // For themes that use alternate logo and no NTP background image is present, |
| // set logo color in the same hue as NTP background. |
| if (logo_alternate == 1 && !has_background_image && has_background_color) { |
| SkColor logo_color = color_utils::IsDark(background_color) |
| ? SK_ColorWHITE |
| : GetContrastingColor(background_color, |
| /*luminosity_change=*/0.3f); |
| SetColor(TP::COLOR_NTP_LOGO, logo_color); |
| } |
| |
| // Calculate NTP section border color. |
| SkColor header_color; |
| if (GetColor(TP::COLOR_NTP_HEADER, &header_color)) { |
| SetColor(TP::COLOR_NTP_SECTION_BORDER, SkColorSetA(header_color, 0x50)); |
| } |
| } |
| |
| void BrowserThemePack::RepackImages(const ImageCache& images, |
| RawImages* reencoded_images) const { |
| for (const auto& image : images) { |
| gfx::ImageSkia image_skia = *image.second.ToImageSkia(); |
| |
| std::vector<gfx::ImageSkiaRep> image_reps = image_skia.image_reps(); |
| DCHECK(!image_reps.empty()) |
| << "No image reps for resource " << image.first << "."; |
| for (const auto& rep : image_reps) { |
| std::optional<std::vector<uint8_t>> bitmap_data = |
| gfx::PNGCodec::EncodeBGRASkBitmap(rep.GetBitmap(), |
| /*discard_transparency=*/false); |
| CHECK(bitmap_data) << "Image file for resource " << image.first |
| << " could not be encoded."; |
| int raw_id = GetRawIDByPersistentID( |
| image.first, ui::GetSupportedResourceScaleFactor(rep.scale())); |
| (*reencoded_images)[raw_id] = base::MakeRefCounted<base::RefCountedBytes>( |
| std::move(bitmap_data).value()); |
| } |
| } |
| } |
| |
| void BrowserThemePack::MergeImageCaches(const ImageCache& source, |
| ImageCache* destination) const { |
| for (const auto& it : source) { |
| (*destination)[it.first] = it.second; |
| } |
| } |
| |
| void BrowserThemePack::AddRawImagesTo(const RawImages& images, |
| RawDataForWriting* out) const { |
| for (const auto& pair : images) { |
| (*out)[pair.first] = base::as_string_view(*pair.second); |
| } |
| } |
| |
| color_utils::HSL BrowserThemePack::GetTintInternal(int id) const { |
| color_utils::HSL hsl; |
| if (GetTint(id, &hsl)) { |
| return hsl; |
| } |
| |
| int original_id = id; |
| if (id == TP::TINT_FRAME_INCOGNITO) { |
| original_id = TP::TINT_FRAME; |
| } else if (id == TP::TINT_FRAME_INCOGNITO_INACTIVE) { |
| original_id = TP::TINT_FRAME_INACTIVE; |
| } |
| |
| return TP::GetDefaultTint(original_id, original_id != id); |
| } |
| |
| int BrowserThemePack::GetRawIDByPersistentID( |
| PersistentID prs_id, |
| ui::ResourceScaleFactor scale_factor) const { |
| if (prs_id == PersistentID::kInvalid) { |
| return -1; |
| } |
| |
| for (size_t i = 0; i < scale_factors_.size(); ++i) { |
| if (scale_factors_[i] == scale_factor) { |
| return ((PersistentID::kMaxValue + 1) * i) + prs_id; |
| } |
| } |
| return -1; |
| } |
| |
| bool BrowserThemePack::GetScaleFactorFromManifestKey( |
| const std::string& key, |
| ui::ResourceScaleFactor* scale_factor) const { |
| int percent = 0; |
| if (base::StringToInt(key, &percent)) { |
| float scale = static_cast<float>(percent) / 100.0f; |
| for (auto i : scale_factors_) { |
| if (fabs(ui::GetScaleForResourceScaleFactor(i) - scale) < 0.001) { |
| *scale_factor = i; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void BrowserThemePack::GenerateRawImageForAllSupportedScales( |
| PersistentID prs_id) { |
| // Compute (by scaling) bitmaps for |prs_id| for any scale factors |
| // for which the theme author did not provide a bitmap. We compute |
| // the bitmaps using the highest scale factor that theme author |
| // provided. |
| // Note: We use only supported scale factors. For example, if scale |
| // factor 2x is supported by the current system, but 1.8x is not and |
| // if the theme author did not provide an image for 2x but one for |
| // 1.8x, we will not use the 1.8x image here. Here we will only use |
| // images provided for scale factors supported by the current system. |
| |
| // See if any image is missing. If not, we're done. |
| bool image_missing = false; |
| for (auto& scale_factor : scale_factors_) { |
| int raw_id = GetRawIDByPersistentID(prs_id, scale_factor); |
| if (image_memory_.find(raw_id) == image_memory_.end()) { |
| image_missing = true; |
| break; |
| } |
| } |
| if (!image_missing) { |
| return; |
| } |
| |
| // Find available scale factor with highest scale. |
| ui::ResourceScaleFactor available_scale_factor = ui::kScaleFactorNone; |
| for (auto& scale_factor : scale_factors_) { |
| int raw_id = GetRawIDByPersistentID(prs_id, scale_factor); |
| if ((available_scale_factor == ui::kScaleFactorNone || |
| (ui::GetScaleForResourceScaleFactor(scale_factor) > |
| ui::GetScaleForResourceScaleFactor(available_scale_factor))) && |
| image_memory_.find(raw_id) != image_memory_.end()) { |
| available_scale_factor = scale_factor; |
| } |
| } |
| // If no scale factor is available, we're done. |
| if (available_scale_factor == ui::kScaleFactorNone) { |
| return; |
| } |
| |
| // Get bitmap for the available scale factor. |
| int available_raw_id = GetRawIDByPersistentID(prs_id, available_scale_factor); |
| RawImages::const_iterator it = image_memory_.find(available_raw_id); |
| SkBitmap available_bitmap = gfx::PNGCodec::Decode(*it->second); |
| if (available_bitmap.isNull()) { |
| // The image is either broken or a different format. |
| return; |
| } |
| |
| // Fill in all missing scale factors by scaling the available bitmap. |
| for (auto& scale_factor : scale_factors_) { |
| int scaled_raw_id = GetRawIDByPersistentID(prs_id, scale_factor); |
| if (image_memory_.find(scaled_raw_id) != image_memory_.end()) { |
| continue; |
| } |
| SkBitmap scaled_bitmap = CreateLowQualityResizedBitmap( |
| available_bitmap, available_scale_factor, scale_factor); |
| std::optional<std::vector<uint8_t>> bitmap_data = |
| gfx::PNGCodec::EncodeBGRASkBitmap(scaled_bitmap, |
| /*discard_transparency=*/false); |
| if (!bitmap_data) { |
| NOTREACHED() << "Unable to encode theme image for prs_id=" << prs_id |
| << " for scale_factor=" << scale_factor; |
| } |
| image_memory_[scaled_raw_id] = base::MakeRefCounted<base::RefCountedBytes>( |
| std::move(bitmap_data).value()); |
| } |
| } |