• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/check.h"
23 #include "core/fxcrt/retain_ptr.h"
24 #include "core/fxcrt/stl_util.h"
25 #include "core/fxge/dib/cfx_dibbase.h"
26 #include "core/fxge/dib/cfx_dibitmap.h"
27 
28 #if defined(PDF_USE_SKIA)
29 #include "core/fxcrt/data_vector.h"
30 #include "core/fxge/cfx_defaultrenderdevice.h"
31 #include "third_party/skia/include/core/SkImage.h"   // nogncheck
32 #include "third_party/skia/include/core/SkRefCnt.h"  // nogncheck
33 #endif
34 
35 namespace {
36 
37 struct CacheInfo {
CacheInfo__anon016dc4c90111::CacheInfo38   CacheInfo(uint32_t t, RetainPtr<const CPDF_Stream> stream)
39       : time(t), pStream(std::move(stream)) {}
40 
41   uint32_t time;
42   RetainPtr<const CPDF_Stream> pStream;
43 
operator <__anon016dc4c90111::CacheInfo44   bool operator<(const CacheInfo& other) const { return time < other.time; }
45 };
46 
47 #if defined(PDF_USE_SKIA)
48 // Wrapper around a `CFX_DIBBase` that memoizes `RealizeSkImage()`. This is only
49 // safe if the underlying `CFX_DIBBase` is not mutable.
50 class CachedImage final : public CFX_DIBBase {
51  public:
CachedImage(RetainPtr<CFX_DIBBase> image)52   explicit CachedImage(RetainPtr<CFX_DIBBase> image)
53       : image_(std::move(image)) {
54     SetFormat(image_->GetFormat());
55     SetWidth(image_->GetWidth());
56     SetHeight(image_->GetHeight());
57     SetPitch(image_->GetPitch());
58 
59     if (image_->HasPalette()) {
60       pdfium::span<const uint32_t> palette = image_->GetPaletteSpan();
61       palette_ = DataVector<uint32_t>(palette.begin(), palette.end());
62     }
63   }
64 
GetScanline(int line) const65   pdfium::span<const uint8_t> GetScanline(int line) const override {
66     // TODO(crbug.com/pdfium/2050): Still needed for `Realize()` call in
67     // `CPDF_ImageRenderer`.
68     return image_->GetScanline(line);
69   }
70 
SkipToScanline(int line,PauseIndicatorIface * pause) const71   bool SkipToScanline(int line, PauseIndicatorIface* pause) const override {
72     return image_->SkipToScanline(line, pause);
73   }
74 
GetEstimatedImageMemoryBurden() const75   size_t GetEstimatedImageMemoryBurden() const override {
76     // A better estimate would account for realizing the `SkImage`.
77     return image_->GetEstimatedImageMemoryBurden();
78   }
79 
80 #if BUILDFLAG(IS_WIN) || defined(PDF_USE_SKIA)
RealizeIfNeeded() const81   RetainPtr<const CFX_DIBitmap> RealizeIfNeeded() const override {
82     return image_->RealizeIfNeeded();
83   }
84 #endif
85 
RealizeSkImage() const86   sk_sp<SkImage> RealizeSkImage() const override {
87     if (!cached_skia_image_) {
88       cached_skia_image_ = image_->RealizeSkImage();
89     }
90     return cached_skia_image_;
91   }
92 
93  private:
94   RetainPtr<CFX_DIBBase> image_;
95   mutable sk_sp<SkImage> cached_skia_image_;
96 };
97 #endif  // defined(PDF_USE_SKIA)
98 
99 // Makes a `CachedImage` backed by `image` if Skia is the default renderer,
100 // otherwise return the image itself. `realize_hint` indicates whether it would
101 // be beneficial to realize `image` before caching.
MakeCachedImage(RetainPtr<CFX_DIBBase> image,bool realize_hint)102 RetainPtr<CFX_DIBBase> MakeCachedImage(RetainPtr<CFX_DIBBase> image,
103                                        bool realize_hint) {
104 #if defined(PDF_USE_SKIA)
105   if (CFX_DefaultRenderDevice::UseSkiaRenderer()) {
106     // Ignore `realize_hint`, as `RealizeSkImage()` doesn't benefit from it.
107     return pdfium::MakeRetain<CachedImage>(std::move(image));
108   }
109 #endif  // defined(PDF_USE_SKIA)
110   return realize_hint ? image->Realize() : image;
111 }
112 
113 }  // namespace
114 
CPDF_PageImageCache(CPDF_Page * pPage)115 CPDF_PageImageCache::CPDF_PageImageCache(CPDF_Page* pPage) : m_pPage(pPage) {}
116 
117 CPDF_PageImageCache::~CPDF_PageImageCache() = default;
118 
CacheOptimization(int32_t dwLimitCacheSize)119 void CPDF_PageImageCache::CacheOptimization(int32_t dwLimitCacheSize) {
120   if (m_nCacheSize <= (uint32_t)dwLimitCacheSize)
121     return;
122 
123   uint32_t nCount = fxcrt::CollectionSize<uint32_t>(m_ImageCache);
124   std::vector<CacheInfo> cache_info;
125   cache_info.reserve(nCount);
126   for (const auto& it : m_ImageCache) {
127     cache_info.emplace_back(it.second->GetTimeCount(),
128                             it.second->GetImage()->GetStream());
129   }
130   std::sort(cache_info.begin(), cache_info.end());
131 
132   // Check if time value is about to roll over and reset all entries.
133   // The comparison is legal because uint32_t is an unsigned type.
134   uint32_t nTimeCount = m_nTimeCount;
135   if (nTimeCount + 1 < nTimeCount) {
136     for (uint32_t i = 0; i < nCount; i++)
137       m_ImageCache[cache_info[i].pStream]->SetTimeCount(i);
138     m_nTimeCount = nCount;
139   }
140 
141   size_t i = 0;
142   while (i + 15 < nCount)
143     ClearImageCacheEntry(cache_info[i++].pStream);
144 
145   while (i < nCount && m_nCacheSize > (uint32_t)dwLimitCacheSize)
146     ClearImageCacheEntry(cache_info[i++].pStream);
147 }
148 
ClearImageCacheEntry(const CPDF_Stream * pStream)149 void CPDF_PageImageCache::ClearImageCacheEntry(const CPDF_Stream* pStream) {
150   auto it = m_ImageCache.find(pStream);
151   if (it == m_ImageCache.end())
152     return;
153 
154   m_nCacheSize -= it->second->EstimateSize();
155 
156   // Avoid leaving `m_pCurImageCacheEntry` as a dangling pointer when `it` is
157   // about to be deleted.
158   if (m_pCurImageCacheEntry.Get() == it->second.get()) {
159     DCHECK(!m_pCurImageCacheEntry.IsOwned());
160     m_pCurImageCacheEntry.Reset();
161   }
162   m_ImageCache.erase(it);
163 }
164 
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)165 bool CPDF_PageImageCache::StartGetCachedBitmap(
166     RetainPtr<CPDF_Image> pImage,
167     const CPDF_Dictionary* pFormResources,
168     const CPDF_Dictionary* pPageResources,
169     bool bStdCS,
170     CPDF_ColorSpace::Family eFamily,
171     bool bLoadMask,
172     const CFX_Size& max_size_required) {
173   // A cross-document image may have come from the embedder.
174   if (m_pPage->GetDocument() != pImage->GetDocument())
175     return false;
176 
177   RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
178   const auto it = m_ImageCache.find(pStream);
179   m_bCurFindCache = it != m_ImageCache.end();
180   if (m_bCurFindCache) {
181     m_pCurImageCacheEntry = it->second.get();
182   } else {
183     m_pCurImageCacheEntry = std::make_unique<Entry>(std::move(pImage));
184   }
185   CPDF_DIB::LoadState ret = m_pCurImageCacheEntry->StartGetCachedBitmap(
186       this, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
187       max_size_required);
188   if (ret == CPDF_DIB::LoadState::kContinue)
189     return true;
190 
191   m_nTimeCount++;
192   if (!m_bCurFindCache)
193     m_ImageCache[pStream] = m_pCurImageCacheEntry.Release();
194 
195   if (ret == CPDF_DIB::LoadState::kFail)
196     m_nCacheSize += m_pCurImageCacheEntry->EstimateSize();
197 
198   return false;
199 }
200 
Continue(PauseIndicatorIface * pPause)201 bool CPDF_PageImageCache::Continue(PauseIndicatorIface* pPause) {
202   bool ret = m_pCurImageCacheEntry->Continue(pPause, this);
203   if (ret)
204     return true;
205 
206   m_nTimeCount++;
207   if (!m_bCurFindCache) {
208     m_ImageCache[m_pCurImageCacheEntry->GetImage()->GetStream()] =
209         m_pCurImageCacheEntry.Release();
210   }
211   m_nCacheSize += m_pCurImageCacheEntry->EstimateSize();
212   return false;
213 }
214 
ResetBitmapForImage(RetainPtr<CPDF_Image> pImage)215 void CPDF_PageImageCache::ResetBitmapForImage(RetainPtr<CPDF_Image> pImage) {
216   RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
217   const auto it = m_ImageCache.find(pStream);
218   if (it == m_ImageCache.end())
219     return;
220 
221   Entry* pEntry = it->second.get();
222   m_nCacheSize -= pEntry->EstimateSize();
223   pEntry->Reset();
224   m_nCacheSize += pEntry->EstimateSize();
225 }
226 
GetCurMatteColor() const227 uint32_t CPDF_PageImageCache::GetCurMatteColor() const {
228   return m_pCurImageCacheEntry->GetMatteColor();
229 }
230 
DetachCurBitmap()231 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::DetachCurBitmap() {
232   return m_pCurImageCacheEntry->DetachBitmap();
233 }
234 
DetachCurMask()235 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::DetachCurMask() {
236   return m_pCurImageCacheEntry->DetachMask();
237 }
238 
Entry(RetainPtr<CPDF_Image> pImage)239 CPDF_PageImageCache::Entry::Entry(RetainPtr<CPDF_Image> pImage)
240     : m_pImage(std::move(pImage)) {}
241 
242 CPDF_PageImageCache::Entry::~Entry() = default;
243 
Reset()244 void CPDF_PageImageCache::Entry::Reset() {
245   m_pCachedBitmap.Reset();
246   CalcSize();
247 }
248 
DetachBitmap()249 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::Entry::DetachBitmap() {
250   return std::move(m_pCurBitmap);
251 }
252 
DetachMask()253 RetainPtr<CFX_DIBBase> CPDF_PageImageCache::Entry::DetachMask() {
254   return std::move(m_pCurMask);
255 }
256 
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)257 CPDF_DIB::LoadState CPDF_PageImageCache::Entry::StartGetCachedBitmap(
258     CPDF_PageImageCache* pPageImageCache,
259     const CPDF_Dictionary* pFormResources,
260     const CPDF_Dictionary* pPageResources,
261     bool bStdCS,
262     CPDF_ColorSpace::Family eFamily,
263     bool bLoadMask,
264     const CFX_Size& max_size_required) {
265   if (m_pCachedBitmap && IsCacheValid(max_size_required)) {
266     m_pCurBitmap = m_pCachedBitmap;
267     m_pCurMask = m_pCachedMask;
268     return CPDF_DIB::LoadState::kSuccess;
269   }
270 
271   m_pCurBitmap = m_pImage->CreateNewDIB();
272   CPDF_DIB::LoadState ret = m_pCurBitmap.AsRaw<CPDF_DIB>()->StartLoadDIBBase(
273       true, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
274       max_size_required);
275   m_bCachedSetMaxSizeRequired =
276       (max_size_required.width != 0 && max_size_required.height != 0);
277   if (ret == CPDF_DIB::LoadState::kContinue)
278     return CPDF_DIB::LoadState::kContinue;
279 
280   if (ret == CPDF_DIB::LoadState::kSuccess)
281     ContinueGetCachedBitmap(pPageImageCache);
282   else
283     m_pCurBitmap.Reset();
284   return CPDF_DIB::LoadState::kFail;
285 }
286 
Continue(PauseIndicatorIface * pPause,CPDF_PageImageCache * pPageImageCache)287 bool CPDF_PageImageCache::Entry::Continue(
288     PauseIndicatorIface* pPause,
289     CPDF_PageImageCache* pPageImageCache) {
290   CPDF_DIB::LoadState ret =
291       m_pCurBitmap.AsRaw<CPDF_DIB>()->ContinueLoadDIBBase(pPause);
292   if (ret == CPDF_DIB::LoadState::kContinue)
293     return true;
294 
295   if (ret == CPDF_DIB::LoadState::kSuccess)
296     ContinueGetCachedBitmap(pPageImageCache);
297   else
298     m_pCurBitmap.Reset();
299   return false;
300 }
301 
ContinueGetCachedBitmap(CPDF_PageImageCache * pPageImageCache)302 void CPDF_PageImageCache::Entry::ContinueGetCachedBitmap(
303     CPDF_PageImageCache* pPageImageCache) {
304   m_MatteColor = m_pCurBitmap.AsRaw<CPDF_DIB>()->GetMatteColor();
305   m_pCurMask = m_pCurBitmap.AsRaw<CPDF_DIB>()->DetachMask();
306   m_dwTimeCount = pPageImageCache->GetTimeCount();
307   if (m_pCurBitmap->GetPitch() * m_pCurBitmap->GetHeight() < kHugeImageSize) {
308     m_pCachedBitmap = MakeCachedImage(m_pCurBitmap, /*realize_hint=*/true);
309     m_pCurBitmap.Reset();
310   } else {
311     m_pCachedBitmap = MakeCachedImage(m_pCurBitmap, /*realize_hint=*/false);
312   }
313   if (m_pCurMask) {
314     m_pCachedMask = MakeCachedImage(m_pCurMask, /*realize_hint=*/true);
315     m_pCurMask.Reset();
316   }
317   m_pCurBitmap = m_pCachedBitmap;
318   m_pCurMask = m_pCachedMask;
319   CalcSize();
320 }
321 
CalcSize()322 void CPDF_PageImageCache::Entry::CalcSize() {
323   m_dwCacheSize = 0;
324   if (m_pCachedBitmap)
325     m_dwCacheSize += m_pCachedBitmap->GetEstimatedImageMemoryBurden();
326   if (m_pCachedMask)
327     m_dwCacheSize += m_pCachedMask->GetEstimatedImageMemoryBurden();
328 }
329 
IsCacheValid(const CFX_Size & max_size_required) const330 bool CPDF_PageImageCache::Entry::IsCacheValid(
331     const CFX_Size& max_size_required) const {
332   if (!m_bCachedSetMaxSizeRequired) {
333     return true;
334   }
335   if (max_size_required.width == 0 && max_size_required.height == 0) {
336     return false;
337   }
338 
339   return (m_pCachedBitmap->GetWidth() >= max_size_required.width) &&
340          (m_pCachedBitmap->GetHeight() >= max_size_required.height);
341 }
342