[CG] Provide a type identifier hint to the CGImageSource so getting the type identifi...
[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(!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->newFrameNativeImageAvailableAtIndex(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     ASSERT(index < m_frames.size());
314
315     // We need to coalesce multiple requests for decoding the same ImageFrame while it
316     // is still being decoded. This may happen if the image rectangle is repainted
317     // multiple times while the ImageFrame has not finished decoding.
318     if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, sizeForDrawing))
319         return true;
320
321     if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(index, subsamplingLevel, sizeForDrawing))
322         return false;
323
324     if (!hasAsyncDecodingQueue())
325         startAsyncDecodingQueue();
326
327     LOG(Images, "ImageFrameCache::%s - %p - url: %s [enqueuing frame %ld for decoding]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
328     m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing });
329     m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing });
330     return true;
331 }
332
333 bool ImageFrameCache::isAsyncDecodingQueueIdle() const
334 {
335     return m_frameCommitQueue.isEmpty();
336 }
337     
338 void ImageFrameCache::stopAsyncDecodingQueue()
339 {
340     if (!hasAsyncDecodingQueue())
341         return;
342     
343     std::for_each(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [this](const ImageFrameRequest& frameRequest) {
344         ImageFrame& frame = m_frames[frameRequest.index];
345         if (!frame.isEmpty()) {
346             LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been cancelled for frame %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
347             frame.clear();
348         }
349     });
350
351     m_frameRequestQueue.close();
352     m_frameCommitQueue.clear();
353     m_decodingQueue = nullptr;
354     LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been stopped]", __FUNCTION__, this, sourceURL().string().utf8().data());
355 }
356
357 const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, ImageFrame::Caching caching, const std::optional<SubsamplingLevel>& subsamplingLevel)
358 {
359     ASSERT(index < m_frames.size());
360     ImageFrame& frame = m_frames[index];
361     if (!isDecoderAvailable() || frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, DecodingMode::Asynchronous))
362         return frame;
363     
364     SubsamplingLevel subsamplingLevelValue = subsamplingLevel ? subsamplingLevel.value() : frame.subsamplingLevel();
365
366     switch (caching) {
367     case ImageFrame::Caching::Metadata:
368         // Retrieve the metadata from ImageDecoder if the ImageFrame isn't complete.
369         if (frame.isComplete())
370             break;
371         cacheFrameMetadataAtIndex(index, subsamplingLevelValue);
372         break;
373             
374     case ImageFrame::Caching::MetadataAndImage:
375         // Cache the image and retrieve the metadata from ImageDecoder only if there was not valid image stored.
376         if (frame.hasFullSizeNativeImage(subsamplingLevel))
377             break;
378         // We have to perform synchronous image decoding in this code. 
379         NativeImagePtr nativeImage = m_decoder->createFrameImageAtIndex(index, subsamplingLevelValue);
380         // Clean the old native image and set a new one.
381         cacheFrameNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
382         break;
383     }
384
385     return frame;
386 }
387
388 void ImageFrameCache::clearMetadata()
389 {
390     m_frameCount = std::nullopt;
391     m_singlePixelSolidColor = std::nullopt;
392     m_encodedDataStatus = std::nullopt;
393     m_uti = std::nullopt;
394 }
395
396 URL ImageFrameCache::sourceURL() const
397 {
398     return m_image ? m_image->sourceURL() : URL();
399 }
400
401 template<typename T, T (ImageDecoder::*functor)() const>
402 T ImageFrameCache::metadata(const T& defaultValue, std::optional<T>* cachedValue)
403 {
404     if (cachedValue && *cachedValue)
405         return cachedValue->value();
406
407     if (!isDecoderAvailable() || !m_decoder->isSizeAvailable())
408         return defaultValue;
409
410     if (!cachedValue)
411         return (*m_decoder.*functor)();
412
413     *cachedValue = (*m_decoder.*functor)();
414     didDecodeProperties(m_decoder->bytesDecodedToDetermineProperties());
415     return cachedValue->value();
416 }
417
418 template<typename T, typename... Args>
419 T ImageFrameCache::frameMetadataAtIndex(size_t index, T (ImageFrame::*functor)(Args...) const, Args&&... args)
420 {
421     const ImageFrame& frame = index < m_frames.size() ? m_frames[index] : ImageFrame::defaultFrame();
422     return (frame.*functor)(std::forward<Args>(args)...);
423 }
424
425 template<typename T, typename... Args>
426 T ImageFrameCache::frameMetadataAtIndexCacheIfNeeded(size_t index, T (ImageFrame::*functor)() const, std::optional<T>* cachedValue, Args&&... args)
427 {
428     if (cachedValue && *cachedValue)
429         return cachedValue->value();
430
431     const ImageFrame& frame = index < m_frames.size() ? frameAtIndexCacheIfNeeded(index, std::forward<Args>(args)...) : ImageFrame::defaultFrame();
432
433     // Don't cache any unavailable frame metadata.
434     if (!frame.hasMetadata() || !cachedValue)
435         return (frame.*functor)();
436
437     *cachedValue = (frame.*functor)();
438     return cachedValue->value();
439 }
440
441 EncodedDataStatus ImageFrameCache::encodedDataStatus()
442 {
443     return metadata<EncodedDataStatus, (&ImageDecoder::encodedDataStatus)>(EncodedDataStatus::Unknown, &m_encodedDataStatus);
444 }
445
446 size_t ImageFrameCache::frameCount()
447 {
448     return metadata<size_t, (&ImageDecoder::frameCount)>(m_frames.size(), &m_frameCount);
449 }
450
451 RepetitionCount ImageFrameCache::repetitionCount()
452 {
453     return metadata<RepetitionCount, (&ImageDecoder::repetitionCount)>(RepetitionCountNone, &m_repetitionCount);
454 }
455     
456 String ImageFrameCache::uti()
457 {
458 #if USE(CG)
459     return metadata<String, (&ImageDecoder::uti)>(String(), &m_uti);
460 #else
461     return String();
462 #endif
463 }
464
465 String ImageFrameCache::filenameExtension()
466 {
467     return metadata<String, (&ImageDecoder::filenameExtension)>(String(), &m_filenameExtension);
468 }
469
470 std::optional<IntPoint> ImageFrameCache::hotSpot()
471 {
472     return metadata<std::optional<IntPoint>, (&ImageDecoder::hotSpot)>(std::nullopt, &m_hotSpot);
473 }
474
475 IntSize ImageFrameCache::size()
476 {
477     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::size), &m_size, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
478 }
479
480 IntSize ImageFrameCache::sizeRespectingOrientation()
481 {
482     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::sizeRespectingOrientation), &m_sizeRespectingOrientation, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
483 }
484
485 Color ImageFrameCache::singlePixelSolidColor()
486 {
487     if (!m_singlePixelSolidColor && (size() != IntSize(1, 1) || frameCount() != 1))
488         m_singlePixelSolidColor = Color();
489
490     if (m_singlePixelSolidColor)
491         return m_singlePixelSolidColor.value();
492
493     return frameMetadataAtIndexCacheIfNeeded<Color>(0, (&ImageFrame::singlePixelSolidColor), &m_singlePixelSolidColor, ImageFrame::Caching::MetadataAndImage);
494 }
495
496 bool ImageFrameCache::frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(size_t index, const DecodingOptions& decodingOptions)
497 {
498     auto it = std::find_if(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [index, &decodingOptions](const ImageFrameRequest& frameRequest) {
499         return frameRequest.index == index && frameRequest.decodingOptions.isAsynchronousCompatibleWith(decodingOptions);
500     });
501     return it != m_frameCommitQueue.end();
502 }
503
504 bool ImageFrameCache::frameIsCompleteAtIndex(size_t index)
505 {
506     return frameMetadataAtIndex<bool>(index, (&ImageFrame::isComplete));
507 }
508
509 bool ImageFrameCache::frameHasAlphaAtIndex(size_t index)
510 {
511     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasAlpha));
512 }
513
514 bool ImageFrameCache::frameHasFullSizeNativeImageAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel)
515 {
516     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasFullSizeNativeImage), subsamplingLevel);
517 }
518
519 bool ImageFrameCache::frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(size_t index, const std::optional<SubsamplingLevel>& subsamplingLevel, const DecodingOptions& decodingOptions)
520 {
521     return frameMetadataAtIndex<bool>(index, (&ImageFrame::hasDecodedNativeImageCompatibleWithOptions), subsamplingLevel, decodingOptions);
522 }
523     
524 SubsamplingLevel ImageFrameCache::frameSubsamplingLevelAtIndex(size_t index)
525 {
526     return frameMetadataAtIndex<SubsamplingLevel>(index, (&ImageFrame::subsamplingLevel));
527 }
528
529 IntSize ImageFrameCache::frameSizeAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
530 {
531     return frameMetadataAtIndexCacheIfNeeded<IntSize>(index, (&ImageFrame::size), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
532 }
533
534 unsigned ImageFrameCache::frameBytesAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
535 {
536     return frameMetadataAtIndexCacheIfNeeded<unsigned>(index, (&ImageFrame::frameBytes), nullptr, ImageFrame::Caching::Metadata, subsamplingLevel);
537 }
538
539 float ImageFrameCache::frameDurationAtIndex(size_t index)
540 {
541     return frameMetadataAtIndexCacheIfNeeded<float>(index, (&ImageFrame::duration), nullptr, ImageFrame::Caching::Metadata);
542 }
543
544 ImageOrientation ImageFrameCache::frameOrientationAtIndex(size_t index)
545 {
546     return frameMetadataAtIndexCacheIfNeeded<ImageOrientation>(index, (&ImageFrame::orientation), nullptr, ImageFrame::Caching::Metadata);
547 }
548
549 NativeImagePtr ImageFrameCache::frameImageAtIndex(size_t index)
550 {
551     return frameMetadataAtIndex<NativeImagePtr>(index, (&ImageFrame::nativeImage));
552 }
553
554 NativeImagePtr ImageFrameCache::frameImageAtIndexCacheIfNeeded(size_t index, SubsamplingLevel subsamplingLevel)
555 {
556     return frameMetadataAtIndexCacheIfNeeded<NativeImagePtr>(index, (&ImageFrame::nativeImage), nullptr, ImageFrame::Caching::MetadataAndImage, subsamplingLevel);
557 }
558
559 }