Flutter Engine
 
Loading...
Searching...
No Matches
image_decoder_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
26#include "fml/logging.h"
29#include "third_party/skia/include/codec/SkCodecAnimation.h"
30#include "third_party/skia/include/core/SkData.h"
31#include "third_party/skia/include/core/SkImage.h"
32#include "third_party/skia/include/core/SkImageInfo.h"
33#include "third_party/skia/include/core/SkSize.h"
34#include "third_party/skia/include/encode/SkPngEncoder.h"
35
36// CREATE_NATIVE_ENTRY is leaky by design
37// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
38// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks)
39
40namespace impeller {
41
43 public:
45
46 BackendType GetBackendType() const override { return BackendType::kMetal; }
47
48 std::string DescribeGpuModel() const override { return "TestGpu"; }
49
50 bool IsValid() const override { return true; }
51
52 const std::shared_ptr<const Capabilities>& GetCapabilities() const override {
53 return capabilities_;
54 }
55
56 std::shared_ptr<Allocator> GetResourceAllocator() const override {
57 return std::make_shared<TestImpellerAllocator>();
58 }
59
60 std::shared_ptr<ShaderLibrary> GetShaderLibrary() const override {
61 return nullptr;
62 }
63
64 std::shared_ptr<SamplerLibrary> GetSamplerLibrary() const override {
65 return nullptr;
66 }
67
68 std::shared_ptr<PipelineLibrary> GetPipelineLibrary() const override {
69 return nullptr;
70 }
71
72 std::shared_ptr<CommandQueue> GetCommandQueue() const override {
74 }
75
76 std::shared_ptr<CommandBuffer> CreateCommandBuffer() const override {
78 return nullptr;
79 }
80
81 void StoreTaskForGPU(const std::function<void()>& task,
82 const std::function<void()>& failure) override {
83 tasks_.push_back(PendingTask{task, failure});
84 }
85
86 void FlushTasks(bool fail = false) {
87 for (auto& task : tasks_) {
88 if (fail) {
89 task.task();
90 } else {
91 task.failure();
92 }
93 }
94 tasks_.clear();
95 }
96
97 void DisposeThreadLocalCachedResources() override { did_dispose_ = true; }
98
99 void Shutdown() override {}
100
104
105 bool DidDisposeResources() const { return did_dispose_; }
106
107 mutable size_t command_buffer_count_ = 0;
108
109 private:
110 struct PendingTask {
111 std::function<void()> task;
112 std::function<void()> failure;
113 };
114 std::vector<PendingTask> tasks_;
115 std::shared_ptr<const Capabilities> capabilities_;
116 bool did_dispose_ = false;
117};
118
119} // namespace impeller
120
121namespace flutter {
122namespace testing {
123
124class TestIOManager final : public IOManager {
125 public:
126 explicit TestIOManager(const fml::RefPtr<fml::TaskRunner>& task_runner,
127 bool has_gpu_context = true)
128 : gl_surface_(DlISize(1, 1)),
129 impeller_context_(std::make_shared<impeller::TestImpellerContext>()),
130 gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr),
131 weak_gl_context_factory_(
132 has_gpu_context
133 ? std::make_unique<fml::WeakPtrFactory<GrDirectContext>>(
134 gl_context_.get())
135 : nullptr),
136 unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>(
137 task_runner,
138 fml::TimeDelta::FromNanoseconds(0),
139 gl_context_)),
140 runner_(task_runner),
141 is_gpu_disabled_sync_switch_(std::make_shared<fml::SyncSwitch>()),
142 weak_factory_(this) {
143 FML_CHECK(task_runner->RunsTasksOnCurrentThread())
144 << "The IO manager must be initialized its primary task runner. The "
145 "test harness may not be set up correctly/safely.";
146 weak_prototype_ = weak_factory_.GetWeakPtr();
147 }
148
149 ~TestIOManager() override {
152 [&latch, queue = unref_queue_]() {
153 queue->Drain();
154 latch.Signal();
155 });
156 latch.Wait();
157 }
158
159 // |IOManager|
161 return weak_prototype_;
162 }
163
164 // |IOManager|
166 return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr()
168 }
169
170 // |IOManager|
172 return unref_queue_;
173 }
174
175 // |IOManager|
176 std::shared_ptr<const fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() override {
178 return is_gpu_disabled_sync_switch_;
179 }
180
181 // |IOManager|
182 std::shared_ptr<impeller::Context> GetImpellerContext() const override {
183 return impeller_context_;
184 }
185
186 void SetGpuDisabled(bool disabled) {
187 is_gpu_disabled_sync_switch_->SetSwitch(disabled);
188 }
189
191
192 private:
193 TestGLSurface gl_surface_;
194 std::shared_ptr<impeller::Context> impeller_context_;
195 sk_sp<GrDirectContext> gl_context_;
196 std::unique_ptr<fml::WeakPtrFactory<GrDirectContext>>
197 weak_gl_context_factory_;
198 fml::RefPtr<SkiaUnrefQueue> unref_queue_;
199 fml::WeakPtr<TestIOManager> weak_prototype_;
201 std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;
203
205};
206
208
209TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) {
211 auto thread_task_runner = CreateNewThread();
212 TaskRunners runners(GetCurrentTestName(), // label
213 thread_task_runner, // platform
214 thread_task_runner, // raster
215 thread_task_runner, // ui
216 thread_task_runner // io
217
218 );
219
220 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
221 TestIOManager manager(runners.GetIOTaskRunner());
222 Settings settings;
223 auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
224 manager.GetWeakIOManager(),
225 std::make_shared<fml::SyncSwitch>());
226 ASSERT_NE(decoder, nullptr);
227 });
228}
229
230/// An Image generator that pretends it can't recognize the data it was given.
232 public:
233 UnknownImageGenerator() : info_(SkImageInfo::MakeUnknown()) {};
235 const SkImageInfo& GetInfo() { return info_; }
236
237 unsigned int GetFrameCount() const { return 1; }
238
239 unsigned int GetPlayCount() const { return 1; }
240
241 const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index) {
242 return {std::nullopt, 0, SkCodecAnimation::DisposalMethod::kKeep};
243 }
244
245 SkISize GetScaledDimensions(float scale) {
246 return SkISize::Make(info_.width(), info_.height());
247 }
248
249 bool GetPixels(const SkImageInfo& info,
250 void* pixels,
251 size_t row_bytes,
252 unsigned int frame_index,
253 std::optional<unsigned int> prior_frame) {
254 return false;
255 };
256
257 private:
258 SkImageInfo info_;
259};
260
261TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) {
263 auto thread_task_runner = CreateNewThread();
264 TaskRunners runners(GetCurrentTestName(), // label
265 thread_task_runner, // platform
266 thread_task_runner, // raster
267 thread_task_runner, // ui
268 thread_task_runner // io
269 );
270
272 thread_task_runner->PostTask([&]() {
274 Settings settings;
275 auto decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
276 manager.GetWeakIOManager(),
277 std::make_shared<fml::SyncSwitch>());
278
279 auto data = flutter::testing::OpenFixtureAsSkData("ThisDoesNotExist.jpg");
280 ASSERT_FALSE(data);
281
282 fml::RefPtr<ImageDescriptor> image_descriptor =
283 fml::MakeRefCounted<ImageDescriptor>(
284 std::move(data), std::make_unique<UnknownImageGenerator>());
285
286 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
287 const std::string& decode_error) {
288 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
289 ASSERT_FALSE(image);
290 latch.Signal();
291 };
292 decoder->Decode(image_descriptor, 0, 0, callback);
293 });
294 latch.Wait();
295}
296
297TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
299 TaskRunners runners(GetCurrentTestName(), // label
300 CreateNewThread("platform"), // platform
301 CreateNewThread("raster"), // raster
302 CreateNewThread("ui"), // ui
303 CreateNewThread("io") // io
304 );
305
307
308 std::unique_ptr<TestIOManager> io_manager;
309
310 auto release_io_manager = [&]() {
311 io_manager.reset();
312 latch.Signal();
313 };
314 auto decode_image = [&]() {
315 Settings settings;
316 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
317 settings, runners, loop->GetTaskRunner(),
318 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
319
320 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
321
322 ASSERT_TRUE(data);
323 ASSERT_GE(data->size(), 0u);
324
325 ImageGeneratorRegistry registry;
326 std::shared_ptr<ImageGenerator> generator =
328 ASSERT_TRUE(generator);
329
330 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
331 std::move(data), std::move(generator));
332
333 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
334 const std::string& decode_error) {
335 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
336 ASSERT_TRUE(image && image->skia_image());
337 EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_);
338 runners.GetIOTaskRunner()->PostTask(release_io_manager);
339 };
340 EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_);
341 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
342 callback);
343 };
344
345 auto set_up_io_manager_and_decode = [&]() {
346 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
347 runners.GetUITaskRunner()->PostTask(decode_image);
348 };
349
350 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
351 latch.Wait();
352}
353
354TEST_F(ImageDecoderFixtureTest, ImpellerUploadToSharedNoGpu) {
355#if !IMPELLER_SUPPORTS_RENDERING
356 GTEST_SKIP() << "Impeller only test.";
357#endif // IMPELLER_SUPPORTS_RENDERING
358
359 auto no_gpu_access_context =
360 std::make_shared<impeller::TestImpellerContext>();
361 auto gpu_disabled_switch = std::make_shared<fml::SyncSwitch>(true);
362
363 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
364 SkAlphaType::kPremul_SkAlphaType);
365 auto bitmap = std::make_shared<SkBitmap>();
366 bitmap->allocPixels(info, 10 * 4);
368 desc.size = bitmap->computeByteSize();
369 auto buffer = std::make_shared<impeller::TestImpellerDeviceBuffer>(desc);
370
371 bool invoked = false;
372 auto cb = [&invoked](const sk_sp<DlImage>& image,
373 const std::string& message) { invoked = true; };
374
376 cb, no_gpu_access_context, buffer, info, bitmap, std::nullopt,
377 gpu_disabled_switch);
378
379 EXPECT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
380 EXPECT_FALSE(invoked);
381 EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), false);
382
384 no_gpu_access_context, bitmap);
385
386 ASSERT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
387 ASSERT_EQ(result.second, "");
388 EXPECT_EQ(no_gpu_access_context->DidDisposeResources(), true);
389 EXPECT_EQ(
390 result.first->impeller_texture()->GetTextureDescriptor().storage_mode,
392
393 no_gpu_access_context->FlushTasks(/*fail=*/true);
394}
395
397 ImpellerUploadToSharedNoGpuTaskFlushingFailure) {
398#if !IMPELLER_SUPPORTS_RENDERING
399 GTEST_SKIP() << "Impeller only test.";
400#endif // IMPELLER_SUPPORTS_RENDERING
401
402 auto no_gpu_access_context =
403 std::make_shared<impeller::TestImpellerContext>();
404 auto gpu_disabled_switch = std::make_shared<fml::SyncSwitch>(true);
405
406 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
407 SkAlphaType::kPremul_SkAlphaType);
408 auto bitmap = std::make_shared<SkBitmap>();
409 bitmap->allocPixels(info, 10 * 4);
411 desc.size = bitmap->computeByteSize();
412 auto buffer = std::make_shared<impeller::TestImpellerDeviceBuffer>(desc);
413
414 sk_sp<DlImage> image;
415 std::string message;
416 bool invoked = false;
417 auto cb = [&invoked, &image, &message](sk_sp<DlImage> p_image,
418 std::string p_message) {
419 invoked = true;
420 image = std::move(p_image);
421 message = std::move(p_message);
422 };
423
425 cb, no_gpu_access_context, buffer, info, bitmap, std::nullopt,
426 gpu_disabled_switch);
427
428 EXPECT_EQ(no_gpu_access_context->command_buffer_count_, 0ul);
429 EXPECT_FALSE(invoked);
430
431 no_gpu_access_context->FlushTasks(/*fail=*/true);
432
433 EXPECT_TRUE(invoked);
434 // Creation of the dl image will still fail with the mocked context.
435 EXPECT_NE(message, "");
436}
437
438TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) {
439 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_8888_SkColorType,
440 SkAlphaType::kPremul_SkAlphaType);
441 SkBitmap bitmap;
442 bitmap.allocPixels(info, 10 * 4);
443 auto data = SkData::MakeWithoutCopy(bitmap.getPixels(), 10 * 10 * 4);
444 auto image = SkImages::RasterFromBitmap(bitmap);
445 ASSERT_TRUE(image != nullptr);
446 EXPECT_EQ(SkISize::Make(10, 10), image->dimensions());
447 EXPECT_EQ(nullptr, image->colorSpace());
448
449 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
450 std::move(data), image->imageInfo(), 10 * 4);
451
452#if IMPELLER_SUPPORTS_RENDERING
453 std::shared_ptr<impeller::Capabilities> capabilities =
456 .Build();
457 std::shared_ptr<impeller::Allocator> allocator =
458 std::make_shared<impeller::TestImpellerAllocator>();
459 std::optional<DecompressResult> decompressed =
461 descriptor.get(), SkISize::Make(100, 100), {100, 100},
462 /*supports_wide_gamut=*/true, capabilities, allocator);
463 ASSERT_TRUE(decompressed.has_value());
464 EXPECT_EQ(decompressed->image_info.colorType(), kRGBA_8888_SkColorType);
465 EXPECT_EQ(decompressed->image_info.colorSpace(),
466 SkColorSpace::MakeSRGB().get());
467#endif // IMPELLER_SUPPORTS_RENDERING
468}
469
470TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) {
471 auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_F32_SkColorType,
472 SkAlphaType::kUnpremul_SkAlphaType);
473 SkBitmap bitmap;
474 bitmap.allocPixels(info, 10 * 16);
475 auto data = SkData::MakeWithoutCopy(bitmap.getPixels(), 10 * 10 * 16);
476 auto image = SkImages::RasterFromBitmap(bitmap);
477
478 ASSERT_TRUE(image != nullptr);
479 EXPECT_EQ(SkISize::Make(10, 10), image->dimensions());
480 EXPECT_EQ(nullptr, image->colorSpace());
481
482 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
483 std::move(data), image->imageInfo(), 10 * 16);
484
485#if IMPELLER_SUPPORTS_RENDERING
486 std::shared_ptr<impeller::Capabilities> capabilities =
489 .Build();
490 std::shared_ptr<impeller::Allocator> allocator =
491 std::make_shared<impeller::TestImpellerAllocator>();
492 std::optional<DecompressResult> decompressed =
494 descriptor.get(), SkISize::Make(100, 100), {100, 100},
495 /*supports_wide_gamut=*/true, capabilities, allocator);
496
497 ASSERT_TRUE(decompressed.has_value());
498 EXPECT_EQ(decompressed->image_info.colorType(), kRGBA_F16_SkColorType);
499 EXPECT_EQ(decompressed->image_info.colorSpace(),
500 SkColorSpace::MakeSRGB().get());
501#endif // IMPELLER_SUPPORTS_RENDERING
502}
503
504TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) {
505 auto data = flutter::testing::OpenFixtureAsSkData("DisplayP3Logo.jpg");
506 auto image = SkImages::DeferredFromEncodedData(data);
507 ASSERT_TRUE(image != nullptr);
508 ASSERT_EQ(SkISize::Make(100, 100), image->dimensions());
509
510 ImageGeneratorRegistry registry;
511 std::shared_ptr<ImageGenerator> generator =
513 ASSERT_TRUE(generator);
514
515 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
516 std::move(generator));
517
518#if IMPELLER_SUPPORTS_RENDERING
519 std::shared_ptr<impeller::Capabilities> capabilities =
522 .Build();
523 std::shared_ptr<impeller::Allocator> allocator =
524 std::make_shared<impeller::TestImpellerAllocator>();
525 std::optional<DecompressResult> wide_result =
527 descriptor.get(), SkISize::Make(100, 100), {100, 100},
528 /*supports_wide_gamut=*/true, capabilities, allocator);
529
530 ASSERT_TRUE(wide_result.has_value());
531 ASSERT_EQ(wide_result->image_info.colorType(), kBGR_101010x_XR_SkColorType);
532 ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB());
533
534 const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap();
535 const uint32_t* pixel_ptr = static_cast<const uint32_t*>(wide_pixmap.addr());
536 bool found_deep_red = false;
537 for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) {
538 uint32_t pixel = *pixel_ptr++;
539 float blue = DecodeBGR10((pixel >> 0) & 0x3ff);
540 float green = DecodeBGR10((pixel >> 10) & 0x3ff);
541 float red = DecodeBGR10((pixel >> 20) & 0x3ff);
542 if (fabsf(red - 1.0931f) < 0.01f && fabsf(green - -0.2268f) < 0.01f &&
543 fabsf(blue - -0.1501f) < 0.01f) {
544 found_deep_red = true;
545 break;
546 }
547 }
548 ASSERT_TRUE(found_deep_red);
549
550 std::optional<DecompressResult> narrow_result =
552 descriptor.get(), SkISize::Make(100, 100), {100, 100},
553 /*supports_wide_gamut=*/false, capabilities, allocator);
554
555 ASSERT_TRUE(narrow_result.has_value());
556 ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType);
557#endif // IMPELLER_SUPPORTS_RENDERING
558}
559
560TEST_F(ImageDecoderFixtureTest, ImpellerNonWideGamut) {
561 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
562 auto image = SkImages::DeferredFromEncodedData(data);
563 ASSERT_TRUE(image != nullptr);
564 ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
565
566 ImageGeneratorRegistry registry;
567 std::shared_ptr<ImageGenerator> generator =
569 ASSERT_TRUE(generator);
570
571 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
572 std::move(generator));
573
574#if IMPELLER_SUPPORTS_RENDERING
575 std::shared_ptr<impeller::Capabilities> capabilities =
578 .Build();
579 std::shared_ptr<impeller::Allocator> allocator =
580 std::make_shared<impeller::TestImpellerAllocator>();
581 std::optional<DecompressResult> result =
583 descriptor.get(), SkISize::Make(600, 200), {600, 200},
584 /*supports_wide_gamut=*/true, capabilities, allocator);
585
586 ASSERT_TRUE(result.has_value());
587 ASSERT_EQ(result->image_info.colorType(), kRGBA_8888_SkColorType);
588#endif // IMPELLER_SUPPORTS_RENDERING
589}
590
591TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
593 TaskRunners runners(GetCurrentTestName(), // label
594 CreateNewThread("platform"), // platform
595 CreateNewThread("raster"), // raster
596 CreateNewThread("ui"), // ui
597 CreateNewThread("io") // io
598 );
599
601
602 std::unique_ptr<IOManager> io_manager;
603
604 auto release_io_manager = [&]() {
605 io_manager.reset();
606 latch.Signal();
607 };
608
609 SkISize decoded_size = SkISize::MakeEmpty();
610 auto decode_image = [&]() {
611 Settings settings;
612 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
613 settings, runners, loop->GetTaskRunner(),
614 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
615
616 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
617
618 ASSERT_TRUE(data);
619 ASSERT_GE(data->size(), 0u);
620
621 ImageGeneratorRegistry registry;
622 std::shared_ptr<ImageGenerator> generator =
624 ASSERT_TRUE(generator);
625
626 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
627 std::move(data), std::move(generator));
628
629 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
630 const std::string& decode_error) {
631 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
632 ASSERT_TRUE(image && image->skia_image());
633 decoded_size = image->skia_image()->dimensions();
634 runners.GetIOTaskRunner()->PostTask(release_io_manager);
635 };
636 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
637 callback);
638 };
639
640 auto set_up_io_manager_and_decode = [&]() {
641 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
642 runners.GetUITaskRunner()->PostTask(decode_image);
643 };
644
645 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
646
647 latch.Wait();
648
649 ASSERT_EQ(decoded_size.width(), 600);
650 ASSERT_EQ(decoded_size.height(), 200);
651}
652
653TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
655 TaskRunners runners(GetCurrentTestName(), // label
656 CreateNewThread("platform"), // platform
657 CreateNewThread("raster"), // raster
658 CreateNewThread("ui"), // ui
659 CreateNewThread("io") // io
660 );
661
663
664 std::unique_ptr<IOManager> io_manager;
665
666 auto release_io_manager = [&]() {
667 io_manager.reset();
668 latch.Signal();
669 };
670
671 auto decode_image = [&]() {
672 Settings settings;
673 std::unique_ptr<ImageDecoder> image_decoder = ImageDecoder::Make(
674 settings, runners, loop->GetTaskRunner(),
675 io_manager->GetWeakIOManager(), std::make_shared<fml::SyncSwitch>());
676
677 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
678
679 ASSERT_TRUE(data);
680 ASSERT_GE(data->size(), 0u);
681
682 ImageGeneratorRegistry registry;
683 std::shared_ptr<ImageGenerator> generator =
685 ASSERT_TRUE(generator);
686
687 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
688 std::move(data), std::move(generator));
689
690 ImageDecoder::ImageResult callback = [&](const sk_sp<DlImage>& image,
691 const std::string& decode_error) {
692 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
693 ASSERT_TRUE(image && image->skia_image());
694 runners.GetIOTaskRunner()->PostTask(release_io_manager);
695 };
696 image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(),
697 callback);
698 };
699
700 auto set_up_io_manager_and_decode = [&]() {
701 io_manager =
702 std::make_unique<TestIOManager>(runners.GetIOTaskRunner(), false);
703 runners.GetUITaskRunner()->PostTask(decode_image);
704 };
705
706 runners.GetIOTaskRunner()->PostTask(set_up_io_manager_and_decode);
707
708 latch.Wait();
709}
710
711TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) {
712 const auto image_dimensions =
713 SkImages::DeferredFromEncodedData(
714 flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg"))
715 ->dimensions();
716
717 ASSERT_FALSE(image_dimensions.isEmpty());
718
719 ASSERT_NE(image_dimensions.width(), image_dimensions.height());
720
722 TaskRunners runners(GetCurrentTestName(), // label
723 CreateNewThread("platform"), // platform
724 CreateNewThread("raster"), // raster
725 CreateNewThread("ui"), // ui
726 CreateNewThread("io") // io
727 );
728
730 std::unique_ptr<IOManager> io_manager;
731 std::unique_ptr<ImageDecoder> image_decoder;
732
733 // Setup the IO manager.
734 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
735 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
736 });
737
738 // Setup the image decoder.
739 PostTaskSync(runners.GetUITaskRunner(), [&]() {
740 Settings settings;
741 image_decoder = ImageDecoder::Make(settings, runners, loop->GetTaskRunner(),
742 io_manager->GetWeakIOManager(),
743 std::make_shared<fml::SyncSwitch>());
744 });
745
746 // Setup a generic decoding utility that gives us the final decoded size.
747 auto decoded_size = [&](uint32_t target_width,
748 uint32_t target_height) -> SkISize {
749 SkISize final_size = SkISize::MakeEmpty();
750 runners.GetUITaskRunner()->PostTask([&]() {
751 auto data = flutter::testing::OpenFixtureAsSkData("DashInNooglerHat.jpg");
752
753 ASSERT_TRUE(data);
754 ASSERT_GE(data->size(), 0u);
755
756 ImageGeneratorRegistry registry;
757 std::shared_ptr<ImageGenerator> generator =
759 ASSERT_TRUE(generator);
760
761 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(
762 std::move(data), std::move(generator));
763
765 [&](const sk_sp<DlImage>& image, const std::string& decode_error) {
766 ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
767 ASSERT_TRUE(image && image->skia_image());
768 final_size = image->skia_image()->dimensions();
769 latch.Signal();
770 };
771 image_decoder->Decode(descriptor, target_width, target_height, callback);
772 });
773 latch.Wait();
774 return final_size;
775 };
776
777 ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions);
778 ASSERT_EQ(decoded_size(3024, 4032), image_dimensions);
779 ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
780
781 // Destroy the IO manager
782 PostTaskSync(runners.GetIOTaskRunner(), [&]() { io_manager.reset(); });
783
784 // Destroy the image decoder
785 PostTaskSync(runners.GetUITaskRunner(), [&]() { image_decoder.reset(); });
786}
787
788// Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in
789// Flutter.
790TEST(ImageDecoderTest,
791 VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) {
792 auto gif_mapping = flutter::testing::OpenFixtureAsSkData("hello_loop_2.gif");
793 auto webp_mapping =
794 flutter::testing::OpenFixtureAsSkData("hello_loop_2.webp");
795
796 ASSERT_TRUE(gif_mapping);
797 ASSERT_TRUE(webp_mapping);
798
799 ImageGeneratorRegistry registry;
800
801 auto gif_generator = registry.CreateCompatibleGenerator(gif_mapping);
802 auto webp_generator = registry.CreateCompatibleGenerator(webp_mapping);
803
804 ASSERT_TRUE(gif_generator);
805 ASSERT_TRUE(webp_generator);
806
807 // Both fixtures have a loop count of 2.
808 ASSERT_EQ(gif_generator->GetPlayCount(), static_cast<unsigned int>(2));
809 ASSERT_EQ(webp_generator->GetPlayCount(), static_cast<unsigned int>(2));
810}
811
812TEST(ImageDecoderTest, VerifySimpleDecoding) {
813 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
814 auto image = SkImages::DeferredFromEncodedData(data);
815 ASSERT_TRUE(image != nullptr);
816 EXPECT_EQ(600, image->width());
817 EXPECT_EQ(200, image->height());
818
819 ImageGeneratorRegistry registry;
820 std::shared_ptr<ImageGenerator> generator =
822 ASSERT_TRUE(generator);
823
824 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
825 std::move(generator));
826 auto compressed_image = ImageDecoderSkia::ImageFromCompressedData(
827 descriptor.get(), 6, 2, fml::tracing::TraceFlow(""));
828 EXPECT_EQ(compressed_image->width(), 6);
829 EXPECT_EQ(compressed_image->height(), 2);
830 EXPECT_EQ(compressed_image->alphaType(), kOpaque_SkAlphaType);
831
832#if IMPELLER_SUPPORTS_RENDERING
833 std::shared_ptr<impeller::Capabilities> capabilities =
836 .Build();
837 std::shared_ptr<impeller::Capabilities> capabilities_no_blit =
840 .Build();
841 // Bitmap sizes reflect the original image size as resizing is done on the
842 // GPU if the src size is smaller than the max texture size.
843 std::shared_ptr<impeller::Allocator> allocator =
844 std::make_shared<impeller::TestImpellerAllocator>();
846 descriptor.get(), SkISize::Make(6, 2), {1000, 1000},
847 /*supports_wide_gamut=*/false, capabilities, allocator);
848 EXPECT_EQ(result_1.sk_bitmap->width(), 75);
849 EXPECT_EQ(result_1.sk_bitmap->height(), 25);
850
851 // Bitmap sizes reflect the scaled size if the source size is larger than
852 // max texture size even if destination size isn't max texture size.
854 descriptor.get(), SkISize::Make(6, 2), {10, 10},
855 /*supports_wide_gamut=*/false, capabilities, allocator);
856 EXPECT_EQ(result_2.sk_bitmap->width(), 6);
857 EXPECT_EQ(result_2.sk_bitmap->height(), 2);
858
859 // If the destination size is larger than the max texture size the image
860 // is scaled down.
862 descriptor.get(), SkISize::Make(60, 20), {10, 10},
863 /*supports_wide_gamut=*/false, capabilities, allocator);
864 EXPECT_EQ(result_3.sk_bitmap->width(), 10);
865 EXPECT_EQ(result_3.sk_bitmap->height(), 10);
866
867 // CPU resize is forced.
869 descriptor.get(), SkISize::Make(6, 2), {1000, 1000},
870 /*supports_wide_gamut=*/false, capabilities_no_blit, allocator);
871 EXPECT_EQ(result_4.sk_bitmap->width(), 6);
872 EXPECT_EQ(result_4.sk_bitmap->height(), 2);
873#endif // IMPELLER_SUPPORTS_RENDERING
874}
875
876TEST(ImageDecoderTest, ImagesWithTransparencyArePremulAlpha) {
877 auto data = flutter::testing::OpenFixtureAsSkData("heart_end.png");
878 ASSERT_TRUE(data);
879 ImageGeneratorRegistry registry;
880 std::shared_ptr<ImageGenerator> generator =
882 ASSERT_TRUE(generator);
883
884 auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data),
885 std::move(generator));
886 auto compressed_image = ImageDecoderSkia::ImageFromCompressedData(
887 descriptor.get(), 250, 250, fml::tracing::TraceFlow(""));
888 ASSERT_TRUE(compressed_image);
889 ASSERT_EQ(compressed_image->width(), 250);
890 ASSERT_EQ(compressed_image->height(), 250);
891 ASSERT_EQ(compressed_image->alphaType(), kPremul_SkAlphaType);
892}
893
894TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
895 auto data = flutter::testing::OpenFixtureAsSkData("Horizontal.jpg");
896
897 ImageGeneratorRegistry registry;
898 std::shared_ptr<ImageGenerator> generator =
900 ASSERT_TRUE(generator);
901 auto descriptor =
902 fml::MakeRefCounted<ImageDescriptor>(data, std::move(generator));
903
904 // If Exif metadata is ignored, the height and width will be swapped because
905 // "Rotate 90 CW" is what is encoded there.
906 ASSERT_EQ(600, descriptor->width());
907 ASSERT_EQ(200, descriptor->height());
908
909 auto image = SkImages::DeferredFromEncodedData(data);
910 ASSERT_TRUE(image != nullptr);
911 ASSERT_EQ(600, image->width());
912 ASSERT_EQ(200, image->height());
913
914 auto decode = [descriptor](uint32_t target_width, uint32_t target_height) {
916 descriptor.get(), target_width, target_height,
918 };
919
920 auto expected_data = flutter::testing::OpenFixtureAsSkData("Horizontal.png");
921 ASSERT_TRUE(expected_data != nullptr);
922 ASSERT_FALSE(expected_data->isEmpty());
923
924 auto assert_image = [&](const auto& decoded_image,
925 const std::string& decode_error) {
926 ASSERT_EQ(decoded_image->dimensions(), SkISize::Make(300, 100));
927 sk_sp<SkData> encoded =
928 SkPngEncoder::Encode(nullptr, decoded_image.get(), {});
929 ASSERT_TRUE(encoded->equals(expected_data.get()));
930 };
931
932 assert_image(decode(300, 100), {});
933}
934
936 MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) {
937 // This test verifies that the MultiFrameCodec safely shares state between
938 // tasks on the IO and UI runners, and does not allow unsafe memory access if
939 // the UI object is collected while the IO thread still has pending decode
940 // work. This could happen in a real application if the engine is collected
941 // while a multi-frame image is decoding. To exercise this, the test:
942 // - Starts a Dart VM
943 // - Latches the IO task runner
944 // - Create a MultiFrameCodec for an animated gif pointed to a callback
945 // in the Dart fixture
946 // - Calls getNextFrame on the UI task runner
947 // - Collects the MultiFrameCodec object before unlatching the IO task
948 // runner.
949 // - Unlatches the IO task runner
950 auto settings = CreateSettingsForFixture();
951 auto vm_ref = DartVMRef::Create(settings);
952 auto vm_data = vm_ref.GetVMData();
953
954 auto gif_mapping = flutter::testing::OpenFixtureAsSkData("hello_loop_2.gif");
955
956 ASSERT_TRUE(gif_mapping);
957
958 ImageGeneratorRegistry registry;
959 std::shared_ptr<ImageGenerator> gif_generator =
960 registry.CreateCompatibleGenerator(gif_mapping);
961 ASSERT_TRUE(gif_generator);
962
963 TaskRunners runners(GetCurrentTestName(), // label
964 CreateNewThread("platform"), // platform
965 CreateNewThread("raster"), // raster
966 CreateNewThread("ui"), // ui
967 CreateNewThread("io") // io
968 );
969
971 std::unique_ptr<TestIOManager> io_manager;
972
973 // Setup the IO manager.
974 PostTaskSync(runners.GetIOTaskRunner(), [&]() {
975 io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner());
976 });
977
978 auto isolate = RunDartCodeInIsolate(vm_ref, settings, runners, "main", {},
980 io_manager->GetWeakIOManager());
981
982 // Latch the IO task runner.
983 runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); });
984
985 PostTaskSync(runners.GetUITaskRunner(), [&]() {
986 fml::AutoResetWaitableEvent isolate_latch;
987 fml::RefPtr<MultiFrameCodec> codec;
988 EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool {
989 Dart_Handle library = Dart_RootLibrary();
990 if (Dart_IsError(library)) {
991 isolate_latch.Signal();
992 return false;
993 }
994 Dart_Handle closure =
995 Dart_GetField(library, Dart_NewStringFromCString("frameCallback"));
996 if (Dart_IsError(closure) || !Dart_IsClosure(closure)) {
997 isolate_latch.Signal();
998 return false;
999 }
1000
1001 codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_generator));
1002 codec->getNextFrame(closure);
1003 codec = nullptr;
1004 isolate_latch.Signal();
1005 return true;
1006 }));
1007 isolate_latch.Wait();
1008
1009 EXPECT_FALSE(codec);
1010
1011 io_latch.Signal();
1012 });
1013
1014 // Destroy the IO manager
1015 PostTaskSync(runners.GetIOTaskRunner(), [&]() { io_manager.reset(); });
1016}
1017
1019 auto context = std::make_shared<impeller::TestImpellerContext>();
1020 auto allocator = ImpellerAllocator(context->GetResourceAllocator());
1021
1022 EXPECT_FALSE(allocator.allocPixelRef(nullptr));
1023}
1024
1025} // namespace testing
1026} // namespace flutter
1027
1028// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
1029// NOLINTEND(clang-analyzer-core.StackAddressEscape)
static DartVMRef Create(const Settings &settings, fml::RefPtr< const DartSnapshot > vm_snapshot=nullptr, fml::RefPtr< const DartSnapshot > isolate_snapshot=nullptr)
std::function< void(sk_sp< DlImage >, std::string)> ImageResult
static std::unique_ptr< ImageDecoder > Make(const Settings &settings, const TaskRunners &runners, std::shared_ptr< fml::ConcurrentTaskRunner > concurrent_task_runner, const fml::WeakPtr< IOManager > &io_manager, const std::shared_ptr< fml::SyncSwitch > &gpu_disabled_switch)
static DecompressResult DecompressTexture(ImageDescriptor *descriptor, SkISize target_size, impeller::ISize max_texture_size, bool supports_wide_gamut, const std::shared_ptr< const impeller::Capabilities > &capabilities, const std::shared_ptr< impeller::Allocator > &allocator)
static std::pair< sk_sp< DlImage >, std::string > UploadTextureToStorage(const std::shared_ptr< impeller::Context > &context, std::shared_ptr< SkBitmap > bitmap)
Create a texture from the provided bitmap.
static void UploadTextureToPrivate(ImageResult result, const std::shared_ptr< impeller::Context > &context, const std::shared_ptr< impeller::DeviceBuffer > &buffer, const SkImageInfo &image_info, const std::shared_ptr< SkBitmap > &bitmap, const std::optional< SkImageInfo > &resize_info, const std::shared_ptr< const fml::SyncSwitch > &gpu_disabled_switch)
Create a device private texture from the provided host buffer.
static sk_sp< SkImage > ImageFromCompressedData(ImageDescriptor *descriptor, uint32_t target_width, uint32_t target_height, const fml::tracing::TraceFlow &flow)
The minimal interface necessary for defining a decoder that can be used for both single and multi-fra...
Keeps a priority-ordered registry of image generator builders to be used when decoding images....
std::shared_ptr< ImageGenerator > CreateCompatibleGenerator(const sk_sp< SkData > &buffer)
Walks the list of image generator builders in descending priority order until a compatible ImageGener...
fml::RefPtr< fml::TaskRunner > GetUITaskRunner() const
fml::RefPtr< fml::TaskRunner > GetIOTaskRunner() const
fml::RefPtr< flutter::SkiaUnrefQueue > GetSkiaUnrefQueue() const override
std::shared_ptr< const fml::SyncSwitch > GetIsGpuDisabledSyncSwitch() override
fml::WeakPtr< IOManager > GetWeakIOManager() const override
std::shared_ptr< impeller::Context > GetImpellerContext() const override
Retrieve the impeller::Context.
TestIOManager(const fml::RefPtr< fml::TaskRunner > &task_runner, bool has_gpu_context=true)
fml::WeakPtr< GrDirectContext > GetResourceContext() const override
An Image generator that pretends it can't recognize the data it was given.
const SkImageInfo & GetInfo()
Returns basic information about the contents of the encoded image. This information can almost always...
unsigned int GetPlayCount() const
The number of times an animated image should play through before playback stops.
const ImageGenerator::FrameInfo GetFrameInfo(unsigned int frame_index)
Get information about a single frame in the context of a multi-frame image, useful for animation and ...
bool GetPixels(const SkImageInfo &info, void *pixels, size_t row_bytes, unsigned int frame_index, std::optional< unsigned int > prior_frame)
Decode the image into a given buffer. This method is currently always used for sub-pixel image decodi...
SkISize GetScaledDimensions(float scale)
Given a scale value, find the closest image size that can be used for efficiently decoding the image....
unsigned int GetFrameCount() const
Get the number of frames that the encoded image stores. This method is always expected to be called b...
static std::shared_ptr< ConcurrentMessageLoop > Create(size_t worker_count=std::thread::hardware_concurrency())
static void RunNowOrPostTask(const fml::RefPtr< fml::TaskRunner > &runner, const fml::closure &task)
virtual void PostTask(const fml::closure &task) override
virtual bool RunsTasksOnCurrentThread()
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
std::unique_ptr< Capabilities > Build()
To do anything rendering related with Impeller, you need a context.
Definition context.h:65
std::shared_ptr< CommandQueue > GetCommandQueue() const override
Return the graphics queue for submitting command buffers.
std::shared_ptr< ShaderLibrary > GetShaderLibrary() const override
Returns the library of shaders used to specify the programmable stages of a pipeline.
RuntimeStageBackend GetRuntimeStageBackend() const override
Retrieve the runtime stage for this context type.
std::shared_ptr< PipelineLibrary > GetPipelineLibrary() const override
Returns the library of pipelines used by render or compute commands.
void Shutdown() override
Force all pending asynchronous work to finish. This is achieved by deleting all owned concurrent mess...
std::shared_ptr< Allocator > GetResourceAllocator() const override
Returns the allocator used to create textures and buffers on the device.
bool IsValid() const override
Determines if a context is valid. If the caller ever receives an invalid context, they must discard i...
const std::shared_ptr< const Capabilities > & GetCapabilities() const override
Get the capabilities of Impeller context. All optionally supported feature of the platform,...
std::string DescribeGpuModel() const override
BackendType GetBackendType() const override
Get the graphics backend of an Impeller context.
std::shared_ptr< CommandBuffer > CreateCommandBuffer() const override
Create a new command buffer. Command buffers can be used to encode graphics, blit,...
void StoreTaskForGPU(const std::function< void()> &task, const std::function< void()> &failure) override
std::shared_ptr< SamplerLibrary > GetSamplerLibrary() const override
Returns the library of combined image samplers used in shaders.
FlutterVulkanImage * image
VkQueue queue
Definition main.cc:71
G_BEGIN_DECLS GBytes * message
FlutterDesktopBinaryReply callback
#define FML_CHECK(condition)
Definition logging.h:104
#define FML_UNREACHABLE()
Definition logging.h:128
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
std::string GetCurrentTestName()
Gets the name of the currently running test. This is useful in generating logs or assets based on tes...
Definition testing.cc:14
TEST_F(DisplayListTest, Defaults)
void PostTaskSync(const fml::RefPtr< fml::TaskRunner > &task_runner, const std::function< void()> &function)
std::string GetDefaultKernelFilePath()
Returns the default path to kernel_blob.bin. This file is within the directory returned by GetFixture...
Definition testing.cc:18
std::unique_ptr< AutoIsolateShutdown > RunDartCodeInIsolate(DartVMRef &vm_ref, const Settings &settings, const TaskRunners &task_runners, std::string entrypoint, const std::vector< std::string > &args, const std::string &kernel_file_path, fml::WeakPtr< IOManager > io_manager, std::unique_ptr< PlatformConfiguration > platform_configuration)
sk_sp< SkData > OpenFixtureAsSkData(const std::string &fixture_name)
Opens a fixture of the given file name and returns a Skia SkData holding its contents.
Definition testing.cc:63
TEST(NativeAssetsManagerTest, NoAvailableAssets)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font manager
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
Definition ref_ptr.h:261
Info about a single frame in the context of a multi-frame image, useful for animation and blending.