blob: 52a77760ffaf655b04ee4c417051e8ba73811aae [file]
// 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