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/extensions/extension_icon_image.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "chrome/browser/chrome_notification_types.h" 11 #include "chrome/browser/extensions/image_loader.h" 12 #include "content/public/browser/notification_service.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 41 namespace { 42 43 const int kMatchBiggerTreshold = 32; 44 45 extensions::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 57 class 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 75 namespace extensions { 76 77 //////////////////////////////////////////////////////////////////////////////// 78 // IconImage::Source 79 80 class 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 102 IconImage::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 107 IconImage::Source::~Source() { 108 } 109 110 void IconImage::Source::ResetHost() { 111 host_ = NULL; 112 } 113 114 gfx::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 130 IconImage::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, chrome::NOTIFICATION_EXTENSION_UNLOADED, 153 content::NotificationService::AllSources()); 154 } 155 156 IconImage::~IconImage() { 157 source_->ResetHost(); 158 } 159 160 gfx::ImageSkiaRep IconImage::LoadImageForScaleFactor( 161 ui::ScaleFactor scale_factor) { 162 // Do nothing if extension is unloaded. 163 if (!extension_) 164 return gfx::ImageSkiaRep(); 165 166 const float scale = ui::GetImageScale(scale_factor); 167 const int resource_size_in_pixel = 168 static_cast<int>(resource_size_in_dip_ * scale); 169 170 extensions::ExtensionResource resource; 171 172 // Find extension resource for non bundled component extensions. 173 // We try loading bigger image only if resource size is >= 32. 174 if (resource_size_in_pixel >= kMatchBiggerTreshold) { 175 resource = GetExtensionIconResource(extension_, icon_set_, 176 resource_size_in_pixel, ExtensionIconSet::MATCH_BIGGER); 177 } 178 179 // If resource is not found by now, try matching smaller one. 180 if (resource.empty()) { 181 resource = GetExtensionIconResource(extension_, icon_set_, 182 resource_size_in_pixel, ExtensionIconSet::MATCH_SMALLER); 183 } 184 185 // If there is no resource found, return default icon. 186 if (resource.empty()) 187 return default_icon_.GetRepresentation(scale); 188 189 std::vector<ImageLoader::ImageRepresentation> info_list; 190 info_list.push_back(ImageLoader::ImageRepresentation( 191 resource, 192 ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 193 gfx::ToFlooredSize(gfx::ScaleSize( 194 gfx::Size(resource_size_in_dip_, resource_size_in_dip_), scale)), 195 scale_factor)); 196 197 extensions::ImageLoader* loader = 198 extensions::ImageLoader::Get(browser_context_); 199 if (extension_->location() == Manifest::COMPONENT) { 200 // bshe's log for http://crbug.com/314872 201 LOG(ERROR) << "Start loading extension icon for " << extension_->name() 202 << ". scale = " << scale; 203 } 204 205 loader->LoadImagesAsync(extension_, info_list, 206 base::Bind(&IconImage::OnImageLoaded, 207 weak_ptr_factory_.GetWeakPtr(), 208 scale)); 209 210 return gfx::ImageSkiaRep(); 211 } 212 213 void IconImage::OnImageLoaded(float scale, const gfx::Image& image_in) { 214 const gfx::ImageSkia* image = 215 image_in.IsEmpty() ? &default_icon_ : image_in.ToImageSkia(); 216 217 if (extension_->location() == Manifest::COMPONENT) { 218 // bshe's log for http://crbug.com/314872 219 LOG(ERROR) << "Component extension icon for " << extension_->name() 220 << " is loaded. scale = " << scale; 221 } 222 // Maybe default icon was not set. 223 if (image->isNull()) 224 return; 225 226 gfx::ImageSkiaRep rep = image->GetRepresentation(scale); 227 DCHECK(!rep.is_null()); 228 DCHECK_EQ(scale, rep.scale()); 229 230 // Remove old representation if there is one. 231 image_skia_.RemoveRepresentation(scale); 232 image_skia_.AddRepresentation(rep); 233 234 if (observer_) 235 observer_->OnExtensionIconImageChanged(this); 236 } 237 238 void IconImage::Observe(int type, 239 const content::NotificationSource& source, 240 const content::NotificationDetails& details) { 241 DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_UNLOADED); 242 243 const Extension* extension = 244 content::Details<extensions::UnloadedExtensionInfo>(details)->extension; 245 246 if (extension_ == extension) 247 extension_ = NULL; 248 } 249 250 } // namespace extensions 251