xref: /aosp_15_r20/external/pdfium/core/fpdfapi/page/cpdf_pageimagecache.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2016 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fpdfapi/page/cpdf_pageimagecache.h"
8 
9 #include <stddef.h>
10 #include <stdint.h>
11 
12 #include <algorithm>
13 #include <utility>
14 #include <vector>
15 
16 #include "core/fpdfapi/page/cpdf_dib.h"
17 #include "core/fpdfapi/page/cpdf_image.h"
18 #include "core/fpdfapi/page/cpdf_page.h"
19 #include "core/fpdfapi/parser/cpdf_dictionary.h"
20 #include "core/fpdfapi/parser/cpdf_document.h"
21 #include "core/fpdfapi/parser/cpdf_stream.h"
22 #include "core/fxcrt/retain_ptr.h"
23 #include "core/fxcrt/stl_util.h"
24 #include "core/fxge/dib/cfx_dibbase.h"
25 #include "core/fxge/dib/cfx_dibitmap.h"
26 #include "third_party/base/check.h"
27 
28 #if defined(_SKIA_SUPPORT_)
29 #include "core/fxcrt/data_vector.h"
30 #include "core/fxge/cfx_defaultrenderdevice.h"
31 #include "third_party/base/notreached.h"
32 #include "third_party/skia/include/core/SkImage.h"   // nogncheck
33 #include "third_party/skia/include/core/SkRefCnt.h"  // nogncheck
34 #endif
35 
36 namespace {
37 
38 struct CacheInfo {
CacheInfo__anonc8edda490111::CacheInfo39   CacheInfo(uint32_t t, RetainPtr<const CPDF_Stream> stream)
40       : time(t), pStream(std::move(stream)) {}
41 
42   uint32_t time;
43   RetainPtr<const CPDF_Stream> pStream;
44 
operator <__anonc8edda490111::CacheInfo45   bool operator<(const CacheInfo& other) const { return time < other.time; }
46 };
47 
48 #if defined(_SKIA_SUPPORT_)
49 // Wrapper around a `CFX_DIBBase` that memoizes `RealizeSkImage()`. This is only
50 // safe if the underlying `CFX_DIBBase` is not mutable.
51 class CachedImage final : public CFX_DIBBase {
52  public:
CachedImage(RetainPtr<CFX_DIBBase> image)53   explicit CachedImage(RetainPtr<CFX_DIBBase> image)
54       : image_(std::move(image)) {
55     m_Format = image_->GetFormat();
56     m_Width = image_->GetWidth();
57     m_Height = image_->GetHeight();
58     m_Pitch = image_->GetPitch();
59 
60     if (image_->HasPalette()) {
61       pdfium::span<const uint32_t> palette = image_->GetPaletteSpan();
62       m_palette = DataVector<uint32_t>(palette.begin(), palette.end());
63     }
64   }
65 
GetBuffer() const66   pdfium::span<const uint8_t> GetBuffer() const override {
67     // TODO(crbug.com/pdfium/2051): `CachedImage` is only used by Skia, which
68     // should call `RealizeSkImage()` instead. Consider removing this, or at
69     // least making it `NOTREACHED_NORETURN()`.
70     NOTREACHED();
71     return image_->GetBuffer();
72   }
73 
GetScanline(int line) const74   pdfium::span<const uint8_t> GetScanline(int line) const override {
75     // TODO(crbug.com/pdfium/2050): Still needed for `Realize()` call in
76     // `CPDF_ImageRenderer`.
77     return image_->GetScanline(line);
78   }
79 
SkipToScanline(int line,PauseIndicatorIface * pause) const80   bool SkipToScanline(int line, PauseIndicatorIface* pause) const override {
81     // TODO(crbug.com/pdfium/2051): `CachedImage` is only used by Skia, which
82     // should call `RealizeSkImage()` instead. Consider removing this, or at
83     // least making it `NOTREACHED_NORETURN()`.
84     NOTREACHED();
85     return image_->SkipToScanline(line, pause);
86   }
87 
GetEstimatedImageMemoryBurden() const88   size_t GetEstimatedImageMemoryBurden() const override {
89     // A better estimate would account for realizing the `SkImage`.
90     return image_->GetEstimatedImageMemoryBurden();
91   }
92 
RealizeSkImage() const93   sk_sp<SkImage> RealizeSkImage() const override {
94     if (!cached_skia_image_) {
95       cached_skia_image_ = image_->RealizeSkImage();
96     }
97     return cached_skia_image_;
98   }
99 
100  private:
101   RetainPtr<CFX_DIBBase> image_;
102   mutable sk_sp<SkImage> cached_skia_image_;
103 };
104 #endif  // defined(_SKIA_SUPPORT_)
105 
106 // Makes a `CachedImage` backed by `image` if Skia is the default renderer,
107 // otherwise return the image itself. `realize_hint` indicates whether it would
108 // be beneficial to realize `image` before caching.
MakeCachedImage(RetainPtr<CFX_DIBBase> image,bool realize_hint)109 RetainPtr<CFX_DIBBase> MakeCachedImage(RetainPtr<CFX_DIBBase> image,
110                                        bool realize_hint) {
111 #if defined(_SKIA_SUPPORT_)
112   if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer()) {
113     // TODO(crbug.com/pdfium/2050): Ignore `realize_hint`, as `RealizeSkImage()`
114     // doesn't benefit from it. The current behavior masks a bug in `CPDF_DIB`
115     // in which `GetBuffer()` and `GetScanline()` don't give the same answer.
116     if (realize_hint) {
117       image = image->Realize();
118       if (!image) {
119         return nullptr;
120       }
121     }
122     return pdfium::MakeRetain<CachedImage>(std::move(image));
123   }
124 #endif  // defined(_SKIA_SUPPORT_)
125   return realize_hint ? image->Realize() : image;
126 }
127 
128 }  // namespace
129 
CPDF_PageImageCache(CPDF_Page * pPage)130 CPDF_PageImageCache::CPDF_PageImageCache(CPDF_Page* pPage) : m_pPage(pPage) {}
131 
132 CPDF_PageImageCache::~CPDF_PageImageCache() = default;
133 
CacheOptimization(int32_t dwLimitCacheSize)134 void CPDF_PageImageCache::CacheOptimization(int32_t dwLimitCacheSize) {
135   if (m_nCacheSize <= (uint32_t)dwLimitCacheSize)
136     return;
137 
138   uint32_t nCount = fxcrt::CollectionSize<uint32_t>(m_ImageCache);
139   std::vector<CacheInfo> cache_info;
140   cache_info.reserve(nCount);
141   for (const auto& it : m_ImageCache) {
142     cache_info.emplace_back(it.second->GetTimeCount(),
143                             it.second->GetImage()->GetStream());
144   }
145   std::sort(cache_info.begin(), cache_info.end());
146 
147   // Check if time value is about to roll over and reset all entries.
148   // The comparison is legal because uint32_t is an unsigned type.
149   uint32_t nTimeCount = m_nTimeCount;
150   if (nTimeCount + 1 < nTimeCount) {
151     for (uint32_t i = 0; i < nCount; i++)
152       m_ImageCache[cache_info[i].pStream]->SetTimeCount(i);
153     m_nTimeCount = nCount;
154   }
155 
156   size_t i = 0;
157   while (i + 15 < nCount)
158     ClearImageCacheEntry(cache_info[i++].pStream);
159 
160   while (i < nCount && m_nCacheSize > (uint32_t)dwLimitCacheSize)
161     ClearImageCacheEntry(cache_info[i++].pStream);
162 }
163 
ClearImageCacheEntry(const CPDF_Stream * pStream)164 void CPDF_PageImageCache::ClearImageCacheEntry(const CPDF_Stream* pStream) {
165   auto it = m_ImageCache.find(pStream);
166   if (it == m_ImageCache.end())
167     return;
168 
169   m_nCacheSize -= it->second->EstimateSize();
170 
171   // Avoid leaving `m_pCurImageCacheEntry` as a dangling pointer when `it` is
172   // about to be deleted.
173   if (m_pCurImageCacheEntry.Get() == it->second.get()) {
174     DCHECK(!m_pCurImageCacheEntry.IsOwned());
175     m_pCurImageCacheEntry.Reset();
176   }
177   m_ImageCache.erase(it);
178 }
179 
StartGetCachedBitmap(RetainPtr<CPDF_Image> pImage,const CPDF_Dictionary * pFormResources,const CPDF_Dictionary * pPageResources,bool bStdCS,CPDF_ColorSpace::Family eFamily,bool bLoadMask,const CFX_Size & max_size_required)180 bool CPDF_PageImageCache::StartGetCachedBitmap(
181     RetainPtr<CPDF_Image> pImage,
182     const CPDF_Dictionary* pFormResources,
183     const CPDF_Dictionary* pPageResources,
184     bool bStdCS,
185     CPDF_ColorSpace::Family eFamily,
186     bool bLoadMask,
187     const CFX_Size& max_size_required) {
188   // A cross-document image may have come from the embedder.
189   if (m_pPage->GetDocument() != pImage->GetDocument())
190     return false;
191 
192   RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
193   const auto it = m_ImageCache.find(pStream);
194   m_bCurFindCache = it != m_ImageCache.end();
195   if (m_bCurFindCache) {
196     m_pCurImageCacheEntry = it->second.get();
197   } else {
198     m_pCurImageCacheEntry = std::make_unique<Entry>(std::move(pImage));
199   }
200   CPDF_DIB::LoadState ret = m_pCurImageCacheEntry->StartGetCachedBitmap(
201       this, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
202       max_size_required);
203   if (ret == CPDF_DIB::LoadState::kContinue)
204     return true;
205 
206   m_nTimeCount++;
207   if (!m_bCurFindCache)
208     m_ImageCache[pStream] = m_pCurImageCacheEntry.Release();
209 
210   if (ret == CPDF_DIB::LoadState::kFail)
211     m_nCacheSize += m_pCurImageCacheEntry->EstimateSize();
212 
213   return false;
214 }
215 
Continue(PauseIndicatorIface * pPause)216 bool CPDF_PageImageCache::Continue(PauseIndicatorIface* pPause) {
217   bool ret = m_pCurImageCacheEntry->Continue(pPause, this);
218   if (ret)
219     return true;
220 
221   m_nTimeCount++;
222   if (!m_bCurFindCache) {
223     m_ImageCache[m_pCurImageCacheEntry->GetImage()->GetStream()] =
224         m_pCurImageCacheEntry.Release();
225   }
226   m_nCacheSize += m_pCurImageCacheEntry->EstimateSize();
227   return false;
228 }
229 
ResetBitmapForImage(RetainPtr<CPDF_Image> pImage)230 void CPDF_PageImageCache::ResetBitmapForImage(RetainPtr<CPDF_Image> pImage) {
231   RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
232   const auto it = m_ImageCache.find(pStream);
233   if (it == m_ImageCache.end())
234     return;
235 
236   Entry* pEntry = it->second.get();
237   m_nCacheSize -= pEntry->EstimateSize();
238   pEntry->Reset();
239   m_nCacheSize += pEntry->EstimateSize();
240 }
241 
GetCurMatteColor() const242 uint32_t CPDF_PageImageCache::GetCurMatteColor() const {
243   return m_pCurImageCacheEntry->GetMatteColor();
244 }
245 
DetachCurBitmap()246 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::DetachCurBitmap() {
247   return m_pCurImageCacheEntry->DetachBitmap();
248 }
249 
DetachCurMask()250 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::DetachCurMask() {
251   return m_pCurImageCacheEntry->DetachMask();
252 }
253 
Entry(RetainPtr<CPDF_Image> pImage)254 CPDF_PageImageCache::Entry::Entry(RetainPtr<CPDF_Image> pImage)
255     : m_pImage(std::move(pImage)) {}
256 
257 CPDF_PageImageCache::Entry::~Entry() = default;
258 
Reset()259 void CPDF_PageImageCache::Entry::Reset() {
260   m_pCachedBitmap.Reset();
261   CalcSize();
262 }
263 
DetachBitmap()264 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::Entry::DetachBitmap() {
265   return std::move(m_pCurBitmap);
266 }
267 
DetachMask()268 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::Entry::DetachMask() {
269   return std::move(m_pCurMask);
270 }
271 
StartGetCachedBitmap(CPDF_PageImageCache * pPageImageCache,const CPDF_Dictionary * pFormResources,const CPDF_Dictionary * pPageResources,bool bStdCS,CPDF_ColorSpace::Family eFamily,bool bLoadMask,const CFX_Size & max_size_required)272 CPDF_DIB::LoadState CPDF_PageImageCache::Entry::StartGetCachedBitmap(
273     CPDF_PageImageCache* pPageImageCache,
274     const CPDF_Dictionary* pFormResources,
275     const CPDF_Dictionary* pPageResources,
276     bool bStdCS,
277     CPDF_ColorSpace::Family eFamily,
278     bool bLoadMask,
279     const CFX_Size& max_size_required) {
280   if (m_pCachedBitmap && IsCacheValid(max_size_required)) {
281     m_pCurBitmap = m_pCachedBitmap;
282     m_pCurMask = m_pCachedMask;
283     return CPDF_DIB::LoadState::kSuccess;
284   }
285 
286   m_pCurBitmap = m_pImage->CreateNewDIB();
287   CPDF_DIB::LoadState ret = m_pCurBitmap.AsRaw<CPDF_DIB>()->StartLoadDIBBase(
288       true, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
289       max_size_required);
290   m_bCachedSetMaxSizeRequired =
291       (max_size_required.width != 0 && max_size_required.height != 0);
292   if (ret == CPDF_DIB::LoadState::kContinue)
293     return CPDF_DIB::LoadState::kContinue;
294 
295   if (ret == CPDF_DIB::LoadState::kSuccess)
296     ContinueGetCachedBitmap(pPageImageCache);
297   else
298     m_pCurBitmap.Reset();
299   return CPDF_DIB::LoadState::kFail;
300 }
301 
Continue(PauseIndicatorIface * pPause,CPDF_PageImageCache * pPageImageCache)302 bool CPDF_PageImageCache::Entry::Continue(
303     PauseIndicatorIface* pPause,
304     CPDF_PageImageCache* pPageImageCache) {
305   CPDF_DIB::LoadState ret =
306       m_pCurBitmap.AsRaw<CPDF_DIB>()->ContinueLoadDIBBase(pPause);
307   if (ret == CPDF_DIB::LoadState::kContinue)
308     return true;
309 
310   if (ret == CPDF_DIB::LoadState::kSuccess)
311     ContinueGetCachedBitmap(pPageImageCache);
312   else
313     m_pCurBitmap.Reset();
314   return false;
315 }
316 
ContinueGetCachedBitmap(CPDF_PageImageCache * pPageImageCache)317 void CPDF_PageImageCache::Entry::ContinueGetCachedBitmap(
318     CPDF_PageImageCache* pPageImageCache) {
319   m_MatteColor = m_pCurBitmap.AsRaw<CPDF_DIB>()->GetMatteColor();
320   m_pCurMask = m_pCurBitmap.AsRaw<CPDF_DIB>()->DetachMask();
321   m_dwTimeCount = pPageImageCache->GetTimeCount();
322   if (m_pCurBitmap->GetPitch() * m_pCurBitmap->GetHeight() < kHugeImageSize) {
323     m_pCachedBitmap = MakeCachedImage(m_pCurBitmap, /*realize_hint=*/true);
324     m_pCurBitmap.Reset();
325   } else {
326     m_pCachedBitmap = MakeCachedImage(m_pCurBitmap, /*realize_hint=*/false);
327   }
328   if (m_pCurMask) {
329     m_pCachedMask = MakeCachedImage(m_pCurMask, /*realize_hint=*/true);
330     m_pCurMask.Reset();
331   }
332   m_pCurBitmap = m_pCachedBitmap;
333   m_pCurMask = m_pCachedMask;
334   CalcSize();
335 }
336 
CalcSize()337 void CPDF_PageImageCache::Entry::CalcSize() {
338   m_dwCacheSize = 0;
339   if (m_pCachedBitmap)
340     m_dwCacheSize += m_pCachedBitmap->GetEstimatedImageMemoryBurden();
341   if (m_pCachedMask)
342     m_dwCacheSize += m_pCachedMask->GetEstimatedImageMemoryBurden();
343 }
344 
IsCacheValid(const CFX_Size & max_size_required) const345 bool CPDF_PageImageCache::Entry::IsCacheValid(
346     const CFX_Size& max_size_required) const {
347   if (!m_bCachedSetMaxSizeRequired) {
348     return true;
349   }
350   if (max_size_required.width == 0 && max_size_required.height == 0) {
351     return false;
352   }
353 
354   return (m_pCachedBitmap->GetWidth() >= max_size_required.width) &&
355          (m_pCachedBitmap->GetHeight() >= max_size_required.height);
356 }
357