REGRESSION(r219045): A partially loaded image may not be repainted when its complete...
[WebKit-https.git] / Source / WebCore / platform / graphics / ImageFrameCache.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ImageFrameCache.h"
28
29 #include "Image.h"
30 #include "ImageObserver.h"
31 #include "Logging.h"
32 #include "URL.h"
33 #include <wtf/SystemTracing.h>
34
35 #if USE(CG)
36 #include "ImageDecoderCG.h"
37 #elif USE(DIRECT2D)
38 #include "ImageDecoderDirect2D.h"
39 #include <WinCodec.h>
40 #else
41 #include "ImageDecoder.h"
42 #endif
43
44 #include <wtf/CheckedArithmetic.h>
45 #include <wtf/MainThread.h>
46 #include <wtf/RunLoop.h>
47
48 namespace WebCore {
49
50 ImageFrameCache::ImageFrameCache(Image* image)
51     : m_image(image)
52 {
53 }
54
55 ImageFrameCache::ImageFrameCache(NativeImagePtr&& nativeImage)
56 {
57     m_frameCount = 1;
58     m_encodedDataStatus = EncodedDataStatus::Complete;
59     growFrames();
60
61     setNativeImage(WTFMove(nativeImage));
62
63     m_decodedSize = m_frames[0].frameBytes();
64
65     // The assumption is the memory image will be displayed with the default
66     // orientation. So set m_sizeRespectingOrientation to be the same as m_size.
67     m_size = m_frames[0].size();
68     m_sizeRespectingOrientation = m_size;
69 }
70
71 ImageFrameCache::~ImageFrameCache()
72 {
73     ASSERT(!hasAsyncDecodingQueue());
74 }
75
76 void ImageFrameCache::setDecoder(ImageDecoder* decoder)
77 {
78     if (m_decoder == decoder)
79         return;
80
81     // Changing the decoder has to stop the decoding thread. The current frame will
82     // continue decoding safely because the decoding thread has its own
83     // reference of the old decoder.
84     stopAsyncDecodingQueue();
85     m_decoder = decoder;
86 }
87
88 ImageDecoder* ImageFrameCache::decoder() const
89 {
90     return m_decoder.get();
91 }
92
93 void ImageFrameCache::destroyDecodedData(size_t frameCount, size_t excludeFrame)
94 {
95     unsigned decodedSize = 0;
96
97     ASSERT(frameCount <= m_frames.size());
98
99     for (size_t index = 0; index < frameCount; ++index) {
100         if (index == excludeFrame)
101             continue;
102         decodedSize += m_frames[index].clearImage();
103     }
104
105     decodedSizeReset(decodedSize);
106 }
107
108 void ImageFrameCache::destroyIncompleteDecodedData()
109 {
110     unsigned decodedSize = 0;
111     
112     for (auto& frame : m_frames) {
113         if (!frame.hasMetadata() || frame.isComplete())
114             continue;
115         
116         decodedSize += frame.clear();
117     }
118
119     decodedSizeDecreased(decodedSize);
120 }
121
122 void ImageFrameCache::decodedSizeChanged(long long decodedSize)
123 {
124     if (!decodedSize || !m_image || !m_image->imageObserver())
125         return;
126     
127     m_image->imageObserver()->decodedSizeChanged(*m_image, decodedSize);
128 }
129
130 void ImageFrameCache::decodedSizeIncreased(unsigned decodedSize)
131 {
132     if (!decodedSize)
133         return;
134     
135     m_decodedSize += decodedSize;
136     
137     // The fully-decoded frame will subsume the partially decoded data used
138     // to determine image properties.
139     long long changeSize = static_cast<long long>(decodedSize) - m_decodedPropertiesSize;
140     m_decodedPropertiesSize = 0;
141     decodedSizeChanged(changeSize);
142 }
143
144 void ImageFrameCache::decodedSizeDecreased(unsigned decodedSize)
145 {
146     if (!decodedSize)
147         return;
148
149     ASSERT(m_decodedSize >= decodedSize);
150     m_decodedSize -= decodedSize;
151     decodedSizeChanged(-static_cast<long long>(decodedSize));
152 }
153
154 void ImageFrameCache::decodedSizeReset(unsigned decodedSize)
155 {
156     ASSERT(m_decodedSize >= decodedSize);
157     m_decodedSize -= decodedSize;
158
159     // Clearing the ImageSource destroys the extra decoded data used for
160     // determining image properties.
161     decodedSize += m_decodedPropertiesSize;
162     m_decodedPropertiesSize = 0;
163     decodedSizeChanged(-static_cast<long long>(decodedSize));
164 }
165
166 void ImageFrameCache::didDecodeProperties(unsigned decodedPropertiesSize)
167 {
168     if (m_decodedSize)
169         return;
170
171     long long decodedSize = static_cast<long long>(decodedPropertiesSize) - m_decodedPropertiesSize;
172     m_decodedPropertiesSize = decodedPropertiesSize;
173     decodedSizeChanged(decodedSize);
174 }
175
176 void ImageFrameCache::growFrames()
177 {
178     ASSERT(isSizeAvailable());
179     auto newSize = frameCount();
180     if (newSize > m_frames.size())
181         m_frames.grow(newSize);
182 }
183
184 void ImageFrameCache::setNativeImage(NativeImagePtr&& nativeImage)
185 {
186     ASSERT(m_frames.size() == 1);
187     ImageFrame& frame = m_frames[0];
188
189     ASSERT(!isDecoderAvailable());
190
191     frame.m_nativeImage = WTFMove(nativeImage);
192
193     frame.m_decodingStatus = DecodingStatus::Complete;
194     frame.m_size = nativeImageSize(frame.m_nativeImage);
195     frame.m_hasAlpha = nativeImageHasAlpha(frame.m_nativeImage);
196 }
197
198 void ImageFrameCache::cacheMetadataAtIndex(size_t index, SubsamplingLevel subsamplingLevel, DecodingStatus decodingStatus)
199 {
200     ASSERT(index < m_frames.size());
201     ImageFrame& frame = m_frames[index];
202
203     ASSERT(isDecoderAvailable());
204     if (decodingStatus == DecodingStatus::Invalid)
205         frame.m_decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? DecodingStatus::Complete : DecodingStatus::Partial;
206     else
207         frame.m_decodingStatus = decodingStatus;
208
209     if (frame.hasMetadata())
210         return;
211     
212     frame.m_subsamplingLevel = subsamplingLevel;
213
214     if (frame.m_decodingOptions.hasSizeForDrawing()) {
215         ASSERT(frame.hasNativeImage());
216         frame.m_size = nativeImageSize(frame.nativeImage());
217     } else
218         frame.m_size = m_decoder->frameSizeAtIndex(index, subsamplingLevel);
219
220     frame.m_orientation = m_decoder->frameOrientationAtIndex(index);
221     frame.m_hasAlpha = m_decoder->frameHasAlphaAtIndex(index);
222
223     if (repetitionCount())
224         frame.m_duration = m_decoder->frameDurationAtIndex(index);
225 }
226
227 void ImageFrameCache::cacheNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, DecodingStatus decodingStatus)
228 {
229     ASSERT(index < m_frames.size());
230     ImageFrame& frame = m_frames[index];
231
232     // Clear the current image frame and update the observer with this clearance.
233     decodedSizeDecreased(frame.clear());
234
235     // Do not cache the NativeImage if adding its frameByes to the MemoryCache will cause numerical overflow.
236     size_t frameBytes = size().unclampedArea() * sizeof(RGBA32);
237     if (!WTF::isInBounds<unsigned>(frameBytes + decodedSize()))
238         return;
239
240     // Move the new image to the cache.
241     frame.m_nativeImage = WTFMove(nativeImage);
242     frame.m_decodingOptions = decodingOptions;
243     cacheMetadataAtIndex(index, subsamplingLevel, decodingStatus);
244
245     // Update the observer with the new image frame bytes.
246     decodedSizeIncreased(frame.frameBytes());
247 }
248
249 void ImageFrameCache::cacheNativeImageAtIndexAsync(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, DecodingStatus decodingStatus)
250 {
251     if (!isDecoderAvailable())
252         return;
253
254     ASSERT(index < m_frames.size());
255
256     // Clean the old native image and set a new one
257     cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevel, decodingOptions, decodingStatus);
258     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
259
260     // Notify the image with the readiness of the new frame NativeImage.
261     if (m_image)
262         m_image->imageFrameAvailableAtIndex(index);
263 }
264
265 Ref<WorkQueue> ImageFrameCache::decodingQueue()
266 {
267     if (!m_decodingQueue)
268         m_decodingQueue = WorkQueue::create("org.webkit.ImageDecoder", WorkQueue::Type::Serial, WorkQueue::QOS::Default);
269     
270     return *m_decodingQueue;
271 }
272
273 void ImageFrameCache::startAsyncDecodingQueue()
274 {
275     if (hasAsyncDecodingQueue() || !isDecoderAvailable())
276         return;
277
278     m_frameRequestQueue.open();
279
280     // We need to protect this, m_decodingQueue and m_decoder from being deleted while we are in the decoding loop.
281     decodingQueue()->dispatch([protectedThis = makeRef(*this), protectedQueue = decodingQueue(), protectedDecoder = makeRef(*m_decoder), sourceURL = sourceURL().string().isolatedCopy()] {
282         ImageFrameRequest frameRequest;
283
284         while (protectedThis->m_frameRequestQueue.dequeue(frameRequest)) {
285             TraceScope tracingScope(AsyncImageDecodeStart, AsyncImageDecodeEnd);
286
287             // Get the frame NativeImage on the decoding thread.
288             NativeImagePtr nativeImage = protectedDecoder->createFrameImageAtIndex(frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions);
289             if (nativeImage)
290                 LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been decoded]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
291             else {
292                 LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding for frame %ld has failed]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
293                 continue;
294             }
295
296             // Update the cached frames on the main thread to avoid updating the MemoryCache from a different thread.
297             callOnMainThread([protectedThis = protectedThis.copyRef(), protectedQueue = protectedQueue.copyRef(), protectedDecoder = protectedDecoder.copyRef(), sourceURL = sourceURL.isolatedCopy(), nativeImage = WTFMove(nativeImage), frameRequest] () mutable {
298                 // The queue may have been closed if after we got the frame NativeImage, stopAsyncDecodingQueue() was called.
299                 if (protectedQueue.ptr() == protectedThis->m_decodingQueue && protectedDecoder.ptr() == protectedThis->m_decoder) {
300                     ASSERT(protectedThis->m_frameCommitQueue.first() == frameRequest);
301                     protectedThis->m_frameCommitQueue.removeFirst();
302                     protectedThis->cacheNativeImageAtIndexAsync(WTFMove(nativeImage), frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions, frameRequest.decodingStatus);
303                 } else
304                     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld will not cached]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
305             });
306         }
307     });
308 }
309
310 void ImageFrameCache::requestFrameAsyncDecodingAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const std::optional<IntSize>& sizeForDrawing)
311 {
312     ASSERT(isDecoderAvailable());
313     if (!hasAsyncDecodingQueue())
314         startAsyncDecodingQueue();
315     
316     ASSERT(index < m_frames.size());
317     DecodingStatus decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? DecodingStatus::Complete : DecodingStatus::Partial;
318
319     LOG(Images, "ImageFrameCache::%s - %p - url: %s [enqueuing frame %ld for decoding]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
320     m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
321     m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
322 }
323
324 bool ImageFrameCache::isAsyncDecodingQueueIdle() const
325 {
326     return m_frameCommitQueue.isEmpty();
327 }
328     
329 void ImageFrameCache::stopAsyncDecodingQueue()
330 {
331     if (!hasAsyncDecodingQueue())
332         return;
333     
334     std::for_each(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [this](const ImageFrameRequest& frameRequest) {
335         ImageFrame& frame = m_frames[frameRequest.index];
336         if (!frame.isInvalid()) {
337             LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been cancelled for frame %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
338             frame.clear();
339         }
340     });
341
342     m_frameRequestQueue.close();
343     m_frameCommitQueue.clear();
344     m_decodingQueue = nullptr;
345     LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been stopped]", __FUNCTION__, this, sourceURL().string().utf8().data());
346 }
347
348 const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, ImageFrame::Caching caching, const std::optional<SubsamplingLevel>& subsamplingLevel)
349 {
350     ASSERT(index < m_frames.size());
351     ImageFrame& frame = m_frames[index];
352     if (!isDecoderAvailable() || frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, DecodingMode::Asynchronous))
353         return frame;
354     
355     SubsamplingLevel subsamplingLevelValue = subsamplingLevel ? subsamplingLevel.value() : frame.subsamplingLevel();
356
357     switch (caching) {
358     case ImageFrame::Caching::Metadata:
359         // Retrieve the metadata from ImageDecoder if the ImageFrame isn't complete.
360         if (frame.isComplete())
361             break;
362         cacheMetadataAtIndex(index, subsamplingLevelValue);
363         break;
364             
365     case ImageFrame::Caching::MetadataAndImage:
366         // Cache the image and retrieve the metadata from ImageDecoder only if there was not valid image stored.
367         if (frame.hasFullSizeNativeImage(subsamplingLevel))
368             break;
369         // We have to perform synchronous image decoding in this code. 
370         NativeImagePtr nativeImage = m_decoder->createFrameImageAtIndex(index, subsamplingLevelValue);
371         // Clean the old native image and set a new one.
372         cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
373         break;
374     }
375
376     return frame;
377 }
378
379 void ImageFrameCache::clearMetadata()
380 {
381     m_frameCount = std::nullopt;
382     m_repetitionCount = std::nullopt;
383     m_singlePixelSolidColor = std::nullopt;
384     m_encodedDataStatus = std::nullopt;
385     m_uti = std::nullopt;
386 }
387
388 URL ImageFrameCache::sourceURL() const
389 {
390     return m_image ? m_image->sourceURL() : URL();
391 }
392
393 template<typename T, T (ImageDecoder::*functor)() const>
394 T ImageFrameCache::metadata(const T& defaultValue, std::optional<T>* cachedValue)
395 {
396     if (cachedValue && *cachedValue)
397         return cachedValue->value();
398
399     if (!isDecoderAvailable() || !m_decoder->isSizeAvailable())
400         return defaultValue;
401
402     if (!cachedValue)
403         return (*m_decoder.*functor)();
404
405     *cachedValue = (*m_decoder.*functor)();
406     didDecodeProperties(m_decoder->bytesDecodedToDetermineProperties());
407     return cachedValue->value();
408 }
409
410 template<typename T, typename... Args>
411 T ImageFrameCache::frameMetadataAtIndex(size_t index, T (ImageFrame::*functor)(Args...) const, Args&&... args)
412 {
413     const ImageFrame& frame = index < m_frames.size() ? m_frames[index] : ImageFrame::defaultFrame();
414     return (frame.*functor)(std::forward<Args>(args)...);
415 }
416
417 template<typename T, typename... Args>
418 T ImageFrameCache::frameMetadataAtIndexCacheIfNeeded(size_t index, T (ImageFrame::*functor)() const, std::optional<T>* cachedValue, Args&&... args)
419 {
420     if (cachedValue && *cachedValue)
421         return cachedValue->value();
422
423     const ImageFrame& frame = index < m_frames.size() ? frameAtIndexCacheIfNeeded(index, std::forward<Args>(args)...) : ImageFrame::defaultFrame();
424
425     // Don't cache any unavailable frame metadata.
426     if (!frame.hasMetadata() || !cachedValue)
427         return (frame.*functor)();
428
429     *cachedValue = (frame.*functor)();
430     return cachedValue->value();
431 }
432
433 EncodedDataStatus ImageFrameCache::encodedDataStatus()
434 {
435     return metadata<EncodedDataStatus, (&ImageDecoder::encodedDataStatus)>(EncodedDataStatus::Unknown, &m_encodedDataStatus);
436 }
437
438 size_t ImageFrameCache::frameCount()
439 {
440     return metadata<size_t, (&ImageDecoder::frameCount)>(m_frames.size(), &m_frameCount);
441 }
442
443 RepetitionCount ImageFrameCache::repetitionCount()
444 {
445     return metadata<RepetitionCount, (&ImageDecoder::repetitionCount)>(RepetitionCountNone, &m_repetitionCount);
446 }
447     
448 String ImageFrameCache::uti()
449 {
450 #if USE(CG)
451     return metadata<String, (&ImageDecoder::uti)>(String(), &m_uti);
452 #else
453     return String();
454 #endif
455 }
456
457 String ImageFrameCache::filenameExtension()
458 {
459     return metadata<String, (&ImageDecoder::filenameExtension)>(String(), &m_filenameExtension);
460 }
461
462 std::optional<IntPoint> ImageFrameCache::hotSpot()
463 {
464     return metadata<std::optional<IntPoint>, (&ImageDecoder::hotSpot)>(std::nullopt, &m_hotSpot);
465 }
466
467 IntSize ImageFrameCache::size()
468 {
469 #if !USE(CG)
470     // It's possible that we have decoded the metadata, but not frame contents yet. In that case ImageDecoder claims to
471     // have the size available, but the frame cache is empty. Return the decoder size without caching in such case.
472     if (m_frames.isEmpty() && isDecoderAvailable())
473         return m_decoder->size();
474 #endif
475     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::size), &m_size, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
476 }
477
478 IntSize ImageFrameCache::sizeRespectingOrientation()
479 {
480     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::sizeRespectingOrientation), &m_sizeRespectingOrientation, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
481 }
482
483 Color ImageFrameCache::singlePixelSolidColor()
484 {
485     if (!m_singlePixelSolidColor && (size() != IntSize(1, 1) || frameCount() != 1))
486         m_singlePixelSolidColor = Color();
487
488     if (m_singlePixelSolidColor)
489         return m_singlePixelSolidColor.value();
490
491     return frameMetadataAtIndexCacheIfNeeded<Color>(0, (&ImageFrame::singlePixelSolidColor), &m_singlePixelSolidColor, ImageFrame::Caching::MetadataAndImage);
492 }
493
494 bool ImageFrameCache::frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(size_t index, const DecodingOptions& decodingOptions)
495 {
496     auto it = std::find_if(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [index, &decodingOptions](const ImageFrameRequest& frameRequest) {
497         return frameRequest.index == index && frameRequest.decodingOptions.isAsynchronousCompatibleWith(decodingOptions);
498     });
499     return it != m_frameCommitQueue.end();
500 }
501
502 DecodingStatus ImageFrameCache::frameDecodingStatusAtIndex(size_t index)
503 {
504     return frameMetadataAtIndex<DecodingStatus>(index, (&ImageFrame::decodingStatus));
505 }
506
507 bool ImageFrameCache::frameHasAlphaAtIndex(size_t index)
508 {
509     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasAlpha));
510 }
511
512 bool ImageFrameCache::frameHasFullSizeNativeImageAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel)
513 {
514     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasFullSizeNativeImage), subsamplingLevel);
515 }
516
517 bool ImageFrameCache::frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel, const DecodingOptions& decodingOptions)
518 {
519     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasDecodedNativeImageCompatibleWithOptions), subsamplingLevel, decodingOptions);
520 }
521     
522 SubsamplingLevel ImageFrameCache::frameSubsamplingLevelAtIndex(size_t index)
523 {
524     return frameMetadataAtIndex<SubsamplingLevel>(index, (&ImageFrame::subsamplingLevel));
525 }
526
527 IntSize ImageFrameCache::frameSizeAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
528 {
529     return frameMetadataAtIndexCacheIfNeeded<IntSize>(index, (&ImageFrame::size), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
530 }
531
532 unsigned ImageFrameCache::frameBytesAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
533 {
534     return frameMetadataAtIndexCacheIfNeeded<unsigned>(index, (&ImageFrame::frameBytes), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
535 }
536
537 float ImageFrameCache::frameDurationAtIndex(size_t index)
538 {
539     return frameMetadataAtIndexCacheIfNeeded<float>(index, (&ImageFrame::duration), nullptr, ImageFrame::Caching::Metadata);
540 }
541
542 ImageOrientation ImageFrameCache::frameOrientationAtIndex(size_t index)
543 {
544     return frameMetadataAtIndexCacheIfNeeded<ImageOrientation>(index, (&ImageFrame::orientation), nullptr, ImageFrame::Caching::Metadata);
545 }
546
547 NativeImagePtr ImageFrameCache::frameImageAtIndex(size_t index)
548 {
549     return frameMetadataAtIndex<NativeImagePtr>(index, (&ImageFrame::nativeImage));
550 }
551
552 NativeImagePtr ImageFrameCache::frameImageAtIndexCacheIfNeeded(size_t index, SubsamplingLevel subsamplingLevel)
553 {
554     return frameMetadataAtIndexCacheIfNeeded<NativeImagePtr>(index, (&ImageFrame::nativeImage), nullptr, ImageFrame::Caching::MetadataAndImage, subsamplingLevel);
555 }
556
557 }