REGRESSION (216471): Infinite repaint-drawing loop when asynchronously decoding incom...
[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     ASSERT(m_frames.size() <= frameCount());
180     m_frames.grow(frameCount());
181 }
182
183 void ImageFrameCache::setNativeImage(NativeImagePtr&& nativeImage)
184 {
185     ASSERT(m_frames.size() == 1);
186     ImageFrame& frame = m_frames[0];
187
188     ASSERT(!isDecoderAvailable());
189
190     frame.m_nativeImage = WTFMove(nativeImage);
191
192     frame.m_decodingStatus = ImageFrame::DecodingStatus::Complete;
193     frame.m_size = nativeImageSize(frame.m_nativeImage);
194     frame.m_hasAlpha = nativeImageHasAlpha(frame.m_nativeImage);
195 }
196
197 void ImageFrameCache::cacheMetadataAtIndex(size_t index, SubsamplingLevel subsamplingLevel, ImageFrame::DecodingStatus decodingStatus)
198 {
199     ASSERT(index < m_frames.size());
200     ImageFrame& frame = m_frames[index];
201
202     ASSERT(isDecoderAvailable());
203     if (decodingStatus == ImageFrame::DecodingStatus::Invalid)
204         frame.m_decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? ImageFrame::DecodingStatus::Complete : ImageFrame::DecodingStatus::Partial;
205     else
206         frame.m_decodingStatus = decodingStatus;
207
208     if (frame.hasMetadata())
209         return;
210     
211     frame.m_subsamplingLevel = subsamplingLevel;
212
213     if (frame.m_decodingOptions.hasSizeForDrawing()) {
214         ASSERT(frame.hasNativeImage());
215         frame.m_size = nativeImageSize(frame.nativeImage());
216     } else
217         frame.m_size = m_decoder->frameSizeAtIndex(index, subsamplingLevel);
218
219     frame.m_orientation = m_decoder->frameOrientationAtIndex(index);
220     frame.m_hasAlpha = m_decoder->frameHasAlphaAtIndex(index);
221
222     if (repetitionCount())
223         frame.m_duration = m_decoder->frameDurationAtIndex(index);
224 }
225
226 void ImageFrameCache::cacheNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, ImageFrame::DecodingStatus decodingStatus)
227 {
228     ASSERT(index < m_frames.size());
229     ImageFrame& frame = m_frames[index];
230
231     // Clear the current image frame and update the observer with this clearance.
232     decodedSizeDecreased(frame.clear());
233
234     // Do not cache the NativeImage if adding its frameByes to the MemoryCache will cause numerical overflow.
235     size_t frameBytes = size().unclampedArea() * sizeof(RGBA32);
236     if (!WTF::isInBounds<unsigned>(frameBytes + decodedSize()))
237         return;
238
239     // Move the new image to the cache.
240     frame.m_nativeImage = WTFMove(nativeImage);
241     frame.m_decodingOptions = decodingOptions;
242     cacheMetadataAtIndex(index, subsamplingLevel, decodingStatus);
243
244     // Update the observer with the new image frame bytes.
245     decodedSizeIncreased(frame.frameBytes());
246 }
247
248 void ImageFrameCache::cacheNativeImageAtIndexAsync(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, ImageFrame::DecodingStatus decodingStatus)
249 {
250     if (!isDecoderAvailable())
251         return;
252
253     ASSERT(index < m_frames.size());
254
255     // Clean the old native image and set a new one
256     cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevel, decodingOptions, decodingStatus);
257     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
258
259     // Notify the image with the readiness of the new frame NativeImage.
260     if (m_image)
261         m_image->imageFrameAvailableAtIndex(index);
262 }
263
264 Ref<WorkQueue> ImageFrameCache::decodingQueue()
265 {
266     if (!m_decodingQueue)
267         m_decodingQueue = WorkQueue::create("org.webkit.ImageDecoder", WorkQueue::Type::Serial, WorkQueue::QOS::Default);
268     
269     return *m_decodingQueue;
270 }
271
272 void ImageFrameCache::startAsyncDecodingQueue()
273 {
274     if (hasAsyncDecodingQueue() || !isDecoderAvailable())
275         return;
276
277     m_frameRequestQueue.open();
278
279     Ref<ImageFrameCache> protectedThis = Ref<ImageFrameCache>(*this);
280     Ref<WorkQueue> protectedQueue = decodingQueue();
281     Ref<ImageDecoder> protectedDecoder = Ref<ImageDecoder>(*m_decoder);
282
283     // We need to protect this, m_decodingQueue and m_decoder from being deleted while we are in the decoding loop.
284     decodingQueue()->dispatch([this, protectedThis = WTFMove(protectedThis), protectedQueue = WTFMove(protectedQueue), protectedDecoder = WTFMove(protectedDecoder)] {
285         ImageFrameRequest frameRequest;
286
287         while (m_frameRequestQueue.dequeue(frameRequest)) {
288             TraceScope tracingScope(AsyncImageDecodeStart, AsyncImageDecodeEnd);
289
290             // Get the frame NativeImage on the decoding thread.
291             NativeImagePtr nativeImage = protectedDecoder->createFrameImageAtIndex(frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions);
292             if (nativeImage)
293                 LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been decoded]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
294             else
295                 LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding for frame %ld has failed]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
296
297             // Update the cached frames on the main thread to avoid updating the MemoryCache from a different thread.
298             callOnMainThread([this, protectedQueue = protectedQueue.copyRef(), nativeImage, frameRequest] () mutable {
299                 // The queue may be closed if after we got the frame NativeImage, stopAsyncDecodingQueue() was called
300                 if (protectedQueue.ptr() == m_decodingQueue) {
301                     ASSERT(m_frameCommitQueue.first() == frameRequest);
302                     m_frameCommitQueue.removeFirst();
303                     cacheNativeImageAtIndexAsync(WTFMove(nativeImage), frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions, frameRequest.decodingStatus);
304                 } else
305                     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld will not cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
306             });
307         }
308     });
309 }
310
311 void ImageFrameCache::requestFrameAsyncDecodingAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const std::optional<IntSize>& sizeForDrawing)
312 {
313     ASSERT(isDecoderAvailable());
314     if (!hasAsyncDecodingQueue())
315         startAsyncDecodingQueue();
316     
317     ASSERT(index < m_frames.size());
318     ImageFrame::DecodingStatus decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? ImageFrame::DecodingStatus::Complete : ImageFrame::DecodingStatus::Partial;
319
320     LOG(Images, "ImageFrameCache::%s - %p - url: %s [enqueuing frame %ld for decoding]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
321     m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
322     m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
323 }
324
325 bool ImageFrameCache::isAsyncDecodingQueueIdle() const
326 {
327     return m_frameCommitQueue.isEmpty();
328 }
329     
330 void ImageFrameCache::stopAsyncDecodingQueue()
331 {
332     if (!hasAsyncDecodingQueue())
333         return;
334     
335     std::for_each(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [this](const ImageFrameRequest& frameRequest) {
336         ImageFrame& frame = m_frames[frameRequest.index];
337         if (!frame.isInvalid()) {
338             LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been cancelled for frame %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
339             frame.clear();
340         }
341     });
342
343     m_frameRequestQueue.close();
344     m_frameCommitQueue.clear();
345     m_decodingQueue = nullptr;
346     LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been stopped]", __FUNCTION__, this, sourceURL().string().utf8().data());
347 }
348
349 const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, ImageFrame::Caching caching, const std::optional<SubsamplingLevel>& subsamplingLevel)
350 {
351     ASSERT(index < m_frames.size());
352     ImageFrame& frame = m_frames[index];
353     if (!isDecoderAvailable() || frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, DecodingMode::Asynchronous))
354         return frame;
355     
356     SubsamplingLevel subsamplingLevelValue = subsamplingLevel ? subsamplingLevel.value() : frame.subsamplingLevel();
357
358     switch (caching) {
359     case ImageFrame::Caching::Metadata:
360         // Retrieve the metadata from ImageDecoder if the ImageFrame isn't complete.
361         if (frame.isComplete())
362             break;
363         cacheMetadataAtIndex(index, subsamplingLevelValue);
364         break;
365             
366     case ImageFrame::Caching::MetadataAndImage:
367         // Cache the image and retrieve the metadata from ImageDecoder only if there was not valid image stored.
368         if (frame.hasFullSizeNativeImage(subsamplingLevel))
369             break;
370         // We have to perform synchronous image decoding in this code. 
371         NativeImagePtr nativeImage = m_decoder->createFrameImageAtIndex(index, subsamplingLevelValue);
372         // Clean the old native image and set a new one.
373         cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
374         break;
375     }
376
377     return frame;
378 }
379
380 void ImageFrameCache::clearMetadata()
381 {
382     m_frameCount = 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 ImageFrame::DecodingStatus ImageFrameCache::frameDecodingStatusAtIndex(size_t index)
503 {
504     return frameMetadataAtIndex<ImageFrame::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 }