Chromium Code Reviews| Index: chrome/browser/extensions/image_utils.cc |
| diff --git a/chrome/browser/extensions/image_utils.cc b/chrome/browser/extensions/image_utils.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b72ea41d97c4c35256abf9173fa97fa38b0226bc |
| --- /dev/null |
| +++ b/chrome/browser/extensions/image_utils.cc |
| @@ -0,0 +1,260 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/extensions/image_utils.h" |
| + |
| +#include "base/callback.h" |
| +#include "base/file_util.h" |
| +#include "base/path_service.h" |
| +#include "base/threading/sequenced_worker_pool.h" |
| +#include "chrome/common/chrome_notification_types.h" |
| +#include "chrome/common/chrome_paths.h" |
| +#include "chrome/common/extensions/extension.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "content/public/browser/notification_observer.h" |
| +#include "content/public/browser/notification_registrar.h" |
| +#include "content/public/browser/notification_service.h" |
| +#include "grit/component_extension_resources_map.h" |
| +#include "grit/theme_resources.h" |
| +#include "skia/ext/image_operations.h" |
| +#include "third_party/skia/include/core/SkBitmap.h" |
| +#include "ui/base/resource/resource_bundle.h" |
| +#include "ui/gfx/image/image.h" |
| +#include "ui/gfx/image/image_skia.h" |
| +#include "webkit/glue/image_decoder.h" |
| + |
| +using content::BrowserThread; |
| +using extension_image_utils::ImageRepresentation; |
| +using extensions::Extension; |
| + |
| +namespace { |
| + |
| +bool ShouldResizeImageRepresentation( |
| + ImageRepresentation::ResizeCondition resize_method, |
| + const gfx::Size& decoded_size, |
| + const gfx::Size& desired_size) { |
| + switch (resize_method) { |
| + case ImageRepresentation::ALWAYS_RESIZE: |
| + return decoded_size != desired_size; |
| + case ImageRepresentation::RESIZE_WHEN_LARGER: |
| + return decoded_size.width() > desired_size.width() || |
| + decoded_size.height() > desired_size.height(); |
| + default: |
| + NOTREACHED(); |
| + return false; |
| + } |
| +} |
| + |
| +SkBitmap ResizeIfNeeded(const SkBitmap& bitmap, |
| + const ImageRepresentation& image_info) { |
| + gfx::Size original_size(bitmap.width(), bitmap.height()); |
| + if (ShouldResizeImageRepresentation(image_info.resize_condition, |
| + original_size, |
| + image_info.desired_size)) { |
| + return skia::ImageOperations::Resize( |
| + bitmap, skia::ImageOperations::RESIZE_LANCZOS3, |
| + image_info.desired_size.width(), image_info.desired_size.height()); |
| + } |
| + |
| + return bitmap; |
| +} |
| + |
| +void LoadResourceOnUIThread(int resource_id, SkBitmap* bitmap) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + gfx::ImageSkia image( |
| + *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id)); |
| + image.MakeThreadSafe(); |
| + *bitmap = *image.bitmap(); |
| +} |
| + |
| +void LoadImageOnBlockingPool(const ImageRepresentation& image_info, |
| + SkBitmap* bitmap) { |
| + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| + |
| + // Read the file from disk. |
| + std::string file_contents; |
| + FilePath path = image_info.resource.GetFilePath(); |
| + if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) { |
| + return; |
| + } |
| + |
| + // Decode the bitmap using WebKit's image decoder. |
| + const unsigned char* data = |
| + reinterpret_cast<const unsigned char*>(file_contents.data()); |
| + webkit_glue::ImageDecoder decoder; |
| + // Note: This class only decodes bitmaps from extension resources. Chrome |
| + // doesn't (for security reasons) directly load extension resources provided |
| + // by the extension author, but instead decodes them in a separate |
| + // locked-down utility process. Only if the decoding succeeds is the image |
| + // saved from memory to disk and subsequently used in the Chrome UI. |
| + // Chrome is therefore decoding bitmaps here that were generated by Chrome. |
| + *bitmap = decoder.Decode(data, file_contents.length()); |
| +} |
| + |
| +void LoadImagesOnBlockingPool( |
| + const std::vector<ImageRepresentation>& info_list, |
| + const std::vector<SkBitmap*>& bitmaps, |
| + const base::Callback<void(const gfx::Image&)>& callback, |
| + BrowserThread::ID thread_id) { |
| + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| + |
| + gfx::ImageSkia image_skia; |
| + |
| + int i = 0; |
| + for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); |
| + it != info_list.end(); ++it, ++i) { |
| + // If we don't have a path there isn't anything we can do, just skip it. |
| + if (it->resource.relative_path().empty()) |
| + continue; |
| + |
| + SkBitmap bitmap; |
| + if (bitmaps[i]) { |
| + bitmap = *bitmaps[i]; |
| + delete bitmaps[i]; |
| + } else { |
| + LoadImageOnBlockingPool(*it, &bitmap); |
| + } |
| + |
| + // If the image failed to load, skip it. |
| + if (bitmap.isNull() || bitmap.empty()) |
| + continue; |
| + |
| + bitmap = ResizeIfNeeded(bitmap, *it); |
| + |
| + image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap, it->scale_factor)); |
| + } |
| + |
| + gfx::Image image; |
| + |
| + if (!image_skia.isNull()) { |
| + image_skia.MakeThreadSafe(); |
| + image = gfx::Image(image_skia); |
| + } |
| + |
| + BrowserThread::PostTask(thread_id, FROM_HERE, base::Bind(callback, image)); |
| +} |
| + |
| +} // namespace |
| + |
| +namespace extension_image_utils { |
| + |
| +bool IsComponentExtensionResource(const Extension* extension, |
| + const FilePath& resource_path, |
| + int* resource_id) { |
| + static const GritResourceMap kExtraComponentExtensionResources[] = { |
| + {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON}, |
| + {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16}, |
| + {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128}, |
| + {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16}, |
| + }; |
| + static const size_t kExtraComponentExtensionResourcesSize = |
| + arraysize(kExtraComponentExtensionResources); |
| + |
| + if (extension->location() != Extension::COMPONENT) |
|
Aaron Boodman
2012/10/08 19:00:31
If the location is component, then we should never
Marijn Kruisselbrink
2012/10/08 20:24:52
So should all the branches that return false have
|
| + return false; |
| + |
| + FilePath directory_path = extension->path(); |
| + FilePath resources_dir; |
| + FilePath relative_path; |
| + if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) || |
| + !resources_dir.AppendRelativePath(directory_path, &relative_path)) { |
| + return false; |
| + } |
| + relative_path = relative_path.Append(resource_path); |
| + relative_path = relative_path.NormalizePathSeparators(); |
| + |
| + // TODO(tc): Make a map of FilePath -> resource ids so we don't have to |
| + // covert to FilePaths all the time. This will be more useful as we add |
| + // more resources. |
| + for (size_t i = 0; i < kComponentExtensionResourcesSize; ++i) { |
| + FilePath resource_path = |
| + FilePath().AppendASCII(kComponentExtensionResources[i].name); |
| + resource_path = resource_path.NormalizePathSeparators(); |
| + |
| + if (relative_path == resource_path) { |
| + *resource_id = kComponentExtensionResources[i].value; |
| + return true; |
| + } |
| + } |
| + for (size_t i = 0; i < kExtraComponentExtensionResourcesSize; ++i) { |
| + FilePath resource_path = |
| + FilePath().AppendASCII(kExtraComponentExtensionResources[i].name); |
| + resource_path = resource_path.NormalizePathSeparators(); |
| + |
| + if (relative_path == resource_path) { |
| + *resource_id = kExtraComponentExtensionResources[i].value; |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +ImageRepresentation::ImageRepresentation( |
| + const ExtensionResource& resource, |
| + ResizeCondition resize_condition, |
| + const gfx::Size& desired_size, |
| + ui::ScaleFactor scale_factor) |
| + : resource(resource), |
| + resize_condition(resize_condition), |
| + desired_size(desired_size), |
| + scale_factor(scale_factor) { |
| +} |
| + |
| +ImageRepresentation::~ImageRepresentation() { |
| +} |
| + |
| +void LoadImageAsync(const Extension* extension, |
| + const ExtensionResource& resource, |
| + const gfx::Size& max_size, |
| + const base::Callback<void(const gfx::Image&)>& callback) { |
| + std::vector<ImageRepresentation> info_list; |
| + info_list.push_back(ImageRepresentation( |
| + resource, |
| + ImageRepresentation::RESIZE_WHEN_LARGER, |
| + max_size, |
| + ui::SCALE_FACTOR_100P)); |
| + LoadImagesAsync(extension, info_list, callback); |
| +} |
| + |
| +void LoadImagesAsync(const Extension* extension, |
| + const std::vector<ImageRepresentation>& info_list, |
| + const base::Callback<void(const gfx::Image&)>& callback) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + // Loading an image from the cache and loading resources have to happen |
| + // on the UI thread. So do those two things first, and pass the rest of the |
| + // work of as a blocking pool task. |
| + std::vector<SkBitmap*> bitmaps; |
| + bitmaps.resize(info_list.size()); |
| + int i = 0; |
| + for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); |
| + it != info_list.end(); ++it, ++i) { |
| + DCHECK(it->resource.relative_path().empty() || |
| + extension->path() == it->resource.extension_root()); |
| + |
| + if (extension->HasCachedImage(it->resource, it->desired_size)) { |
| + bitmaps[i] = new SkBitmap(extension->GetCachedImage(it->resource, |
| + it->desired_size)); |
| + continue; |
| + } |
| + |
| + int resource_id; |
| + if (IsComponentExtensionResource(extension, it->resource.relative_path(), |
| + &resource_id)) { |
| + bitmaps[i] = new SkBitmap; |
| + LoadResourceOnUIThread(resource_id, bitmaps[i]); |
| + } |
| + } |
|
Aaron Boodman
2012/10/08 19:00:31
If we're done at this point, it would be nice to c
Marijn Kruisselbrink
2012/10/08 20:24:52
Done. I wasn't sure if it made sense to complicate
|
| + |
| + BrowserThread::ID thread_id; |
| + CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_id)); |
| + DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| + BrowserThread::PostBlockingPoolTask( |
| + FROM_HERE, |
| + base::Bind(&LoadImagesOnBlockingPool, info_list, |
| + bitmaps, callback, thread_id)); |
| +} |
| + |
| +} // namespace extension_image_utils |