blob: 5004e5e557cd198d2b80a4537ced53bb03fecf1d [file] [log] [blame]
// Copyright (c) 2006-2008 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/icon_loader.h"
#include <windows.h>
#include <shellapi.h>
#include "base/file_util.h"
#include "base/gfx/size.h"
#include "base/message_loop.h"
#include "base/ref_counted.h"
#include "base/string_util.h"
#include "base/task.h"
#include "base/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/gfx/icon_util.h"
#include "SkBitmap.h"
namespace {
class IconLoaderProcessor :
public base::RefCountedThreadSafe<IconLoaderProcessor> {
public:
explicit IconLoaderProcessor(IconLoader* target)
: target_(target),
bitmap_(NULL),
small_icon_(NULL),
large_icon_(NULL),
loading_from_resource_(target->loading_from_resource_),
icon_size_(target->icon_size_) {
DCHECK(target);
path_ = target->path_;
target_message_loop_ = MessageLoop::current();
}
virtual ~IconLoaderProcessor() {
delete bitmap_;
if (small_icon_)
::DestroyIcon(small_icon_);
if (large_icon_) {
::DestroyIcon(large_icon_);
}
}
// Loads the icon with the specified dimensions.
HICON LoadSizedIcon(int width, int height) {
return static_cast<HICON>(LoadImage(NULL,
path_.value().c_str(),
width, height,
IMAGE_ICON,
LR_LOADTRANSPARENT | LR_LOADFROMFILE));
}
// Invoked from the original thread.
void Cancel() {
target_ = NULL;
}
// Invoked in the file thread. Never access target_ from this method.
void ReadIcon() {
if (loading_from_resource_)
ReadIconFromFileResource();
else
ReadIconFile();
target_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
this, &IconLoaderProcessor::NotifyFetcher));
}
// Invoked on the file thread to read a normal .ico file.
void ReadIconFile() {
// We start by loading the same icon image in the two desired dimensions,
// based on the dimensions we get back when we query the system.
int small_width = ::GetSystemMetrics(SM_CXSMICON);
int small_height = ::GetSystemMetrics(SM_CYSMICON);
int large_width = ::GetSystemMetrics(SM_CXICON);
int large_height = ::GetSystemMetrics(SM_CYICON);
small_icon_ = LoadSizedIcon(small_width, small_height);
large_icon_ = LoadSizedIcon(large_width, large_height);
// TODO(idana): Bug 991356. Currently, if the client asks for the icon in
// the form of an SkBitmap object, then we try converting the large icon
// into an SkBitmap, if the icon was successfully loaded. If the large icon
// is not available, we convert the small icon. The problem with converting
// the small or the large icon is that the resulting image is going to have
// the same dimensions as the icon (for example, 32X32 pixels). This can be
// problematic if, for example, the client tries to display the resulting
// image as a thumbnail. This will result in the client streching the
// bitmap from 32X32 to 128X128 which will decrease the image's quality.
//
// Since it is possible for web applications to define large .PNG images as
// their icons, the resulting .ico files created for these web applications
// contain icon images in sizes much larger the the system default sizes
// for icons. We can solve the aforementioned limitation by allowing the
// client to specify the size of the resulting image, when requesting an
// SkBitmap. The IconLoader code can then load a larger icon from the .ico
// file.
//
// Note that currently the components in Chrome that deal with SkBitmaps
// that represent application icons use the images to display icon size
// images and therefore the limitation above doesn't really manifest
// itself.
HICON icon_to_convert = NULL;
gfx::Size s;
if (large_icon_) {
icon_to_convert = large_icon_;
s.SetSize(large_width, large_height);
} else if (small_icon_) {
icon_to_convert = small_icon_;
s.SetSize(small_width, small_height);
}
if (icon_to_convert) {
bitmap_ = IconUtil::CreateSkBitmapFromHICON(icon_to_convert, s);
DCHECK(bitmap_);
if (small_icon_)
::DestroyIcon(small_icon_);
if (large_icon_)
::DestroyIcon(large_icon_);
small_icon_ = NULL;
large_icon_ = NULL;
}
}
void ReadIconFromFileResource() {
int size = 0;
switch (icon_size_) {
case IconLoader::SMALL:
size = SHGFI_SMALLICON;
break;
case IconLoader::NORMAL:
size = 0;
break;
case IconLoader::LARGE:
size = SHGFI_LARGEICON;
break;
default:
NOTREACHED();
}
SHFILEINFO file_info = { 0 };
if (!SHGetFileInfo(path_.value().c_str(), FILE_ATTRIBUTE_NORMAL, &file_info,
sizeof(SHFILEINFO),
SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES))
return;
ICONINFO icon_info = { 0 };
BITMAP bitmap_info = { 0 };
BOOL r = ::GetIconInfo(file_info.hIcon, &icon_info);
DCHECK(r);
r = ::GetObject(icon_info.hbmMask, sizeof(bitmap_info), &bitmap_info);
DCHECK(r);
gfx::Size icon_size(bitmap_info.bmWidth, bitmap_info.bmHeight);
bitmap_ = IconUtil::CreateSkBitmapFromHICON(file_info.hIcon, icon_size);
}
// Invoked in the target thread.
void NotifyFetcher() {
if (target_ && target_->OnLoadComplete(bitmap_)) {
// Receiver took ownership of the bitmap.
bitmap_ = NULL;
}
}
private:
// The message loop object of the thread in which we notify the delegate.
MessageLoop* target_message_loop_;
// The corresponding file fetcher or NULL if this task has been canceled.
IconLoader* target_;
// The path of the file.
FilePath path_;
// Fields from IconLoader that we need to copy as we cannot access them
// directly from the target_ in a thread-safe way.
bool loading_from_resource_;
IconLoader::IconSize icon_size_;
// The result bitmap.
SkBitmap* bitmap_;
// The result small icon.
HICON small_icon_;
// The result large icon.
HICON large_icon_;
DISALLOW_COPY_AND_ASSIGN(IconLoaderProcessor);
};
} // namespace
// static
IconLoader* IconLoader::CreateIconLoaderForFileResource(
const FilePath& path, IconSize size, Delegate* delegate) {
return new IconLoader(path, true, size, delegate);
}
IconLoader::IconLoader(const FilePath& path,
bool from_resource,
IconSize size,
Delegate* delegate)
: path_(path),
loading_from_resource_(from_resource),
icon_size_(size),
delegate_(delegate),
processor_(NULL) {
DCHECK(delegate_);
}
IconLoader::~IconLoader() {
Cancel();
}
void IconLoader::Start() {
processor_ = new IconLoaderProcessor(this);
processor_->AddRef();
g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(processor_, &IconLoaderProcessor::ReadIcon));
}
void IconLoader::Cancel() {
if (processor_) {
processor_->Cancel();
processor_->Release();
processor_ = NULL;
}
delegate_ = NULL;
}
bool IconLoader::OnLoadComplete(SkBitmap* bitmap) {
if (delegate_) {
return delegate_->OnSkBitmapLoaded(this, bitmap);
// We are likely deleted after this point.
}
return false;
}