| Index: chrome/browser/extensions/image_loader.cc
|
| diff --git a/chrome/browser/extensions/image_loader.cc b/chrome/browser/extensions/image_loader.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..95ea9dbc1aa69dadfacb7124666485977d04690e
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/image_loader.cc
|
| @@ -0,0 +1,326 @@
|
| +// 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_loader.h"
|
| +
|
| +#include "base/callback.h"
|
| +#include "base/file_util.h"
|
| +#include "base/path_service.h"
|
| +#include "base/string_number_conversions.h"
|
| +#include "base/threading/sequenced_worker_pool.h"
|
| +#include "chrome/browser/extensions/image_loader_factory.h"
|
| +#include "chrome/common/chrome_paths.h"
|
| +#include "chrome/common/extensions/extension.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "grit/component_extension_resources_map.h"
|
| +#include "grit/theme_resources.h"
|
| +#include "skia/ext/image_operations.h"
|
| +#include "ui/base/resource/resource_bundle.h"
|
| +#include "ui/gfx/image/image_skia.h"
|
| +#include "webkit/glue/image_decoder.h"
|
| +
|
| +using content::BrowserThread;
|
| +using extensions::Extension;
|
| +using extensions::ImageLoader;
|
| +
|
| +namespace {
|
| +
|
| +bool ShouldResizeImageRepresentation(
|
| + ImageLoader::ImageRepresentation::ResizeCondition resize_method,
|
| + const gfx::Size& decoded_size,
|
| + const gfx::Size& desired_size) {
|
| + switch (resize_method) {
|
| + case ImageLoader::ImageRepresentation::ALWAYS_RESIZE:
|
| + return decoded_size != desired_size;
|
| + case ImageLoader::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 ImageLoader::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 ImageLoader::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());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace extensions {
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// ImageLoader::ImageRepresentation
|
| +
|
| +ImageLoader::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) {
|
| +}
|
| +
|
| +ImageLoader::ImageRepresentation::~ImageRepresentation() {
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// ImageLoader::LoadResult
|
| +
|
| +struct ImageLoader::LoadResult {
|
| + LoadResult(const SkBitmap& bitmap,
|
| + const gfx::Size& original_size,
|
| + const ImageRepresentation& image_representation);
|
| + ~LoadResult();
|
| +
|
| + SkBitmap bitmap;
|
| + gfx::Size original_size;
|
| + ImageRepresentation image_representation;
|
| +};
|
| +
|
| +ImageLoader::LoadResult::LoadResult(
|
| + const SkBitmap& bitmap,
|
| + const gfx::Size& original_size,
|
| + const ImageLoader::ImageRepresentation& image_representation)
|
| + : bitmap(bitmap),
|
| + original_size(original_size),
|
| + image_representation(image_representation) {
|
| +}
|
| +
|
| +ImageLoader::LoadResult::~LoadResult() {
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// ImageLoader
|
| +
|
| +ImageLoader::ImageLoader() {
|
| +}
|
| +
|
| +ImageLoader::~ImageLoader() {
|
| +}
|
| +
|
| +// static
|
| +ImageLoader* ImageLoader::Get(Profile* profile) {
|
| + return ImageLoaderFactory::GetForProfile(profile);
|
| +}
|
| +
|
| +// static
|
| +bool ImageLoader::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},
|
| + {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128},
|
| + {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16},
|
| + };
|
| + static const size_t kExtraComponentExtensionResourcesSize =
|
| + arraysize(kExtraComponentExtensionResources);
|
| +
|
| + if (extension->location() != Extension::COMPONENT)
|
| + 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;
|
| +}
|
| +
|
| +void ImageLoader::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 ImageLoader::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());
|
| +
|
| + int resource_id;
|
| + if (IsComponentExtensionResource(extension, it->resource.relative_path(),
|
| + &resource_id)) {
|
| + LoadResourceOnUIThread(resource_id, &bitmaps[i]);
|
| + }
|
| + }
|
| +
|
| + DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
|
| + BrowserThread::PostBlockingPoolTask(
|
| + FROM_HERE,
|
| + base::Bind(&ImageLoader::LoadImagesOnBlockingPool, base::Unretained(this),
|
| + info_list, bitmaps, callback));
|
| +}
|
| +
|
| +void ImageLoader::LoadImagesOnBlockingPool(
|
| + const std::vector<ImageRepresentation>& info_list,
|
| + const std::vector<SkBitmap>& bitmaps,
|
| + const base::Callback<void(const gfx::Image&)>& callback) {
|
| + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
|
| +
|
| + gfx::ImageSkia image_skia;
|
| +
|
| + std::vector<LoadResult> load_result;
|
| +
|
| + 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].isNull()) {
|
| + bitmap = bitmaps[i];
|
| + } else {
|
| + LoadImageOnBlockingPool(*it, &bitmap);
|
| + }
|
| +
|
| + // If the image failed to load, skip it.
|
| + if (bitmap.isNull() || bitmap.empty())
|
| + continue;
|
| +
|
| + gfx::Size original_size(bitmap.width(), bitmap.height());
|
| + bitmap = ResizeIfNeeded(bitmap, *it);
|
| +
|
| + load_result.push_back(LoadResult(bitmap, original_size, *it));
|
| + }
|
| +
|
| + gfx::Image image;
|
| +
|
| + if (!image_skia.isNull()) {
|
| + image_skia.MakeThreadSafe();
|
| + image = gfx::Image(image_skia);
|
| + }
|
| +
|
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
|
| + base::Bind(&ImageLoader::ReplyBack,
|
| + base::Unretained(this), load_result,
|
| + callback));
|
| +}
|
| +
|
| +void ImageLoader::ReplyBack(
|
| + const std::vector<LoadResult>& load_result,
|
| + const base::Callback<void(const gfx::Image&)>& callback) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + gfx::ImageSkia image_skia;
|
| +
|
| + for (std::vector<LoadResult>::const_iterator it = load_result.begin();
|
| + it != load_result.end(); ++it) {
|
| + const SkBitmap& bitmap = it->bitmap;
|
| + const ImageRepresentation& image_rep = it->image_representation;
|
| +
|
| + image_skia.AddRepresentation(gfx::ImageSkiaRep(
|
| + bitmap, image_rep.scale_factor));
|
| + }
|
| +
|
| + gfx::Image image;
|
| + if (!image_skia.isNull()) {
|
| + image_skia.MakeThreadSafe();
|
| + image = gfx::Image(image_skia);
|
| + }
|
| +
|
| + callback.Run(image);
|
| +}
|
| +
|
| +} // namespace extensions
|
|
|