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