1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/themes/browser_theme_pack.h"
6
7 #include <limits.h>
8 #include <stddef.h>
9
10 #include <algorithm>
11 #include <limits>
12 #include <memory>
13 #include <utility>
14
15 #include "base/containers/flat_set.h"
16 #include "base/files/file.h"
17 #include "base/memory/ref_counted_memory.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/no_destructor.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/task/post_task.h"
26 #include "base/task/thread_pool.h"
27 #include "base/threading/thread_restrictions.h"
28 #include "base/values.h"
29 #include "build/build_config.h"
30 #include "chrome/browser/themes/theme_properties.h"
31 #include "chrome/browser/ui/color/chrome_color_id.h"
32 #include "chrome/browser/ui/frame/window_frame_util.h"
33 #include "chrome/common/extensions/manifest_handlers/theme_handler.h"
34 #include "chrome/common/themes/autogenerated_theme_util.h"
35 #include "chrome/grit/theme_resources.h"
36 #include "components/crx_file/id_util.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "third_party/skia/include/core/SkCanvas.h"
39 #include "third_party/skia/include/core/SkColor.h"
40 #include "ui/base/resource/data_pack.h"
41 #include "ui/color/color_mixer.h"
42 #include "ui/color/color_provider.h"
43 #include "ui/gfx/canvas.h"
44 #include "ui/gfx/codec/png_codec.h"
45 #include "ui/gfx/color_analysis.h"
46 #include "ui/gfx/color_palette.h"
47 #include "ui/gfx/color_utils.h"
48 #include "ui/gfx/geometry/size_conversions.h"
49 #include "ui/gfx/image/canvas_image_source.h"
50 #include "ui/gfx/image/image.h"
51 #include "ui/gfx/image/image_skia.h"
52 #include "ui/gfx/image/image_skia_operations.h"
53 #include "ui/gfx/skia_util.h"
54
55 using content::BrowserThread;
56 using extensions::Extension;
57 using TP = ThemeProperties;
58
59 // Persistent constants for the main images that we need. These have the same
60 // names as their IDR_* counterparts but these values will always stay the
61 // same.
62 enum BrowserThemePack::PersistentID : int {
63 kInvalid = -1,
64 kFrame = 1,
65 kFrameInactive = 2,
66 kFrameIncognito = 3,
67 kFrameIncognitoInactive = 4,
68 kToolbar = 5,
69 kTabBackground = 6,
70 kTabBackgroundIncognito = 7,
71 kTabBackgroundV = 8,
72 kNtpBackground = 9,
73 kFrameOverlay = 10,
74 kFrameOverlayInactive = 11,
75 kButtonBackground = 12,
76 kNtpAttribution = 13,
77 kWindowControlBackground = 14,
78 kTabBackgroundInactive = 15,
79 kTabBackgroundIncognitoInactive = 16,
80 kMaxValue = kTabBackgroundIncognitoInactive,
81 };
82
83 namespace {
84
85 using PRS = BrowserThemePack::PersistentID;
86
87 // The tallest tab height in any mode.
88 constexpr int kTallestTabHeight = 41;
89
90 // The tallest height above the tabs in any mode is 19 DIP.
91 constexpr int kTallestFrameHeight = kTallestTabHeight + 19;
92
93 // Version number of the current theme pack. We just throw out and rebuild
94 // theme packs that aren't int-equal to this. Increment this number if you
95 // change default theme assets, if you need themes to recreate their generated
96 // images (which are cached), or if you changed how missing values are
97 // generated.
98 const int kThemePackVersion = 75;
99
100 // IDs that are in the DataPack won't clash with the positive integer
101 // uint16_t. kHeaderID should always have the maximum value because we want the
102 // "header" to be written last. That way we can detect whether the pack was
103 // successfully written and ignore and regenerate if it was only partially
104 // written (i.e. chrome crashed on a different thread while writing the pack).
105 const int kMaxID = 0x0000FFFF; // Max unsigned 16-bit int.
106 const int kHeaderID = kMaxID - 1;
107 const int kTintsID = kMaxID - 2;
108 const int kColorsID = kMaxID - 3;
109 const int kDisplayPropertiesID = kMaxID - 4;
110 const int kSourceImagesID = kMaxID - 5;
111 const int kScaleFactorsID = kMaxID - 6;
112
113 struct PersistingImagesTable {
114 // A non-changing integer ID meant to be saved in theme packs. This ID must
115 // not change between versions of chrome.
116 BrowserThemePack::PersistentID persistent_id;
117
118 // The IDR that depends on the whims of GRIT and therefore changes whenever
119 // someone adds a new resource.
120 int idr_id;
121
122 // String to check for when parsing theme manifests.
123 const char* const key;
124 };
125
126 // IDR_* resource names change whenever new resources are added; use persistent
127 // IDs when storing to a cached pack.
128 constexpr PersistingImagesTable kPersistingImages[] = {
129 {PRS::kFrame, IDR_THEME_FRAME, "theme_frame"},
130 {PRS::kFrameInactive, IDR_THEME_FRAME_INACTIVE, "theme_frame_inactive"},
131 {PRS::kFrameIncognito, IDR_THEME_FRAME_INCOGNITO, "theme_frame_incognito"},
132 {PRS::kFrameIncognitoInactive, IDR_THEME_FRAME_INCOGNITO_INACTIVE,
133 "theme_frame_incognito_inactive"},
134 {PRS::kToolbar, IDR_THEME_TOOLBAR, "theme_toolbar"},
135 {PRS::kTabBackground, IDR_THEME_TAB_BACKGROUND, "theme_tab_background"},
136 {PRS::kTabBackgroundInactive, IDR_THEME_TAB_BACKGROUND_INACTIVE,
137 "theme_tab_background_inactive"},
138 {PRS::kTabBackgroundIncognito, IDR_THEME_TAB_BACKGROUND_INCOGNITO,
139 "theme_tab_background_incognito"},
140 {PRS::kTabBackgroundIncognitoInactive,
141 IDR_THEME_TAB_BACKGROUND_INCOGNITO_INACTIVE,
142 "theme_tab_background_incognito_inactive"},
143 {PRS::kTabBackgroundV, IDR_THEME_TAB_BACKGROUND_V,
144 "theme_tab_background_v"},
145 {PRS::kNtpBackground, IDR_THEME_NTP_BACKGROUND, "theme_ntp_background"},
146 {PRS::kFrameOverlay, IDR_THEME_FRAME_OVERLAY, "theme_frame_overlay"},
147 {PRS::kFrameOverlayInactive, IDR_THEME_FRAME_OVERLAY_INACTIVE,
148 "theme_frame_overlay_inactive"},
149 {PRS::kButtonBackground, IDR_THEME_BUTTON_BACKGROUND,
150 "theme_button_background"},
151 {PRS::kNtpAttribution, IDR_THEME_NTP_ATTRIBUTION, "theme_ntp_attribution"},
152 {PRS::kWindowControlBackground, IDR_THEME_WINDOW_CONTROL_BACKGROUND,
153 "theme_window_control_background"},
154
155 // NOTE! If you make any changes here, please update kThemePackVersion.
156 };
157
GetPersistentIDByName(const std::string & key)158 BrowserThemePack::PersistentID GetPersistentIDByName(const std::string& key) {
159 auto* it = std::find_if(std::begin(kPersistingImages),
160 std::end(kPersistingImages), [&](const auto& image) {
161 return base::LowerCaseEqualsASCII(key, image.key);
162 });
163 return it == std::end(kPersistingImages) ? PRS::kInvalid : it->persistent_id;
164 }
165
GetPersistentIDByIDR(int idr)166 BrowserThemePack::PersistentID GetPersistentIDByIDR(int idr) {
167 auto* it =
168 std::find_if(std::begin(kPersistingImages), std::end(kPersistingImages),
169 [&](const auto& image) { return image.idr_id == idr; });
170 return it == std::end(kPersistingImages) ? PRS::kInvalid : it->persistent_id;
171 }
172
173 // Returns true if the scales in |input| match those in |expected|.
174 // The order must match as the index is used in determining the raw id.
InputScalesValid(const base::StringPiece & input,const std::vector<ui::ScaleFactor> & expected)175 bool InputScalesValid(const base::StringPiece& input,
176 const std::vector<ui::ScaleFactor>& expected) {
177 if (input.size() != expected.size() * sizeof(float))
178 return false;
179 std::unique_ptr<float[]> scales(new float[expected.size()]);
180 // Do a memcpy to avoid misaligned memory access.
181 memcpy(scales.get(), input.data(), input.size());
182 for (size_t index = 0; index < expected.size(); ++index) {
183 if (scales[index] != ui::GetScaleForScaleFactor(expected[index]))
184 return false;
185 }
186 return true;
187 }
188
189 // Returns |scale_factors| as a string to be written to disk.
GetScaleFactorsAsString(const std::vector<ui::ScaleFactor> & scale_factors)190 std::string GetScaleFactorsAsString(
191 const std::vector<ui::ScaleFactor>& scale_factors) {
192 std::unique_ptr<float[]> scales(new float[scale_factors.size()]);
193 for (size_t i = 0; i < scale_factors.size(); ++i)
194 scales[i] = ui::GetScaleForScaleFactor(scale_factors[i]);
195 std::string out_string = std::string(
196 reinterpret_cast<const char*>(scales.get()),
197 scale_factors.size() * sizeof(float));
198 return out_string;
199 }
200
201 struct StringToIntTable {
202 const char* const key;
203 TP::OverwritableByUserThemeProperty id;
204 };
205
206 // Strings used by themes to identify tints in the JSON.
207 const StringToIntTable kTintTable[] = {
208 {"buttons", TP::TINT_BUTTONS},
209 {"frame", TP::TINT_FRAME},
210 {"frame_inactive", TP::TINT_FRAME_INACTIVE},
211 {"frame_incognito", TP::TINT_FRAME_INCOGNITO},
212 {"frame_incognito_inactive", TP::TINT_FRAME_INCOGNITO_INACTIVE},
213 {"background_tab", TP::TINT_BACKGROUND_TAB},
214
215 // NOTE! If you make any changes here, please update kThemePackVersion.
216 };
217 const size_t kTintTableLength = base::size(kTintTable);
218
219 // Strings used by themes to identify colors in the JSON.
220 constexpr StringToIntTable kOverwritableColorTable[] = {
221 {"frame", TP::COLOR_FRAME_ACTIVE},
222 {"frame_inactive", TP::COLOR_FRAME_INACTIVE},
223 {"frame_incognito", TP::COLOR_FRAME_ACTIVE_INCOGNITO},
224 {"frame_incognito_inactive", TP::COLOR_FRAME_INACTIVE_INCOGNITO},
225 {"background_tab", TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE},
226 {"background_tab_inactive",
227 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE},
228 {"background_tab_incognito",
229 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO},
230 {"background_tab_incognito_inactive",
231 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO},
232 {"bookmark_text", TP::COLOR_BOOKMARK_TEXT},
233 {"button_background", TP::COLOR_CONTROL_BUTTON_BACKGROUND},
234 {"tab_background_text", TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE},
235 {"tab_background_text_inactive",
236 TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE},
237 {"tab_background_text_incognito",
238 TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO},
239 {"tab_background_text_incognito_inactive",
240 TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO},
241 {"tab_text", TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE},
242 {"toolbar", TP::COLOR_TOOLBAR},
243 {"toolbar_button_icon", TP::COLOR_TOOLBAR_BUTTON_ICON},
244 {"omnibox_text", TP::COLOR_OMNIBOX_TEXT},
245 {"omnibox_background", TP::COLOR_OMNIBOX_BACKGROUND},
246 {"ntp_background", TP::COLOR_NTP_BACKGROUND},
247 {"ntp_header", TP::COLOR_NTP_HEADER},
248 {"ntp_link", TP::COLOR_NTP_LINK},
249 {"ntp_text", TP::COLOR_NTP_TEXT},
250
251 // NOTE! If you make any changes here, please update kThemePackVersion.
252 };
253 constexpr size_t kOverwritableColorTableLength =
254 base::size(kOverwritableColorTable);
255
256 // Colors generated based on the theme, but not overwritable in the theme file.
257 constexpr int kNonOverwritableColorTable[] = {
258 TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
259 TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
260 TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE,
261 TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE,
262 TP::COLOR_INFOBAR,
263 TP::COLOR_DOWNLOAD_SHELF,
264 TP::COLOR_STATUS_BUBBLE,
265 TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED,
266 TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED,
267 TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE,
268 TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE,
269 TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE
270
271 // NOTE! If you make any changes here, please update kThemePackVersion.
272 };
273 constexpr size_t kNonOverwritableColorTableLength =
274 base::size(kNonOverwritableColorTable);
275
276 // The maximum number of colors we may need to store (includes ones that can be
277 // specified by the theme, and ones that we calculate but can't be specified).
278 constexpr size_t kColorsArrayLength =
279 kOverwritableColorTableLength + kNonOverwritableColorTableLength;
280
281 // Strings used by themes to identify display properties keys in JSON.
282 const StringToIntTable kDisplayProperties[] = {
283 {"ntp_background_alignment", TP::NTP_BACKGROUND_ALIGNMENT},
284 {"ntp_background_repeat", TP::NTP_BACKGROUND_TILING},
285 {"ntp_logo_alternate", TP::NTP_LOGO_ALTERNATE},
286
287 // NOTE! If you make any changes here, please update kThemePackVersion.
288 };
289 const size_t kDisplayPropertiesSize = base::size(kDisplayProperties);
290
GetIntForString(const std::string & key,const StringToIntTable * table,size_t table_length)291 int GetIntForString(const std::string& key,
292 const StringToIntTable* table,
293 size_t table_length) {
294 for (size_t i = 0; i < table_length; ++i) {
295 if (base::LowerCaseEqualsASCII(key, table[i].key)) {
296 return table[i].id;
297 }
298 }
299
300 return -1;
301 }
302
303 struct CropEntry {
304 BrowserThemePack::PersistentID prs_id;
305
306 // The maximum useful height of the image at |prs_id|.
307 int max_height;
308 };
309
310 // The images which should be cropped before being saved to the data pack. The
311 // maximum heights are meant to be conservative as to give room for the UI to
312 // change without the maximum heights having to be modified.
313 // |kThemePackVersion| must be incremented if any of the maximum heights below
314 // are modified.
315 const struct CropEntry kImagesToCrop[] = {
316 {PRS::kFrame, kTallestFrameHeight},
317 {PRS::kFrameInactive, kTallestFrameHeight},
318 {PRS::kFrameIncognito, kTallestFrameHeight},
319 {PRS::kFrameIncognitoInactive, kTallestFrameHeight},
320 {PRS::kFrameOverlay, kTallestFrameHeight},
321 {PRS::kFrameOverlayInactive, kTallestFrameHeight},
322 {PRS::kToolbar, 200},
323 {PRS::kButtonBackground, 60},
324 {PRS::kWindowControlBackground, 50},
325 };
326
327 // A list of images that don't need tinting or any other modification and can
328 // be byte-copied directly into the finished DataPack. This should contain the
329 // persistent IDs for all themeable image IDs that aren't in kFrameValues,
330 // kTabBackgroundMap or kImagesToCrop.
331 const BrowserThemePack::PersistentID kPreloadIDs[] = {
332 PRS::kNtpBackground,
333 PRS::kNtpAttribution,
334 };
335
336 // Returns a piece of memory with the contents of the file |path|.
ReadFileData(const base::FilePath & path)337 scoped_refptr<base::RefCountedMemory> ReadFileData(const base::FilePath& path) {
338 if (!path.empty()) {
339 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
340 if (file.IsValid()) {
341 int64_t length = file.GetLength();
342 if (length > 0 && length < INT_MAX) {
343 int size = static_cast<int>(length);
344 std::vector<unsigned char> raw_data;
345 raw_data.resize(size);
346 char* data = reinterpret_cast<char*>(&(raw_data.front()));
347 if (file.ReadAtCurrentPos(data, size) == length)
348 return base::RefCountedBytes::TakeVector(&raw_data);
349 }
350 }
351 }
352
353 return nullptr;
354 }
355
356 // Computes a bitmap at one scale from a bitmap at a different scale.
CreateLowQualityResizedBitmap(const SkBitmap & source_bitmap,ui::ScaleFactor source_scale_factor,ui::ScaleFactor desired_scale_factor)357 SkBitmap CreateLowQualityResizedBitmap(const SkBitmap& source_bitmap,
358 ui::ScaleFactor source_scale_factor,
359 ui::ScaleFactor desired_scale_factor) {
360 gfx::Size scaled_size = gfx::ScaleToCeiledSize(
361 gfx::Size(source_bitmap.width(), source_bitmap.height()),
362 ui::GetScaleForScaleFactor(desired_scale_factor) /
363 ui::GetScaleForScaleFactor(source_scale_factor));
364 SkBitmap scaled_bitmap;
365 scaled_bitmap.allocN32Pixels(scaled_size.width(), scaled_size.height());
366 scaled_bitmap.eraseARGB(0, 0, 0, 0);
367 SkCanvas canvas(scaled_bitmap, SkSurfaceProps{});
368 SkRect scaled_bounds = RectToSkRect(gfx::Rect(scaled_size));
369 // Note(oshima): The following scaling code doesn't work with
370 // a mask image.
371 canvas.drawBitmapRect(source_bitmap, scaled_bounds, nullptr);
372 return scaled_bitmap;
373 }
374
375 // A ImageSkiaSource that scales 100P image to the target scale factor
376 // if the ImageSkiaRep for the target scale factor isn't available.
377 class ThemeImageSource: public gfx::ImageSkiaSource {
378 public:
ThemeImageSource(const gfx::ImageSkia & source)379 explicit ThemeImageSource(const gfx::ImageSkia& source) : source_(source) {
380 }
~ThemeImageSource()381 ~ThemeImageSource() override {}
382
GetImageForScale(float scale)383 gfx::ImageSkiaRep GetImageForScale(float scale) override {
384 if (source_.HasRepresentation(scale))
385 return source_.GetRepresentation(scale);
386 const gfx::ImageSkiaRep& rep_100p = source_.GetRepresentation(1.0f);
387 SkBitmap scaled_bitmap = CreateLowQualityResizedBitmap(
388 rep_100p.GetBitmap(), ui::SCALE_FACTOR_100P,
389 ui::GetSupportedScaleFactor(scale));
390 return gfx::ImageSkiaRep(scaled_bitmap, scale);
391 }
392
393 private:
394 const gfx::ImageSkia source_;
395
396 DISALLOW_COPY_AND_ASSIGN(ThemeImageSource);
397 };
398
399 // An ImageSkiaSource that delays decoding PNG data into bitmaps until
400 // needed. Missing data for a scale factor is computed by scaling data for an
401 // available scale factor. Computed bitmaps are stored for future look up.
402 class ThemeImagePngSource : public gfx::ImageSkiaSource {
403 public:
404 typedef std::map<ui::ScaleFactor,
405 scoped_refptr<base::RefCountedMemory> > PngMap;
406
ThemeImagePngSource(const PngMap & png_map)407 explicit ThemeImagePngSource(const PngMap& png_map) : png_map_(png_map) {}
408
~ThemeImagePngSource()409 ~ThemeImagePngSource() override {}
410
411 private:
GetImageForScale(float scale)412 gfx::ImageSkiaRep GetImageForScale(float scale) override {
413 ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactor(scale);
414 // Look up the bitmap for |scale factor| in the bitmap map. If found
415 // return it.
416 BitmapMap::const_iterator exact_bitmap_it = bitmap_map_.find(scale_factor);
417 if (exact_bitmap_it != bitmap_map_.end())
418 return gfx::ImageSkiaRep(exact_bitmap_it->second, scale);
419
420 // Look up the raw PNG data for |scale_factor| in the png map. If found,
421 // decode it, store the result in the bitmap map and return it.
422 PngMap::const_iterator exact_png_it = png_map_.find(scale_factor);
423 if (exact_png_it != png_map_.end()) {
424 SkBitmap bitmap;
425 if (!gfx::PNGCodec::Decode(exact_png_it->second->front(),
426 exact_png_it->second->size(),
427 &bitmap)) {
428 NOTREACHED();
429 return gfx::ImageSkiaRep();
430 }
431 bitmap_map_[scale_factor] = bitmap;
432 return gfx::ImageSkiaRep(bitmap, scale);
433 }
434
435 // Find an available PNG for another scale factor. We want to use the
436 // highest available scale factor.
437 PngMap::const_iterator available_png_it = png_map_.end();
438 for (PngMap::const_iterator png_it = png_map_.begin();
439 png_it != png_map_.end(); ++png_it) {
440 if (available_png_it == png_map_.end() ||
441 ui::GetScaleForScaleFactor(png_it->first) >
442 ui::GetScaleForScaleFactor(available_png_it->first)) {
443 available_png_it = png_it;
444 }
445 }
446 if (available_png_it == png_map_.end())
447 return gfx::ImageSkiaRep();
448 ui::ScaleFactor available_scale_factor = available_png_it->first;
449
450 // Look up the bitmap for |available_scale_factor| in the bitmap map.
451 // If not found, decode the corresponging png data, store the result
452 // in the bitmap map.
453 BitmapMap::const_iterator available_bitmap_it =
454 bitmap_map_.find(available_scale_factor);
455 if (available_bitmap_it == bitmap_map_.end()) {
456 SkBitmap available_bitmap;
457 if (!gfx::PNGCodec::Decode(available_png_it->second->front(),
458 available_png_it->second->size(),
459 &available_bitmap)) {
460 NOTREACHED();
461 return gfx::ImageSkiaRep();
462 }
463 bitmap_map_[available_scale_factor] = available_bitmap;
464 available_bitmap_it = bitmap_map_.find(available_scale_factor);
465 }
466
467 // Scale the available bitmap to the desired scale factor, store the result
468 // in the bitmap map and return it.
469 SkBitmap scaled_bitmap = CreateLowQualityResizedBitmap(
470 available_bitmap_it->second,
471 available_scale_factor,
472 scale_factor);
473 bitmap_map_[scale_factor] = scaled_bitmap;
474 return gfx::ImageSkiaRep(scaled_bitmap, scale);
475 }
476
477 PngMap png_map_;
478
479 typedef std::map<ui::ScaleFactor, SkBitmap> BitmapMap;
480 BitmapMap bitmap_map_;
481
482 DISALLOW_COPY_AND_ASSIGN(ThemeImagePngSource);
483 };
484
485 class TabBackgroundImageSource: public gfx::CanvasImageSource {
486 public:
TabBackgroundImageSource(SkColor background_color,const gfx::ImageSkia & image_to_tint,const gfx::ImageSkia & overlay,const color_utils::HSL & hsl_shift,int vertical_offset)487 TabBackgroundImageSource(SkColor background_color,
488 const gfx::ImageSkia& image_to_tint,
489 const gfx::ImageSkia& overlay,
490 const color_utils::HSL& hsl_shift,
491 int vertical_offset)
492 : gfx::CanvasImageSource(image_to_tint.isNull() ? overlay.size()
493 : image_to_tint.size()),
494 background_color_(background_color),
495 image_to_tint_(image_to_tint),
496 overlay_(overlay),
497 hsl_shift_(hsl_shift),
498 vertical_offset_(vertical_offset) {}
499
~TabBackgroundImageSource()500 ~TabBackgroundImageSource() override {}
501
502 // Overridden from CanvasImageSource:
Draw(gfx::Canvas * canvas)503 void Draw(gfx::Canvas* canvas) override {
504 canvas->DrawColor(background_color_);
505
506 // Begin with the frame background image, if any. Since the frame and tabs
507 // have grown taller and changed alignment over time, not all themes have a
508 // sufficiently tall image; tiling by vertically mirroring in this case is
509 // the least-glitchy-looking option. Note that the behavior here needs to
510 // stay in sync with how the browser frame will actually be drawn.
511 if (!image_to_tint_.isNull()) {
512 gfx::ImageSkia bg_tint = gfx::ImageSkiaOperations::CreateHSLShiftedImage(
513 image_to_tint_, hsl_shift_);
514 canvas->TileImageInt(bg_tint, 0, vertical_offset_, 0, 0, size().width(),
515 size().height(), 1.0f, SkTileMode::kRepeat,
516 SkTileMode::kMirror);
517 }
518
519 // If the theme has a custom tab background image, overlay it. Vertical
520 // mirroring is used for the same reason as above. This behavior needs to
521 // stay in sync with how tabs are drawn.
522 if (!overlay_.isNull()) {
523 canvas->TileImageInt(overlay_, 0, 0, 0, 0, size().width(),
524 size().height(), 1.0f, SkTileMode::kRepeat,
525 SkTileMode::kMirror);
526 }
527 }
528
529 private:
530 const SkColor background_color_;
531 const gfx::ImageSkia image_to_tint_;
532 const gfx::ImageSkia overlay_;
533 const color_utils::HSL hsl_shift_;
534 const int vertical_offset_;
535
536 DISALLOW_COPY_AND_ASSIGN(TabBackgroundImageSource);
537 };
538
539 class ControlButtonBackgroundImageSource : public gfx::CanvasImageSource {
540 public:
ControlButtonBackgroundImageSource(SkColor background_color,const gfx::ImageSkia & bg_image,const gfx::Size & dest_size)541 ControlButtonBackgroundImageSource(SkColor background_color,
542 const gfx::ImageSkia& bg_image,
543 const gfx::Size& dest_size)
544 : gfx::CanvasImageSource(dest_size),
545 background_color_(background_color),
546 bg_image_(bg_image) {
547 DCHECK(!bg_image.isNull());
548 }
549
550 ~ControlButtonBackgroundImageSource() override = default;
551
Draw(gfx::Canvas * canvas)552 void Draw(gfx::Canvas* canvas) override {
553 canvas->DrawColor(background_color_);
554
555 if (!bg_image_.isNull())
556 canvas->DrawImageInt(bg_image_, 0, 0);
557 }
558
559 private:
560 const SkColor background_color_;
561 const gfx::ImageSkia bg_image_;
562
563 DISALLOW_COPY_AND_ASSIGN(ControlButtonBackgroundImageSource);
564 };
565
566 // Returns whether the color is grayscale.
IsColorGrayscale(SkColor color)567 bool IsColorGrayscale(SkColor color) {
568 constexpr int kChannelTolerance = 9;
569 auto channels = {SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)};
570 const int range = std::max(channels) - std::min(channels);
571 return range < kChannelTolerance;
572 }
573
574 } // namespace
575
576 namespace internal { // for testing
577
578 // Calculate contrasting color for given |bg_color|. Returns lighter color if
579 // the color is very dark and returns darker color otherwise.
GetContrastingColorForBackground(SkColor bg_color,float luminosity_change)580 SkColor GetContrastingColorForBackground(SkColor bg_color,
581 float luminosity_change) {
582 color_utils::HSL hsl;
583 SkColorToHSL(bg_color, &hsl);
584
585 // If luminosity is 0, it means |bg_color| is black. Use white for black
586 // backgrounds.
587 if (hsl.l == 0)
588 return SK_ColorWHITE;
589
590 // Decrease luminosity, unless color is already dark.
591 if (hsl.l > 0.15)
592 luminosity_change *= -1;
593
594 hsl.l *= 1 + luminosity_change;
595 if (hsl.l >= 0.0f && hsl.l <= 1.0f)
596 return HSLToSkColor(hsl, 255);
597 return bg_color;
598 }
599 } // namespace internal
600
~BrowserThemePack()601 BrowserThemePack::~BrowserThemePack() {
602 if (data_pack_) {
603 auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
604 {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
605 DCHECK(task_runner);
606 task_runner->DeleteSoon(FROM_HERE, data_pack_.release());
607 } else {
608 delete header_;
609 delete [] tints_;
610 delete [] colors_;
611 delete [] display_properties_;
612 delete [] source_images_;
613 }
614 }
615
SetColor(int id,SkColor color)616 void BrowserThemePack::SetColor(int id, SkColor color) {
617 DCHECK(colors_);
618
619 int first_available_color = -1;
620 for (size_t i = 0; i < kColorsArrayLength; ++i) {
621 if (colors_[i].id == id) {
622 colors_[i].color = color;
623 return;
624 }
625 if (colors_[i].id == -1 && first_available_color == -1)
626 first_available_color = i;
627 }
628
629 DCHECK_NE(-1, first_available_color);
630 colors_[first_available_color].id = id;
631 colors_[first_available_color].color = color;
632 }
633
SetColorIfUnspecified(int id,SkColor color)634 void BrowserThemePack::SetColorIfUnspecified(int id, SkColor color) {
635 SkColor temp_color;
636 if (!GetColor(id, &temp_color))
637 SetColor(id, color);
638 }
639
SetTint(int id,color_utils::HSL tint)640 void BrowserThemePack::SetTint(int id, color_utils::HSL tint) {
641 DCHECK(tints_);
642
643 int first_available_index = -1;
644 for (size_t i = 0; i < kTintTableLength; ++i) {
645 if (tints_[i].id == id) {
646 tints_[i].h = tint.h;
647 tints_[i].s = tint.s;
648 tints_[i].l = tint.l;
649 return;
650 }
651 if (tints_[i].id == -1 && first_available_index == -1)
652 first_available_index = i;
653 }
654
655 DCHECK_NE(-1, first_available_index);
656 tints_[first_available_index].id = id;
657 tints_[first_available_index].h = tint.h;
658 tints_[first_available_index].s = tint.s;
659 tints_[first_available_index].l = tint.l;
660 }
661
SetDisplayProperty(int id,int value)662 void BrowserThemePack::SetDisplayProperty(int id, int value) {
663 DCHECK(display_properties_);
664
665 int first_available_index = -1;
666 for (size_t i = 0; i < kDisplayPropertiesSize; ++i) {
667 if (display_properties_[i].id == id) {
668 display_properties_[i].property = value;
669 return;
670 }
671 if (display_properties_[i].id == -1 && first_available_index == -1)
672 first_available_index = i;
673 }
674
675 DCHECK_NE(-1, first_available_index);
676 display_properties_[first_available_index].id = id;
677 display_properties_[first_available_index].property = value;
678 }
679
ComputeImageColor(const gfx::Image & image,int height)680 SkColor BrowserThemePack::ComputeImageColor(const gfx::Image& image,
681 int height) {
682 // Include all colors in the analysis.
683 constexpr color_utils::HSL kNoBounds = {-1, -1, -1};
684 const SkColor color = color_utils::CalculateKMeanColorOfBitmap(
685 *image.ToSkBitmap(), height, kNoBounds, kNoBounds, false);
686
687 return color;
688 }
689
690 // static
BuildFromExtension(const extensions::Extension * extension,BrowserThemePack * pack)691 void BrowserThemePack::BuildFromExtension(
692 const extensions::Extension* extension,
693 BrowserThemePack* pack) {
694 DCHECK(extension);
695 DCHECK(extension->is_theme());
696 DCHECK(!pack->is_valid());
697
698 // NOTE! If you make any changes here, please update kThemePackVersion.
699
700 pack->InitEmptyPack();
701 pack->set_extension_id(extension->id());
702 pack->SetHeaderId(extension);
703 pack->SetTintsFromJSON(extensions::ThemeInfo::GetTints(extension));
704 pack->SetColorsFromJSON(extensions::ThemeInfo::GetColors(extension));
705 pack->SetDisplayPropertiesFromJSON(
706 extensions::ThemeInfo::GetDisplayProperties(extension));
707
708 // Builds the images. (Image building is dependent on tints).
709 FilePathMap file_paths;
710 pack->ParseImageNamesFromJSON(extensions::ThemeInfo::GetImages(extension),
711 extension->path(), &file_paths);
712 pack->BuildSourceImagesArray(file_paths);
713
714 if (!pack->LoadRawBitmapsTo(file_paths, &pack->images_))
715 return;
716
717 pack->AdjustThemePack();
718
719 // The BrowserThemePack is now in a consistent state.
720 pack->is_valid_ = true;
721 }
722
723 // static
BuildFromDataPack(const base::FilePath & path,const std::string & expected_id)724 scoped_refptr<BrowserThemePack> BrowserThemePack::BuildFromDataPack(
725 const base::FilePath& path, const std::string& expected_id) {
726 DCHECK_CURRENTLY_ON(BrowserThread::UI);
727 // Allow IO on UI thread due to deep-seated theme design issues.
728 // (see http://crbug.com/80206)
729 base::ThreadRestrictions::ScopedAllowIO allow_io;
730
731 // For now data pack can only have extension type.
732 scoped_refptr<BrowserThemePack> pack(
733 new BrowserThemePack(ThemeType::EXTENSION));
734 pack->set_extension_id(expected_id);
735 // Scale factor parameter is moot as data pack has image resources for all
736 // supported scale factors.
737 pack->data_pack_.reset(
738 new ui::DataPack(ui::SCALE_FACTOR_NONE));
739
740 if (!pack->data_pack_->LoadFromPath(path)) {
741 LOG(ERROR) << "Failed to load theme data pack.";
742 return nullptr;
743 }
744
745 base::StringPiece pointer;
746 if (!pack->data_pack_->GetStringPiece(kHeaderID, &pointer))
747 return nullptr;
748 pack->header_ = reinterpret_cast<BrowserThemePackHeader*>(const_cast<char*>(
749 pointer.data()));
750
751 if (pack->header_->version != kThemePackVersion) {
752 DLOG(ERROR) << "BuildFromDataPack failure! Version mismatch!";
753 return nullptr;
754 }
755 // TODO(erg): Check endianess once DataPack works on the other endian.
756 std::string theme_id(reinterpret_cast<char*>(pack->header_->theme_id),
757 crx_file::id_util::kIdSize);
758 std::string truncated_id = expected_id.substr(0, crx_file::id_util::kIdSize);
759 if (theme_id != truncated_id) {
760 DLOG(ERROR) << "Wrong id: " << theme_id << " vs " << expected_id;
761 return nullptr;
762 }
763
764 if (!pack->data_pack_->GetStringPiece(kTintsID, &pointer))
765 return nullptr;
766 pack->tints_ = reinterpret_cast<TintEntry*>(const_cast<char*>(
767 pointer.data()));
768
769 if (!pack->data_pack_->GetStringPiece(kColorsID, &pointer))
770 return nullptr;
771 pack->colors_ =
772 reinterpret_cast<ColorPair*>(const_cast<char*>(pointer.data()));
773
774 if (!pack->data_pack_->GetStringPiece(kDisplayPropertiesID, &pointer))
775 return nullptr;
776 pack->display_properties_ = reinterpret_cast<DisplayPropertyPair*>(
777 const_cast<char*>(pointer.data()));
778
779 if (!pack->data_pack_->GetStringPiece(kSourceImagesID, &pointer))
780 return nullptr;
781 pack->source_images_ = reinterpret_cast<int*>(
782 const_cast<char*>(pointer.data()));
783
784 if (!pack->data_pack_->GetStringPiece(kScaleFactorsID, &pointer))
785 return nullptr;
786
787 if (!InputScalesValid(pointer, pack->scale_factors_)) {
788 DLOG(ERROR) << "BuildFromDataPack failure! The pack scale factors differ "
789 << "from those supported by platform.";
790 return nullptr;
791 }
792 pack->is_valid_ = true;
793 return pack;
794 }
795
796 // static
IsPersistentImageID(int id)797 bool BrowserThemePack::IsPersistentImageID(int id) {
798 return GetPersistentIDByIDR(id) != PersistentID::kInvalid;
799 }
800
801 // static
BuildFromColor(SkColor color,BrowserThemePack * pack)802 void BrowserThemePack::BuildFromColor(SkColor color, BrowserThemePack* pack) {
803 BuildFromColors(GetAutogeneratedThemeColors(color), pack);
804 }
805
806 // static
BuildFromColors(AutogeneratedThemeColors colors,BrowserThemePack * pack)807 void BrowserThemePack::BuildFromColors(AutogeneratedThemeColors colors,
808 BrowserThemePack* pack) {
809 DCHECK(!pack->is_valid());
810
811 pack->InitEmptyPack();
812
813 // Init |source_images_| only here as other code paths initialize it
814 // differently.
815 pack->InitSourceImages();
816
817 // NOTE! If you make any changes here, please update kThemePackVersion.
818
819 // Frame.
820 pack->SetColor(TP::COLOR_FRAME_ACTIVE, colors.frame_color);
821
822 // Inactive tab uses frame color.
823 pack->SetColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
824 colors.frame_color);
825 pack->SetColor(TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE,
826 colors.frame_text_color);
827
828 // Toolbar and active tab (set in SetFrameAndToolbarRelatedColors) use active
829 // tab color.
830 pack->SetColor(TP::COLOR_TOOLBAR, colors.active_tab_color);
831 pack->SetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE,
832 colors.active_tab_text_color);
833 pack->SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, colors.active_tab_text_color);
834 pack->SetColor(TP::COLOR_BOOKMARK_TEXT, colors.active_tab_text_color);
835
836 // NTP.
837 pack->SetColor(TP::COLOR_NTP_BACKGROUND, colors.ntp_color);
838 pack->SetColor(TP::COLOR_NTP_TEXT,
839 color_utils::GetColorWithMaxContrast(colors.ntp_color));
840
841 // Always use alternate logo (not colorful one) for all backgrounds except
842 // white.
843 if (colors.active_tab_color != SK_ColorWHITE)
844 pack->SetDisplayProperty(TP::NTP_LOGO_ALTERNATE, 1);
845
846 // Don't change frame color for inactive window.
847 pack->SetTint(TP::TINT_FRAME_INACTIVE, {-1, -1, -1});
848 pack->SetTint(TP::TINT_FRAME_INCOGNITO_INACTIVE, {-1, -1, -1});
849
850 pack->AdjustThemePack();
851
852 // The BrowserThemePack is now in a consistent state.
853 pack->is_valid_ = true;
854 }
855
BrowserThemePack(ThemeType theme_type)856 BrowserThemePack::BrowserThemePack(ThemeType theme_type)
857 : CustomThemeSupplier(theme_type) {
858 scale_factors_ = ui::GetSupportedScaleFactors();
859 // On Windows HiDPI SCALE_FACTOR_100P may not be supported by default.
860 if (!base::Contains(scale_factors_, ui::SCALE_FACTOR_100P))
861 scale_factors_.push_back(ui::SCALE_FACTOR_100P);
862 }
863
WriteToDisk(const base::FilePath & path) const864 bool BrowserThemePack::WriteToDisk(const base::FilePath& path) const {
865 // Add resources for each of the property arrays.
866 RawDataForWriting resources;
867 resources[kHeaderID] = base::StringPiece(
868 reinterpret_cast<const char*>(header_), sizeof(BrowserThemePackHeader));
869 resources[kTintsID] = base::StringPiece(
870 reinterpret_cast<const char*>(tints_),
871 sizeof(TintEntry[kTintTableLength]));
872 resources[kColorsID] =
873 base::StringPiece(reinterpret_cast<const char*>(colors_),
874 sizeof(ColorPair[kColorsArrayLength]));
875 resources[kDisplayPropertiesID] = base::StringPiece(
876 reinterpret_cast<const char*>(display_properties_),
877 sizeof(DisplayPropertyPair[kDisplayPropertiesSize]));
878
879 int source_count = 1;
880 int* end = source_images_;
881 for (; *end != -1; end++)
882 source_count++;
883 resources[kSourceImagesID] =
884 base::StringPiece(reinterpret_cast<const char*>(source_images_),
885 source_count * sizeof(*source_images_));
886
887 // Store results of GetScaleFactorsAsString() in std::string as
888 // base::StringPiece does not copy data in constructor.
889 std::string scale_factors_string = GetScaleFactorsAsString(scale_factors_);
890 resources[kScaleFactorsID] = scale_factors_string;
891
892 AddRawImagesTo(image_memory_, &resources);
893
894 RawImages reencoded_images;
895 RepackImages(images_on_file_thread_, &reencoded_images);
896 AddRawImagesTo(reencoded_images, &resources);
897
898 return ui::DataPack::WritePack(path, resources, ui::DataPack::BINARY);
899 }
900
GetTint(int id,color_utils::HSL * hsl) const901 bool BrowserThemePack::GetTint(int id, color_utils::HSL* hsl) const {
902 if (tints_) {
903 for (size_t i = 0; i < kTintTableLength; ++i) {
904 if (tints_[i].id == id) {
905 hsl->h = tints_[i].h;
906 hsl->s = tints_[i].s;
907 hsl->l = tints_[i].l;
908 return true;
909 }
910 }
911 }
912
913 return false;
914 }
915
GetColor(int id,SkColor * color) const916 bool BrowserThemePack::GetColor(int id, SkColor* color) const {
917 static const base::NoDestructor<
918 base::flat_set<TP::OverwritableByUserThemeProperty>>
919 kOpaqueColors(
920 // Explicitly creating a base::flat_set here is not strictly
921 // necessary according to C++, but we do so to work around
922 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84849.
923 base::flat_set<TP::OverwritableByUserThemeProperty>({
924 // Background tabs must be opaque since the tabstrip expects to be
925 // able to render text opaquely atop them.
926 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
927 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE,
928 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO,
929 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO,
930 // The frame colors will be used for background tabs when not
931 // otherwise overridden and thus must be opaque as well.
932 TP::COLOR_FRAME_ACTIVE,
933 TP::COLOR_FRAME_INACTIVE,
934 TP::COLOR_FRAME_ACTIVE_INCOGNITO,
935 TP::COLOR_FRAME_INACTIVE_INCOGNITO,
936 // The toolbar is used as the foreground tab color, so it must be
937 // opaque just like background tabs.
938 TP::COLOR_TOOLBAR,
939 }));
940
941 if (colors_) {
942 for (size_t i = 0; i < kColorsArrayLength; ++i) {
943 if (colors_[i].id == id) {
944 *color = colors_[i].color;
945 if (base::Contains(*kOpaqueColors, id))
946 *color = SkColorSetA(*color, SK_AlphaOPAQUE);
947 return true;
948 }
949 }
950 }
951
952 return false;
953 }
954
GetDisplayProperty(int id,int * result) const955 bool BrowserThemePack::GetDisplayProperty(int id, int* result) const {
956 if (display_properties_) {
957 for (size_t i = 0; i < kDisplayPropertiesSize; ++i) {
958 if (display_properties_[i].id == id) {
959 *result = display_properties_[i].property;
960 return true;
961 }
962 }
963 }
964
965 return false;
966 }
967
GetImageNamed(int idr_id) const968 gfx::Image BrowserThemePack::GetImageNamed(int idr_id) const {
969 PersistentID prs_id = GetPersistentIDByIDR(idr_id);
970 if (prs_id == PersistentID::kInvalid)
971 return gfx::Image();
972
973 // Check if the image is cached.
974 ImageCache::const_iterator image_iter = images_.find(prs_id);
975 if (image_iter != images_.end())
976 return image_iter->second;
977
978 ThemeImagePngSource::PngMap png_map;
979 for (size_t i = 0; i < scale_factors_.size(); ++i) {
980 scoped_refptr<base::RefCountedMemory> memory =
981 GetRawData(idr_id, scale_factors_[i]);
982 if (memory.get())
983 png_map[scale_factors_[i]] = memory;
984 }
985 if (!png_map.empty()) {
986 gfx::ImageSkia image_skia(std::make_unique<ThemeImagePngSource>(png_map),
987 1.0f);
988 gfx::Image ret = gfx::Image(image_skia);
989 images_[prs_id] = ret;
990 return ret;
991 }
992
993 return gfx::Image();
994 }
995
GetRawData(int idr_id,ui::ScaleFactor scale_factor) const996 base::RefCountedMemory* BrowserThemePack::GetRawData(
997 int idr_id,
998 ui::ScaleFactor scale_factor) const {
999 base::RefCountedMemory* memory = nullptr;
1000 PersistentID prs_id = GetPersistentIDByIDR(idr_id);
1001 int raw_id = GetRawIDByPersistentID(prs_id, scale_factor);
1002
1003 if (raw_id != -1) {
1004 if (data_pack_.get()) {
1005 memory = data_pack_->GetStaticMemory(raw_id);
1006 } else {
1007 auto it = image_memory_.find(raw_id);
1008 if (it != image_memory_.end()) {
1009 memory = it->second.get();
1010 }
1011 }
1012 }
1013
1014 return memory;
1015 }
1016
HasCustomImage(int idr_id) const1017 bool BrowserThemePack::HasCustomImage(int idr_id) const {
1018 PersistentID prs_id = GetPersistentIDByIDR(idr_id);
1019 if (prs_id == PersistentID::kInvalid)
1020 return false;
1021
1022 int* img = source_images_;
1023 for (; *img != -1; ++img) {
1024 if (*img == prs_id)
1025 return true;
1026 }
1027
1028 return false;
1029 }
1030
AddCustomThemeColorMixers(ui::ColorProvider * provider) const1031 void BrowserThemePack::AddCustomThemeColorMixers(
1032 ui::ColorProvider* provider) const {
1033 // A map from theme property IDs to color IDs for use in color mixers.
1034 constexpr struct {
1035 int property_id;
1036 int color_id;
1037 } kThemePropertiesMap[] = {
1038 {TP::COLOR_TOOLBAR, kColorToolbar},
1039 {TP::COLOR_OMNIBOX_TEXT, kColorOmniboxText},
1040 {TP::COLOR_OMNIBOX_BACKGROUND, kColorOmniboxBackground},
1041 };
1042
1043 ui::ColorSet::ColorMap theme_colors;
1044 SkColor color;
1045 for (const auto& entry : kThemePropertiesMap) {
1046 if (GetColor(entry.property_id, &color))
1047 theme_colors.insert({entry.color_id, color});
1048 }
1049 if (theme_colors.empty())
1050 return;
1051 provider->AddMixer().AddSet({kColorSetCustomTheme, std::move(theme_colors)});
1052 }
1053
1054 // private:
1055
AdjustThemePack()1056 void BrowserThemePack::AdjustThemePack() {
1057 CropImages(&images_);
1058
1059 // Set frame and toolbar related elements' colors (e.g. status bubble,
1060 // info bar, download shelf) to frame or toolbar color.
1061 SetFrameAndToolbarRelatedColors();
1062
1063 // Create toolbar image, and generate toolbar color from image where relevant.
1064 // This must be done after reading colors from JSON (so they can be used for
1065 // compositing the image).
1066 CreateToolbarImageAndColors(&images_);
1067
1068 // Create frame images, and generate frame colors from images where relevant.
1069 // This must be done after reading colors from JSON (so they can be used for
1070 // compositing the image).
1071 CreateFrameImagesAndColors(&images_);
1072
1073 // Generate any missing frame colors from tints. This must be done after
1074 // generating colors from the frame images, so only colors with no matching
1075 // images are generated.
1076 GenerateFrameColorsFromTints();
1077
1078 // Generate background color information for window control buttons. This
1079 // must be done after frame colors are set, since they are used when
1080 // determining window control button colors.
1081 GenerateWindowControlButtonColor(&images_);
1082
1083 // Create the tab background images, and generate colors where relevant. This
1084 // must be done after all frame colors are set, since they are used when
1085 // creating these.
1086 CreateTabBackgroundImagesAndColors(&images_);
1087
1088 // Make sure the |images_on_file_thread_| has bitmaps for supported
1089 // scale factors before passing to FILE thread.
1090 images_on_file_thread_ = images_;
1091 for (auto& image : images_on_file_thread_) {
1092 gfx::ImageSkia* image_skia =
1093 const_cast<gfx::ImageSkia*>(image.second.ToImageSkia());
1094 image_skia->MakeThreadSafe();
1095 }
1096
1097 // Set ThemeImageSource on |images_| to resample the source
1098 // image if a caller of BrowserThemePack::GetImageNamed() requests an
1099 // ImageSkiaRep for a scale factor not specified by the theme author.
1100 // Callers of BrowserThemePack::GetImageNamed() to be able to retrieve
1101 // ImageSkiaReps for all supported scale factors.
1102 for (auto& image : images_) {
1103 const gfx::ImageSkia source_image_skia = image.second.AsImageSkia();
1104 auto source = std::make_unique<ThemeImageSource>(source_image_skia);
1105 gfx::ImageSkia image_skia(std::move(source), source_image_skia.size());
1106 image.second = gfx::Image(image_skia);
1107 }
1108
1109 // Generate raw images (for new-tab-page attribution and background) for
1110 // any missing scale from an available scale image.
1111 for (size_t i = 0; i < base::size(kPreloadIDs); ++i) {
1112 GenerateRawImageForAllSupportedScales(kPreloadIDs[i]);
1113 }
1114
1115 // Generates missing NTP related colors. Should be called after theme images
1116 // are prepared.
1117 GenerateMissingNtpColors();
1118 }
1119
InitEmptyPack()1120 void BrowserThemePack::InitEmptyPack() {
1121 InitHeader();
1122
1123 InitTints();
1124
1125 InitColors();
1126
1127 InitDisplayProperties();
1128 }
1129
InitHeader()1130 void BrowserThemePack::InitHeader() {
1131 header_ = new BrowserThemePackHeader;
1132 header_->version = kThemePackVersion;
1133
1134 // TODO(erg): Need to make this endian safe on other computers. Prerequisite
1135 // is that ui::DataPack removes this same check.
1136 #if defined(__BYTE_ORDER)
1137 // Linux check
1138 static_assert(__BYTE_ORDER == __LITTLE_ENDIAN,
1139 "datapack assumes little endian");
1140 #elif defined(__BIG_ENDIAN__)
1141 // Mac check
1142 #error DataPack assumes little endian
1143 #endif
1144 header_->little_endian = 1;
1145 }
1146
InitTints()1147 void BrowserThemePack::InitTints() {
1148 tints_ = new TintEntry[kTintTableLength];
1149 for (size_t i = 0; i < kTintTableLength; ++i) {
1150 tints_[i].id = -1;
1151 tints_[i].h = -1;
1152 tints_[i].s = -1;
1153 tints_[i].l = -1;
1154 }
1155 }
1156
InitColors()1157 void BrowserThemePack::InitColors() {
1158 colors_ = new ColorPair[kColorsArrayLength];
1159 for (size_t i = 0; i < kColorsArrayLength; ++i) {
1160 colors_[i].id = -1;
1161 colors_[i].color = SkColorSetRGB(0, 0, 0);
1162 }
1163 }
1164
InitDisplayProperties()1165 void BrowserThemePack::InitDisplayProperties() {
1166 display_properties_ = new DisplayPropertyPair[kDisplayPropertiesSize];
1167 for (size_t i = 0; i < kDisplayPropertiesSize; ++i) {
1168 display_properties_[i].id = -1;
1169 display_properties_[i].property = 0;
1170 }
1171 }
1172
InitSourceImages()1173 void BrowserThemePack::InitSourceImages() {
1174 source_images_ = new int[1];
1175 source_images_[0] = -1;
1176 }
1177
SetHeaderId(const Extension * extension)1178 void BrowserThemePack::SetHeaderId(const Extension* extension) {
1179 DCHECK(header_);
1180 const std::string& id = extension->id();
1181 memcpy(header_->theme_id, id.c_str(), crx_file::id_util::kIdSize);
1182 }
1183
SetTintsFromJSON(const base::DictionaryValue * tints_value)1184 void BrowserThemePack::SetTintsFromJSON(
1185 const base::DictionaryValue* tints_value) {
1186 DCHECK(tints_);
1187
1188 if (!tints_value)
1189 return;
1190
1191 // Parse the incoming data from |tints_value| into an intermediary structure.
1192 std::map<int, color_utils::HSL> temp_tints;
1193 for (base::DictionaryValue::Iterator iter(*tints_value); !iter.IsAtEnd();
1194 iter.Advance()) {
1195 const base::ListValue* tint_list;
1196 if (iter.value().GetAsList(&tint_list) &&
1197 (tint_list->GetSize() == 3)) {
1198 color_utils::HSL hsl = { -1, -1, -1 };
1199
1200 if (tint_list->GetDouble(0, &hsl.h) &&
1201 tint_list->GetDouble(1, &hsl.s) &&
1202 tint_list->GetDouble(2, &hsl.l)) {
1203 MakeHSLShiftValid(&hsl);
1204 int id = GetIntForString(iter.key(), kTintTable, kTintTableLength);
1205 if (id != -1) {
1206 temp_tints[id] = hsl;
1207 }
1208 }
1209 }
1210 }
1211
1212 // Copy data from the intermediary data structure to the array.
1213 size_t count = 0;
1214 for (std::map<int, color_utils::HSL>::const_iterator it =
1215 temp_tints.begin();
1216 it != temp_tints.end() && count < kTintTableLength;
1217 ++it, ++count) {
1218 tints_[count].id = it->first;
1219 tints_[count].h = it->second.h;
1220 tints_[count].s = it->second.s;
1221 tints_[count].l = it->second.l;
1222 }
1223 }
1224
SetColorsFromJSON(const base::DictionaryValue * colors_value)1225 void BrowserThemePack::SetColorsFromJSON(
1226 const base::DictionaryValue* colors_value) {
1227 DCHECK(colors_);
1228
1229 std::map<int, SkColor> temp_colors;
1230 if (colors_value)
1231 ReadColorsFromJSON(colors_value, &temp_colors);
1232
1233 // Copy data from the intermediary data structure to the array.
1234 size_t count = 0;
1235 for (std::map<int, SkColor>::const_iterator it = temp_colors.begin();
1236 it != temp_colors.end() && count < kOverwritableColorTableLength;
1237 ++it, ++count) {
1238 colors_[count].id = it->first;
1239 colors_[count].color = it->second;
1240 }
1241 }
1242
ReadColorsFromJSON(const base::DictionaryValue * colors_value,std::map<int,SkColor> * temp_colors)1243 void BrowserThemePack::ReadColorsFromJSON(
1244 const base::DictionaryValue* colors_value,
1245 std::map<int, SkColor>* temp_colors) {
1246 // Parse the incoming data from |colors_value| into an intermediary structure.
1247 for (base::DictionaryValue::Iterator iter(*colors_value); !iter.IsAtEnd();
1248 iter.Advance()) {
1249 const base::ListValue* color_list;
1250 if (iter.value().GetAsList(&color_list) &&
1251 ((color_list->GetSize() == 3) || (color_list->GetSize() == 4))) {
1252 SkColor color = SK_ColorWHITE;
1253 int r, g, b;
1254 if (color_list->GetInteger(0, &r) && r >= 0 && r <= 255 &&
1255 color_list->GetInteger(1, &g) && g >= 0 && g <= 255 &&
1256 color_list->GetInteger(2, &b) && b >= 0 && b <= 255) {
1257 if (color_list->GetSize() == 4) {
1258 double alpha;
1259 int alpha_int;
1260 if (color_list->GetDouble(3, &alpha) && alpha >= 0 && alpha <= 1) {
1261 color =
1262 SkColorSetARGB(base::ClampRound<U8CPU>(alpha * 255), r, g, b);
1263 } else if (color_list->GetInteger(3, &alpha_int) &&
1264 (alpha_int == 0 || alpha_int == 1)) {
1265 color = SkColorSetARGB(alpha_int ? 255 : 0, r, g, b);
1266 } else {
1267 // Invalid entry for part 4.
1268 continue;
1269 }
1270 } else {
1271 color = SkColorSetRGB(r, g, b);
1272 }
1273
1274 if (iter.key() == "ntp_section") {
1275 // We no longer use ntp_section, but to support legacy
1276 // themes we still need to use it as a fallback for
1277 // ntp_header.
1278 if (!temp_colors->count(TP::COLOR_NTP_HEADER))
1279 (*temp_colors)[TP::COLOR_NTP_HEADER] = color;
1280 } else {
1281 int id = GetIntForString(iter.key(), kOverwritableColorTable,
1282 kOverwritableColorTableLength);
1283 if (id != -1)
1284 (*temp_colors)[id] = color;
1285 }
1286 }
1287 }
1288 }
1289 }
1290
SetDisplayPropertiesFromJSON(const base::DictionaryValue * display_properties_value)1291 void BrowserThemePack::SetDisplayPropertiesFromJSON(
1292 const base::DictionaryValue* display_properties_value) {
1293 DCHECK(display_properties_);
1294
1295 if (!display_properties_value)
1296 return;
1297
1298 std::map<int, int> temp_properties;
1299 for (base::DictionaryValue::Iterator iter(*display_properties_value);
1300 !iter.IsAtEnd(); iter.Advance()) {
1301 int property_id = GetIntForString(iter.key(), kDisplayProperties,
1302 kDisplayPropertiesSize);
1303 switch (property_id) {
1304 case TP::NTP_BACKGROUND_ALIGNMENT: {
1305 std::string val;
1306 if (iter.value().GetAsString(&val)) {
1307 temp_properties[TP::NTP_BACKGROUND_ALIGNMENT] =
1308 TP::StringToAlignment(val);
1309 }
1310 break;
1311 }
1312 case TP::NTP_BACKGROUND_TILING: {
1313 std::string val;
1314 if (iter.value().GetAsString(&val)) {
1315 temp_properties[TP::NTP_BACKGROUND_TILING] = TP::StringToTiling(val);
1316 }
1317 break;
1318 }
1319 case TP::NTP_LOGO_ALTERNATE: {
1320 int val = 0;
1321 if (iter.value().GetAsInteger(&val))
1322 temp_properties[TP::NTP_LOGO_ALTERNATE] = val;
1323 break;
1324 }
1325 }
1326 }
1327
1328 // Copy data from the intermediary data structure to the array.
1329 size_t count = 0;
1330 for (std::map<int, int>::const_iterator it = temp_properties.begin();
1331 it != temp_properties.end() && count < kDisplayPropertiesSize;
1332 ++it, ++count) {
1333 display_properties_[count].id = it->first;
1334 display_properties_[count].property = it->second;
1335 }
1336 }
1337
ParseImageNamesFromJSON(const base::DictionaryValue * images_value,const base::FilePath & images_path,FilePathMap * file_paths) const1338 void BrowserThemePack::ParseImageNamesFromJSON(
1339 const base::DictionaryValue* images_value,
1340 const base::FilePath& images_path,
1341 FilePathMap* file_paths) const {
1342 if (!images_value)
1343 return;
1344
1345 for (base::DictionaryValue::Iterator iter(*images_value); !iter.IsAtEnd();
1346 iter.Advance()) {
1347 if (iter.value().is_dict()) {
1348 const base::DictionaryValue* inner_value = nullptr;
1349 if (iter.value().GetAsDictionary(&inner_value)) {
1350 for (base::DictionaryValue::Iterator inner_iter(*inner_value);
1351 !inner_iter.IsAtEnd();
1352 inner_iter.Advance()) {
1353 std::string name;
1354 ui::ScaleFactor scale_factor = ui::SCALE_FACTOR_NONE;
1355 if (GetScaleFactorFromManifestKey(inner_iter.key(), &scale_factor) &&
1356 inner_iter.value().is_string() &&
1357 inner_iter.value().GetAsString(&name)) {
1358 AddFileAtScaleToMap(iter.key(),
1359 scale_factor,
1360 images_path.AppendASCII(name),
1361 file_paths);
1362 }
1363 }
1364 }
1365 } else if (iter.value().is_string()) {
1366 std::string name;
1367 if (iter.value().GetAsString(&name)) {
1368 AddFileAtScaleToMap(iter.key(),
1369 ui::SCALE_FACTOR_100P,
1370 images_path.AppendASCII(name),
1371 file_paths);
1372 }
1373 }
1374 }
1375 }
1376
AddFileAtScaleToMap(const std::string & image_name,ui::ScaleFactor scale_factor,const base::FilePath & image_path,FilePathMap * file_paths) const1377 void BrowserThemePack::AddFileAtScaleToMap(const std::string& image_name,
1378 ui::ScaleFactor scale_factor,
1379 const base::FilePath& image_path,
1380 FilePathMap* file_paths) const {
1381 PersistentID id = GetPersistentIDByName(image_name);
1382 if (id != PersistentID::kInvalid)
1383 (*file_paths)[id][scale_factor] = image_path;
1384 }
1385
BuildSourceImagesArray(const FilePathMap & file_paths)1386 void BrowserThemePack::BuildSourceImagesArray(const FilePathMap& file_paths) {
1387 source_images_ = new int[file_paths.size() + 1];
1388 std::transform(file_paths.begin(), file_paths.end(), source_images_,
1389 [](const auto& entry) { return entry.first; });
1390 source_images_[file_paths.size()] = -1;
1391 }
1392
LoadRawBitmapsTo(const FilePathMap & file_paths,ImageCache * image_cache)1393 bool BrowserThemePack::LoadRawBitmapsTo(
1394 const FilePathMap& file_paths,
1395 ImageCache* image_cache) {
1396 // Themes should be loaded on the file thread, not the UI thread.
1397 // http://crbug.com/61838
1398 base::ThreadRestrictions::ScopedAllowIO allow_io;
1399
1400 for (const auto& entry : file_paths) {
1401 PersistentID prs_id = entry.first;
1402 // Some images need to go directly into |image_memory_|. No modification is
1403 // necessary or desirable.
1404 const bool is_copyable = base::Contains(kPreloadIDs, prs_id);
1405 gfx::ImageSkia image_skia;
1406 for (int pass = 0; pass < 2; ++pass) {
1407 // Two passes: In the first pass, we process only scale factor
1408 // 100% and in the second pass all other scale factors. We
1409 // process scale factor 100% first because the first image added
1410 // in image_skia.AddRepresentation() determines the DIP size for
1411 // all representations.
1412 for (const auto& s2f : entry.second) {
1413 ui::ScaleFactor scale_factor = s2f.first;
1414 if ((pass == 0 && scale_factor != ui::SCALE_FACTOR_100P) ||
1415 (pass == 1 && scale_factor == ui::SCALE_FACTOR_100P)) {
1416 continue;
1417 }
1418 scoped_refptr<base::RefCountedMemory> raw_data(
1419 ReadFileData(s2f.second));
1420 if (!raw_data.get() || !raw_data->size()) {
1421 LOG(ERROR) << "Could not load theme image"
1422 << " prs_id=" << prs_id
1423 << " scale_factor_enum=" << scale_factor
1424 << " file=" << s2f.second.value()
1425 << (raw_data.get() ? " (zero size)" : " (read error)");
1426 return false;
1427 }
1428 if (is_copyable) {
1429 int raw_id = GetRawIDByPersistentID(prs_id, scale_factor);
1430 image_memory_[raw_id] = raw_data;
1431 } else {
1432 SkBitmap bitmap;
1433 if (gfx::PNGCodec::Decode(raw_data->front(), raw_data->size(),
1434 &bitmap)) {
1435 image_skia.AddRepresentation(
1436 gfx::ImageSkiaRep(bitmap,
1437 ui::GetScaleForScaleFactor(scale_factor)));
1438 } else {
1439 NOTREACHED() << "Unable to decode theme image resource "
1440 << entry.first;
1441 }
1442 }
1443 }
1444 }
1445 if (!is_copyable && !image_skia.isNull())
1446 (*image_cache)[prs_id] = gfx::Image(image_skia);
1447 }
1448
1449 return true;
1450 }
1451
CropImages(ImageCache * images) const1452 void BrowserThemePack::CropImages(ImageCache* images) const {
1453 for (const auto& image_to_crop : kImagesToCrop) {
1454 auto it = images->find(image_to_crop.prs_id);
1455 if (it == images->end())
1456 continue;
1457
1458 gfx::ImageSkia image_skia = it->second.AsImageSkia();
1459 (*images)[image_to_crop.prs_id] =
1460 gfx::Image(gfx::ImageSkiaOperations::ExtractSubset(
1461 image_skia,
1462 gfx::Rect(0, 0, image_skia.width(), image_to_crop.max_height)));
1463 }
1464 }
1465
SetFrameAndToolbarRelatedColors()1466 void BrowserThemePack::SetFrameAndToolbarRelatedColors() {
1467 // Propagate the user-specified Frame and Toolbar Colors to similar elements.
1468 SkColor frame_color;
1469 if (GetColor(TP::COLOR_FRAME_ACTIVE, &frame_color))
1470 SetColor(TP::COLOR_STATUS_BUBBLE, frame_color);
1471
1472 SkColor toolbar_color;
1473 if (GetColor(TP::COLOR_TOOLBAR, &toolbar_color)) {
1474 SetColor(TP::COLOR_INFOBAR, toolbar_color);
1475 SetColor(TP::COLOR_DOWNLOAD_SHELF, toolbar_color);
1476 SetColor(TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE, toolbar_color);
1477 SetColor(TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE, toolbar_color);
1478 }
1479 SkColor toolbar_button_icon_color;
1480 if (GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &toolbar_button_icon_color)) {
1481 SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, toolbar_button_icon_color);
1482 SetColor(TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, toolbar_button_icon_color);
1483 }
1484 SkColor tab_foreground_color;
1485 if (GetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE,
1486 &tab_foreground_color)) {
1487 SetColor(TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE,
1488 tab_foreground_color);
1489 }
1490 }
1491
CreateToolbarImageAndColors(ImageCache * images)1492 void BrowserThemePack::CreateToolbarImageAndColors(ImageCache* images) {
1493 ImageCache temp_output;
1494
1495 constexpr PersistentID kSrcImageId = PRS::kToolbar;
1496
1497 const auto image_it = images->find(kSrcImageId);
1498 if (image_it == images->end())
1499 return;
1500
1501 auto image = image_it->second.AsImageSkia();
1502
1503 constexpr int kToolbarColorId = TP::COLOR_TOOLBAR;
1504 SkColor toolbar_color;
1505 if (!GetColor(kToolbarColorId, &toolbar_color)) {
1506 toolbar_color = TP::GetDefaultColor(kToolbarColorId, false);
1507 }
1508
1509 // Generate a composite image by drawing the toolbar image on top of the
1510 // specified toolbar color (if any).
1511 color_utils::HSL hsl_shift{-1, -1, -1};
1512 gfx::ImageSkia overlay;
1513 auto source = std::make_unique<TabBackgroundImageSource>(
1514 toolbar_color, image, overlay, hsl_shift, 0);
1515 gfx::Size dest_size = image.size();
1516
1517 const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size));
1518 temp_output[kSrcImageId] = dest_image;
1519
1520 SetColorIfUnspecified(kToolbarColorId,
1521 ComputeImageColor(dest_image, dest_size.height()));
1522
1523 MergeImageCaches(temp_output, images);
1524 }
1525
CreateFrameImagesAndColors(ImageCache * images)1526 void BrowserThemePack::CreateFrameImagesAndColors(ImageCache* images) {
1527 static constexpr struct FrameValues {
1528 PersistentID prs_id;
1529 int tint_id;
1530 base::Optional<int> color_id;
1531 } kFrameValues[] = {
1532 {PRS::kFrame, TP::TINT_FRAME, TP::COLOR_FRAME_ACTIVE},
1533 {PRS::kFrameInactive, TP::TINT_FRAME_INACTIVE, TP::COLOR_FRAME_INACTIVE},
1534 {PRS::kFrameOverlay, TP::TINT_FRAME, base::nullopt},
1535 {PRS::kFrameOverlayInactive, TP::TINT_FRAME_INACTIVE, base::nullopt},
1536 {PRS::kFrameIncognito, TP::TINT_FRAME_INCOGNITO,
1537 TP::COLOR_FRAME_ACTIVE_INCOGNITO},
1538 {PRS::kFrameIncognitoInactive, TP::TINT_FRAME_INCOGNITO_INACTIVE,
1539 TP::COLOR_FRAME_INACTIVE_INCOGNITO},
1540 };
1541
1542 // Create all the output images in a separate cache and move them back into
1543 // the input images because there can be name collisions.
1544 ImageCache temp_output;
1545
1546 for (const auto& frame_values : kFrameValues) {
1547 PersistentID src_id = frame_values.prs_id;
1548 // If the theme doesn't provide an image, attempt to fall back to one it
1549 // does.
1550 if (!images->count(src_id)) {
1551 // Fall back from inactive overlay to active overlay.
1552 if (src_id == PRS::kFrameOverlayInactive)
1553 src_id = PRS::kFrameOverlay;
1554
1555 // Fall back from inactive incognito to active incognito.
1556 if (src_id == PRS::kFrameIncognitoInactive)
1557 src_id = PRS::kFrameIncognito;
1558
1559 // For all non-overlay images, fall back to PRS_THEME_FRAME as a last
1560 // resort.
1561 if (!images->count(src_id) && src_id != PRS::kFrameOverlay)
1562 src_id = PRS::kFrame;
1563 }
1564
1565 // Note that if the original ID and all the fallbacks are absent, the caller
1566 // will rely on the frame colors instead.
1567 const auto image = images->find(src_id);
1568 if (image != images->end()) {
1569 const gfx::Image dest_image(
1570 gfx::ImageSkiaOperations::CreateHSLShiftedImage(
1571 *image->second.ToImageSkia(),
1572 GetTintInternal(frame_values.tint_id)));
1573
1574 temp_output[frame_values.prs_id] = dest_image;
1575
1576 if (frame_values.color_id) {
1577 SetColorIfUnspecified(
1578 frame_values.color_id.value(),
1579 ComputeImageColor(dest_image, kTallestFrameHeight));
1580 }
1581 }
1582 }
1583 MergeImageCaches(temp_output, images);
1584 }
1585
GenerateFrameColorsFromTints()1586 void BrowserThemePack::GenerateFrameColorsFromTints() {
1587 SkColor frame;
1588 if (!GetColor(TP::COLOR_FRAME_ACTIVE, &frame)) {
1589 frame = TP::GetDefaultColor(TP::COLOR_FRAME_ACTIVE, false);
1590 SetColor(TP::COLOR_FRAME_ACTIVE,
1591 HSLShift(frame, GetTintInternal(TP::TINT_FRAME)));
1592 }
1593
1594 SetColorIfUnspecified(
1595 TP::COLOR_FRAME_INACTIVE,
1596 HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INACTIVE)));
1597
1598 SetColorIfUnspecified(
1599 TP::COLOR_FRAME_ACTIVE_INCOGNITO,
1600 HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INCOGNITO)));
1601
1602 SetColorIfUnspecified(
1603 TP::COLOR_FRAME_INACTIVE_INCOGNITO,
1604 HSLShift(frame, GetTintInternal(TP::TINT_FRAME_INCOGNITO_INACTIVE)));
1605 }
1606
GenerateWindowControlButtonColor(ImageCache * images)1607 void BrowserThemePack::GenerateWindowControlButtonColor(ImageCache* images) {
1608 static constexpr struct ControlBGValue {
1609 // The color to compute and store.
1610 int color_id;
1611
1612 // The frame color to use as the base of this button background.
1613 int frame_color_id;
1614 } kControlButtonBackgroundMap[] = {
1615 {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
1616 TP::COLOR_FRAME_ACTIVE},
1617 {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
1618 TP::COLOR_FRAME_INACTIVE},
1619 {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_ACTIVE,
1620 TP::COLOR_FRAME_ACTIVE_INCOGNITO},
1621 {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INCOGNITO_INACTIVE,
1622 TP::COLOR_FRAME_INACTIVE_INCOGNITO},
1623 };
1624
1625 // Get data related to the control button background image and color first,
1626 // since they are shared by all variants.
1627 gfx::ImageSkia bg_image;
1628 ImageCache::const_iterator bg_img_it =
1629 images->find(PRS::kWindowControlBackground);
1630 if (bg_img_it != images->end())
1631 bg_image = bg_img_it->second.AsImageSkia();
1632
1633 SkColor button_bg_color;
1634 SkAlpha button_bg_alpha = SK_AlphaTRANSPARENT;
1635 if (GetColor(TP::COLOR_CONTROL_BUTTON_BACKGROUND, &button_bg_color))
1636 button_bg_alpha = SkColorGetA(button_bg_color);
1637
1638 button_bg_alpha =
1639 WindowFrameUtil::CalculateWindows10GlassCaptionButtonBackgroundAlpha(
1640 button_bg_alpha);
1641
1642 // Determine what portion of the image to use in our calculations (we won't
1643 // use more along the X-axis than the width of the caption buttons). This
1644 // should theoretically be the maximum of the size of the caption button area
1645 // on the glass frame and opaque frame, but it would be rather complicated to
1646 // determine the size of the opaque frame's caption button area at pack
1647 // processing time (as it is determined by the size of icons, which we don't
1648 // have easy access to here), so we use the glass frame area as an
1649 // approximation.
1650 gfx::Size dest_size =
1651 WindowFrameUtil::GetWindows10GlassCaptionButtonAreaSize();
1652
1653 // To get an accurate sampling, all we need to do is get a representative
1654 // image that is at MOST the size of the caption button area. In the case of
1655 // an image that is smaller - we only need to sample an area the size of the
1656 // provided image (trying to take tiling into account would be overkill).
1657 if (!bg_image.isNull()) {
1658 dest_size.SetToMin(bg_image.size());
1659 }
1660
1661 for (const ControlBGValue& bg_pair : kControlButtonBackgroundMap) {
1662 SkColor frame_color;
1663 GetColor(bg_pair.frame_color_id, &frame_color);
1664 SkColor base_color =
1665 color_utils::AlphaBlend(button_bg_color, frame_color, button_bg_alpha);
1666
1667 if (bg_image.isNull()) {
1668 SetColor(bg_pair.color_id, base_color);
1669 continue;
1670 }
1671
1672 auto source = std::make_unique<ControlButtonBackgroundImageSource>(
1673 base_color, bg_image, dest_size);
1674 const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size));
1675
1676 SetColorIfUnspecified(bg_pair.color_id,
1677 ComputeImageColor(dest_image, dest_size.height()));
1678 }
1679 }
1680
CreateTabBackgroundImagesAndColors(ImageCache * images)1681 void BrowserThemePack::CreateTabBackgroundImagesAndColors(ImageCache* images) {
1682 static constexpr struct TabValues {
1683 // The background image to create/update.
1684 PersistentID tab_id;
1685
1686 // For inactive images, the corresponding active image. If the active
1687 // images are customized and the inactive ones are not, the inactive ones
1688 // will be based on the active ones.
1689 base::Optional<PersistentID> fallback_tab_id;
1690
1691 // The frame image to use as the base of this tab background image.
1692 PersistentID frame_id;
1693
1694 // The frame color to use as the base of this tab background image.
1695 int frame_color_id;
1696
1697 // The color to compute and store for this image, if not present.
1698 int color_id;
1699 } kTabBackgroundMap[] = {
1700 {PRS::kTabBackground, base::nullopt, PRS::kFrame, TP::COLOR_FRAME_ACTIVE,
1701 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE},
1702 {PRS::kTabBackgroundInactive, PRS::kTabBackground, PRS::kFrameInactive,
1703 TP::COLOR_FRAME_INACTIVE,
1704 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE},
1705 {PRS::kTabBackgroundIncognito, base::nullopt, PRS::kFrameIncognito,
1706 TP::COLOR_FRAME_ACTIVE_INCOGNITO,
1707 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO},
1708 {PRS::kTabBackgroundIncognitoInactive, PRS::kTabBackgroundIncognito,
1709 PRS::kFrameIncognitoInactive, TP::COLOR_FRAME_INACTIVE_INCOGNITO,
1710 TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO},
1711 };
1712
1713 ImageCache temp_output;
1714 for (const auto& entry : kTabBackgroundMap) {
1715 ImageCache::const_iterator tab_it = images->find(entry.tab_id);
1716
1717 // Inactive images should be based on the active ones if the active ones
1718 // were customized.
1719 if (tab_it == images->end() && entry.fallback_tab_id)
1720 tab_it = images->find(*entry.fallback_tab_id);
1721
1722 // Generate background tab images when provided with custom frame or
1723 // background tab images; in the former case the theme author may want the
1724 // background tabs to appear to tint the frame, and in the latter case the
1725 // provided background tab image may have transparent regions, which must be
1726 // made opaque by overlaying atop the original frame.
1727 const ImageCache::const_iterator frame_it = images->find(entry.frame_id);
1728 if (frame_it != images->end() || tab_it != images->end()) {
1729 SkColor frame_color;
1730 GetColor(entry.frame_color_id, &frame_color);
1731
1732 gfx::ImageSkia image_to_tint;
1733 if (frame_it != images->end())
1734 image_to_tint = (frame_it->second).AsImageSkia();
1735
1736 gfx::ImageSkia overlay;
1737 if (tab_it != images->end())
1738 overlay = tab_it->second.AsImageSkia();
1739
1740 auto source = std::make_unique<TabBackgroundImageSource>(
1741 frame_color, image_to_tint, overlay,
1742 GetTintInternal(TP::TINT_BACKGROUND_TAB), TP::kFrameHeightAboveTabs);
1743 gfx::Size dest_size = image_to_tint.size();
1744 dest_size.SetToMax(overlay.size());
1745 dest_size.set_height(kTallestTabHeight);
1746 const gfx::Image dest_image(gfx::ImageSkia(std::move(source), dest_size));
1747 temp_output[entry.tab_id] = dest_image;
1748
1749 SetColorIfUnspecified(entry.color_id,
1750 ComputeImageColor(dest_image, kTallestTabHeight));
1751 }
1752 }
1753 MergeImageCaches(temp_output, images);
1754 }
1755
GenerateMissingNtpColors()1756 void BrowserThemePack::GenerateMissingNtpColors() {
1757 gfx::Image image = GetImageNamed(IDR_THEME_NTP_BACKGROUND);
1758 bool has_background_image = !image.IsEmpty();
1759
1760 SkColor background_color;
1761 bool has_background_color =
1762 GetColor(TP::COLOR_NTP_BACKGROUND, &background_color);
1763
1764 // Calculate NTP text color based on NTP background.
1765 SkColor text_color;
1766 if (!GetColor(TP::COLOR_NTP_TEXT, &text_color)) {
1767 if (has_background_image)
1768 background_color = ComputeImageColor(image, image.Height());
1769
1770 if (has_background_image || has_background_color) {
1771 SetColor(TP::COLOR_NTP_TEXT,
1772 color_utils::GetColorWithMaxContrast(background_color));
1773 }
1774 }
1775
1776 // Calculate logo alternate, if not specified.
1777 int logo_alternate = 0;
1778 if (!GetDisplayProperty(TP::NTP_LOGO_ALTERNATE, &logo_alternate)) {
1779 logo_alternate =
1780 has_background_image ||
1781 (has_background_color && !IsColorGrayscale(background_color));
1782 SetDisplayProperty(TP::NTP_LOGO_ALTERNATE, logo_alternate);
1783 }
1784
1785 // For themes that use alternate logo and no NTP background image is present,
1786 // set logo color in the same hue as NTP background.
1787 if (logo_alternate == 1 && !has_background_image && has_background_color) {
1788 SkColor logo_color = color_utils::IsDark(background_color)
1789 ? SK_ColorWHITE
1790 : internal::GetContrastingColorForBackground(
1791 background_color,
1792 /*luminosity_change=*/0.3f);
1793 SetColor(TP::COLOR_NTP_LOGO, logo_color);
1794 }
1795
1796 // Calculate NTP shortcut color.
1797 // Use light color for NTPs with images, and themed color for NTPs with solid
1798 // color.
1799 if (!has_background_image && has_background_color &&
1800 background_color != SK_ColorWHITE) {
1801 SetColor(TP::COLOR_NTP_SHORTCUT, internal::GetContrastingColorForBackground(
1802 background_color,
1803 /*luminosity_change=*/0.2f));
1804 }
1805 }
1806
RepackImages(const ImageCache & images,RawImages * reencoded_images) const1807 void BrowserThemePack::RepackImages(const ImageCache& images,
1808 RawImages* reencoded_images) const {
1809 for (const auto& image : images) {
1810 gfx::ImageSkia image_skia = *image.second.ToImageSkia();
1811
1812 std::vector<gfx::ImageSkiaRep> image_reps = image_skia.image_reps();
1813 DCHECK(!image_reps.empty())
1814 << "No image reps for resource " << image.first << ".";
1815 for (const auto& rep : image_reps) {
1816 std::vector<unsigned char> bitmap_data;
1817 const bool encoded = gfx::PNGCodec::EncodeBGRASkBitmap(
1818 rep.GetBitmap(), false, &bitmap_data);
1819 DCHECK(encoded) << "Image file for resource " << image.first
1820 << " could not be encoded.";
1821 int raw_id = GetRawIDByPersistentID(
1822 image.first, ui::GetSupportedScaleFactor(rep.scale()));
1823 (*reencoded_images)[raw_id] =
1824 base::RefCountedBytes::TakeVector(&bitmap_data);
1825 }
1826 }
1827 }
1828
MergeImageCaches(const ImageCache & source,ImageCache * destination) const1829 void BrowserThemePack::MergeImageCaches(
1830 const ImageCache& source, ImageCache* destination) const {
1831 for (auto it = source.begin(); it != source.end(); ++it) {
1832 (*destination)[it->first] = it->second;
1833 }
1834 }
1835
AddRawImagesTo(const RawImages & images,RawDataForWriting * out) const1836 void BrowserThemePack::AddRawImagesTo(const RawImages& images,
1837 RawDataForWriting* out) const {
1838 for (auto it = images.begin(); it != images.end(); ++it) {
1839 (*out)[it->first] = base::StringPiece(
1840 it->second->front_as<char>(), it->second->size());
1841 }
1842 }
1843
GetTintInternal(int id) const1844 color_utils::HSL BrowserThemePack::GetTintInternal(int id) const {
1845 color_utils::HSL hsl;
1846 if (GetTint(id, &hsl))
1847 return hsl;
1848
1849 int original_id = id;
1850 if (id == TP::TINT_FRAME_INCOGNITO)
1851 original_id = TP::TINT_FRAME;
1852 else if (id == TP::TINT_FRAME_INCOGNITO_INACTIVE)
1853 original_id = TP::TINT_FRAME_INACTIVE;
1854
1855 return TP::GetDefaultTint(original_id, original_id != id);
1856 }
1857
GetRawIDByPersistentID(PersistentID prs_id,ui::ScaleFactor scale_factor) const1858 int BrowserThemePack::GetRawIDByPersistentID(
1859 PersistentID prs_id,
1860 ui::ScaleFactor scale_factor) const {
1861 if (prs_id == PersistentID::kInvalid)
1862 return -1;
1863
1864 for (size_t i = 0; i < scale_factors_.size(); ++i) {
1865 if (scale_factors_[i] == scale_factor)
1866 return ((PersistentID::kMaxValue + 1) * i) + prs_id;
1867 }
1868 return -1;
1869 }
1870
GetScaleFactorFromManifestKey(const std::string & key,ui::ScaleFactor * scale_factor) const1871 bool BrowserThemePack::GetScaleFactorFromManifestKey(
1872 const std::string& key,
1873 ui::ScaleFactor* scale_factor) const {
1874 int percent = 0;
1875 if (base::StringToInt(key, &percent)) {
1876 float scale = static_cast<float>(percent) / 100.0f;
1877 for (size_t i = 0; i < scale_factors_.size(); ++i) {
1878 if (fabs(ui::GetScaleForScaleFactor(scale_factors_[i]) - scale)
1879 < 0.001) {
1880 *scale_factor = scale_factors_[i];
1881 return true;
1882 }
1883 }
1884 }
1885 return false;
1886 }
1887
GenerateRawImageForAllSupportedScales(PersistentID prs_id)1888 void BrowserThemePack::GenerateRawImageForAllSupportedScales(
1889 PersistentID prs_id) {
1890 // Compute (by scaling) bitmaps for |prs_id| for any scale factors
1891 // for which the theme author did not provide a bitmap. We compute
1892 // the bitmaps using the highest scale factor that theme author
1893 // provided.
1894 // Note: We use only supported scale factors. For example, if scale
1895 // factor 2x is supported by the current system, but 1.8x is not and
1896 // if the theme author did not provide an image for 2x but one for
1897 // 1.8x, we will not use the 1.8x image here. Here we will only use
1898 // images provided for scale factors supported by the current system.
1899
1900 // See if any image is missing. If not, we're done.
1901 bool image_missing = false;
1902 for (size_t i = 0; i < scale_factors_.size(); ++i) {
1903 int raw_id = GetRawIDByPersistentID(prs_id, scale_factors_[i]);
1904 if (image_memory_.find(raw_id) == image_memory_.end()) {
1905 image_missing = true;
1906 break;
1907 }
1908 }
1909 if (!image_missing)
1910 return;
1911
1912 // Find available scale factor with highest scale.
1913 ui::ScaleFactor available_scale_factor = ui::SCALE_FACTOR_NONE;
1914 for (size_t i = 0; i < scale_factors_.size(); ++i) {
1915 int raw_id = GetRawIDByPersistentID(prs_id, scale_factors_[i]);
1916 if ((available_scale_factor == ui::SCALE_FACTOR_NONE ||
1917 (ui::GetScaleForScaleFactor(scale_factors_[i]) >
1918 ui::GetScaleForScaleFactor(available_scale_factor))) &&
1919 image_memory_.find(raw_id) != image_memory_.end()) {
1920 available_scale_factor = scale_factors_[i];
1921 }
1922 }
1923 // If no scale factor is available, we're done.
1924 if (available_scale_factor == ui::SCALE_FACTOR_NONE)
1925 return;
1926
1927 // Get bitmap for the available scale factor.
1928 int available_raw_id = GetRawIDByPersistentID(prs_id, available_scale_factor);
1929 RawImages::const_iterator it = image_memory_.find(available_raw_id);
1930 SkBitmap available_bitmap;
1931 if (!gfx::PNGCodec::Decode(it->second->front(),
1932 it->second->size(),
1933 &available_bitmap)) {
1934 NOTREACHED() << "Unable to decode theme image for prs_id=" << prs_id
1935 << " for scale_factor=" << available_scale_factor;
1936 return;
1937 }
1938
1939 // Fill in all missing scale factors by scaling the available bitmap.
1940 for (size_t i = 0; i < scale_factors_.size(); ++i) {
1941 int scaled_raw_id = GetRawIDByPersistentID(prs_id, scale_factors_[i]);
1942 if (image_memory_.find(scaled_raw_id) != image_memory_.end())
1943 continue;
1944 SkBitmap scaled_bitmap =
1945 CreateLowQualityResizedBitmap(available_bitmap,
1946 available_scale_factor,
1947 scale_factors_[i]);
1948 std::vector<unsigned char> bitmap_data;
1949 if (!gfx::PNGCodec::EncodeBGRASkBitmap(scaled_bitmap,
1950 false,
1951 &bitmap_data)) {
1952 NOTREACHED() << "Unable to encode theme image for prs_id=" << prs_id
1953 << " for scale_factor=" << scale_factors_[i];
1954 break;
1955 }
1956 image_memory_[scaled_raw_id] =
1957 base::RefCountedBytes::TakeVector(&bitmap_data);
1958 }
1959 }
1960