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