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 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
|
// Copyright 2012 The Chromium Authors
// 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 <stddef.h>
#include <cmath>
#include <limits>
#include <memory>
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/gfx/switches.h"
namespace gfx {
namespace {
// static
gfx::ImageSkiaRep& NullImageRep() {
static base::NoDestructor<ImageSkiaRep> null_image_rep;
return *null_image_rep;
}
} // namespace
namespace internal {
namespace {
ImageSkiaRep ScaleImageSkiaRep(const ImageSkiaRep& rep, float target_scale) {
if (rep.is_null() || rep.scale() == target_scale)
return rep;
gfx::Size scaled_size =
gfx::ScaleToCeiledSize(rep.pixel_size(), target_scale / rep.scale());
return ImageSkiaRep(
skia::ImageOperations::Resize(rep.GetBitmap(),
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.
// The ImageSkia, and this class, are designed to be thread-safe in their const
// methods, but also are bound to a single sequence for mutating methods.
// NOTE: The FindRepresentation() method const and thread-safe *iff* it is
// called with `fetch_new_image` set to true. Otherwise it may mutate the
// class, which is not thread-safe. Internally, mutation is bound to a single
// sequence with a `base::SequenceChecker`.
class ImageSkiaStorage : public base::RefCountedThreadSafe<ImageSkiaStorage> {
public:
ImageSkiaStorage(std::unique_ptr<ImageSkiaSource> source,
const gfx::Size& size);
ImageSkiaStorage(std::unique_ptr<ImageSkiaSource> source, float scale);
ImageSkiaStorage(const ImageSkiaStorage&) = delete;
ImageSkiaStorage& operator=(const ImageSkiaStorage&) = delete;
bool has_source() const { return source_ != nullptr; }
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();
void SetReadOnly();
void DetachFromSequence();
// Checks if the current thread can safely modify the storage.
bool CanModify() const;
// Checks if the current thread can safely read the storage.
bool CanRead() const;
// 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);
// Returns whether the underlying image source can provide a representation at
// any scale. In this case, the caller is guaranteed that
// FindRepresentation(..., true) will always succeed.
bool HasRepresentationAtAllScales() const;
// 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
// |source_| is set, it fetches new image by calling
// |ImageSkiaSource::GetImageForScale|. Arbitrary scale factors are dealt by
// invoking GetImageForScale with the closest known scale to the requested
// one and rescaling the image.
std::vector<ImageSkiaRep>::const_iterator FindRepresentation(
float scale,
bool fetch_new_image) const;
private:
friend class base::RefCountedThreadSafe<ImageSkiaStorage>;
virtual ~ImageSkiaStorage();
// Each entry in here has a different scale and is returned when looking for
// an ImageSkiaRep of that scale.
std::vector<gfx::ImageSkiaRep> image_reps_;
// If no ImageSkiaRep exists in `image_reps_` for a given scale, the `source_`
// is queried to produce an ImageSkiaRep at that scale.
std::unique_ptr<ImageSkiaSource> source_;
// Size of the image in DIP.
gfx::Size size_;
bool read_only_;
// This isn't using SEQUENCE_CHECKER() macros because we use the sequence
// checker outside of DCHECKs to make branching decisions.
base::SequenceChecker sequence_checker_; // nocheck
};
ImageSkiaStorage::ImageSkiaStorage(std::unique_ptr<ImageSkiaSource> source,
const gfx::Size& size)
: source_(std::move(source)), size_(size), read_only_(false) {}
ImageSkiaStorage::ImageSkiaStorage(std::unique_ptr<ImageSkiaSource> source,
float scale)
: source_(std::move(source)), read_only_(false) {
DCHECK(source_);
auto it = FindRepresentation(scale, true);
if (it == image_reps_.end() || it->is_null())
source_.reset();
else
size_.SetSize(it->GetWidth(), it->GetHeight());
}
void ImageSkiaStorage::DeleteSource() {
source_.reset();
}
void ImageSkiaStorage::SetReadOnly() {
read_only_ = true;
}
void ImageSkiaStorage::DetachFromSequence() {
sequence_checker_.DetachFromSequence();
}
bool ImageSkiaStorage::CanModify() const {
return !read_only_ && sequence_checker_.CalledOnValidSequence();
}
bool ImageSkiaStorage::CanRead() const {
return (read_only_ && !source_) || sequence_checker_.CalledOnValidSequence();
}
void ImageSkiaStorage::AddRepresentation(const ImageSkiaRep& image) {
// Explicitly adding a representation makes no sense for images that
// inherently have representations at all scales already.
DCHECK(!HasRepresentationAtAllScales());
if (image.scale() != 1.0f) {
ImageSkia::ImageSkiaReps::iterator it;
for (it = image_reps_.begin(); it < image_reps_.end(); ++it) {
if (it->unscaled()) {
DCHECK_EQ(1.0f, it->scale());
*it = ImageSkiaRep(it->GetBitmap(), it->scale());
break;
}
}
}
image_reps_.push_back(image);
}
bool ImageSkiaStorage::HasRepresentationAtAllScales() const {
return source_ && source_->HasRepresentationAtAllScales();
}
std::vector<ImageSkiaRep>::const_iterator ImageSkiaStorage::FindRepresentation(
float scale,
bool fetch_new_image) const {
TRACE_EVENT0("ui", "ImageSkiaStorage::FindRepresentation");
auto exact_iter = image_reps_.end();
auto closest_downscale_iter = image_reps_.end();
auto closest_upscale_iter = image_reps_.end();
float smallest_downscale_diff = std::numeric_limits<float>::max();
float smallest_upscale_diff = std::numeric_limits<float>::max();
for (auto it = 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;
}
if (it->is_null()) {
continue;
}
if (it->scale() > scale) {
float diff = it->scale() - scale;
if (diff < smallest_downscale_diff) {
closest_downscale_iter = it;
smallest_downscale_diff = diff;
}
} else {
float diff = scale - it->scale();
if (diff < smallest_upscale_diff) {
closest_upscale_iter = it;
smallest_upscale_diff = diff;
}
}
}
if (fetch_new_image && source_) {
DCHECK(sequence_checker_.CalledOnValidSequence())
<< "An ImageSkia with the source must be accessed by the same "
"sequence.";
// This method is const and thread-safe, unless `fetch_new_image` is true,
// in which case the method is no longer considered const and we ensure
// that it is used in this way on a single sequence at a time with the above
// `sequence_checker_`.
auto* mutable_this = const_cast<ImageSkiaStorage*>(this);
ImageSkiaRep image;
float resource_scale = scale;
if (!HasRepresentationAtAllScales()) {
resource_scale = ui::GetScaleForResourceScaleFactor(
ui::GetSupportedResourceScaleFactorForRescale(scale));
}
if (scale != resource_scale) {
auto iter = FindRepresentation(resource_scale, fetch_new_image);
CHECK(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. Fall 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() &&
!base::Contains(image_reps_, image.scale(), &ImageSkiaRep::scale)) {
mutable_this->image_reps_.push_back(image);
}
// If the source returned the new image, `image_reps_` should have the exact
// match now, or we will fallback to the new closest value. We pass false to
// prevent the generation step from running again and repeating the
// recursion.
return FindRepresentation(!image.is_null() ? image.scale() : scale, false);
}
if (exact_iter != image_reps_.end()) {
return exact_iter;
}
// Prefer downscale over upscale which results in better quality, and is
// consistent with other places such as `IconImage::LoadImageForScaleAsync`.
// TODO(crbug.com/329953472): Use a predefined threshold.
if (closest_downscale_iter != image_reps_.end()) {
return closest_downscale_iter;
}
return closest_upscale_iter;
}
ImageSkiaStorage::~ImageSkiaStorage() = default;
} // internal
ImageSkia::ImageSkia() = default;
ImageSkia::ImageSkia(std::unique_ptr<ImageSkiaSource> source,
const gfx::Size& size)
: storage_(
base::MakeRefCounted<internal::ImageSkiaStorage>(std::move(source),
size)) {
DCHECK(storage_->has_source());
// No other thread has reference to this, so it's safe to detach the sequence.
DetachStorageFromSequence();
}
ImageSkia::ImageSkia(std::unique_ptr<ImageSkiaSource> source, float scale)
: storage_(
base::MakeRefCounted<internal::ImageSkiaStorage>(std::move(source),
scale)) {
if (!storage_->has_source())
storage_ = nullptr;
// No other thread has reference to this, so it's safe to detach the sequence.
DetachStorageFromSequence();
}
ImageSkia::ImageSkia(const ImageSkiaRep& image_rep) {
DCHECK(!image_rep.is_null());
Init(image_rep);
// No other thread has reference to this, so it's safe to detach the sequence.
DetachStorageFromSequence();
}
ImageSkia::ImageSkia(const ImageSkia& other) : storage_(other.storage_) {
}
ImageSkia& ImageSkia::operator=(const ImageSkia& other) {
storage_ = other.storage_;
return *this;
}
ImageSkia::~ImageSkia() {
}
// static
ImageSkia ImageSkia::CreateFromBitmap(const SkBitmap& bitmap, float scale) {
// An uninitialized/empty/null bitmap makes a null ImageSkia.
if (bitmap.drawsNothing())
return ImageSkia();
return ImageSkia(ImageSkiaRep(bitmap, scale));
}
// static
ImageSkia ImageSkia::CreateFrom1xBitmap(const SkBitmap& bitmap) {
// An uninitialized/empty/null bitmap makes a null ImageSkia.
if (bitmap.drawsNothing())
return ImageSkia();
return ImageSkia(ImageSkiaRep(bitmap, 0.0f));
}
ImageSkia ImageSkia::DeepCopy() const {
TRACE_EVENT0("ui", "ImageSkia::DeepCopy");
ImageSkia copy;
if (isNull())
return copy;
CHECK(CanRead());
std::vector<gfx::ImageSkiaRep>& reps = storage_->image_reps();
for (auto iter = reps.begin(); iter != reps.end(); ++iter) {
copy.AddRepresentation(*iter);
}
// The copy has its own storage. Detach the copy from the current
// sequence so that other sequences can use this.
if (!copy.isNull())
copy.storage_->DetachFromSequence();
return copy;
}
bool ImageSkia::BackedBySameObjectAs(const gfx::ImageSkia& other) const {
return storage_.get() == other.storage_.get();
}
const void* ImageSkia::GetBackingObject() const {
return 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();
auto 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());
// This check is not only faster than FindRepresentation(), it's important for
// getting the right answer in cases of image types that are not based on
// discrete preset underlying representations, which otherwise might report
// "false" for this if GetRepresentation() has not yet been called for this
// |scale|.
if (storage_->HasRepresentationAtAllScales())
return true;
auto it = storage_->FindRepresentation(scale, false);
return (it != storage_->image_reps().end() && it->scale() == scale);
}
const ImageSkiaRep& ImageSkia::GetRepresentation(float scale) const {
TRACE_EVENT0("ui", "ImageSkia::GetRepresentation");
if (isNull())
return NullImageRep();
CHECK(CanRead());
auto it = storage_->FindRepresentation(scale, true);
if (it == storage_->image_reps().end())
return NullImageRep();
return *it;
}
void ImageSkia::SetReadOnly() {
CHECK(storage_.get());
storage_->SetReadOnly();
DetachStorageFromSequence();
}
void ImageSkia::MakeThreadSafe() {
TRACE_EVENT0("ui", "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 (auto 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 {
TRACE_EVENT0("ui", "ImageSkia::EnsureRepsForSupportedScales");
const std::vector<ui::ResourceScaleFactor>& supported_scales =
ui::GetSupportedResourceScaleFactors();
// Don't check ReadOnly because the source may generate images even for read
// only ImageSkia. Concurrent access will be protected by
// `DCHECK(sequence_checker_.CalledOnValidSequence())` in FindRepresentation.
if (storage_.get() && storage_->has_source()) {
for (const auto scale : supported_scales) {
storage_->FindRepresentation(ui::GetScaleForResourceScaleFactor(scale),
true);
}
}
}
void ImageSkia::RemoveUnsupportedRepresentationsForScale(float scale) {
for (const ImageSkiaRep& image_rep_to_test : image_reps()) {
const float test_scale = image_rep_to_test.scale();
if (test_scale != scale &&
ui::GetScaleForResourceScaleFactor(
ui::GetSupportedResourceScaleFactorForRescale(test_scale)) ==
scale) {
RemoveRepresentation(test_scale);
}
}
}
bool ImageSkia::IsUniquelyOwned() const {
return storage_->HasOneRef();
}
void ImageSkia::Init(const ImageSkiaRep& image_rep) {
DCHECK(!image_rep.is_null());
storage_ = new internal::ImageSkiaStorage(
nullptr, gfx::Size(image_rep.GetWidth(), image_rep.GetHeight()));
storage_->image_reps().push_back(image_rep);
}
const SkBitmap& ImageSkia::GetBitmap() const {
TRACE_EVENT0("ui", "ImageSkia::GetBitmap");
if (isNull()) {
// Callers expect a ImageSkiaRep even if it is |isNull()|.
// TODO(pkotwicz): Fix this.
return NullImageRep().GetBitmap();
}
// TODO(oshima): This made a few tests flaky on Windows.
// Fix the root cause and re-enable this. crbug.com/145623.
#if !BUILDFLAG(IS_WIN)
CHECK(CanRead());
#endif
auto it = storage_->FindRepresentation(1.0f, true);
if (it != storage_->image_reps().end())
return it->GetBitmap();
return NullImageRep().GetBitmap();
}
bool ImageSkia::CanRead() const {
return !storage_.get() || storage_->CanRead();
}
bool ImageSkia::CanModify() const {
return !storage_.get() || storage_->CanModify();
}
void ImageSkia::DetachStorageFromSequence() {
if (storage_.get())
storage_->DetachFromSequence();
}
} // namespace gfx
|