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