mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Cleans up header order/grouping for consistency: associated header, C/C++ system/standard library headers, library headers, platform-specific #includes. Adds <cstring> where strlen, memcpy are being used: there are a bunch of places we use them transitively. Applies linter-required cleanups. Disables linter on one file due to included RapidJson header. See https://github.com/flutter/flutter/issues/65676 This patch does not cover flutter/shell/platform/darwin. There's a separate, slightly more intensive cleanup for those in progress.
689 lines
23 KiB
C++
689 lines
23 KiB
C++
// 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/lib/ui/painting/image_decoder.h"
|
|
|
|
#include "flutter/common/task_runners.h"
|
|
#include "flutter/fml/mapping.h"
|
|
#include "flutter/fml/synchronization/waitable_event.h"
|
|
#include "flutter/lib/ui/painting/multi_frame_codec.h"
|
|
#include "flutter/runtime/dart_vm.h"
|
|
#include "flutter/runtime/dart_vm_lifecycle.h"
|
|
#include "flutter/testing/dart_isolate_runner.h"
|
|
#include "flutter/testing/elf_loader.h"
|
|
#include "flutter/testing/fixture_test.h"
|
|
#include "flutter/testing/test_dart_native_resolver.h"
|
|
#include "flutter/testing/test_gl_surface.h"
|
|
#include "flutter/testing/testing.h"
|
|
#include "third_party/skia/include/codec/SkCodec.h"
|
|
|
|
namespace flutter {
|
|
namespace testing {
|
|
|
|
class TestIOManager final : public IOManager {
|
|
public:
|
|
explicit TestIOManager(fml::RefPtr<fml::TaskRunner> task_runner,
|
|
bool has_gpu_context = true)
|
|
: gl_surface_(SkISize::Make(1, 1)),
|
|
gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr),
|
|
weak_gl_context_factory_(
|
|
has_gpu_context
|
|
? std::make_unique<fml::WeakPtrFactory<GrDirectContext>>(
|
|
gl_context_.get())
|
|
: nullptr),
|
|
unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>(
|
|
task_runner,
|
|
fml::TimeDelta::FromNanoseconds(0))),
|
|
runner_(task_runner),
|
|
is_gpu_disabled_sync_switch_(std::make_shared<fml::SyncSwitch>()),
|
|
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<GrDirectContext> GetResourceContext() const override {
|
|
return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr()
|
|
: fml::WeakPtr<GrDirectContext>{};
|
|
}
|
|
|
|
// |IOManager|
|
|
fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override {
|
|
return unref_queue_;
|
|
}
|
|
|
|
// |IOManager|
|
|
std::shared_ptr<fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() override {
|
|
did_access_is_gpu_disabled_sync_switch_ = true;
|
|
return is_gpu_disabled_sync_switch_;
|
|
}
|
|
|
|
bool did_access_is_gpu_disabled_sync_switch_ = false;
|
|
|
|
private:
|
|
TestGLSurface gl_surface_;
|
|
sk_sp<GrDirectContext> gl_context_;
|
|
std::unique_ptr<fml::WeakPtrFactory<GrDirectContext>>
|
|
weak_gl_context_factory_;
|
|
fml::RefPtr<SkiaUnrefQueue> unref_queue_;
|
|
fml::WeakPtr<TestIOManager> weak_prototype_;
|
|
fml::RefPtr<fml::TaskRunner> runner_;
|
|
std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;
|
|
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;
|
|
}
|
|
|
|
class ImageDecoderFixtureTest : public FixtureTest {};
|
|
|
|
TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) {
|
|
auto loop = fml::ConcurrentMessageLoop::Create();
|
|
auto thread_task_runner = CreateNewThread();
|
|
TaskRunners runners(GetCurrentTestName(), // label
|
|
thread_task_runner, // platform
|
|
thread_task_runner, // raster
|
|
thread_task_runner, // ui
|
|
thread_task_runner // 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();
|
|
auto thread_task_runner = CreateNewThread();
|
|
TaskRunners runners(GetCurrentTestName(), // label
|
|
thread_task_runner, // platform
|
|
thread_task_runner, // raster
|
|
thread_task_runner, // ui
|
|
thread_task_runner // io
|
|
);
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
thread_task_runner->PostTask([&]() {
|
|
TestIOManager manager(runners.GetIOTaskRunner());
|
|
ImageDecoder decoder(runners, loop->GetTaskRunner(),
|
|
manager.GetWeakIOManager());
|
|
|
|
auto data = OpenFixtureAsSkData("ThisDoesNotExist.jpg");
|
|
ASSERT_FALSE(data);
|
|
|
|
fml::RefPtr<ImageDescriptor> image_descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(std::move(data),
|
|
std::unique_ptr<SkCodec>(nullptr));
|
|
|
|
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
|
|
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
ASSERT_FALSE(image.get());
|
|
latch.Signal();
|
|
};
|
|
decoder.Decode(image_descriptor, 0, 0, callback);
|
|
});
|
|
latch.Wait();
|
|
}
|
|
|
|
TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
|
|
auto loop = fml::ConcurrentMessageLoop::Create();
|
|
TaskRunners runners(GetCurrentTestName(), // label
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
CreateNewThread("ui"), // ui
|
|
CreateNewThread("io") // io
|
|
);
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
|
|
std::unique_ptr<TestIOManager> io_manager;
|
|
|
|
auto release_io_manager = [&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
};
|
|
auto decode_image = [&]() {
|
|
std::unique_ptr<ImageDecoder> image_decoder =
|
|
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
|
|
io_manager->GetWeakIOManager());
|
|
|
|
auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
|
|
|
|
ASSERT_TRUE(data);
|
|
ASSERT_GE(data->size(), 0u);
|
|
|
|
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
|
|
auto descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
|
|
|
|
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
|
|
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
ASSERT_TRUE(image.get());
|
|
EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_);
|
|
runners.GetIOTaskRunner()->PostTask(release_io_manager);
|
|
};
|
|
EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_);
|
|
image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
|
|
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
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
CreateNewThread("ui"), // ui
|
|
CreateNewThread("io") // io
|
|
);
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
|
|
std::unique_ptr<IOManager> io_manager;
|
|
|
|
auto release_io_manager = [&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
};
|
|
|
|
SkISize decoded_size = SkISize::MakeEmpty();
|
|
auto decode_image = [&]() {
|
|
std::unique_ptr<ImageDecoder> image_decoder =
|
|
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
|
|
io_manager->GetWeakIOManager());
|
|
|
|
auto data = OpenFixtureAsSkData("Horizontal.jpg");
|
|
|
|
ASSERT_TRUE(data);
|
|
ASSERT_GE(data->size(), 0u);
|
|
|
|
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
|
|
auto descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
|
|
|
|
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
|
|
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
ASSERT_TRUE(image.get());
|
|
decoded_size = image.get()->dimensions();
|
|
runners.GetIOTaskRunner()->PostTask(release_io_manager);
|
|
};
|
|
image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
|
|
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
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
CreateNewThread("ui"), // ui
|
|
CreateNewThread("io") // io
|
|
);
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
|
|
std::unique_ptr<IOManager> io_manager;
|
|
|
|
auto release_io_manager = [&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
};
|
|
|
|
auto decode_image = [&]() {
|
|
std::unique_ptr<ImageDecoder> image_decoder =
|
|
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
|
|
io_manager->GetWeakIOManager());
|
|
|
|
auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
|
|
|
|
ASSERT_TRUE(data);
|
|
ASSERT_GE(data->size(), 0u);
|
|
|
|
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
|
|
auto descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
|
|
|
|
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
|
|
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
|
ASSERT_TRUE(image.get());
|
|
runners.GetIOTaskRunner()->PostTask(release_io_manager);
|
|
};
|
|
image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
|
|
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
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
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 = [&](uint32_t target_width,
|
|
uint32_t target_height) -> SkISize {
|
|
SkISize final_size = SkISize::MakeEmpty();
|
|
runners.GetUITaskRunner()->PostTask([&]() {
|
|
auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
|
|
|
|
ASSERT_TRUE(data);
|
|
ASSERT_GE(data->size(), 0u);
|
|
|
|
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
|
|
auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
|
|
std::move(codec));
|
|
|
|
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(descriptor, target_width, target_height, callback);
|
|
});
|
|
latch.Wait();
|
|
return final_size;
|
|
};
|
|
|
|
ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
|
|
ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
|
|
ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
|
|
|
|
// Destroy the IO manager
|
|
runners.GetIOTaskRunner()->PostTask([&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
|
|
// Destroy the image decoder
|
|
runners.GetUITaskRunner()->PostTask([&]() {
|
|
image_decoder.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
}
|
|
|
|
TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) {
|
|
SkImageInfo info = {};
|
|
size_t row_bytes;
|
|
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 = SkImageInfo::MakeN32Premul(image_dimensions);
|
|
row_bytes = pixmap.rowBytes();
|
|
decompressed_data =
|
|
SkData::MakeWithCopy(pixmap.writable_addr(), pixmap.computeByteSize());
|
|
}
|
|
|
|
// This is not susceptible 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(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
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
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 = [&](uint32_t target_width,
|
|
uint32_t target_height) -> SkISize {
|
|
SkISize final_size = SkISize::MakeEmpty();
|
|
runners.GetUITaskRunner()->PostTask([&]() {
|
|
ASSERT_TRUE(decompressed_data);
|
|
ASSERT_GE(decompressed_data->size(), 0u);
|
|
|
|
auto descriptor = fml::MakeRefCounted<ImageDescriptor>(decompressed_data,
|
|
info, row_bytes);
|
|
|
|
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(descriptor, target_width, target_height, callback);
|
|
});
|
|
latch.Wait();
|
|
return final_size;
|
|
};
|
|
|
|
ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
|
|
ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
|
|
ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
|
|
|
|
// Destroy the IO manager
|
|
runners.GetIOTaskRunner()->PostTask([&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
|
|
// Destroy the image decoder
|
|
runners.GetUITaskRunner()->PostTask([&]() {
|
|
image_decoder.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
}
|
|
|
|
// Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in
|
|
// Flutter.
|
|
TEST(ImageDecoderTest,
|
|
VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) {
|
|
auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif");
|
|
auto webp_mapping = OpenFixtureAsSkData("hello_loop_2.webp");
|
|
|
|
ASSERT_TRUE(gif_mapping);
|
|
ASSERT_TRUE(webp_mapping);
|
|
|
|
auto gif_codec = SkCodec::MakeFromData(gif_mapping);
|
|
auto webp_codec = SkCodec::MakeFromData(webp_mapping);
|
|
|
|
ASSERT_TRUE(gif_codec);
|
|
ASSERT_TRUE(webp_codec);
|
|
|
|
// Both fixtures have a loop count of 2 which should lead to the repeat count
|
|
// of 1
|
|
ASSERT_EQ(gif_codec->getRepetitionCount(), 1);
|
|
ASSERT_EQ(webp_codec->getRepetitionCount(), 1);
|
|
}
|
|
|
|
TEST(ImageDecoderTest, VerifySimpleDecoding) {
|
|
auto data = OpenFixtureAsSkData("Horizontal.jpg");
|
|
auto image = SkImage::MakeFromEncoded(data);
|
|
ASSERT_TRUE(image != nullptr);
|
|
ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
|
|
|
|
auto codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
auto descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
|
|
|
|
ASSERT_EQ(
|
|
ImageFromCompressedData(descriptor, 6, 2, fml::tracing::TraceFlow(""))
|
|
->dimensions(),
|
|
SkISize::Make(6, 2));
|
|
}
|
|
|
|
TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
|
|
auto data = OpenFixtureAsSkData("Horizontal.jpg");
|
|
auto codec = SkCodec::MakeFromData(data);
|
|
ASSERT_TRUE(codec);
|
|
auto descriptor =
|
|
fml::MakeRefCounted<ImageDescriptor>(data, std::move(codec));
|
|
|
|
auto image = SkImage::MakeFromEncoded(data);
|
|
ASSERT_TRUE(image != nullptr);
|
|
ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
|
|
|
|
auto decode = [descriptor](uint32_t target_width, uint32_t target_height) {
|
|
return ImageFromCompressedData(descriptor, target_width, target_height,
|
|
fml::tracing::TraceFlow(""));
|
|
};
|
|
|
|
auto expected_data = OpenFixtureAsSkData("Horizontal.png");
|
|
ASSERT_TRUE(expected_data != nullptr);
|
|
ASSERT_FALSE(expected_data->isEmpty());
|
|
|
|
auto assert_image = [&](auto decoded_image) {
|
|
ASSERT_EQ(decoded_image->dimensions(), SkISize::Make(300, 100));
|
|
ASSERT_TRUE(decoded_image->encodeToData(SkEncodedImageFormat::kPNG, 100)
|
|
->equals(expected_data.get()));
|
|
};
|
|
|
|
assert_image(decode(300, 100));
|
|
}
|
|
|
|
TEST_F(ImageDecoderFixtureTest,
|
|
MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) {
|
|
// This test verifies that the MultiFrameCodec safely shares state between
|
|
// tasks on the IO and UI runners, and does not allow unsafe memory access if
|
|
// the UI object is collected while the IO thread still has pending decode
|
|
// work. This could happen in a real application if the engine is collected
|
|
// while a multi-frame image is decoding. To exercise this, the test:
|
|
// - Starts a Dart VM
|
|
// - Latches the IO task runner
|
|
// - Create a MultiFrameCodec for an animated gif pointed to a callback
|
|
// in the Dart fixture
|
|
// - Calls getNextFrame on the UI task runner
|
|
// - Collects the MultiFrameCodec object before unlatching the IO task
|
|
// runner.
|
|
// - Unlatches the IO task runner
|
|
auto settings = CreateSettingsForFixture();
|
|
auto vm_ref = DartVMRef::Create(settings);
|
|
auto vm_data = vm_ref.GetVMData();
|
|
|
|
auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif");
|
|
|
|
ASSERT_TRUE(gif_mapping);
|
|
|
|
auto gif_codec = std::shared_ptr<SkCodecImageGenerator>(
|
|
static_cast<SkCodecImageGenerator*>(
|
|
SkCodecImageGenerator::MakeFromEncodedCodec(gif_mapping).release()));
|
|
ASSERT_TRUE(gif_codec);
|
|
|
|
TaskRunners runners(GetCurrentTestName(), // label
|
|
CreateNewThread("platform"), // platform
|
|
CreateNewThread("raster"), // raster
|
|
CreateNewThread("ui"), // ui
|
|
CreateNewThread("io") // io
|
|
);
|
|
|
|
fml::AutoResetWaitableEvent latch;
|
|
fml::AutoResetWaitableEvent io_latch;
|
|
std::unique_ptr<TestIOManager> io_manager;
|
|
|
|
// Setup the IO manager.
|
|
runners.GetIOTaskRunner()->PostTask([&]() {
|
|
io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
|
|
auto isolate =
|
|
RunDartCodeInIsolate(vm_ref, settings, runners, "main", {},
|
|
GetFixturesPath(), io_manager->GetWeakIOManager());
|
|
|
|
// Latch the IO task runner.
|
|
runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); });
|
|
|
|
runners.GetUITaskRunner()->PostTask([&]() {
|
|
fml::AutoResetWaitableEvent isolate_latch;
|
|
fml::RefPtr<MultiFrameCodec> codec;
|
|
EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool {
|
|
Dart_Handle library = Dart_RootLibrary();
|
|
if (Dart_IsError(library)) {
|
|
isolate_latch.Signal();
|
|
return false;
|
|
}
|
|
Dart_Handle closure =
|
|
Dart_GetField(library, Dart_NewStringFromCString("frameCallback"));
|
|
if (Dart_IsError(closure) || !Dart_IsClosure(closure)) {
|
|
isolate_latch.Signal();
|
|
return false;
|
|
}
|
|
|
|
codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_codec));
|
|
codec->getNextFrame(closure);
|
|
codec = nullptr;
|
|
isolate_latch.Signal();
|
|
return true;
|
|
}));
|
|
isolate_latch.Wait();
|
|
|
|
EXPECT_FALSE(codec);
|
|
|
|
io_latch.Signal();
|
|
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
|
|
// Destroy the IO manager
|
|
runners.GetIOTaskRunner()->PostTask([&]() {
|
|
io_manager.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
}
|
|
|
|
} // namespace testing
|
|
} // namespace flutter
|