// 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 "base/command_line.h" #include "base/logging.h" #include "base/threading/simple_thread.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/image/image_skia_rep.h" #include "ui/gfx/image/image_skia_source.h" #include "ui/gfx/size.h" #include "ui/gfx/switches.h" // Duplicated from base/threading/non_thread_safe.h so that we can be // good citizens there and undef the macro. #if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) #define ENABLE_NON_THREAD_SAFE 1 #else #define ENABLE_NON_THREAD_SAFE 0 #endif namespace gfx { namespace { class FixedSource : public ImageSkiaSource { public: explicit FixedSource(const ImageSkiaRep& image) : image_(image) {} virtual ~FixedSource() { } virtual ImageSkiaRep GetImageForScale(float scale) override { return image_; } private: ImageSkiaRep image_; DISALLOW_COPY_AND_ASSIGN(FixedSource); }; class FixedScaleSource : public ImageSkiaSource { public: explicit FixedScaleSource(const ImageSkiaRep& image) : image_(image) {} virtual ~FixedScaleSource() { } virtual ImageSkiaRep GetImageForScale(float scale) override { if (!image_.unscaled() && image_.scale() != scale) return ImageSkiaRep(); return image_; } private: ImageSkiaRep image_; DISALLOW_COPY_AND_ASSIGN(FixedScaleSource); }; class DynamicSource : public ImageSkiaSource { public: explicit DynamicSource(const gfx::Size& size) : size_(size), last_requested_scale_(0.0f) {} virtual ~DynamicSource() { } virtual ImageSkiaRep GetImageForScale(float scale) override { last_requested_scale_ = scale; return gfx::ImageSkiaRep(size_, scale); } float GetLastRequestedScaleAndReset() { float result = last_requested_scale_; last_requested_scale_ = 0.0f; return result; } private: gfx::Size size_; float last_requested_scale_; DISALLOW_COPY_AND_ASSIGN(DynamicSource); }; class NullSource: public ImageSkiaSource { public: NullSource() { } virtual ~NullSource() { } virtual ImageSkiaRep GetImageForScale(float scale) override { return gfx::ImageSkiaRep(); } private: DISALLOW_COPY_AND_ASSIGN(NullSource); }; } // namespace namespace test { class TestOnThread : public base::SimpleThread { public: explicit TestOnThread(ImageSkia* image_skia) : SimpleThread("image_skia_on_thread"), image_skia_(image_skia), can_read_(false), can_modify_(false) { } virtual void Run() override { can_read_ = image_skia_->CanRead(); can_modify_ = image_skia_->CanModify(); if (can_read_) image_skia_->image_reps(); } void StartAndJoin() { Start(); Join(); } bool can_read() const { return can_read_; } bool can_modify() const { return can_modify_; } private: ImageSkia* image_skia_; bool can_read_; bool can_modify_; DISALLOW_COPY_AND_ASSIGN(TestOnThread); }; } // namespace test class ImageSkiaTest : public testing::Test { public: ImageSkiaTest() { // In the test, we assume that we support 1.0f and 2.0f DSFs. old_scales_ = ImageSkia::GetSupportedScales(); // Sets the list of scale factors supported by resource bundle. std::vector supported_scales; supported_scales.push_back(1.0f); supported_scales.push_back(2.0f); ImageSkia::SetSupportedScales(supported_scales); } virtual ~ImageSkiaTest() { ImageSkia::SetSupportedScales(old_scales_); } private: std::vector old_scales_; DISALLOW_COPY_AND_ASSIGN(ImageSkiaTest); }; TEST_F(ImageSkiaTest, FixedSource) { ImageSkiaRep image(Size(100, 200), 1.0f); ImageSkia image_skia(new FixedSource(image), Size(100, 200)); EXPECT_EQ(0U, image_skia.image_reps().size()); const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); EXPECT_EQ(100, result_100p.GetWidth()); EXPECT_EQ(200, result_100p.GetHeight()); EXPECT_EQ(1.0f, result_100p.scale()); EXPECT_EQ(1U, image_skia.image_reps().size()); const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); EXPECT_EQ(100, result_200p.GetWidth()); EXPECT_EQ(200, result_200p.GetHeight()); EXPECT_EQ(100, result_200p.pixel_width()); EXPECT_EQ(200, result_200p.pixel_height()); EXPECT_EQ(1.0f, result_200p.scale()); EXPECT_EQ(1U, image_skia.image_reps().size()); // Get the representation again and make sure it doesn't // generate new image skia rep. image_skia.GetRepresentation(1.0f); image_skia.GetRepresentation(2.0f); EXPECT_EQ(1U, image_skia.image_reps().size()); } TEST_F(ImageSkiaTest, FixedScaledSource) { ImageSkiaRep image(Size(100, 200), 1.0f); ImageSkia image_skia(new FixedScaleSource(image), Size(100, 200)); EXPECT_EQ(0U, image_skia.image_reps().size()); const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); EXPECT_EQ(100, result_100p.GetWidth()); EXPECT_EQ(200, result_100p.GetHeight()); EXPECT_EQ(1.0f, result_100p.scale()); EXPECT_EQ(1U, image_skia.image_reps().size()); // 2.0f data doesn't exist, then it falls back to 1.0f and rescale it. const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); EXPECT_EQ(100, result_200p.GetWidth()); EXPECT_EQ(200, result_200p.GetHeight()); EXPECT_EQ(200, result_200p.pixel_width()); EXPECT_EQ(400, result_200p.pixel_height()); EXPECT_EQ(2.0f, result_200p.scale()); EXPECT_EQ(2U, image_skia.image_reps().size()); // Get the representation again and make sure it doesn't // generate new image skia rep. image_skia.GetRepresentation(1.0f); image_skia.GetRepresentation(2.0f); EXPECT_EQ(2U, image_skia.image_reps().size()); } TEST_F(ImageSkiaTest, FixedUnscaledSource) { ImageSkiaRep image(Size(100, 200), 0.0f); ImageSkia image_skia(new FixedScaleSource(image), Size(100, 200)); EXPECT_EQ(0U, image_skia.image_reps().size()); const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); EXPECT_EQ(100, result_100p.pixel_width()); EXPECT_EQ(200, result_100p.pixel_height()); EXPECT_TRUE(result_100p.unscaled()); EXPECT_EQ(1U, image_skia.image_reps().size()); // 2.0f data doesn't exist, but unscaled ImageSkiaRep shouldn't be rescaled. const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); EXPECT_EQ(100, result_200p.pixel_width()); EXPECT_EQ(200, result_200p.pixel_height()); EXPECT_TRUE(result_200p.unscaled()); EXPECT_EQ(1U, image_skia.image_reps().size()); // Get the representation again and make sure it doesn't // generate new image skia rep. image_skia.GetRepresentation(1.0f); image_skia.GetRepresentation(2.0f); EXPECT_EQ(1U, image_skia.image_reps().size()); } TEST_F(ImageSkiaTest, DynamicSource) { ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200)); EXPECT_EQ(0U, image_skia.image_reps().size()); const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f); EXPECT_EQ(100, result_100p.GetWidth()); EXPECT_EQ(200, result_100p.GetHeight()); EXPECT_EQ(1.0f, result_100p.scale()); EXPECT_EQ(1U, image_skia.image_reps().size()); const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f); EXPECT_EQ(100, result_200p.GetWidth()); EXPECT_EQ(200, result_200p.GetHeight()); EXPECT_EQ(200, result_200p.pixel_width()); EXPECT_EQ(400, result_200p.pixel_height()); EXPECT_EQ(2.0f, result_200p.scale()); EXPECT_EQ(2U, image_skia.image_reps().size()); // Get the representation again and make sure it doesn't // generate new image skia rep. image_skia.GetRepresentation(1.0f); EXPECT_EQ(2U, image_skia.image_reps().size()); image_skia.GetRepresentation(2.0f); EXPECT_EQ(2U, image_skia.image_reps().size()); } // Tests that image_reps returns all of the representations in the // image when there are multiple representations for a scale factor. // This currently is the case with ImageLoader::LoadImages. TEST_F(ImageSkiaTest, ManyRepsPerScaleFactor) { const int kSmallIcon1x = 16; const int kSmallIcon2x = 32; const int kLargeIcon1x = 32; ImageSkia image(new NullSource(), gfx::Size(kSmallIcon1x, kSmallIcon1x)); // Simulate a source which loads images on a delay. Upon // GetImageForScaleFactor, it immediately returns null and starts loading // image reps slowly. image.GetRepresentation(1.0f); image.GetRepresentation(2.0f); // After a lengthy amount of simulated time, finally loaded image reps. image.AddRepresentation(ImageSkiaRep( gfx::Size(kSmallIcon1x, kSmallIcon1x), 1.0f)); image.AddRepresentation(ImageSkiaRep( gfx::Size(kSmallIcon2x, kSmallIcon2x), 2.0f)); image.AddRepresentation(ImageSkiaRep( gfx::Size(kLargeIcon1x, kLargeIcon1x), 1.0f)); std::vector image_reps = image.image_reps(); EXPECT_EQ(3u, image_reps.size()); int num_1x = 0; int num_2x = 0; for (size_t i = 0; i < image_reps.size(); ++i) { if (image_reps[i].scale() == 1.0f) num_1x++; else if (image_reps[i].scale() == 2.0f) num_2x++; } EXPECT_EQ(2, num_1x); EXPECT_EQ(1, num_2x); } TEST_F(ImageSkiaTest, GetBitmap) { ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200)); const SkBitmap* bitmap = image_skia.bitmap(); EXPECT_NE(static_cast(NULL), bitmap); EXPECT_FALSE(bitmap->isNull()); } TEST_F(ImageSkiaTest, GetBitmapFromEmpty) { // Create an image with 1 representation and remove it so the ImageSkiaStorage // is left with no representations. ImageSkia empty_image(ImageSkiaRep(Size(100, 200), 1.0f)); ImageSkia empty_image_copy(empty_image); empty_image.RemoveRepresentation(1.0f); // Check that ImageSkia::bitmap() still returns a valid SkBitmap pointer for // the image and all its copies. const SkBitmap* bitmap = empty_image_copy.bitmap(); ASSERT_NE(static_cast(NULL), bitmap); EXPECT_TRUE(bitmap->isNull()); EXPECT_TRUE(bitmap->empty()); } TEST_F(ImageSkiaTest, BackedBySameObjectAs) { // Null images should all be backed by the same object (NULL). ImageSkia image; ImageSkia unrelated; EXPECT_TRUE(image.BackedBySameObjectAs(unrelated)); image.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), 1.0f)); ImageSkia copy = image; copy.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), 2.0f)); unrelated.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), 1.0f)); EXPECT_TRUE(image.BackedBySameObjectAs(copy)); EXPECT_FALSE(image.BackedBySameObjectAs(unrelated)); EXPECT_FALSE(copy.BackedBySameObjectAs(unrelated)); } #if ENABLE_NON_THREAD_SAFE TEST_F(ImageSkiaTest, EmptyOnThreadTest) { ImageSkia empty; test::TestOnThread empty_on_thread(&empty); empty_on_thread.Start(); empty_on_thread.Join(); EXPECT_TRUE(empty_on_thread.can_read()); EXPECT_TRUE(empty_on_thread.can_modify()); } TEST_F(ImageSkiaTest, StaticOnThreadTest) { ImageSkia image(ImageSkiaRep(Size(100, 200), 1.0f)); EXPECT_FALSE(image.IsThreadSafe()); test::TestOnThread image_on_thread(&image); // an image that was never accessed on this thread can be // read by other thread. image_on_thread.StartAndJoin(); EXPECT_TRUE(image_on_thread.can_read()); EXPECT_TRUE(image_on_thread.can_modify()); EXPECT_FALSE(image.CanRead()); EXPECT_FALSE(image.CanModify()); image.DetachStorageFromThread(); // An image is accessed by this thread, // so other thread cannot read/modify it. image.image_reps(); test::TestOnThread image_on_thread2(&image); image_on_thread2.StartAndJoin(); EXPECT_FALSE(image_on_thread2.can_read()); EXPECT_FALSE(image_on_thread2.can_modify()); EXPECT_TRUE(image.CanRead()); EXPECT_TRUE(image.CanModify()); image.DetachStorageFromThread(); scoped_ptr deep_copy(image.DeepCopy()); EXPECT_FALSE(deep_copy->IsThreadSafe()); test::TestOnThread deepcopy_on_thread(deep_copy.get()); deepcopy_on_thread.StartAndJoin(); EXPECT_TRUE(deepcopy_on_thread.can_read()); EXPECT_TRUE(deepcopy_on_thread.can_modify()); EXPECT_FALSE(deep_copy->CanRead()); EXPECT_FALSE(deep_copy->CanModify()); scoped_ptr deep_copy2(image.DeepCopy()); EXPECT_EQ(1U, deep_copy2->image_reps().size()); // Access it from current thread so that it can't be // accessed from another thread. deep_copy2->image_reps(); EXPECT_FALSE(deep_copy2->IsThreadSafe()); test::TestOnThread deepcopy2_on_thread(deep_copy2.get()); deepcopy2_on_thread.StartAndJoin(); EXPECT_FALSE(deepcopy2_on_thread.can_read()); EXPECT_FALSE(deepcopy2_on_thread.can_modify()); EXPECT_TRUE(deep_copy2->CanRead()); EXPECT_TRUE(deep_copy2->CanModify()); image.DetachStorageFromThread(); image.SetReadOnly(); // A read-only ImageSkia with no source is thread safe. EXPECT_TRUE(image.IsThreadSafe()); test::TestOnThread readonly_on_thread(&image); readonly_on_thread.StartAndJoin(); EXPECT_TRUE(readonly_on_thread.can_read()); EXPECT_FALSE(readonly_on_thread.can_modify()); EXPECT_TRUE(image.CanRead()); EXPECT_FALSE(image.CanModify()); image.DetachStorageFromThread(); image.MakeThreadSafe(); EXPECT_TRUE(image.IsThreadSafe()); test::TestOnThread threadsafe_on_thread(&image); threadsafe_on_thread.StartAndJoin(); EXPECT_TRUE(threadsafe_on_thread.can_read()); EXPECT_FALSE(threadsafe_on_thread.can_modify()); EXPECT_TRUE(image.CanRead()); EXPECT_FALSE(image.CanModify()); } TEST_F(ImageSkiaTest, SourceOnThreadTest) { ImageSkia image(new DynamicSource(Size(100, 200)), Size(100, 200)); EXPECT_FALSE(image.IsThreadSafe()); test::TestOnThread image_on_thread(&image); image_on_thread.StartAndJoin(); // an image that was never accessed on this thread can be // read by other thread. EXPECT_TRUE(image_on_thread.can_read()); EXPECT_TRUE(image_on_thread.can_modify()); EXPECT_FALSE(image.CanRead()); EXPECT_FALSE(image.CanModify()); image.DetachStorageFromThread(); // An image is accessed by this thread, // so other thread cannot read/modify it. image.image_reps(); test::TestOnThread image_on_thread2(&image); image_on_thread2.StartAndJoin(); EXPECT_FALSE(image_on_thread2.can_read()); EXPECT_FALSE(image_on_thread2.can_modify()); EXPECT_TRUE(image.CanRead()); EXPECT_TRUE(image.CanModify()); image.DetachStorageFromThread(); image.SetReadOnly(); EXPECT_FALSE(image.IsThreadSafe()); test::TestOnThread readonly_on_thread(&image); readonly_on_thread.StartAndJoin(); EXPECT_TRUE(readonly_on_thread.can_read()); EXPECT_FALSE(readonly_on_thread.can_modify()); EXPECT_FALSE(image.CanRead()); EXPECT_FALSE(image.CanModify()); image.DetachStorageFromThread(); image.MakeThreadSafe(); EXPECT_TRUE(image.IsThreadSafe()); // Check if image reps are generated for supported scale factors. EXPECT_EQ(ImageSkia::GetSupportedScales().size(), image.image_reps().size()); test::TestOnThread threadsafe_on_thread(&image); threadsafe_on_thread.StartAndJoin(); EXPECT_TRUE(threadsafe_on_thread.can_read()); EXPECT_FALSE(threadsafe_on_thread.can_modify()); EXPECT_TRUE(image.CanRead()); EXPECT_FALSE(image.CanModify()); } #endif // ENABLE_NON_THREAD_SAFE // Just in case we ever get lumped together with other compilation units. #undef ENABLE_NON_THREAD_SAFE TEST_F(ImageSkiaTest, Unscaled) { SkBitmap bitmap; // An ImageSkia created with 1x bitmap is unscaled. ImageSkia image_skia = ImageSkia::CreateFrom1xBitmap(bitmap); EXPECT_TRUE(image_skia.GetRepresentation(1.0f).unscaled()); ImageSkiaRep rep_2x(Size(100, 100), 2.0f); // When reps for other scales are added, the unscaled image // becomes scaled. image_skia.AddRepresentation(rep_2x); EXPECT_FALSE(image_skia.GetRepresentation(1.0f).unscaled()); EXPECT_FALSE(image_skia.GetRepresentation(2.0f).unscaled()); } namespace { std::vector GetSortedScaleFactors(const gfx::ImageSkia& image) { const std::vector& image_reps = image.image_reps(); std::vector scale_factors; for (size_t i = 0; i < image_reps.size(); ++i) { scale_factors.push_back(image_reps[i].scale()); } std::sort(scale_factors.begin(), scale_factors.end()); return scale_factors; } } // namespace TEST_F(ImageSkiaTest, ArbitraryScaleFactor) { // source is owned by |image| DynamicSource* source = new DynamicSource(Size(100, 200)); ImageSkia image(source, gfx::Size(100, 200)); image.GetRepresentation(1.5f); EXPECT_EQ(2.0f, source->GetLastRequestedScaleAndReset()); std::vector image_reps = image.image_reps(); EXPECT_EQ(2u, image_reps.size()); std::vector scale_factors = GetSortedScaleFactors(image); EXPECT_EQ(1.5f, scale_factors[0]); EXPECT_EQ(2.0f, scale_factors[1]); // Requesting 1.75 scale factor also falls back to 2.0f and rescale. // However, the image already has the 2.0f data, so it won't fetch again. image.GetRepresentation(1.75f); EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); image_reps = image.image_reps(); EXPECT_EQ(3u, image_reps.size()); scale_factors = GetSortedScaleFactors(image); EXPECT_EQ(1.5f, scale_factors[0]); EXPECT_EQ(1.75f, scale_factors[1]); EXPECT_EQ(2.0f, scale_factors[2]); // Requesting 1.25 scale factor also falls back to 2.0f and rescale. // However, the image already has the 2.0f data, so it won't fetch again. image.GetRepresentation(1.25f); EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); image_reps = image.image_reps(); EXPECT_EQ(4u, image_reps.size()); scale_factors = GetSortedScaleFactors(image); EXPECT_EQ(1.25f, scale_factors[0]); EXPECT_EQ(1.5f, scale_factors[1]); EXPECT_EQ(1.75f, scale_factors[2]); EXPECT_EQ(2.0f, scale_factors[3]); // 1.20 is falled back to 1.0. image.GetRepresentation(1.20f); EXPECT_EQ(1.0f, source->GetLastRequestedScaleAndReset()); image_reps = image.image_reps(); EXPECT_EQ(6u, image_reps.size()); scale_factors = GetSortedScaleFactors(image); EXPECT_EQ(1.0f, scale_factors[0]); EXPECT_EQ(1.2f, scale_factors[1]); EXPECT_EQ(1.25f, scale_factors[2]); EXPECT_EQ(1.5f, scale_factors[3]); EXPECT_EQ(1.75f, scale_factors[4]); EXPECT_EQ(2.0f, scale_factors[5]); // Scale factor less than 1.0f will be falled back to 1.0f image.GetRepresentation(0.75f); EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); image_reps = image.image_reps(); EXPECT_EQ(7u, image_reps.size()); scale_factors = GetSortedScaleFactors(image); EXPECT_EQ(0.75f, scale_factors[0]); EXPECT_EQ(1.0f, scale_factors[1]); EXPECT_EQ(1.2f, scale_factors[2]); EXPECT_EQ(1.25f, scale_factors[3]); EXPECT_EQ(1.5f, scale_factors[4]); EXPECT_EQ(1.75f, scale_factors[5]); EXPECT_EQ(2.0f, scale_factors[6]); // Scale factor greater than 2.0f is falled back to 2.0f because it's not // supported. image.GetRepresentation(3.0f); EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset()); image_reps = image.image_reps(); EXPECT_EQ(8u, image_reps.size()); } TEST_F(ImageSkiaTest, ArbitraryScaleFactorWithMissingResource) { ImageSkia image(new FixedScaleSource( ImageSkiaRep(Size(100, 200), 1.0f)), Size(100, 200)); // Requesting 1.5f -- falls back to 2.0f, but couldn't find. It should // look up 1.0f and then rescale it. Note that the rescaled ImageSkiaRep will // have 2.0f scale. const ImageSkiaRep& rep = image.GetRepresentation(1.5f); EXPECT_EQ(1.5f, rep.scale()); EXPECT_EQ(2U, image.image_reps().size()); EXPECT_EQ(2.0f, image.image_reps()[0].scale()); EXPECT_EQ(1.5f, image.image_reps()[1].scale()); } TEST_F(ImageSkiaTest, UnscaledImageForArbitraryScaleFactor) { // 0.0f means unscaled. ImageSkia image(new FixedScaleSource( ImageSkiaRep(Size(100, 200), 0.0f)), Size(100, 200)); // Requesting 2.0f, which should return 1.0f unscaled image. const ImageSkiaRep& rep = image.GetRepresentation(2.0f); EXPECT_EQ(1.0f, rep.scale()); EXPECT_EQ("100x200", rep.pixel_size().ToString()); EXPECT_TRUE(rep.unscaled()); EXPECT_EQ(1U, image.image_reps().size()); // Same for any other scale factors. const ImageSkiaRep& rep15 = image.GetRepresentation(1.5f); EXPECT_EQ(1.0f, rep15.scale()); EXPECT_EQ("100x200", rep15.pixel_size().ToString()); EXPECT_TRUE(rep15.unscaled()); EXPECT_EQ(1U, image.image_reps().size()); const ImageSkiaRep& rep12 = image.GetRepresentation(1.2f); EXPECT_EQ(1.0f, rep12.scale()); EXPECT_EQ("100x200", rep12.pixel_size().ToString()); EXPECT_TRUE(rep12.unscaled()); EXPECT_EQ(1U, image.image_reps().size()); } } // namespace gfx