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 "content/public/browser/notification_service.h" 11#include "extensions/browser/image_loader.h" 12#include "extensions/browser/notification_types.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 43extensions::ExtensionResource GetExtensionIconResource( 44 const extensions::Extension* extension, 45 const ExtensionIconSet& icons, 46 int size, 47 ExtensionIconSet::MatchType match_type) { 48 const std::string& path = icons.Get(size, match_type); 49 return path.empty() ? extensions::ExtensionResource() 50 : extension->GetResource(path); 51} 52 53class BlankImageSource : public gfx::CanvasImageSource { 54 public: 55 explicit BlankImageSource(const gfx::Size& size_in_dip) 56 : CanvasImageSource(size_in_dip, /*is_opaque =*/ false) { 57 } 58 virtual ~BlankImageSource() {} 59 60 private: 61 // gfx::CanvasImageSource overrides: 62 virtual void Draw(gfx::Canvas* canvas) OVERRIDE { 63 canvas->DrawColor(SkColorSetARGB(0, 0, 0, 0)); 64 } 65 66 DISALLOW_COPY_AND_ASSIGN(BlankImageSource); 67}; 68 69} // namespace 70 71namespace extensions { 72 73//////////////////////////////////////////////////////////////////////////////// 74// IconImage::Source 75 76class IconImage::Source : public gfx::ImageSkiaSource { 77 public: 78 Source(IconImage* host, const gfx::Size& size_in_dip); 79 virtual ~Source(); 80 81 void ResetHost(); 82 83 private: 84 // gfx::ImageSkiaSource overrides: 85 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE; 86 87 // Used to load images, possibly asynchronously. NULLed out when the IconImage 88 // is destroyed. 89 IconImage* host_; 90 91 // Image whose representations will be used until |host_| loads the real 92 // representations for the image. 93 gfx::ImageSkia blank_image_; 94 95 DISALLOW_COPY_AND_ASSIGN(Source); 96}; 97 98IconImage::Source::Source(IconImage* host, const gfx::Size& size_in_dip) 99 : host_(host), 100 blank_image_(new BlankImageSource(size_in_dip), size_in_dip) { 101} 102 103IconImage::Source::~Source() { 104} 105 106void IconImage::Source::ResetHost() { 107 host_ = NULL; 108} 109 110gfx::ImageSkiaRep IconImage::Source::GetImageForScale(float scale) { 111 gfx::ImageSkiaRep representation; 112 if (host_) { 113 representation = 114 host_->LoadImageForScaleFactor(ui::GetSupportedScaleFactor(scale)); 115 } 116 117 if (!representation.is_null()) 118 return representation; 119 120 return blank_image_.GetRepresentation(scale); 121} 122 123//////////////////////////////////////////////////////////////////////////////// 124// IconImage 125 126IconImage::IconImage( 127 content::BrowserContext* context, 128 const Extension* extension, 129 const ExtensionIconSet& icon_set, 130 int resource_size_in_dip, 131 const gfx::ImageSkia& default_icon, 132 Observer* observer) 133 : browser_context_(context), 134 extension_(extension), 135 icon_set_(icon_set), 136 resource_size_in_dip_(resource_size_in_dip), 137 observer_(observer), 138 source_(NULL), 139 default_icon_(gfx::ImageSkiaOperations::CreateResizedImage( 140 default_icon, 141 skia::ImageOperations::RESIZE_BEST, 142 gfx::Size(resource_size_in_dip, resource_size_in_dip))), 143 weak_ptr_factory_(this) { 144 gfx::Size resource_size(resource_size_in_dip, resource_size_in_dip); 145 source_ = new Source(this, resource_size); 146 image_skia_ = gfx::ImageSkia(source_, resource_size); 147 148 registrar_.Add(this, 149 extensions::NOTIFICATION_EXTENSION_REMOVED, 150 content::NotificationService::AllSources()); 151} 152 153IconImage::~IconImage() { 154 source_->ResetHost(); 155} 156 157gfx::ImageSkiaRep IconImage::LoadImageForScaleFactor( 158 ui::ScaleFactor scale_factor) { 159 // Do nothing if extension is unloaded. 160 if (!extension_) 161 return gfx::ImageSkiaRep(); 162 163 const float scale = ui::GetScaleForScaleFactor(scale_factor); 164 const int resource_size_in_pixel = 165 static_cast<int>(resource_size_in_dip_ * scale); 166 167 extensions::ExtensionResource resource; 168 169 // Find extension resource for non bundled component extensions. 170 resource = GetExtensionIconResource(extension_, 171 icon_set_, 172 resource_size_in_pixel, 173 ExtensionIconSet::MATCH_BIGGER); 174 175 // If resource is not found by now, try matching smaller one. 176 if (resource.empty()) { 177 resource = GetExtensionIconResource(extension_, icon_set_, 178 resource_size_in_pixel, ExtensionIconSet::MATCH_SMALLER); 179 } 180 181 // If there is no resource found, return default icon. 182 if (resource.empty()) 183 return default_icon_.GetRepresentation(scale); 184 185 std::vector<ImageLoader::ImageRepresentation> info_list; 186 info_list.push_back(ImageLoader::ImageRepresentation( 187 resource, 188 ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 189 gfx::ToFlooredSize(gfx::ScaleSize( 190 gfx::Size(resource_size_in_dip_, resource_size_in_dip_), scale)), 191 scale_factor)); 192 193 extensions::ImageLoader* loader = 194 extensions::ImageLoader::Get(browser_context_); 195 loader->LoadImagesAsync(extension_, info_list, 196 base::Bind(&IconImage::OnImageLoaded, 197 weak_ptr_factory_.GetWeakPtr(), 198 scale)); 199 200 return gfx::ImageSkiaRep(); 201} 202 203void IconImage::OnImageLoaded(float scale, const gfx::Image& image_in) { 204 const gfx::ImageSkia* image = 205 image_in.IsEmpty() ? &default_icon_ : image_in.ToImageSkia(); 206 207 // Maybe default icon was not set. 208 if (image->isNull()) 209 return; 210 211 gfx::ImageSkiaRep rep = image->GetRepresentation(scale); 212 DCHECK(!rep.is_null()); 213 DCHECK_EQ(scale, rep.scale()); 214 215 // Remove old representation if there is one. 216 image_skia_.RemoveRepresentation(scale); 217 image_skia_.AddRepresentation(rep); 218 219 if (observer_) 220 observer_->OnExtensionIconImageChanged(this); 221} 222 223void IconImage::Observe(int type, 224 const content::NotificationSource& source, 225 const content::NotificationDetails& details) { 226 DCHECK_EQ(type, extensions::NOTIFICATION_EXTENSION_REMOVED); 227 228 const Extension* extension = content::Details<const Extension>(details).ptr(); 229 230 if (extension_ == extension) 231 extension_ = NULL; 232} 233 234} // namespace extensions 235