| // Copyright 2013 The Flutter 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 "flutter/common/task_runners.h" |
| #include "flutter/fml/mapping.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/lib/ui/painting/image_decoder.h" |
| #include "flutter/testing/test_gl_surface.h" |
| #include "flutter/testing/testing.h" |
| #include "flutter/testing/thread_test.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| using ImageDecoderFixtureTest = ThreadTest; |
| |
| class TestIOManager final : public IOManager { |
| public: |
| TestIOManager(fml::RefPtr<fml::TaskRunner> task_runner, |
| bool has_gpu_context = true) |
| : gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr), |
| weak_gl_context_factory_( |
| has_gpu_context ? std::make_unique<fml::WeakPtrFactory<GrContext>>( |
| gl_context_.get()) |
| : nullptr), |
| unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>( |
| task_runner, |
| fml::TimeDelta::FromNanoseconds(0))), |
| runner_(task_runner), |
| weak_factory_(this) { |
| FML_CHECK(task_runner->RunsTasksOnCurrentThread()) |
| << "The IO manager must be initialized its primary task runner. The " |
| "test harness may not be setup correctly/safely."; |
| weak_prototype_ = weak_factory_.GetWeakPtr(); |
| } |
| |
| ~TestIOManager() override { |
| fml::AutoResetWaitableEvent latch; |
| fml::TaskRunner::RunNowOrPostTask(runner_, |
| [&latch, queue = unref_queue_]() { |
| queue->Drain(); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| } |
| |
| // |IOManager| |
| fml::WeakPtr<IOManager> GetWeakIOManager() const override { |
| return weak_prototype_; |
| } |
| |
| // |IOManager| |
| fml::WeakPtr<GrContext> GetResourceContext() const override { |
| return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr() |
| : fml::WeakPtr<GrContext>{}; |
| } |
| |
| // |IOManager| |
| fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override { |
| return unref_queue_; |
| } |
| |
| private: |
| TestGLSurface gl_surface_; |
| sk_sp<GrContext> gl_context_; |
| std::unique_ptr<fml::WeakPtrFactory<GrContext>> weak_gl_context_factory_; |
| fml::RefPtr<SkiaUnrefQueue> unref_queue_; |
| fml::WeakPtr<TestIOManager> weak_prototype_; |
| fml::RefPtr<fml::TaskRunner> runner_; |
| fml::WeakPtrFactory<TestIOManager> weak_factory_; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(TestIOManager); |
| }; |
| |
| static sk_sp<SkData> OpenFixtureAsSkData(const char* name) { |
| auto fixtures_directory = |
| fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); |
| if (!fixtures_directory.is_valid()) { |
| return nullptr; |
| } |
| |
| auto fixture_mapping = |
| fml::FileMapping::CreateReadOnly(fixtures_directory, name); |
| |
| if (!fixture_mapping) { |
| return nullptr; |
| } |
| |
| SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void { |
| delete reinterpret_cast<fml::FileMapping*>(context); |
| }; |
| |
| auto data = SkData::MakeWithProc(fixture_mapping->GetMapping(), |
| fixture_mapping->GetSize(), on_release, |
| fixture_mapping.get()); |
| |
| if (!data) { |
| return nullptr; |
| } |
| // The data is now owned by Skia. |
| fixture_mapping.release(); |
| return data; |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) { |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| GetThreadTaskRunner(), // gpu |
| GetThreadTaskRunner(), // ui |
| GetThreadTaskRunner() // io |
| |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| runners.GetIOTaskRunner()->PostTask([&]() { |
| TestIOManager manager(runners.GetIOTaskRunner()); |
| ImageDecoder decoder(std::move(runners), loop->GetTaskRunner(), |
| manager.GetWeakIOManager()); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) { |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| GetThreadTaskRunner(), // gpu |
| GetThreadTaskRunner(), // ui |
| GetThreadTaskRunner() // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| GetThreadTaskRunner()->PostTask([&]() { |
| TestIOManager manager(runners.GetIOTaskRunner()); |
| ImageDecoder decoder(runners, loop->GetTaskRunner(), |
| manager.GetWeakIOManager()); |
| |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.data = OpenFixtureAsSkData("ThisDoesNotExist.jpg"); |
| |
| ASSERT_FALSE(image_descriptor.data); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_FALSE(image.get()); |
| latch.Signal(); |
| }; |
| decoder.Decode(std::move(image_descriptor), callback); |
| }); |
| latch.Wait(); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| CreateNewThread("gpu"), // gpu |
| CreateNewThread("ui"), // ui |
| CreateNewThread("io") // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| std::unique_ptr<IOManager> io_manager; |
| std::unique_ptr<ImageDecoder> image_decoder; |
| |
| auto decode_image = [&]() { |
| image_decoder = std::make_unique<ImageDecoder>( |
| runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
| |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.data = OpenFixtureAsSkData("DashInNooglerHat.jpg"); |
| |
| ASSERT_TRUE(image_descriptor.data); |
| ASSERT_GE(image_descriptor.data->size(), 0u); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_TRUE(image.get()); |
| latch.Signal(); |
| }; |
| image_decoder->Decode(std::move(image_descriptor), callback); |
| }; |
| |
| auto setup_io_manager_and_decode = [&]() { |
| io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
| runners.GetUITaskRunner()->PostTask(decode_image); |
| }; |
| |
| runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) { |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| CreateNewThread("gpu"), // gpu |
| CreateNewThread("ui"), // ui |
| CreateNewThread("io") // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| std::unique_ptr<IOManager> io_manager; |
| std::unique_ptr<ImageDecoder> image_decoder; |
| |
| SkISize decoded_size = SkISize::MakeEmpty(); |
| auto decode_image = [&]() { |
| image_decoder = std::make_unique<ImageDecoder>( |
| runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
| |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.data = OpenFixtureAsSkData("Horizontal.jpg"); |
| |
| ASSERT_TRUE(image_descriptor.data); |
| ASSERT_GE(image_descriptor.data->size(), 0u); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_TRUE(image.get()); |
| decoded_size = image.get()->dimensions(); |
| latch.Signal(); |
| }; |
| image_decoder->Decode(std::move(image_descriptor), callback); |
| }; |
| |
| auto setup_io_manager_and_decode = [&]() { |
| io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
| runners.GetUITaskRunner()->PostTask(decode_image); |
| }; |
| |
| runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
| |
| latch.Wait(); |
| |
| ASSERT_EQ(decoded_size.width(), 600); |
| ASSERT_EQ(decoded_size.height(), 200); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) { |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| CreateNewThread("gpu"), // gpu |
| CreateNewThread("ui"), // ui |
| CreateNewThread("io") // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| |
| std::unique_ptr<IOManager> io_manager; |
| std::unique_ptr<ImageDecoder> image_decoder; |
| |
| auto decode_image = [&]() { |
| image_decoder = std::make_unique<ImageDecoder>( |
| runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
| |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.data = OpenFixtureAsSkData("DashInNooglerHat.jpg"); |
| |
| ASSERT_TRUE(image_descriptor.data); |
| ASSERT_GE(image_descriptor.data->size(), 0u); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_TRUE(image.get()); |
| latch.Signal(); |
| }; |
| image_decoder->Decode(std::move(image_descriptor), callback); |
| }; |
| |
| auto setup_io_manager_and_decode = [&]() { |
| io_manager = |
| std::make_unique<TestIOManager>(runners.GetIOTaskRunner(), false); |
| runners.GetUITaskRunner()->PostTask(decode_image); |
| }; |
| |
| runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
| |
| latch.Wait(); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) { |
| const auto image_dimensions = |
| SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg")) |
| ->dimensions(); |
| |
| ASSERT_FALSE(image_dimensions.isEmpty()); |
| |
| ASSERT_NE(image_dimensions.width(), image_dimensions.height()); |
| |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| CreateNewThread("gpu"), // gpu |
| CreateNewThread("ui"), // ui |
| CreateNewThread("io") // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| std::unique_ptr<IOManager> io_manager; |
| std::unique_ptr<ImageDecoder> image_decoder; |
| |
| // Setup the IO manager. |
| runners.GetIOTaskRunner()->PostTask([&]() { |
| io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| |
| // Setup the image decoder. |
| runners.GetUITaskRunner()->PostTask([&]() { |
| image_decoder = std::make_unique<ImageDecoder>( |
| runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
| |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| |
| // Setup a generic decoding utility that gives us the final decoded size. |
| auto decoded_size = [&](std::optional<uint32_t> target_width, |
| std::optional<uint32_t> target_height) -> SkISize { |
| SkISize final_size = SkISize::MakeEmpty(); |
| runners.GetUITaskRunner()->PostTask([&]() { |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.target_width = target_width; |
| image_descriptor.target_height = target_height; |
| image_descriptor.data = OpenFixtureAsSkData("DashInNooglerHat.jpg"); |
| |
| ASSERT_TRUE(image_descriptor.data); |
| ASSERT_GE(image_descriptor.data->size(), 0u); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_TRUE(image.get()); |
| final_size = image.get()->dimensions(); |
| latch.Signal(); |
| }; |
| image_decoder->Decode(std::move(image_descriptor), callback); |
| }); |
| latch.Wait(); |
| return final_size; |
| }; |
| |
| ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions); |
| ASSERT_EQ(decoded_size({}, {}), image_dimensions); |
| ASSERT_EQ(decoded_size(100, {}), SkISize::Make(100, 133)); |
| ASSERT_EQ(decoded_size({}, 100), SkISize::Make(75, 100)); |
| ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100)); |
| } |
| |
| TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) { |
| ImageDecoder::ImageInfo info = {}; |
| sk_sp<SkData> decompressed_data; |
| SkISize image_dimensions = SkISize::MakeEmpty(); |
| { |
| auto image = |
| SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg")) |
| ->makeRasterImage(); |
| image_dimensions = image->dimensions(); |
| SkPixmap pixmap; |
| ASSERT_TRUE(image->peekPixels(&pixmap)); |
| info.sk_info = SkImageInfo::MakeN32Premul(image_dimensions); |
| info.row_bytes = pixmap.rowBytes(); |
| decompressed_data = |
| SkData::MakeWithCopy(pixmap.writable_addr(), pixmap.computeByteSize()); |
| } |
| |
| // This is not susecptible to changes in the underlying image decoder. |
| ASSERT_EQ(decompressed_data->size(), 48771072u); |
| ASSERT_EQ(decompressed_data->size(), |
| image_dimensions.width() * image_dimensions.height() * 4u); |
| ASSERT_EQ(info.row_bytes, image_dimensions.width() * 4u); |
| ASSERT_FALSE(image_dimensions.isEmpty()); |
| ASSERT_NE(image_dimensions.width(), image_dimensions.height()); |
| |
| auto loop = fml::ConcurrentMessageLoop::Create(); |
| TaskRunners runners(GetCurrentTestName(), // label |
| GetThreadTaskRunner(), // platform |
| CreateNewThread("gpu"), // gpu |
| CreateNewThread("ui"), // ui |
| CreateNewThread("io") // io |
| ); |
| |
| fml::AutoResetWaitableEvent latch; |
| std::unique_ptr<IOManager> io_manager; |
| std::unique_ptr<ImageDecoder> image_decoder; |
| |
| // Setup the IO manager. |
| runners.GetIOTaskRunner()->PostTask([&]() { |
| io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| |
| // Setup the image decoder. |
| runners.GetUITaskRunner()->PostTask([&]() { |
| image_decoder = std::make_unique<ImageDecoder>( |
| runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
| |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| |
| // Setup a generic decoding utility that gives us the final decoded size. |
| auto decoded_size = [&](std::optional<uint32_t> target_width, |
| std::optional<uint32_t> target_height) -> SkISize { |
| SkISize final_size = SkISize::MakeEmpty(); |
| runners.GetUITaskRunner()->PostTask([&]() { |
| ImageDecoder::ImageDescriptor image_descriptor; |
| image_descriptor.target_width = target_width; |
| image_descriptor.target_height = target_height; |
| image_descriptor.data = decompressed_data; |
| image_descriptor.decompressed_image_info = info; |
| |
| ASSERT_TRUE(image_descriptor.data); |
| ASSERT_GE(image_descriptor.data->size(), 0u); |
| |
| ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
| ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| ASSERT_TRUE(image.get()); |
| final_size = image.get()->dimensions(); |
| latch.Signal(); |
| }; |
| image_decoder->Decode(std::move(image_descriptor), callback); |
| }); |
| latch.Wait(); |
| return final_size; |
| }; |
| |
| ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions); |
| ASSERT_EQ(decoded_size({}, {}), image_dimensions); |
| ASSERT_EQ(decoded_size(100, {}), SkISize::Make(100, 133)); |
| ASSERT_EQ(decoded_size({}, 100), SkISize::Make(75, 100)); |
| ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100)); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |