OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/extensions/image_loader.h" | 5 #include "chrome/browser/extensions/image_loader.h" |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/base64.h" | |
11 #include "base/callback.h" | 10 #include "base/callback.h" |
12 #include "base/compiler_specific.h" | 11 #include "base/compiler_specific.h" |
13 #include "base/file_util.h" | 12 #include "base/file_util.h" |
14 #include "base/lazy_instance.h" | 13 #include "base/lazy_instance.h" |
15 #include "base/path_service.h" | 14 #include "base/path_service.h" |
16 #include "base/strings/string_number_conversions.h" | 15 #include "base/strings/string_number_conversions.h" |
17 #include "base/threading/sequenced_worker_pool.h" | 16 #include "base/threading/sequenced_worker_pool.h" |
18 #include "chrome/browser/extensions/image_loader_factory.h" | 17 #include "chrome/browser/extensions/image_loader_factory.h" |
19 #include "chrome/browser/favicon/favicon_service.h" | |
20 #include "chrome/browser/favicon/favicon_service_factory.h" | |
21 #include "chrome/common/chrome_paths.h" | 18 #include "chrome/common/chrome_paths.h" |
22 #include "chrome/common/extensions/extension.h" | 19 #include "chrome/common/extensions/extension.h" |
23 #include "chrome/common/extensions/extension_icon_set.h" | |
24 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" | |
25 #include "chrome/common/extensions/manifest_handlers/icons_handler.h" | |
26 #include "content/public/browser/browser_thread.h" | 20 #include "content/public/browser/browser_thread.h" |
27 #include "content/public/common/url_constants.h" | |
28 #include "grit/chrome_unscaled_resources.h" | 21 #include "grit/chrome_unscaled_resources.h" |
29 #include "grit/component_extension_resources_map.h" | 22 #include "grit/component_extension_resources_map.h" |
30 #include "grit/theme_resources.h" | 23 #include "grit/theme_resources.h" |
31 #include "skia/ext/image_operations.h" | 24 #include "skia/ext/image_operations.h" |
32 #include "ui/base/resource/resource_bundle.h" | 25 #include "ui/base/resource/resource_bundle.h" |
33 #include "ui/gfx/codec/png_codec.h" | 26 #include "ui/gfx/codec/png_codec.h" |
34 #include "ui/gfx/color_utils.h" | |
35 #include "ui/gfx/favicon_size.h" | |
36 #include "ui/gfx/image/image_skia.h" | 27 #include "ui/gfx/image/image_skia.h" |
37 #include "ui/gfx/size.h" | |
38 #include "ui/gfx/skbitmap_operations.h" | |
39 | 28 |
40 #if defined(USE_AURA) | 29 #if defined(USE_AURA) |
41 #include "ui/keyboard/keyboard_util.h" | 30 #include "ui/keyboard/keyboard_util.h" |
42 #endif | 31 #endif |
43 | 32 |
44 using content::BrowserThread; | 33 using content::BrowserThread; |
45 using extensions::Extension; | 34 using extensions::Extension; |
46 using extensions::ImageLoader; | 35 using extensions::ImageLoader; |
47 using extensions::Manifest; | 36 using extensions::Manifest; |
48 | 37 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
119 base::FilePath resource_path = base::FilePath().AppendASCII( | 108 base::FilePath resource_path = base::FilePath().AppendASCII( |
120 entries[i].name); | 109 entries[i].name); |
121 resource_path = resource_path.NormalizePathSeparators(); | 110 resource_path = resource_path.NormalizePathSeparators(); |
122 | 111 |
123 DCHECK(path_to_resource_id->find(resource_path) == | 112 DCHECK(path_to_resource_id->find(resource_path) == |
124 path_to_resource_id->end()); | 113 path_to_resource_id->end()); |
125 (*path_to_resource_id)[resource_path] = entries[i].value; | 114 (*path_to_resource_id)[resource_path] = entries[i].value; |
126 } | 115 } |
127 } | 116 } |
128 | 117 |
129 // Returns a PNG image data URL of a given image. | |
130 GURL GetImageDataURL(const gfx::Image& image) { | |
131 if (image.IsEmpty()) | |
132 return GURL(); | |
133 | |
134 scoped_refptr<base::RefCountedMemory> mem = | |
135 ImageLoader::BitmapToMemory(image.ToSkBitmap()); | |
136 std::string contents_base64; | |
137 // TODO(dvh): contents_base64 calculation of the result of the function is | |
138 // sub-optimal: it's a concatenation of 3 strings and |contents_base64| can | |
139 // be huge. Additionally, the GURL is returned by copy. | |
140 // crbug/304759. | |
141 if (!base::Base64Encode( | |
142 std::string(reinterpret_cast<const char*>(mem->front()), mem->size()), | |
143 &contents_base64)) | |
144 return GURL(); | |
145 | |
146 const char kDataURLPrefix[] = ":image/png;base64,"; | |
147 return GURL(std::string(chrome::kDataScheme) + kDataURLPrefix + | |
148 contents_base64); | |
149 } | |
150 | |
151 // Converts the image to grayscale. | |
152 SkBitmap DesaturateImage(const SkBitmap* image) { | |
153 color_utils::HSL shift = {-1, 0, 0.6}; | |
154 return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); | |
155 } | |
156 | |
157 // Creates an image from PNG data. | |
158 SkBitmap* ToBitmap(const unsigned char* data, size_t size) { | |
159 SkBitmap* decoded = new SkBitmap(); | |
160 bool success = gfx::PNGCodec::Decode(data, size, decoded); | |
161 DCHECK(success); | |
162 return decoded; | |
163 } | |
164 | |
165 // Load an image from a resource given its identifier |resource_id|. | |
166 SkBitmap* GetImageByResourceId(int resource_id) { | |
167 std::string contents = ResourceBundle::GetSharedInstance() | |
168 .GetRawDataResourceForScale(resource_id, | |
169 ui::SCALE_FACTOR_100P).as_string(); | |
170 | |
171 // Convert and return it. | |
172 const unsigned char* data = | |
173 reinterpret_cast<const unsigned char*>(contents.data()); | |
174 return ToBitmap(data, contents.length()); | |
175 } | |
176 | |
177 } // namespace | 118 } // namespace |
178 | 119 |
179 namespace extensions { | 120 namespace extensions { |
180 | 121 |
181 //////////////////////////////////////////////////////////////////////////////// | 122 //////////////////////////////////////////////////////////////////////////////// |
182 // ImageLoader::ImageRepresentation | 123 // ImageLoader::ImageRepresentation |
183 | 124 |
184 ImageLoader::ImageRepresentation::ImageRepresentation( | 125 ImageLoader::ImageRepresentation::ImageRepresentation( |
185 const ExtensionResource& resource, | 126 const ExtensionResource& resource, |
186 ResizeCondition resize_condition, | 127 ResizeCondition resize_condition, |
(...skipping 30 matching lines...) Expand all Loading... |
217 original_size(original_size), | 158 original_size(original_size), |
218 image_representation(image_representation) { | 159 image_representation(image_representation) { |
219 } | 160 } |
220 | 161 |
221 ImageLoader::LoadResult::~LoadResult() { | 162 ImageLoader::LoadResult::~LoadResult() { |
222 } | 163 } |
223 | 164 |
224 //////////////////////////////////////////////////////////////////////////////// | 165 //////////////////////////////////////////////////////////////////////////////// |
225 // ImageLoader | 166 // ImageLoader |
226 | 167 |
227 ImageLoader::ImageLoader(Profile* profile) | 168 ImageLoader::ImageLoader() |
228 : weak_ptr_factory_(this), profile_(profile) { | 169 : weak_ptr_factory_(this) { |
229 } | 170 } |
230 | 171 |
231 ImageLoader::~ImageLoader() { | 172 ImageLoader::~ImageLoader() { |
232 } | 173 } |
233 | 174 |
234 // static | 175 // static |
235 ImageLoader* ImageLoader::Get(Profile* profile) { | 176 ImageLoader* ImageLoader::Get(Profile* profile) { |
236 return ImageLoaderFactory::GetForProfile(profile); | 177 return ImageLoaderFactory::GetForProfile(profile); |
237 } | 178 } |
238 | 179 |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
399 | 340 |
400 gfx::Image image; | 341 gfx::Image image; |
401 if (!image_skia.isNull()) { | 342 if (!image_skia.isNull()) { |
402 image_skia.MakeThreadSafe(); | 343 image_skia.MakeThreadSafe(); |
403 image = gfx::Image(image_skia); | 344 image = gfx::Image(image_skia); |
404 } | 345 } |
405 | 346 |
406 callback.Run(image); | 347 callback.Run(image); |
407 } | 348 } |
408 | 349 |
409 void ImageLoader::LoadExtensionIconAsync( | |
410 const extensions::Extension* extension, | |
411 int icon_size, | |
412 ExtensionIconSet::MatchType match, | |
413 bool grayscale, | |
414 const base::Callback<void(const gfx::Image&)>& callback) { | |
415 // |extension| can be NULL when the default icon is requested. In this case, | |
416 // fail the icon loading, which will result in the default icon being loaded. | |
417 if (extension == NULL) { | |
418 LoadIconFailed(extension, icon_size, grayscale, callback); | |
419 return; | |
420 } | |
421 | |
422 ExtensionResource resource = IconsInfo::GetIconResource( | |
423 extension, icon_size, match); | |
424 LoadImageAsync(extension, | |
425 resource, | |
426 gfx::Size(icon_size, icon_size), | |
427 base::Bind(&ImageLoader::LoadExtensionIconDone, | |
428 base::Unretained(this), | |
429 extension, | |
430 icon_size, | |
431 grayscale, | |
432 callback)); | |
433 } | |
434 | |
435 void ImageLoader::LoadExtensionIconDone( | |
436 const extensions::Extension* extension, | |
437 int icon_size, | |
438 bool grayscale, | |
439 const base::Callback<void(const gfx::Image&)>& callback, | |
440 const gfx::Image& image) { | |
441 if (image.IsEmpty()) | |
442 LoadIconFailed(extension, icon_size, grayscale, callback); | |
443 else | |
444 FinalizeImage(image, grayscale, callback); | |
445 } | |
446 | |
447 void ImageLoader::LoadIconFailed( | |
448 const extensions::Extension* extension, | |
449 int icon_size, | |
450 bool grayscale, | |
451 const base::Callback<void(const gfx::Image&)>& callback) { | |
452 if ((extension != NULL) && | |
453 (icon_size == extension_misc::EXTENSION_ICON_BITTY)) | |
454 LoadFaviconImage(extension, icon_size, grayscale, callback); | |
455 else | |
456 LoadDefaultImage(extension, icon_size, grayscale, callback); | |
457 } | |
458 | |
459 void ImageLoader::LoadFaviconImage( | |
460 const extensions::Extension* extension, | |
461 int icon_size, | |
462 bool grayscale, | |
463 const base::Callback<void(const gfx::Image&)>& callback) { | |
464 FaviconService* favicon_service = NULL; | |
465 if (profile_ != NULL) | |
466 favicon_service = FaviconServiceFactory::GetForProfile( | |
467 profile_, Profile::EXPLICIT_ACCESS); | |
468 // Fall back to the default icons if the service isn't available. | |
469 if (favicon_service == NULL) { | |
470 LoadDefaultImage(extension, icon_size, grayscale, callback); | |
471 return; | |
472 } | |
473 | |
474 GURL favicon_url = AppLaunchInfo::GetFullLaunchURL(extension); | |
475 favicon_service->GetRawFaviconForURL( | |
476 FaviconService::FaviconForURLParams( | |
477 profile_, favicon_url, chrome::FAVICON, gfx::kFaviconSize), | |
478 ui::SCALE_FACTOR_100P, | |
479 base::Bind(&ImageLoader::OnFaviconDataAvailable, | |
480 base::Unretained(this), | |
481 extension, | |
482 icon_size, | |
483 grayscale, | |
484 callback), | |
485 &cancelable_task_tracker_); | |
486 } | |
487 | |
488 void ImageLoader::OnFaviconDataAvailable( | |
489 const extensions::Extension* extension, | |
490 int icon_size, | |
491 bool grayscale, | |
492 const base::Callback<void(const gfx::Image&)>& callback, | |
493 const chrome::FaviconBitmapResult& bitmap_result) { | |
494 // Fallback to the default icon if there wasn't a favicon. | |
495 if (!bitmap_result.is_valid()) { | |
496 LoadDefaultImage(extension, icon_size, grayscale, callback); | |
497 return; | |
498 } | |
499 | |
500 gfx::Image image = gfx::Image::CreateFrom1xBitmap(*ToBitmap( | |
501 bitmap_result.bitmap_data->front(), bitmap_result.bitmap_data->size())); | |
502 FinalizeImage(image, grayscale, callback); | |
503 } | |
504 | |
505 void ImageLoader::LoadDefaultImage( | |
506 const extensions::Extension* extension, | |
507 int icon_size, | |
508 bool grayscale, | |
509 const base::Callback<void(const gfx::Image&)>& callback) { | |
510 content::BrowserThread::PostTask( | |
511 content::BrowserThread::FILE, | |
512 FROM_HERE, | |
513 base::Bind(&ImageLoader::LoadDefaultImageOnFileThread, | |
514 base::Unretained(this), | |
515 extension, | |
516 icon_size, | |
517 grayscale, | |
518 callback)); | |
519 } | |
520 | |
521 void ImageLoader::LoadDefaultImageOnFileThread( | |
522 const extensions::Extension* extension, | |
523 int icon_size, | |
524 bool grayscale, | |
525 const base::Callback<void(const gfx::Image&)>& callback) { | |
526 const SkBitmap* default_image = NULL; | |
527 if ((extension == NULL) || (extension->is_app())) | |
528 default_image = GetDefaultAppImage(); | |
529 else | |
530 default_image = GetDefaultExtensionImage(); | |
531 | |
532 SkBitmap result_image; | |
533 if (icon_size == -1) { | |
534 // If a specific size was not requested. | |
535 result_image = *default_image; | |
536 } else { | |
537 SkBitmap resized_image( | |
538 skia::ImageOperations::Resize(*default_image, | |
539 skia::ImageOperations::RESIZE_LANCZOS3, | |
540 icon_size, | |
541 icon_size)); | |
542 // There are cases where Resize returns an empty bitmap, for example if you | |
543 // ask for an image too large. In this case it is better to return the | |
544 // default image than returning nothing at all. | |
545 if (resized_image.empty()) | |
546 resized_image = *default_image; | |
547 result_image = resized_image; | |
548 } | |
549 | |
550 content::BrowserThread::PostTask( | |
551 content::BrowserThread::UI, | |
552 FROM_HERE, | |
553 base::Bind(&ImageLoader::LoadDefaultImageDone, | |
554 base::Unretained(this), | |
555 gfx::Image::CreateFrom1xBitmap(result_image), | |
556 grayscale, | |
557 callback)); | |
558 } | |
559 | |
560 void ImageLoader::LoadDefaultImageDone( | |
561 const gfx::Image image, | |
562 bool grayscale, | |
563 const base::Callback<void(const gfx::Image&)>& callback) { | |
564 FinalizeImage(image, grayscale, callback); | |
565 } | |
566 | |
567 void ImageLoader::FinalizeImage( | |
568 const gfx::Image& image, | |
569 bool grayscale, | |
570 const base::Callback<void(const gfx::Image&)>& callback) { | |
571 content::BrowserThread::PostTask( | |
572 content::BrowserThread::FILE, | |
573 FROM_HERE, | |
574 base::Bind(&ImageLoader::FinalizeImageOnFileThread, | |
575 base::Unretained(this), | |
576 image, | |
577 grayscale, | |
578 callback)); | |
579 } | |
580 | |
581 void ImageLoader::FinalizeImageOnFileThread( | |
582 const gfx::Image image, | |
583 bool grayscale, | |
584 const base::Callback<void(const gfx::Image&)>& callback) { | |
585 SkBitmap bitmap; | |
586 if (grayscale) | |
587 bitmap = DesaturateImage(image.ToSkBitmap()); | |
588 else | |
589 bitmap = *image.ToSkBitmap(); | |
590 | |
591 gfx::Image modifiedImage = gfx::Image::CreateFrom1xBitmap(bitmap); | |
592 content::BrowserThread::PostTask(content::BrowserThread::UI, | |
593 FROM_HERE, | |
594 base::Bind(&ImageLoader::FinalizeImageDone, | |
595 base::Unretained(this), | |
596 modifiedImage, | |
597 callback)); | |
598 } | |
599 | |
600 void ImageLoader::FinalizeImageDone( | |
601 const gfx::Image image, | |
602 const base::Callback<void(const gfx::Image&)>& callback) { | |
603 callback.Run(image); | |
604 } | |
605 | |
606 void ImageLoader::LoadExtensionIconDataURLAsync( | |
607 const extensions::Extension* extension, | |
608 int icon_size, | |
609 ExtensionIconSet::MatchType match, | |
610 bool grayscale, | |
611 const base::Callback<void(const GURL&)>& callback) { | |
612 LoadExtensionIconAsync(extension, | |
613 icon_size, | |
614 match, | |
615 grayscale, | |
616 base::Bind(&ImageLoader::OnIconAvailable, | |
617 base::Unretained(this), | |
618 callback)); | |
619 } | |
620 | |
621 void ImageLoader::OnIconAvailable( | |
622 const base::Callback<void(const GURL&)>& callback, | |
623 const gfx::Image& image) { | |
624 content::BrowserThread::PostTask( | |
625 content::BrowserThread::FILE, | |
626 FROM_HERE, | |
627 base::Bind(&ImageLoader::ConvertIconToURLOnFileThread, | |
628 base::Unretained(this), | |
629 image, | |
630 callback)); | |
631 } | |
632 | |
633 void ImageLoader::ConvertIconToURLOnFileThread( | |
634 const gfx::Image image, | |
635 const base::Callback<void(const GURL&)>& callback) { | |
636 GURL url = GetImageDataURL(image); | |
637 // TODO(dvh): |url| can be huge and it's passed by copy to | |
638 // OnIconConvertedToURL(). It can be optimized. | |
639 // crbug/304759. | |
640 content::BrowserThread::PostTask( | |
641 content::BrowserThread::UI, | |
642 FROM_HERE, | |
643 base::Bind(&ImageLoader::OnIconConvertedToURL, | |
644 base::Unretained(this), | |
645 url, | |
646 callback)); | |
647 } | |
648 | |
649 void ImageLoader::OnIconConvertedToURL( | |
650 const GURL url, | |
651 const base::Callback<void(const GURL&)>& callback) { | |
652 callback.Run(url); | |
653 } | |
654 | |
655 const SkBitmap* ImageLoader::GetDefaultAppImage() { | |
656 if (!default_app_icon_.get()) | |
657 default_app_icon_.reset(GetImageByResourceId(IDR_APP_DEFAULT_ICON)); | |
658 | |
659 return default_app_icon_.get(); | |
660 } | |
661 | |
662 const SkBitmap* ImageLoader::GetDefaultExtensionImage() { | |
663 if (!default_extension_icon_.get()) { | |
664 default_extension_icon_.reset( | |
665 GetImageByResourceId(IDR_EXTENSION_DEFAULT_ICON)); | |
666 } | |
667 | |
668 return default_extension_icon_.get(); | |
669 } | |
670 | |
671 scoped_refptr<base::RefCountedMemory> ImageLoader::BitmapToMemory( | |
672 const SkBitmap* image) { | |
673 base::RefCountedBytes* image_bytes = new base::RefCountedBytes; | |
674 gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_bytes->data()); | |
675 return image_bytes; | |
676 } | |
677 | |
678 } // namespace extensions | 350 } // namespace extensions |
OLD | NEW |