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 | |
5 | #include "flutter/common/task_runners.h" |
6 | #include "flutter/fml/mapping.h" |
7 | #include "flutter/fml/synchronization/waitable_event.h" |
8 | #include "flutter/lib/ui/painting/image_decoder.h" |
9 | #include "flutter/lib/ui/painting/multi_frame_codec.h" |
10 | #include "flutter/runtime/dart_vm.h" |
11 | #include "flutter/runtime/dart_vm_lifecycle.h" |
12 | #include "flutter/testing/dart_isolate_runner.h" |
13 | #include "flutter/testing/elf_loader.h" |
14 | #include "flutter/testing/fixture_test.h" |
15 | #include "flutter/testing/test_dart_native_resolver.h" |
16 | #include "flutter/testing/test_gl_surface.h" |
17 | #include "flutter/testing/testing.h" |
18 | #include "third_party/skia/include/codec/SkCodec.h" |
19 | |
20 | namespace flutter { |
21 | namespace testing { |
22 | |
23 | class TestIOManager final : public IOManager { |
24 | public: |
25 | explicit TestIOManager(fml::RefPtr<fml::TaskRunner> task_runner, |
26 | bool has_gpu_context = true) |
27 | : gl_surface_(SkISize::Make(1, 1)), |
28 | gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr), |
29 | weak_gl_context_factory_( |
30 | has_gpu_context |
31 | ? std::make_unique<fml::WeakPtrFactory<GrDirectContext>>( |
32 | gl_context_.get()) |
33 | : nullptr), |
34 | unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>( |
35 | task_runner, |
36 | fml::TimeDelta::FromNanoseconds(0))), |
37 | runner_(task_runner), |
38 | weak_factory_(this), |
39 | is_gpu_disabled_sync_switch_(std::make_shared<fml::SyncSwitch>()) { |
40 | FML_CHECK(task_runner->RunsTasksOnCurrentThread()) |
41 | << "The IO manager must be initialized its primary task runner. The " |
42 | "test harness may not be setup correctly/safely." ; |
43 | weak_prototype_ = weak_factory_.GetWeakPtr(); |
44 | } |
45 | |
46 | ~TestIOManager() override { |
47 | fml::AutoResetWaitableEvent latch; |
48 | fml::TaskRunner::RunNowOrPostTask(runner_, |
49 | [&latch, queue = unref_queue_]() { |
50 | queue->Drain(); |
51 | latch.Signal(); |
52 | }); |
53 | latch.Wait(); |
54 | } |
55 | |
56 | // |IOManager| |
57 | fml::WeakPtr<IOManager> GetWeakIOManager() const override { |
58 | return weak_prototype_; |
59 | } |
60 | |
61 | // |IOManager| |
62 | fml::WeakPtr<GrDirectContext> GetResourceContext() const override { |
63 | return weak_gl_context_factory_ ? weak_gl_context_factory_->GetWeakPtr() |
64 | : fml::WeakPtr<GrDirectContext>{}; |
65 | } |
66 | |
67 | // |IOManager| |
68 | fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override { |
69 | return unref_queue_; |
70 | } |
71 | |
72 | // |IOManager| |
73 | std::shared_ptr<fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() override { |
74 | did_access_is_gpu_disabled_sync_switch_ = true; |
75 | return is_gpu_disabled_sync_switch_; |
76 | } |
77 | |
78 | bool did_access_is_gpu_disabled_sync_switch_ = false; |
79 | |
80 | private: |
81 | TestGLSurface gl_surface_; |
82 | sk_sp<GrDirectContext> gl_context_; |
83 | std::unique_ptr<fml::WeakPtrFactory<GrDirectContext>> |
84 | weak_gl_context_factory_; |
85 | fml::RefPtr<SkiaUnrefQueue> unref_queue_; |
86 | fml::WeakPtr<TestIOManager> weak_prototype_; |
87 | fml::RefPtr<fml::TaskRunner> runner_; |
88 | fml::WeakPtrFactory<TestIOManager> weak_factory_; |
89 | std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_; |
90 | |
91 | FML_DISALLOW_COPY_AND_ASSIGN(TestIOManager); |
92 | }; |
93 | |
94 | static sk_sp<SkData> OpenFixtureAsSkData(const char* name) { |
95 | auto fixtures_directory = |
96 | fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); |
97 | if (!fixtures_directory.is_valid()) { |
98 | return nullptr; |
99 | } |
100 | |
101 | auto fixture_mapping = |
102 | fml::FileMapping::CreateReadOnly(fixtures_directory, name); |
103 | |
104 | if (!fixture_mapping) { |
105 | return nullptr; |
106 | } |
107 | |
108 | SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void { |
109 | delete reinterpret_cast<fml::FileMapping*>(context); |
110 | }; |
111 | |
112 | auto data = SkData::MakeWithProc(fixture_mapping->GetMapping(), |
113 | fixture_mapping->GetSize(), on_release, |
114 | fixture_mapping.get()); |
115 | |
116 | if (!data) { |
117 | return nullptr; |
118 | } |
119 | // The data is now owned by Skia. |
120 | fixture_mapping.release(); |
121 | return data; |
122 | } |
123 | |
124 | class ImageDecoderFixtureTest : public FixtureTest {}; |
125 | |
126 | TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) { |
127 | auto loop = fml::ConcurrentMessageLoop::Create(); |
128 | auto thread_task_runner = CreateNewThread(); |
129 | TaskRunners runners(GetCurrentTestName(), // label |
130 | thread_task_runner, // platform |
131 | thread_task_runner, // raster |
132 | thread_task_runner, // ui |
133 | thread_task_runner // io |
134 | |
135 | ); |
136 | |
137 | fml::AutoResetWaitableEvent latch; |
138 | runners.GetIOTaskRunner()->PostTask([&]() { |
139 | TestIOManager manager(runners.GetIOTaskRunner()); |
140 | ImageDecoder decoder(std::move(runners), loop->GetTaskRunner(), |
141 | manager.GetWeakIOManager()); |
142 | latch.Signal(); |
143 | }); |
144 | latch.Wait(); |
145 | } |
146 | |
147 | TEST_F(ImageDecoderFixtureTest, InvalidImageResultsError) { |
148 | auto loop = fml::ConcurrentMessageLoop::Create(); |
149 | auto thread_task_runner = CreateNewThread(); |
150 | TaskRunners runners(GetCurrentTestName(), // label |
151 | thread_task_runner, // platform |
152 | thread_task_runner, // raster |
153 | thread_task_runner, // ui |
154 | thread_task_runner // io |
155 | ); |
156 | |
157 | fml::AutoResetWaitableEvent latch; |
158 | thread_task_runner->PostTask([&]() { |
159 | TestIOManager manager(runners.GetIOTaskRunner()); |
160 | ImageDecoder decoder(runners, loop->GetTaskRunner(), |
161 | manager.GetWeakIOManager()); |
162 | |
163 | auto data = OpenFixtureAsSkData("ThisDoesNotExist.jpg" ); |
164 | ASSERT_FALSE(data); |
165 | |
166 | fml::RefPtr<ImageDescriptor> image_descriptor = |
167 | fml::MakeRefCounted<ImageDescriptor>(std::move(data), |
168 | std::unique_ptr<SkCodec>(nullptr)); |
169 | |
170 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
171 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
172 | ASSERT_FALSE(image.get()); |
173 | latch.Signal(); |
174 | }; |
175 | decoder.Decode(image_descriptor, 0, 0, callback); |
176 | }); |
177 | latch.Wait(); |
178 | } |
179 | |
180 | TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { |
181 | auto loop = fml::ConcurrentMessageLoop::Create(); |
182 | TaskRunners runners(GetCurrentTestName(), // label |
183 | CreateNewThread("platform" ), // platform |
184 | CreateNewThread("raster" ), // raster |
185 | CreateNewThread("ui" ), // ui |
186 | CreateNewThread("io" ) // io |
187 | ); |
188 | |
189 | fml::AutoResetWaitableEvent latch; |
190 | |
191 | std::unique_ptr<TestIOManager> io_manager; |
192 | |
193 | auto release_io_manager = [&]() { |
194 | io_manager.reset(); |
195 | latch.Signal(); |
196 | }; |
197 | auto decode_image = [&]() { |
198 | std::unique_ptr<ImageDecoder> image_decoder = |
199 | std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(), |
200 | io_manager->GetWeakIOManager()); |
201 | |
202 | auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg" ); |
203 | |
204 | ASSERT_TRUE(data); |
205 | ASSERT_GE(data->size(), 0u); |
206 | |
207 | std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data); |
208 | ASSERT_TRUE(codec); |
209 | |
210 | auto descriptor = |
211 | fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec)); |
212 | |
213 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
214 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
215 | ASSERT_TRUE(image.get()); |
216 | EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_); |
217 | runners.GetIOTaskRunner()->PostTask(release_io_manager); |
218 | }; |
219 | EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_); |
220 | image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(), |
221 | callback); |
222 | }; |
223 | |
224 | auto setup_io_manager_and_decode = [&]() { |
225 | io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
226 | runners.GetUITaskRunner()->PostTask(decode_image); |
227 | }; |
228 | |
229 | runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
230 | latch.Wait(); |
231 | } |
232 | |
233 | TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) { |
234 | auto loop = fml::ConcurrentMessageLoop::Create(); |
235 | TaskRunners runners(GetCurrentTestName(), // label |
236 | CreateNewThread("platform" ), // platform |
237 | CreateNewThread("raster" ), // raster |
238 | CreateNewThread("ui" ), // ui |
239 | CreateNewThread("io" ) // io |
240 | ); |
241 | |
242 | fml::AutoResetWaitableEvent latch; |
243 | |
244 | std::unique_ptr<IOManager> io_manager; |
245 | |
246 | auto release_io_manager = [&]() { |
247 | io_manager.reset(); |
248 | latch.Signal(); |
249 | }; |
250 | |
251 | SkISize decoded_size = SkISize::MakeEmpty(); |
252 | auto decode_image = [&]() { |
253 | std::unique_ptr<ImageDecoder> image_decoder = |
254 | std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(), |
255 | io_manager->GetWeakIOManager()); |
256 | |
257 | auto data = OpenFixtureAsSkData("Horizontal.jpg" ); |
258 | |
259 | ASSERT_TRUE(data); |
260 | ASSERT_GE(data->size(), 0u); |
261 | |
262 | std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data); |
263 | ASSERT_TRUE(codec); |
264 | |
265 | auto descriptor = |
266 | fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec)); |
267 | |
268 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
269 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
270 | ASSERT_TRUE(image.get()); |
271 | decoded_size = image.get()->dimensions(); |
272 | runners.GetIOTaskRunner()->PostTask(release_io_manager); |
273 | }; |
274 | image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(), |
275 | callback); |
276 | }; |
277 | |
278 | auto setup_io_manager_and_decode = [&]() { |
279 | io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
280 | runners.GetUITaskRunner()->PostTask(decode_image); |
281 | }; |
282 | |
283 | runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
284 | |
285 | latch.Wait(); |
286 | |
287 | ASSERT_EQ(decoded_size.width(), 600); |
288 | ASSERT_EQ(decoded_size.height(), 200); |
289 | } |
290 | |
291 | TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) { |
292 | auto loop = fml::ConcurrentMessageLoop::Create(); |
293 | TaskRunners runners(GetCurrentTestName(), // label |
294 | CreateNewThread("platform" ), // platform |
295 | CreateNewThread("raster" ), // raster |
296 | CreateNewThread("ui" ), // ui |
297 | CreateNewThread("io" ) // io |
298 | ); |
299 | |
300 | fml::AutoResetWaitableEvent latch; |
301 | |
302 | std::unique_ptr<IOManager> io_manager; |
303 | |
304 | auto release_io_manager = [&]() { |
305 | io_manager.reset(); |
306 | latch.Signal(); |
307 | }; |
308 | |
309 | auto decode_image = [&]() { |
310 | std::unique_ptr<ImageDecoder> image_decoder = |
311 | std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(), |
312 | io_manager->GetWeakIOManager()); |
313 | |
314 | auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg" ); |
315 | |
316 | ASSERT_TRUE(data); |
317 | ASSERT_GE(data->size(), 0u); |
318 | |
319 | std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data); |
320 | ASSERT_TRUE(codec); |
321 | |
322 | auto descriptor = |
323 | fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec)); |
324 | |
325 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
326 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
327 | ASSERT_TRUE(image.get()); |
328 | runners.GetIOTaskRunner()->PostTask(release_io_manager); |
329 | }; |
330 | image_decoder->Decode(descriptor, descriptor->width(), descriptor->height(), |
331 | callback); |
332 | }; |
333 | |
334 | auto setup_io_manager_and_decode = [&]() { |
335 | io_manager = |
336 | std::make_unique<TestIOManager>(runners.GetIOTaskRunner(), false); |
337 | runners.GetUITaskRunner()->PostTask(decode_image); |
338 | }; |
339 | |
340 | runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); |
341 | |
342 | latch.Wait(); |
343 | } |
344 | |
345 | TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) { |
346 | const auto image_dimensions = |
347 | SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg" )) |
348 | ->dimensions(); |
349 | |
350 | ASSERT_FALSE(image_dimensions.isEmpty()); |
351 | |
352 | ASSERT_NE(image_dimensions.width(), image_dimensions.height()); |
353 | |
354 | auto loop = fml::ConcurrentMessageLoop::Create(); |
355 | TaskRunners runners(GetCurrentTestName(), // label |
356 | CreateNewThread("platform" ), // platform |
357 | CreateNewThread("raster" ), // raster |
358 | CreateNewThread("ui" ), // ui |
359 | CreateNewThread("io" ) // io |
360 | ); |
361 | |
362 | fml::AutoResetWaitableEvent latch; |
363 | std::unique_ptr<IOManager> io_manager; |
364 | std::unique_ptr<ImageDecoder> image_decoder; |
365 | |
366 | // Setup the IO manager. |
367 | runners.GetIOTaskRunner()->PostTask([&]() { |
368 | io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
369 | latch.Signal(); |
370 | }); |
371 | latch.Wait(); |
372 | |
373 | // Setup the image decoder. |
374 | runners.GetUITaskRunner()->PostTask([&]() { |
375 | image_decoder = std::make_unique<ImageDecoder>( |
376 | runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
377 | |
378 | latch.Signal(); |
379 | }); |
380 | latch.Wait(); |
381 | |
382 | // Setup a generic decoding utility that gives us the final decoded size. |
383 | auto decoded_size = [&](uint32_t target_width, |
384 | uint32_t target_height) -> SkISize { |
385 | SkISize final_size = SkISize::MakeEmpty(); |
386 | runners.GetUITaskRunner()->PostTask([&]() { |
387 | auto data = OpenFixtureAsSkData("DashInNooglerHat.jpg" ); |
388 | |
389 | ASSERT_TRUE(data); |
390 | ASSERT_GE(data->size(), 0u); |
391 | |
392 | std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data); |
393 | ASSERT_TRUE(codec); |
394 | |
395 | auto descriptor = fml::MakeRefCounted<ImageDescriptor>(std::move(data), |
396 | std::move(codec)); |
397 | |
398 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
399 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
400 | ASSERT_TRUE(image.get()); |
401 | final_size = image.get()->dimensions(); |
402 | latch.Signal(); |
403 | }; |
404 | image_decoder->Decode(descriptor, target_width, target_height, callback); |
405 | }); |
406 | latch.Wait(); |
407 | return final_size; |
408 | }; |
409 | |
410 | ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions); |
411 | ASSERT_EQ(decoded_size(3024, 4032), image_dimensions); |
412 | ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100)); |
413 | |
414 | // Destroy the IO manager |
415 | runners.GetIOTaskRunner()->PostTask([&]() { |
416 | io_manager.reset(); |
417 | latch.Signal(); |
418 | }); |
419 | latch.Wait(); |
420 | |
421 | // Destroy the image decoder |
422 | runners.GetUITaskRunner()->PostTask([&]() { |
423 | image_decoder.reset(); |
424 | latch.Signal(); |
425 | }); |
426 | latch.Wait(); |
427 | } |
428 | |
429 | TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) { |
430 | SkImageInfo info = {}; |
431 | size_t row_bytes; |
432 | sk_sp<SkData> decompressed_data; |
433 | SkISize image_dimensions = SkISize::MakeEmpty(); |
434 | { |
435 | auto image = |
436 | SkImage::MakeFromEncoded(OpenFixtureAsSkData("DashInNooglerHat.jpg" )) |
437 | ->makeRasterImage(); |
438 | image_dimensions = image->dimensions(); |
439 | SkPixmap pixmap; |
440 | ASSERT_TRUE(image->peekPixels(&pixmap)); |
441 | info = SkImageInfo::MakeN32Premul(image_dimensions); |
442 | row_bytes = pixmap.rowBytes(); |
443 | decompressed_data = |
444 | SkData::MakeWithCopy(pixmap.writable_addr(), pixmap.computeByteSize()); |
445 | } |
446 | |
447 | // This is not susceptible to changes in the underlying image decoder. |
448 | ASSERT_EQ(decompressed_data->size(), 48771072u); |
449 | ASSERT_EQ(decompressed_data->size(), |
450 | image_dimensions.width() * image_dimensions.height() * 4u); |
451 | ASSERT_EQ(row_bytes, image_dimensions.width() * 4u); |
452 | ASSERT_FALSE(image_dimensions.isEmpty()); |
453 | ASSERT_NE(image_dimensions.width(), image_dimensions.height()); |
454 | |
455 | auto loop = fml::ConcurrentMessageLoop::Create(); |
456 | TaskRunners runners(GetCurrentTestName(), // label |
457 | CreateNewThread("platform" ), // platform |
458 | CreateNewThread("raster" ), // raster |
459 | CreateNewThread("ui" ), // ui |
460 | CreateNewThread("io" ) // io |
461 | ); |
462 | |
463 | fml::AutoResetWaitableEvent latch; |
464 | std::unique_ptr<IOManager> io_manager; |
465 | std::unique_ptr<ImageDecoder> image_decoder; |
466 | |
467 | // Setup the IO manager. |
468 | runners.GetIOTaskRunner()->PostTask([&]() { |
469 | io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
470 | latch.Signal(); |
471 | }); |
472 | latch.Wait(); |
473 | |
474 | // Setup the image decoder. |
475 | runners.GetUITaskRunner()->PostTask([&]() { |
476 | image_decoder = std::make_unique<ImageDecoder>( |
477 | runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager()); |
478 | |
479 | latch.Signal(); |
480 | }); |
481 | latch.Wait(); |
482 | |
483 | // Setup a generic decoding utility that gives us the final decoded size. |
484 | auto decoded_size = [&](uint32_t target_width, |
485 | uint32_t target_height) -> SkISize { |
486 | SkISize final_size = SkISize::MakeEmpty(); |
487 | runners.GetUITaskRunner()->PostTask([&]() { |
488 | ASSERT_TRUE(decompressed_data); |
489 | ASSERT_GE(decompressed_data->size(), 0u); |
490 | |
491 | auto descriptor = fml::MakeRefCounted<ImageDescriptor>(decompressed_data, |
492 | info, row_bytes); |
493 | |
494 | ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) { |
495 | ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
496 | ASSERT_TRUE(image.get()); |
497 | final_size = image.get()->dimensions(); |
498 | latch.Signal(); |
499 | }; |
500 | image_decoder->Decode(descriptor, target_width, target_height, callback); |
501 | }); |
502 | latch.Wait(); |
503 | return final_size; |
504 | }; |
505 | |
506 | ASSERT_EQ(SkISize::Make(3024, 4032), image_dimensions); |
507 | ASSERT_EQ(decoded_size(3024, 4032), image_dimensions); |
508 | ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100)); |
509 | |
510 | // Destroy the IO manager |
511 | runners.GetIOTaskRunner()->PostTask([&]() { |
512 | io_manager.reset(); |
513 | latch.Signal(); |
514 | }); |
515 | latch.Wait(); |
516 | |
517 | // Destroy the image decoder |
518 | runners.GetUITaskRunner()->PostTask([&]() { |
519 | image_decoder.reset(); |
520 | latch.Signal(); |
521 | }); |
522 | latch.Wait(); |
523 | } |
524 | |
525 | // Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in |
526 | // Flutter. |
527 | TEST(ImageDecoderTest, |
528 | VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) { |
529 | auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif" ); |
530 | auto webp_mapping = OpenFixtureAsSkData("hello_loop_2.webp" ); |
531 | |
532 | ASSERT_TRUE(gif_mapping); |
533 | ASSERT_TRUE(webp_mapping); |
534 | |
535 | auto gif_codec = SkCodec::MakeFromData(gif_mapping); |
536 | auto webp_codec = SkCodec::MakeFromData(webp_mapping); |
537 | |
538 | ASSERT_TRUE(gif_codec); |
539 | ASSERT_TRUE(webp_codec); |
540 | |
541 | // Both fixtures have a loop count of 2 which should lead to the repeat count |
542 | // of 1 |
543 | ASSERT_EQ(gif_codec->getRepetitionCount(), 1); |
544 | ASSERT_EQ(webp_codec->getRepetitionCount(), 1); |
545 | } |
546 | |
547 | TEST(ImageDecoderTest, VerifySimpleDecoding) { |
548 | auto data = OpenFixtureAsSkData("Horizontal.jpg" ); |
549 | auto image = SkImage::MakeFromEncoded(data); |
550 | ASSERT_TRUE(image != nullptr); |
551 | ASSERT_EQ(SkISize::Make(600, 200), image->dimensions()); |
552 | |
553 | auto codec = SkCodec::MakeFromData(data); |
554 | ASSERT_TRUE(codec); |
555 | auto descriptor = |
556 | fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec)); |
557 | |
558 | ASSERT_EQ( |
559 | ImageFromCompressedData(descriptor, 6, 2, fml::tracing::TraceFlow("" )) |
560 | ->dimensions(), |
561 | SkISize::Make(6, 2)); |
562 | } |
563 | |
564 | TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) { |
565 | auto data = OpenFixtureAsSkData("Horizontal.jpg" ); |
566 | auto codec = SkCodec::MakeFromData(data); |
567 | ASSERT_TRUE(codec); |
568 | auto descriptor = |
569 | fml::MakeRefCounted<ImageDescriptor>(data, std::move(codec)); |
570 | |
571 | auto image = SkImage::MakeFromEncoded(data); |
572 | ASSERT_TRUE(image != nullptr); |
573 | ASSERT_EQ(SkISize::Make(600, 200), image->dimensions()); |
574 | |
575 | auto decode = [descriptor](uint32_t target_width, uint32_t target_height) { |
576 | return ImageFromCompressedData(descriptor, target_width, target_height, |
577 | fml::tracing::TraceFlow("" )); |
578 | }; |
579 | |
580 | auto expected_data = OpenFixtureAsSkData("Horizontal.png" ); |
581 | ASSERT_TRUE(expected_data != nullptr); |
582 | ASSERT_FALSE(expected_data->isEmpty()); |
583 | |
584 | auto assert_image = [&](auto decoded_image) { |
585 | ASSERT_EQ(decoded_image->dimensions(), SkISize::Make(300, 100)); |
586 | ASSERT_TRUE(decoded_image->encodeToData(SkEncodedImageFormat::kPNG, 100) |
587 | ->equals(expected_data.get())); |
588 | }; |
589 | |
590 | assert_image(decode(300, 100)); |
591 | } |
592 | |
593 | TEST_F(ImageDecoderFixtureTest, |
594 | MultiFrameCodecCanBeCollectedBeforeIOTasksFinish) { |
595 | // This test verifies that the MultiFrameCodec safely shares state between |
596 | // tasks on the IO and UI runners, and does not allow unsafe memory access if |
597 | // the UI object is collected while the IO thread still has pending decode |
598 | // work. This could happen in a real application if the engine is collected |
599 | // while a multi-frame image is decoding. To exercise this, the test: |
600 | // - Starts a Dart VM |
601 | // - Latches the IO task runner |
602 | // - Create a MultiFrameCodec for an animated gif pointed to a callback |
603 | // in the Dart fixture |
604 | // - Calls getNextFrame on the UI task runner |
605 | // - Collects the MultiFrameCodec object before unlatching the IO task |
606 | // runner. |
607 | // - Unlatches the IO task runner |
608 | auto settings = CreateSettingsForFixture(); |
609 | auto vm_ref = DartVMRef::Create(settings); |
610 | auto vm_data = vm_ref.GetVMData(); |
611 | |
612 | auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif" ); |
613 | |
614 | ASSERT_TRUE(gif_mapping); |
615 | |
616 | auto gif_codec = std::shared_ptr<SkCodecImageGenerator>( |
617 | static_cast<SkCodecImageGenerator*>( |
618 | SkCodecImageGenerator::MakeFromEncodedCodec(gif_mapping).release())); |
619 | ASSERT_TRUE(gif_codec); |
620 | |
621 | TaskRunners runners(GetCurrentTestName(), // label |
622 | CreateNewThread("platform" ), // platform |
623 | CreateNewThread("raster" ), // raster |
624 | CreateNewThread("ui" ), // ui |
625 | CreateNewThread("io" ) // io |
626 | ); |
627 | |
628 | fml::AutoResetWaitableEvent latch; |
629 | fml::AutoResetWaitableEvent io_latch; |
630 | std::unique_ptr<TestIOManager> io_manager; |
631 | |
632 | // Setup the IO manager. |
633 | runners.GetIOTaskRunner()->PostTask([&]() { |
634 | io_manager = std::make_unique<TestIOManager>(runners.GetIOTaskRunner()); |
635 | latch.Signal(); |
636 | }); |
637 | latch.Wait(); |
638 | |
639 | auto isolate = |
640 | RunDartCodeInIsolate(vm_ref, settings, runners, "main" , {}, |
641 | GetFixturesPath(), io_manager->GetWeakIOManager()); |
642 | |
643 | // Latch the IO task runner. |
644 | runners.GetIOTaskRunner()->PostTask([&]() { io_latch.Wait(); }); |
645 | |
646 | runners.GetUITaskRunner()->PostTask([&]() { |
647 | fml::AutoResetWaitableEvent isolate_latch; |
648 | fml::RefPtr<MultiFrameCodec> codec; |
649 | EXPECT_TRUE(isolate->RunInIsolateScope([&]() -> bool { |
650 | Dart_Handle library = Dart_RootLibrary(); |
651 | if (Dart_IsError(library)) { |
652 | isolate_latch.Signal(); |
653 | return false; |
654 | } |
655 | Dart_Handle closure = |
656 | Dart_GetField(library, Dart_NewStringFromCString("frameCallback" )); |
657 | if (Dart_IsError(closure) || !Dart_IsClosure(closure)) { |
658 | isolate_latch.Signal(); |
659 | return false; |
660 | } |
661 | |
662 | codec = fml::MakeRefCounted<MultiFrameCodec>(std::move(gif_codec)); |
663 | codec->getNextFrame(closure); |
664 | codec = nullptr; |
665 | isolate_latch.Signal(); |
666 | return true; |
667 | })); |
668 | isolate_latch.Wait(); |
669 | |
670 | EXPECT_FALSE(codec); |
671 | |
672 | io_latch.Signal(); |
673 | |
674 | latch.Signal(); |
675 | }); |
676 | latch.Wait(); |
677 | |
678 | // Destroy the IO manager |
679 | runners.GetIOTaskRunner()->PostTask([&]() { |
680 | io_manager.reset(); |
681 | latch.Signal(); |
682 | }); |
683 | latch.Wait(); |
684 | } |
685 | |
686 | } // namespace testing |
687 | } // namespace flutter |
688 | |