9cf42fbad0df55168c172b318e115d5668181c51
[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_decoding = ImageFrame::Decoding::Complete;
193     frame.m_size = nativeImageSize(frame.m_nativeImage);
194     frame.m_hasAlpha = nativeImageHasAlpha(frame.m_nativeImage);
195 }
196
197 void ImageFrameCache::cacheFrameMetadataAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
198 {
199     ASSERT(index < m_frames.size());
200     ImageFrame& frame = m_frames[index];
201
202     ASSERT(isDecoderAvailable());
203     frame.m_decoding = m_decoder->frameIsCompleteAtIndex(index) ? ImageFrame::Decoding::Complete : ImageFrame::Decoding::Partial;
204     if (frame.hasMetadata())
205         return;
206     
207     frame.m_subsamplingLevel = subsamplingLevel;
208
209     if (frame.m_decodingOptions.hasSizeForDrawing()) {
210         ASSERT(frame.hasNativeImage());
211         frame.m_size = nativeImageSize(frame.nativeImage());
212     } else
213         frame.m_size = m_decoder->frameSizeAtIndex(index, subsamplingLevel);
214
215     frame.m_orientation = m_decoder->frameOrientationAtIndex(index);
216     frame.m_hasAlpha = m_decoder->frameHasAlphaAtIndex(index);
217
218     if (repetitionCount())
219         frame.m_duration = m_decoder->frameDurationAtIndex(index);
220 }
221
222 void ImageFrameCache::cacheFrameNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions)
223 {
224     ASSERT(index < m_frames.size());
225     ImageFrame& frame = m_frames[index];
226
227     // Clear the current image frame and update the observer with this clearance.
228     decodedSizeDecreased(frame.clear());
229
230     // Do not cache the NativeImage if adding its frameByes to the MemoryCache will cause numerical overflow.
231     size_t frameBytes = size().unclampedArea() * sizeof(RGBA32);
232     if (!WTF::isInBounds<unsigned>(frameBytes + decodedSize()))
233         return;
234
235     // Move the new image to the cache.
236     frame.m_nativeImage = WTFMove(nativeImage);
237     frame.m_decodingOptions = decodingOptions;
238     cacheFrameMetadataAtIndex(index, subsamplingLevel);
239
240     // Update the observer with the new image frame bytes.
241     decodedSizeIncreased(frame.frameBytes());
242 }
243
244 void ImageFrameCache::cacheAsyncFrameNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions)
245 {
246     if (!isDecoderAvailable())
247         return;
248
249     ASSERT(index < m_frames.size());
250     ASSERT(!frameIsCompleteAtIndex(index) || !frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(index, subsamplingLevel, decodingOptions));
251
252     // Clean the old native image and set a new one
253     cacheFrameNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevel, decodingOptions);
254     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
255
256     // Notify the image with the readiness of the new frame NativeImage.
257     if (m_image)
258         m_image->imageFrameAvailableAtIndex(index);
259 }
260
261 Ref<WorkQueue> ImageFrameCache::decodingQueue()
262 {
263     if (!m_decodingQueue)
264         m_decodingQueue = WorkQueue::create("org.webkit.ImageDecoder", WorkQueue::Type::Serial, WorkQueue::QOS::Default);
265     
266     return *m_decodingQueue;
267 }
268
269 void ImageFrameCache::startAsyncDecodingQueue()
270 {
271     if (hasAsyncDecodingQueue() || !isDecoderAvailable())
272         return;
273
274     m_frameRequestQueue.open();
275
276     Ref<ImageFrameCache> protectedThis = Ref<ImageFrameCache>(*this);
277     Ref<WorkQueue> protectedQueue = decodingQueue();
278     Ref<ImageDecoder> protectedDecoder = Ref<ImageDecoder>(*m_decoder);
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([this, protectedThis = WTFMove(protectedThis), protectedQueue = WTFMove(protectedQueue), protectedDecoder = WTFMove(protectedDecoder)] {
282         ImageFrameRequest frameRequest;
283
284         while (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__, this, sourceURL().string().utf8().data(), frameRequest.index);
291             else
292                 LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding for frame %ld has failed]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
293
294             // Update the cached frames on the main thread to avoid updating the MemoryCache from a different thread.
295             callOnMainThread([this, protectedQueue = protectedQueue.copyRef(), nativeImage, frameRequest] () mutable {
296                 // The queue may be closed if after we got the frame NativeImage, stopAsyncDecodingQueue() was called
297                 if (protectedQueue.ptr() == m_decodingQueue) {
298                     ASSERT(m_frameCommitQueue.first() == frameRequest);
299                     m_frameCommitQueue.removeFirst();
300                     cacheAsyncFrameNativeImageAtIndex(WTFMove(nativeImage), frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions);
301                 } else
302                     LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld will not cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
303             });
304         }
305     });
306 }
307
308 bool ImageFrameCache::requestFrameAsyncDecodingAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const std::optional<IntSize>& sizeForDrawing)
309 {
310     if (!isDecoderAvailable())
311         return false;
312
313     // Allow new requests for the same frame if the frame is incomplete.
314     ASSERT(index < m_frames.size());
315     if (frameIsCompleteAtIndex(index)) {
316         // We need to coalesce multiple requests for decoding the same ImageFrame while it
317         // is still being decoded. This may happen if the image rectangle is repainted
318         // multiple times while the ImageFrame has not finished decoding.
319         if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, sizeForDrawing))
320             return true;
321
322         if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(index, subsamplingLevel, sizeForDrawing))
323             return false;
324     }
325
326     if (!hasAsyncDecodingQueue())
327         startAsyncDecodingQueue();
328
329     LOG(Images, "ImageFrameCache::%s - %p - url: %s [enqueuing frame %ld for decoding]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
330     m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing });
331     m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing });
332     return true;
333 }
334
335 bool ImageFrameCache::isAsyncDecodingQueueIdle() const
336 {
337     return m_frameCommitQueue.isEmpty();
338 }
339     
340 void ImageFrameCache::stopAsyncDecodingQueue()
341 {
342     if (!hasAsyncDecodingQueue())
343         return;
344     
345     std::for_each(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [this](const ImageFrameRequest& frameRequest) {
346         ImageFrame& frame = m_frames[frameRequest.index];
347         if (!frame.isEmpty()) {
348             LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been cancelled for frame %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
349             frame.clear();
350         }
351     });
352
353     m_frameRequestQueue.close();
354     m_frameCommitQueue.clear();
355     m_decodingQueue = nullptr;
356     LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been stopped]", __FUNCTION__, this, sourceURL().string().utf8().data());
357 }
358
359 const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, ImageFrame::Caching caching, const std::optional<SubsamplingLevel>& subsamplingLevel)
360 {
361     ASSERT(index < m_frames.size());
362     ImageFrame& frame = m_frames[index];
363     if (!isDecoderAvailable() || frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, DecodingMode::Asynchronous))
364         return frame;
365     
366     SubsamplingLevel subsamplingLevelValue = subsamplingLevel ? subsamplingLevel.value() : frame.subsamplingLevel();
367
368     switch (caching) {
369     case ImageFrame::Caching::Metadata:
370         // Retrieve the metadata from ImageDecoder if the ImageFrame isn't complete.
371         if (frame.isComplete())
372             break;
373         cacheFrameMetadataAtIndex(index, subsamplingLevelValue);
374         break;
375             
376     case ImageFrame::Caching::MetadataAndImage:
377         // Cache the image and retrieve the metadata from ImageDecoder only if there was not valid image stored.
378         if (frame.hasFullSizeNativeImage(subsamplingLevel))
379             break;
380         // We have to perform synchronous image decoding in this code. 
381         NativeImagePtr nativeImage = m_decoder->createFrameImageAtIndex(index, subsamplingLevelValue);
382         // Clean the old native image and set a new one.
383         cacheFrameNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
384         break;
385     }
386
387     return frame;
388 }
389
390 void ImageFrameCache::clearMetadata()
391 {
392     m_frameCount = std::nullopt;
393     m_singlePixelSolidColor = std::nullopt;
394     m_encodedDataStatus = std::nullopt;
395     m_uti = std::nullopt;
396 }
397
398 URL ImageFrameCache::sourceURL() const
399 {
400     return m_image ? m_image->sourceURL() : URL();
401 }
402
403 template<typename T, T (ImageDecoder::*functor)() const>
404 T ImageFrameCache::metadata(const T& defaultValue, std::optional<T>* cachedValue)
405 {
406     if (cachedValue && *cachedValue)
407         return cachedValue->value();
408
409     if (!isDecoderAvailable() || !m_decoder->isSizeAvailable())
410         return defaultValue;
411
412     if (!cachedValue)
413         return (*m_decoder.*functor)();
414
415     *cachedValue = (*m_decoder.*functor)();
416     didDecodeProperties(m_decoder->bytesDecodedToDetermineProperties());
417     return cachedValue->value();
418 }
419
420 template<typename T, typename... Args>
421 T ImageFrameCache::frameMetadataAtIndex(size_t index, T (ImageFrame::*functor)(Args...) const, Args&&... args)
422 {
423     const ImageFrame& frame = index < m_frames.size() ? m_frames[index] : ImageFrame::defaultFrame();
424     return (frame.*functor)(std::forward<Args>(args)...);
425 }
426
427 template<typename T, typename... Args>
428 T ImageFrameCache::frameMetadataAtIndexCacheIfNeeded(size_t index, T (ImageFrame::*functor)() const, std::optional<T>* cachedValue, Args&&... args)
429 {
430     if (cachedValue && *cachedValue)
431         return cachedValue->value();
432
433     const ImageFrame& frame = index < m_frames.size() ? frameAtIndexCacheIfNeeded(index, std::forward<Args>(args)...) : ImageFrame::defaultFrame();
434
435     // Don't cache any unavailable frame metadata.
436     if (!frame.hasMetadata() || !cachedValue)
437         return (frame.*functor)();
438
439     *cachedValue = (frame.*functor)();
440     return cachedValue->value();
441 }
442
443 EncodedDataStatus ImageFrameCache::encodedDataStatus()
444 {
445     return metadata<EncodedDataStatus, (&ImageDecoder::encodedDataStatus)>(EncodedDataStatus::Unknown, &m_encodedDataStatus);
446 }
447
448 size_t ImageFrameCache::frameCount()
449 {
450     return metadata<size_t, (&ImageDecoder::frameCount)>(m_frames.size(), &m_frameCount);
451 }
452
453 RepetitionCount ImageFrameCache::repetitionCount()
454 {
455     return metadata<RepetitionCount, (&ImageDecoder::repetitionCount)>(RepetitionCountNone, &m_repetitionCount);
456 }
457     
458 String ImageFrameCache::uti()
459 {
460 #if USE(CG)
461     return metadata<String, (&ImageDecoder::uti)>(String(), &m_uti);
462 #else
463     return String();
464 #endif
465 }
466
467 String ImageFrameCache::filenameExtension()
468 {
469     return metadata<String, (&ImageDecoder::filenameExtension)>(String(), &m_filenameExtension);
470 }
471
472 std::optional<IntPoint> ImageFrameCache::hotSpot()
473 {
474     return metadata<std::optional<IntPoint>, (&ImageDecoder::hotSpot)>(std::nullopt, &m_hotSpot);
475 }
476
477 IntSize ImageFrameCache::size()
478 {
479 #if !USE(CG)
480     // It's possible that we have decoded the metadata, but not frame contents yet. In that case ImageDecoder claims to
481     // have the size available, but the frame cache is empty. Return the decoder size without caching in such case.
482     if (m_frames.isEmpty() && isDecoderAvailable())
483         return m_decoder->size();
484 #endif
485     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::size), &m_size, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
486 }
487
488 IntSize ImageFrameCache::sizeRespectingOrientation()
489 {
490     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::sizeRespectingOrientation), &m_sizeRespectingOrientation, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
491 }
492
493 Color ImageFrameCache::singlePixelSolidColor()
494 {
495     if (!m_singlePixelSolidColor && (size() != IntSize(1, 1) || frameCount() != 1))
496         m_singlePixelSolidColor = Color();
497
498     if (m_singlePixelSolidColor)
499         return m_singlePixelSolidColor.value();
500
501     return frameMetadataAtIndexCacheIfNeeded<Color>(0, (&ImageFrame::singlePixelSolidColor), &m_singlePixelSolidColor, ImageFrame::Caching::MetadataAndImage);
502 }
503
504 bool ImageFrameCache::frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(size_t index, const DecodingOptions& decodingOptions)
505 {
506     auto it = std::find_if(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [index, &decodingOptions](const ImageFrameRequest& frameRequest) {
507         return frameRequest.index == index && frameRequest.decodingOptions.isAsynchronousCompatibleWith(decodingOptions);
508     });
509     return it != m_frameCommitQueue.end();
510 }
511
512 bool ImageFrameCache::frameIsCompleteAtIndex(size_t index)
513 {
514     return frameMetadataAtIndex<bool>(index, (&ImageFrame::isComplete));
515 }
516
517 bool ImageFrameCache::frameHasAlphaAtIndex(size_t index)
518 {
519     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasAlpha));
520 }
521
522 bool ImageFrameCache::frameHasFullSizeNativeImageAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel)
523 {
524     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasFullSizeNativeImage), subsamplingLevel);
525 }
526
527 bool ImageFrameCache::frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel, const DecodingOptions& decodingOptions)
528 {
529     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasDecodedNativeImageCompatibleWithOptions), subsamplingLevel, decodingOptions);
530 }
531     
532 SubsamplingLevel ImageFrameCache::frameSubsamplingLevelAtIndex(size_t index)
533 {
534     return frameMetadataAtIndex<SubsamplingLevel>(index, (&ImageFrame::subsamplingLevel));
535 }
536
537 IntSize ImageFrameCache::frameSizeAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
538 {
539     return frameMetadataAtIndexCacheIfNeeded<IntSize>(index, (&ImageFrame::size), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
540 }
541
542 unsigned ImageFrameCache::frameBytesAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
543 {
544     return frameMetadataAtIndexCacheIfNeeded<unsigned>(index, (&ImageFrame::frameBytes), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
545 }
546
547 float ImageFrameCache::frameDurationAtIndex(size_t index)
548 {
549     return frameMetadataAtIndexCacheIfNeeded<float>(index, (&ImageFrame::duration), nullptr, ImageFrame::Caching::Metadata);
550 }
551
552 ImageOrientation ImageFrameCache::frameOrientationAtIndex(size_t index)
553 {
554     return frameMetadataAtIndexCacheIfNeeded<ImageOrientation>(index, (&ImageFrame::orientation), nullptr, ImageFrame::Caching::Metadata);
555 }
556
557 NativeImagePtr ImageFrameCache::frameImageAtIndex(size_t index)
558 {
559     return frameMetadataAtIndex<NativeImagePtr>(index, (&ImageFrame::nativeImage));
560 }
561
562 NativeImagePtr ImageFrameCache::frameImageAtIndexCacheIfNeeded(size_t index, SubsamplingLevel subsamplingLevel)
563 {
564     return frameMetadataAtIndexCacheIfNeeded<NativeImagePtr>(index, (&ImageFrame::nativeImage), nullptr, ImageFrame::Caching::MetadataAndImage, subsamplingLevel);
565 }
566
567 }