1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <algorithm>
24 #include <map>
25 #include <vector>
26 
27 #include "vclhelperbufferdevice.hxx"
28 #include <basegfx/range/b2drange.hxx>
29 #include <vcl/bitmapex.hxx>
30 #include <basegfx/matrix/b2dhommatrix.hxx>
31 #include <tools/stream.hxx>
32 #include <vcl/timer.hxx>
33 #include <cppuhelper/basemutex.hxx>
34 #include <vcl/lazydelete.hxx>
35 #include <vcl/dibtools.hxx>
36 
37 // buffered VDev usage
38 
39 namespace
40 {
41 class VDevBuffer : public Timer, protected cppu::BaseMutex
42 {
43 private:
44     struct Entry
45     {
46         VclPtr<VirtualDevice> buf;
47         bool isTransparent = false;
Entry__anon1cd3ff5a0111::VDevBuffer::Entry48         Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent)
49             : buf(vdev)
50             , isTransparent(bTransparent)
51         {
52         }
53     };
54 
55     // available buffers
56     std::vector<Entry> maFreeBuffers;
57 
58     // allocated/used buffers (remembered to allow deleting them in destructor)
59     std::vector<Entry> maUsedBuffers;
60 
61     // remember what outputdevice was the template passed to VirtualDevice::Create
62     // so we can test if that OutputDevice was disposed before reusing a
63     // virtualdevice because that isn't safe to do at least for Gtk2
64     std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
65 
66 public:
67     VDevBuffer();
68     virtual ~VDevBuffer() override;
69 
70     VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bTransparent);
71     void free(VirtualDevice& rDevice);
72 
73     // Timer virtuals
74     virtual void Invoke() override;
75 };
76 
VDevBuffer()77 VDevBuffer::VDevBuffer()
78     : Timer("VDevBuffer timer")
79     , maFreeBuffers()
80     , maUsedBuffers()
81 {
82     SetTimeout(10L * 1000L); // ten seconds
83     SetDebugName("drawinglayer::VDevBuffer via Invoke()");
84 }
85 
~VDevBuffer()86 VDevBuffer::~VDevBuffer()
87 {
88     ::osl::MutexGuard aGuard(m_aMutex);
89     Stop();
90 
91     while (!maFreeBuffers.empty())
92     {
93         maFreeBuffers.back().buf.disposeAndClear();
94         maFreeBuffers.pop_back();
95     }
96 
97     while (!maUsedBuffers.empty())
98     {
99         maUsedBuffers.back().buf.disposeAndClear();
100         maUsedBuffers.pop_back();
101     }
102 }
103 
alloc(OutputDevice & rOutDev,const Size & rSizePixel,bool bTransparent)104 VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel,
105                                         bool bTransparent)
106 {
107     ::osl::MutexGuard aGuard(m_aMutex);
108     VclPtr<VirtualDevice> pRetval;
109 
110     sal_Int32 nBits = rOutDev.GetBitCount();
111 
112     bool bOkay(false);
113     if (!maFreeBuffers.empty())
114     {
115         auto aFound(maFreeBuffers.end());
116 
117         for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
118         {
119             assert(a->buf && "Empty pointer in VDevBuffer (!)");
120 
121             if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent)
122             {
123                 // candidate is valid due to bit depth
124                 if (aFound != maFreeBuffers.end())
125                 {
126                     // already found
127                     if (bOkay)
128                     {
129                         // found is valid
130                         const bool bCandidateOkay(
131                             a->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
132                             && a->buf->GetOutputHeightPixel() >= rSizePixel.getHeight());
133 
134                         if (bCandidateOkay)
135                         {
136                             // found and candidate are valid
137                             const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel()
138                                                     * aFound->buf->GetOutputHeightPixel());
139                             const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel()
140                                                              * a->buf->GetOutputHeightPixel());
141 
142                             if (aCandidateSquare < aSquare)
143                             {
144                                 // candidate is valid and smaller, use it
145                                 aFound = a;
146                             }
147                         }
148                         else
149                         {
150                             // found is valid, candidate is not. Keep found
151                         }
152                     }
153                     else
154                     {
155                         // found is invalid, use candidate
156                         aFound = a;
157                         bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
158                                 && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
159                     }
160                 }
161                 else
162                 {
163                     // none yet, use candidate
164                     aFound = a;
165                     bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
166                             && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
167                 }
168             }
169         }
170 
171         if (aFound != maFreeBuffers.end())
172         {
173             pRetval = aFound->buf;
174             maFreeBuffers.erase(aFound);
175         }
176     }
177 
178     if (pRetval)
179     {
180         // found a suitable cached virtual device, but the
181         // outputdevice it was based on has been disposed,
182         // drop it and create a new one instead as reusing
183         // such devices is unsafe under at least Gtk2
184         if (maDeviceTemplates[pRetval]->isDisposed())
185         {
186             maDeviceTemplates.erase(pRetval);
187             pRetval.disposeAndClear();
188         }
189         else
190         {
191             if (bOkay)
192             {
193                 pRetval->Erase(pRetval->PixelToLogic(
194                     tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
195             }
196             else
197             {
198                 pRetval->SetOutputSizePixel(rSizePixel, true);
199             }
200         }
201     }
202 
203     // no success yet, create new buffer
204     if (!pRetval)
205     {
206         pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT,
207                                                 bTransparent ? DeviceFormat::DEFAULT
208                                                              : DeviceFormat::NONE);
209         maDeviceTemplates[pRetval] = &rOutDev;
210         pRetval->SetOutputSizePixel(rSizePixel, true);
211     }
212     else
213     {
214         // reused, reset some values
215         pRetval->SetMapMode();
216         pRetval->SetRasterOp(RasterOp::OverPaint);
217     }
218 
219     // remember allocated buffer
220     maUsedBuffers.emplace_back(pRetval, bTransparent);
221 
222     return pRetval;
223 }
224 
free(VirtualDevice & rDevice)225 void VDevBuffer::free(VirtualDevice& rDevice)
226 {
227     ::osl::MutexGuard aGuard(m_aMutex);
228     const auto aUsedFound
229         = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(),
230                        [&rDevice](const Entry& el) { return el.buf == &rDevice; });
231     SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer",
232                 "OOps, non-registered buffer freed (!)");
233     if (aUsedFound != maUsedBuffers.end())
234     {
235         maFreeBuffers.emplace_back(*aUsedFound);
236         maUsedBuffers.erase(aUsedFound);
237         SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
238                     "excessive cached buffers, " << maFreeBuffers.size() << " entries!");
239     }
240     Start();
241 }
242 
Invoke()243 void VDevBuffer::Invoke()
244 {
245     ::osl::MutexGuard aGuard(m_aMutex);
246 
247     while (!maFreeBuffers.empty())
248     {
249         auto aLastOne = maFreeBuffers.back();
250         maDeviceTemplates.erase(aLastOne.buf);
251         aLastOne.buf.disposeAndClear();
252         maFreeBuffers.pop_back();
253     }
254 }
255 }
256 
257 // support for rendering Bitmap and BitmapEx contents
258 
259 namespace drawinglayer
260 {
261 // static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D)
getVDevBuffer()262 VDevBuffer& getVDevBuffer()
263 {
264     // secure global instance with Vcl's safe destroyer of external (seen by
265     // library base) stuff, the remembered VDevs need to be deleted before
266     // Vcl's deinit
267     static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer(new VDevBuffer());
268     return *aVDevBuffer.get();
269 }
270 
impBufferDevice(OutputDevice & rOutDev,const basegfx::B2DRange & rRange)271 impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange)
272     : mrOutDev(rOutDev)
273     , mpContent(nullptr)
274     , mpAlpha(nullptr)
275 {
276     basegfx::B2DRange aRangePixel(rRange);
277     aRangePixel.transform(mrOutDev.GetViewTransformation());
278     const ::tools::Rectangle aRectPixel(static_cast<sal_Int32>(floor(aRangePixel.getMinX())),
279                                         static_cast<sal_Int32>(floor(aRangePixel.getMinY())),
280                                         static_cast<sal_Int32>(ceil(aRangePixel.getMaxX())),
281                                         static_cast<sal_Int32>(ceil(aRangePixel.getMaxY())));
282     const Point aEmptyPoint;
283     maDestPixel = ::tools::Rectangle(aEmptyPoint, mrOutDev.GetOutputSizePixel());
284     maDestPixel.Intersection(aRectPixel);
285 
286     if (!isVisible())
287         return;
288 
289     mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true);
290 
291     // #i93485# assert when copying from window to VDev is used
292     SAL_WARN_IF(
293         mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer",
294         "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)");
295 
296     MapMode aNewMapMode(mrOutDev.GetMapMode());
297 
298     const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
299     aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
300 
301     mpContent->SetMapMode(aNewMapMode);
302 
303     // copy AA flag for new target
304     mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
305 
306     // copy RasterOp (e.g. may be RasterOp::Xor on destination)
307     mpContent->SetRasterOp(mrOutDev.GetRasterOp());
308 }
309 
~impBufferDevice()310 impBufferDevice::~impBufferDevice()
311 {
312     if (mpContent)
313     {
314         getVDevBuffer().free(*mpContent);
315     }
316 
317     if (mpAlpha)
318     {
319         getVDevBuffer().free(*mpAlpha);
320     }
321 }
322 
paint(double fTrans)323 void impBufferDevice::paint(double fTrans)
324 {
325     if (!isVisible())
326         return;
327 
328     const Point aEmptyPoint;
329     const Size aSizePixel(maDestPixel.GetSize());
330     const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
331 #ifdef DBG_UTIL
332     static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
333 #endif
334 
335     mrOutDev.EnableMapMode(false);
336     mpContent->EnableMapMode(false);
337 
338 #ifdef DBG_UTIL
339     if (bDoSaveForVisualControl)
340     {
341         SvFileStream aNew(
342 #ifdef _WIN32
343             "c:\\content.bmp",
344 #else
345             "~/content.bmp",
346 #endif
347             StreamMode::WRITE | StreamMode::TRUNC);
348         Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
349         WriteDIB(aContent, aNew, false, true);
350     }
351 #endif
352 
353     // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
354     const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
355     mrOutDev.SetRasterOp(RasterOp::OverPaint);
356 
357     if (mpAlpha)
358     {
359         mpAlpha->EnableMapMode(false);
360         AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
361 
362 #ifdef DBG_UTIL
363         if (bDoSaveForVisualControl)
364         {
365             SvFileStream aNew(
366 #ifdef _WIN32
367                 "c:\\transparence.bmp",
368 #else
369                 "~/transparence.bmp",
370 #endif
371                 StreamMode::WRITE | StreamMode::TRUNC);
372             WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
373         }
374 #endif
375 
376         BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel));
377         aAlphaMask.BlendWith(aContent.GetAlpha());
378         mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask));
379     }
380     else if (0.0 != fTrans)
381     {
382         basegfx::B2DHomMatrix trans, scale;
383         trans.translate(maDestPixel.TopLeft().X(), maDestPixel.TopLeft().Y());
384         scale.scale(aSizePixel.Width(), aSizePixel.Height());
385         mrOutDev.DrawTransformedBitmapEx(
386             trans * scale, mpContent->GetBitmapEx(aEmptyPoint, aSizePixel), 1 - fTrans);
387     }
388     else
389     {
390         mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
391     }
392 
393     mrOutDev.SetRasterOp(aOrigRasterOp);
394     mrOutDev.EnableMapMode(bWasEnabledDst);
395 }
396 
getContent()397 VirtualDevice& impBufferDevice::getContent()
398 {
399     SAL_WARN_IF(!mpContent, "drawinglayer",
400                 "impBufferDevice: No content, check isVisible() before accessing (!)");
401     return *mpContent;
402 }
403 
getTransparence()404 VirtualDevice& impBufferDevice::getTransparence()
405 {
406     SAL_WARN_IF(!mpContent, "drawinglayer",
407                 "impBufferDevice: No content, check isVisible() before accessing (!)");
408     if (!mpAlpha)
409     {
410         mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false);
411         mpAlpha->SetMapMode(mpContent->GetMapMode());
412 
413         // copy AA flag for new target; masking needs to be smooth
414         mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
415     }
416 
417     return *mpAlpha;
418 }
419 } // end of namespace drawinglayer
420 
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
422