1// Copyright 2014 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 "extensions/browser/extension_icon_image.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "content/public/browser/notification_service.h"
12#include "extensions/browser/image_loader.h"
13#include "extensions/common/extension.h"
14#include "ui/gfx/canvas.h"
15#include "ui/gfx/image/canvas_image_source.h"
16#include "ui/gfx/image/image.h"
17#include "ui/gfx/image/image_skia_operations.h"
18#include "ui/gfx/image/image_skia_source.h"
19#include "ui/gfx/size.h"
20#include "ui/gfx/size_conversions.h"
21
22// The ImageSkia provided by extensions::IconImage contains ImageSkiaReps that
23// are computed and updated using the following algorithm (if no default icon
24// was supplied, transparent icon is considered the default):
25// - |LoadImageForScaleFactors()| searches the extension for an icon of an
26//   appropriate size. If the extension doesn't have a icon resource needed for
27//   the image representation, the default icon's representation for the
28//   requested scale factor is returned by ImageSkiaSource.
29// - If the extension has the resource, IconImage tries to load it using
30//   ImageLoader.
31// - |ImageLoader| is asynchronous.
32//  - ImageSkiaSource will initially return transparent image resource of the
33//    desired size.
34//  - The image will be updated with an appropriate image representation when
35//    the |ImageLoader| finishes. The image representation is chosen the same
36//    way as in the synchronous case. The observer is notified of the image
37//    change, unless the added image representation is transparent (in which
38//    case the image had already contained the appropriate image
39//    representation).
40
41namespace {
42
43const int kMatchBiggerTreshold = 32;
44
45extensions::ExtensionResource GetExtensionIconResource(
46    const extensions::Extension* extension,
47    const ExtensionIconSet& icons,
48    int size,
49    ExtensionIconSet::MatchType match_type) {
50  std::string path = icons.Get(size, match_type);
51  if (path.empty())
52    return extensions::ExtensionResource();
53
54  return extension->GetResource(path);
55}
56
57class BlankImageSource : public gfx::CanvasImageSource {
58 public:
59  explicit BlankImageSource(const gfx::Size& size_in_dip)
60      : CanvasImageSource(size_in_dip, /*is_opaque =*/ false) {
61  }
62  virtual ~BlankImageSource() {}
63
64 private:
65  // gfx::CanvasImageSource overrides:
66  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
67    canvas->DrawColor(SkColorSetARGB(0, 0, 0, 0));
68  }
69
70  DISALLOW_COPY_AND_ASSIGN(BlankImageSource);
71};
72
73}  // namespace
74
75namespace extensions {
76
77////////////////////////////////////////////////////////////////////////////////
78// IconImage::Source
79
80class IconImage::Source : public gfx::ImageSkiaSource {
81 public:
82  Source(IconImage* host, const gfx::Size& size_in_dip);
83  virtual ~Source();
84
85  void ResetHost();
86
87 private:
88  // gfx::ImageSkiaSource overrides:
89  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE;
90
91  // Used to load images, possibly asynchronously. NULLed out when the IconImage
92  // is destroyed.
93  IconImage* host_;
94
95  // Image whose representations will be used until |host_| loads the real
96  // representations for the image.
97  gfx::ImageSkia blank_image_;
98
99  DISALLOW_COPY_AND_ASSIGN(Source);
100};
101
102IconImage::Source::Source(IconImage* host, const gfx::Size& size_in_dip)
103    : host_(host),
104      blank_image_(new BlankImageSource(size_in_dip), size_in_dip) {
105}
106
107IconImage::Source::~Source() {
108}
109
110void IconImage::Source::ResetHost() {
111  host_ = NULL;
112}
113
114gfx::ImageSkiaRep IconImage::Source::GetImageForScale(float scale) {
115  gfx::ImageSkiaRep representation;
116  if (host_) {
117    representation =
118        host_->LoadImageForScaleFactor(ui::GetSupportedScaleFactor(scale));
119  }
120
121  if (!representation.is_null())
122    return representation;
123
124  return blank_image_.GetRepresentation(scale);
125}
126
127////////////////////////////////////////////////////////////////////////////////
128// IconImage
129
130IconImage::IconImage(
131    content::BrowserContext* context,
132    const Extension* extension,
133    const ExtensionIconSet& icon_set,
134    int resource_size_in_dip,
135    const gfx::ImageSkia& default_icon,
136    Observer* observer)
137    : browser_context_(context),
138      extension_(extension),
139      icon_set_(icon_set),
140      resource_size_in_dip_(resource_size_in_dip),
141      observer_(observer),
142      source_(NULL),
143      default_icon_(gfx::ImageSkiaOperations::CreateResizedImage(
144          default_icon,
145          skia::ImageOperations::RESIZE_BEST,
146          gfx::Size(resource_size_in_dip, resource_size_in_dip))),
147      weak_ptr_factory_(this) {
148  gfx::Size resource_size(resource_size_in_dip, resource_size_in_dip);
149  source_ = new Source(this, resource_size);
150  image_skia_ = gfx::ImageSkia(source_, resource_size);
151
152  registrar_.Add(this,
153                 chrome::NOTIFICATION_EXTENSION_REMOVED,
154                 content::NotificationService::AllSources());
155}
156
157IconImage::~IconImage() {
158  source_->ResetHost();
159}
160
161gfx::ImageSkiaRep IconImage::LoadImageForScaleFactor(
162    ui::ScaleFactor scale_factor) {
163  // Do nothing if extension is unloaded.
164  if (!extension_)
165    return gfx::ImageSkiaRep();
166
167  const float scale = ui::GetScaleForScaleFactor(scale_factor);
168  const int resource_size_in_pixel =
169      static_cast<int>(resource_size_in_dip_ * scale);
170
171  extensions::ExtensionResource resource;
172
173  // Find extension resource for non bundled component extensions.
174  // We try loading bigger image only if resource size is >= 32.
175  if (resource_size_in_pixel >= kMatchBiggerTreshold) {
176    resource = GetExtensionIconResource(extension_, icon_set_,
177        resource_size_in_pixel, ExtensionIconSet::MATCH_BIGGER);
178  }
179
180  // If resource is not found by now, try matching smaller one.
181  if (resource.empty()) {
182    resource = GetExtensionIconResource(extension_, icon_set_,
183        resource_size_in_pixel, ExtensionIconSet::MATCH_SMALLER);
184  }
185
186  // If there is no resource found, return default icon.
187  if (resource.empty())
188    return default_icon_.GetRepresentation(scale);
189
190  std::vector<ImageLoader::ImageRepresentation> info_list;
191  info_list.push_back(ImageLoader::ImageRepresentation(
192      resource,
193      ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
194      gfx::ToFlooredSize(gfx::ScaleSize(
195          gfx::Size(resource_size_in_dip_, resource_size_in_dip_), scale)),
196      scale_factor));
197
198  extensions::ImageLoader* loader =
199      extensions::ImageLoader::Get(browser_context_);
200  loader->LoadImagesAsync(extension_, info_list,
201                          base::Bind(&IconImage::OnImageLoaded,
202                                     weak_ptr_factory_.GetWeakPtr(),
203                                     scale));
204
205  return gfx::ImageSkiaRep();
206}
207
208void IconImage::OnImageLoaded(float scale, const gfx::Image& image_in) {
209  const gfx::ImageSkia* image =
210      image_in.IsEmpty() ? &default_icon_ : image_in.ToImageSkia();
211
212  // Maybe default icon was not set.
213  if (image->isNull())
214    return;
215
216  gfx::ImageSkiaRep rep = image->GetRepresentation(scale);
217  DCHECK(!rep.is_null());
218  DCHECK_EQ(scale, rep.scale());
219
220  // Remove old representation if there is one.
221  image_skia_.RemoveRepresentation(scale);
222  image_skia_.AddRepresentation(rep);
223
224  if (observer_)
225    observer_->OnExtensionIconImageChanged(this);
226}
227
228void IconImage::Observe(int type,
229                        const content::NotificationSource& source,
230                        const content::NotificationDetails& details) {
231  DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_REMOVED);
232
233  const Extension* extension = content::Details<const Extension>(details).ptr();
234
235  if (extension_ == extension)
236    extension_ = NULL;
237}
238
239}  // namespace extensions
240