1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
|
// 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 "ui/gfx/image/image_skia.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/switches.h"
namespace gfx {
namespace {
// static
gfx::ImageSkiaRep& NullImageRep() {
CR_DEFINE_STATIC_LOCAL(ImageSkiaRep, null_image_rep, ());
return null_image_rep;
}
std::vector<float>* g_supported_scales = NULL;
// The difference to fall back to the smaller scale factor rather than the
// larger one. For example, assume 1.20 is requested but only 1.0 and 2.0 are
// supported. In that case, not fall back to 2.0 but 1.0, and then expand
// the image to 1.25.
const float kFallbackToSmallerScaleDiff = 0.20f;
} // namespace
namespace internal {
namespace {
class Matcher {
public:
explicit Matcher(float scale) : scale_(scale) {
}
bool operator()(const ImageSkiaRep& rep) const {
return rep.scale() == scale_;
}
private:
float scale_;
};
ImageSkiaRep ScaleImageSkiaRep(const ImageSkiaRep& rep, float target_scale) {
if (rep.is_null() || rep.scale() == target_scale)
return rep;
gfx::Size scaled_size = ToCeiledSize(
gfx::ScaleSize(rep.pixel_size(), target_scale / rep.scale()));
return ImageSkiaRep(skia::ImageOperations::Resize(
rep.sk_bitmap(),
skia::ImageOperations::RESIZE_LANCZOS3,
scaled_size.width(),
scaled_size.height()), target_scale);
}
} // namespace
// A helper class such that ImageSkia can be cheaply copied. ImageSkia holds a
// refptr instance of ImageSkiaStorage, which in turn holds all of ImageSkia's
// information. Having both |base::RefCountedThreadSafe| and
// |base::NonThreadSafe| may sounds strange but necessary to turn
// the 'thread-non-safe modifiable ImageSkiaStorage' into
// the 'thread-safe read-only ImageSkiaStorage'.
class ImageSkiaStorage : public base::RefCountedThreadSafe<ImageSkiaStorage>,
public base::NonThreadSafe {
public:
ImageSkiaStorage(ImageSkiaSource* source, const gfx::Size& size)
: source_(source),
size_(size),
read_only_(false) {
}
ImageSkiaStorage(ImageSkiaSource* source, float scale)
: source_(source),
read_only_(false) {
ImageSkia::ImageSkiaReps::iterator it = FindRepresentation(scale, true);
if (it == image_reps_.end() || it->is_null())
source_.reset();
else
size_.SetSize(it->GetWidth(), it->GetHeight());
}
bool has_source() const { return source_.get() != NULL; }
std::vector<gfx::ImageSkiaRep>& image_reps() { return image_reps_; }
const gfx::Size& size() const { return size_; }
bool read_only() const { return read_only_; }
void DeleteSource() {
source_.reset();
}
void SetReadOnly() {
read_only_ = true;
}
void DetachFromThread() {
base::NonThreadSafe::DetachFromThread();
}
// Checks if the current thread can safely modify the storage.
bool CanModify() const {
return !read_only_ && CalledOnValidThread();
}
// Checks if the current thread can safely read the storage.
bool CanRead() const {
return (read_only_ && !source_.get()) || CalledOnValidThread();
}
// Add a new representation. This checks if the scale of the added image
// is not 1.0f, and mark the existing rep as scaled to make
// the image high DPI aware.
void AddRepresentation(const ImageSkiaRep& image) {
if (image.scale() != 1.0f) {
for (ImageSkia::ImageSkiaReps::iterator it = image_reps_.begin();
it < image_reps_.end();
++it) {
if (it->unscaled()) {
DCHECK_EQ(1.0f, it->scale());
it->SetScaled();
break;
}
}
}
image_reps_.push_back(image);
}
// Returns the iterator of the image rep whose density best matches
// |scale|. If the image for the |scale| doesn't exist in the storage and
// |storage| is set, it fetches new image by calling
// |ImageSkiaSource::GetImageForScale|. There are two modes to deal with
// arbitrary scale factors.
// 1: Invoke GetImageForScale with requested scale and if the source
// returns the image with different scale (if the image doesn't exist in
// resource, for example), it will fallback to closest image rep.
// 2: Invoke GetImageForScale with the closest known scale to the requested
// one and rescale the image.
// Right now only Windows uses 2 and other platforms use 1 by default.
// TODO(mukai, oshima): abandon 1 code path and use 2 for every platforms.
std::vector<ImageSkiaRep>::iterator FindRepresentation(
float scale, bool fetch_new_image) const {
ImageSkiaStorage* non_const = const_cast<ImageSkiaStorage*>(this);
ImageSkia::ImageSkiaReps::iterator closest_iter =
non_const->image_reps().end();
ImageSkia::ImageSkiaReps::iterator exact_iter =
non_const->image_reps().end();
float smallest_diff = std::numeric_limits<float>::max();
for (ImageSkia::ImageSkiaReps::iterator it =
non_const->image_reps().begin();
it < image_reps_.end(); ++it) {
if (it->scale() == scale) {
// found exact match
fetch_new_image = false;
if (it->is_null())
continue;
exact_iter = it;
break;
}
float diff = std::abs(it->scale() - scale);
if (diff < smallest_diff && !it->is_null()) {
closest_iter = it;
smallest_diff = diff;
}
}
if (fetch_new_image && source_.get()) {
DCHECK(CalledOnValidThread()) <<
"An ImageSkia with the source must be accessed by the same thread.";
ImageSkiaRep image;
float resource_scale = scale;
if (g_supported_scales) {
if (g_supported_scales->back() <= scale) {
resource_scale = g_supported_scales->back();
} else {
for (size_t i = 0; i < g_supported_scales->size(); ++i) {
if ((*g_supported_scales)[i] + kFallbackToSmallerScaleDiff >=
scale) {
resource_scale = (*g_supported_scales)[i];
break;
}
}
}
}
if (scale != resource_scale) {
std::vector<ImageSkiaRep>::iterator iter = FindRepresentation(
resource_scale, fetch_new_image);
DCHECK(iter != image_reps_.end());
image = iter->unscaled() ? (*iter) : ScaleImageSkiaRep(*iter, scale);
} else {
image = source_->GetImageForScale(scale);
// Image may be missing for the specified scale in some cases, such like
// looking up 2x resources but the 2x resource pack is missing. Falls
// back to 1x and re-scale it.
if (image.is_null() && scale != 1.0f)
image = ScaleImageSkiaRep(source_->GetImageForScale(1.0f), scale);
}
// If the source returned the new image, store it.
if (!image.is_null() &&
std::find_if(image_reps_.begin(), image_reps_.end(),
Matcher(image.scale())) == image_reps_.end()) {
non_const->image_reps().push_back(image);
}
// If the result image's scale isn't same as the expected scale, create
// null ImageSkiaRep with the |scale| so that the next lookup will
// fallback to the closest scale.
if (image.is_null() || image.scale() != scale) {
non_const->image_reps().push_back(ImageSkiaRep(SkBitmap(), scale));
}
// image_reps_ must have the exact much now, so find again.
return FindRepresentation(scale, false);
}
return exact_iter != image_reps_.end() ? exact_iter : closest_iter;
}
private:
virtual ~ImageSkiaStorage() {
// We only care if the storage is modified by the same thread.
// Don't blow up even if someone else deleted the ImageSkia.
DetachFromThread();
}
// Vector of bitmaps and their associated scale.
std::vector<gfx::ImageSkiaRep> image_reps_;
scoped_ptr<ImageSkiaSource> source_;
// Size of the image in DIP.
gfx::Size size_;
bool read_only_;
friend class base::RefCountedThreadSafe<ImageSkiaStorage>;
};
} // internal
ImageSkia::ImageSkia() : storage_(NULL) {
}
ImageSkia::ImageSkia(ImageSkiaSource* source, const gfx::Size& size)
: storage_(new internal::ImageSkiaStorage(source, size)) {
DCHECK(source);
// No other thread has reference to this, so it's safe to detach the thread.
DetachStorageFromThread();
}
ImageSkia::ImageSkia(ImageSkiaSource* source, float scale)
: storage_(new internal::ImageSkiaStorage(source, scale)) {
DCHECK(source);
if (!storage_->has_source())
storage_ = NULL;
// No other thread has reference to this, so it's safe to detach the thread.
DetachStorageFromThread();
}
ImageSkia::ImageSkia(const ImageSkiaRep& image_rep) {
Init(image_rep);
// No other thread has reference to this, so it's safe to detach the thread.
DetachStorageFromThread();
}
ImageSkia::ImageSkia(const ImageSkia& other) : storage_(other.storage_) {
}
ImageSkia& ImageSkia::operator=(const ImageSkia& other) {
storage_ = other.storage_;
return *this;
}
ImageSkia::~ImageSkia() {
}
// static
void ImageSkia::SetSupportedScales(const std::vector<float>& supported_scales) {
if (g_supported_scales != NULL)
delete g_supported_scales;
g_supported_scales = new std::vector<float>(supported_scales);
std::sort(g_supported_scales->begin(), g_supported_scales->end());
}
// static
const std::vector<float>& ImageSkia::GetSupportedScales() {
DCHECK(g_supported_scales != NULL);
return *g_supported_scales;
}
// static
float ImageSkia::GetMaxSupportedScale() {
return g_supported_scales->back();
}
// static
ImageSkia ImageSkia::CreateFrom1xBitmap(const SkBitmap& bitmap) {
return ImageSkia(ImageSkiaRep(bitmap, 0.0f));
}
scoped_ptr<ImageSkia> ImageSkia::DeepCopy() const {
ImageSkia* copy = new ImageSkia;
if (isNull())
return make_scoped_ptr(copy);
CHECK(CanRead());
std::vector<gfx::ImageSkiaRep>& reps = storage_->image_reps();
for (std::vector<gfx::ImageSkiaRep>::iterator iter = reps.begin();
iter != reps.end(); ++iter) {
copy->AddRepresentation(*iter);
}
// The copy has its own storage. Detach the copy from the current
// thread so that other thread can use this.
if (!copy->isNull())
copy->storage_->DetachFromThread();
return make_scoped_ptr(copy);
}
bool ImageSkia::BackedBySameObjectAs(const gfx::ImageSkia& other) const {
return storage_.get() == other.storage_.get();
}
void ImageSkia::AddRepresentation(const ImageSkiaRep& image_rep) {
DCHECK(!image_rep.is_null());
// TODO(oshima): This method should be called |SetRepresentation|
// and replace the existing rep if there is already one with the
// same scale so that we can guarantee that a ImageSkia instance contains only
// one image rep per scale. This is not possible now as ImageLoader currently
// stores need this feature, but this needs to be fixed.
if (isNull()) {
Init(image_rep);
} else {
CHECK(CanModify());
// If someone is adding ImageSkia explicitly, check if we should
// make the image high DPI aware.
storage_->AddRepresentation(image_rep);
}
}
void ImageSkia::RemoveRepresentation(float scale) {
if (isNull())
return;
CHECK(CanModify());
ImageSkiaReps& image_reps = storage_->image_reps();
ImageSkiaReps::iterator it =
storage_->FindRepresentation(scale, false);
if (it != image_reps.end() && it->scale() == scale)
image_reps.erase(it);
}
bool ImageSkia::HasRepresentation(float scale) const {
if (isNull())
return false;
CHECK(CanRead());
ImageSkiaReps::iterator it = storage_->FindRepresentation(scale, false);
return (it != storage_->image_reps().end() && it->scale() == scale);
}
const ImageSkiaRep& ImageSkia::GetRepresentation(float scale) const {
if (isNull())
return NullImageRep();
CHECK(CanRead());
ImageSkiaReps::iterator it = storage_->FindRepresentation(scale, true);
if (it == storage_->image_reps().end())
return NullImageRep();
return *it;
}
void ImageSkia::SetReadOnly() {
CHECK(storage_.get());
storage_->SetReadOnly();
DetachStorageFromThread();
}
void ImageSkia::MakeThreadSafe() {
CHECK(storage_.get());
EnsureRepsForSupportedScales();
// Delete source as we no longer needs it.
if (storage_.get())
storage_->DeleteSource();
storage_->SetReadOnly();
CHECK(IsThreadSafe());
}
bool ImageSkia::IsThreadSafe() const {
return !storage_.get() || (storage_->read_only() && !storage_->has_source());
}
int ImageSkia::width() const {
return isNull() ? 0 : storage_->size().width();
}
gfx::Size ImageSkia::size() const {
return gfx::Size(width(), height());
}
int ImageSkia::height() const {
return isNull() ? 0 : storage_->size().height();
}
std::vector<ImageSkiaRep> ImageSkia::image_reps() const {
if (isNull())
return std::vector<ImageSkiaRep>();
CHECK(CanRead());
ImageSkiaReps internal_image_reps = storage_->image_reps();
// Create list of image reps to return, skipping null image reps which were
// added for caching purposes only.
ImageSkiaReps image_reps;
for (ImageSkiaReps::iterator it = internal_image_reps.begin();
it != internal_image_reps.end(); ++it) {
if (!it->is_null())
image_reps.push_back(*it);
}
return image_reps;
}
void ImageSkia::EnsureRepsForSupportedScales() const {
DCHECK(g_supported_scales != NULL);
// Don't check ReadOnly because the source may generate images
// even for read only ImageSkia. Concurrent access will be protected
// by |DCHECK(CalledOnValidThread())| in FindRepresentation.
if (storage_.get() && storage_->has_source()) {
for (std::vector<float>::const_iterator it = g_supported_scales->begin();
it != g_supported_scales->end(); ++it)
storage_->FindRepresentation(*it, true);
}
}
void ImageSkia::Init(const ImageSkiaRep& image_rep) {
// TODO(pkotwicz): The image should be null whenever image rep is null.
if (image_rep.sk_bitmap().empty()) {
storage_ = NULL;
return;
}
storage_ = new internal::ImageSkiaStorage(
NULL, gfx::Size(image_rep.GetWidth(), image_rep.GetHeight()));
storage_->image_reps().push_back(image_rep);
}
SkBitmap& ImageSkia::GetBitmap() const {
if (isNull()) {
// Callers expect a ImageSkiaRep even if it is |isNull()|.
// TODO(pkotwicz): Fix this.
return NullImageRep().mutable_sk_bitmap();
}
// TODO(oshima): This made a few tests flaky on Windows.
// Fix the root cause and re-enable this. crbug.com/145623.
#if !defined(OS_WIN)
CHECK(CanRead());
#endif
ImageSkiaReps::iterator it = storage_->FindRepresentation(1.0f, true);
if (it != storage_->image_reps().end())
return it->mutable_sk_bitmap();
return NullImageRep().mutable_sk_bitmap();
}
bool ImageSkia::CanRead() const {
return !storage_.get() || storage_->CanRead();
}
bool ImageSkia::CanModify() const {
return !storage_.get() || storage_->CanModify();
}
void ImageSkia::DetachStorageFromThread() {
if (storage_.get())
storage_->DetachFromThread();
}
} // namespace gfx
|