office-gobmx/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx
Noel Grandin 88c85c8aa3 flatten vcl::LazyDelete
we can allocate the stored value in-line and avoid some pointer-chasing

Change-Id: I828814b127923cdcec1bf57b3b64dffab4cffaaf
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/119298
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2021-07-22 10:15:49 +02:00

421 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <sal/log.hxx>
#include <algorithm>
#include <map>
#include <vector>
#include "vclhelperbufferdevice.hxx"
#include <basegfx/range/b2drange.hxx>
#include <vcl/bitmapex.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <tools/stream.hxx>
#include <vcl/timer.hxx>
#include <cppuhelper/basemutex.hxx>
#include <vcl/lazydelete.hxx>
#include <vcl/dibtools.hxx>
// buffered VDev usage
namespace
{
class VDevBuffer : public Timer, protected cppu::BaseMutex
{
private:
struct Entry
{
VclPtr<VirtualDevice> buf;
bool isTransparent = false;
Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent)
: buf(vdev)
, isTransparent(bTransparent)
{
}
};
// available buffers
std::vector<Entry> maFreeBuffers;
// allocated/used buffers (remembered to allow deleting them in destructor)
std::vector<Entry> maUsedBuffers;
// remember what outputdevice was the template passed to VirtualDevice::Create
// so we can test if that OutputDevice was disposed before reusing a
// virtualdevice because that isn't safe to do at least for Gtk2
std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
public:
VDevBuffer();
virtual ~VDevBuffer() override;
VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bTransparent);
void free(VirtualDevice& rDevice);
// Timer virtuals
virtual void Invoke() override;
};
VDevBuffer::VDevBuffer()
: Timer("VDevBuffer timer")
, maFreeBuffers()
, maUsedBuffers()
{
SetTimeout(10L * 1000L); // ten seconds
SetDebugName("drawinglayer::VDevBuffer via Invoke()");
}
VDevBuffer::~VDevBuffer()
{
::osl::MutexGuard aGuard(m_aMutex);
Stop();
while (!maFreeBuffers.empty())
{
maFreeBuffers.back().buf.disposeAndClear();
maFreeBuffers.pop_back();
}
while (!maUsedBuffers.empty())
{
maUsedBuffers.back().buf.disposeAndClear();
maUsedBuffers.pop_back();
}
}
VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel,
bool bTransparent)
{
::osl::MutexGuard aGuard(m_aMutex);
VclPtr<VirtualDevice> pRetval;
sal_Int32 nBits = rOutDev.GetBitCount();
bool bOkay(false);
if (!maFreeBuffers.empty())
{
auto aFound(maFreeBuffers.end());
for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
{
assert(a->buf && "Empty pointer in VDevBuffer (!)");
if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent)
{
// candidate is valid due to bit depth
if (aFound != maFreeBuffers.end())
{
// already found
if (bOkay)
{
// found is valid
const bool bCandidateOkay(
a->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& a->buf->GetOutputHeightPixel() >= rSizePixel.getHeight());
if (bCandidateOkay)
{
// found and candidate are valid
const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel()
* aFound->buf->GetOutputHeightPixel());
const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel()
* a->buf->GetOutputHeightPixel());
if (aCandidateSquare < aSquare)
{
// candidate is valid and smaller, use it
aFound = a;
}
}
else
{
// found is valid, candidate is not. Keep found
}
}
else
{
// found is invalid, use candidate
aFound = a;
bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
}
}
else
{
// none yet, use candidate
aFound = a;
bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
}
}
}
if (aFound != maFreeBuffers.end())
{
pRetval = aFound->buf;
maFreeBuffers.erase(aFound);
}
}
if (pRetval)
{
// found a suitable cached virtual device, but the
// outputdevice it was based on has been disposed,
// drop it and create a new one instead as reusing
// such devices is unsafe under at least Gtk2
if (maDeviceTemplates[pRetval]->isDisposed())
{
maDeviceTemplates.erase(pRetval);
pRetval.disposeAndClear();
}
else
{
if (bOkay)
{
pRetval->Erase(pRetval->PixelToLogic(
tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
}
else
{
pRetval->SetOutputSizePixel(rSizePixel, true);
}
}
}
// no success yet, create new buffer
if (!pRetval)
{
pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT,
bTransparent ? DeviceFormat::DEFAULT
: DeviceFormat::NONE);
maDeviceTemplates[pRetval] = &rOutDev;
pRetval->SetOutputSizePixel(rSizePixel, true);
}
else
{
// reused, reset some values
pRetval->SetMapMode();
pRetval->SetRasterOp(RasterOp::OverPaint);
}
// remember allocated buffer
maUsedBuffers.emplace_back(pRetval, bTransparent);
return pRetval;
}
void VDevBuffer::free(VirtualDevice& rDevice)
{
::osl::MutexGuard aGuard(m_aMutex);
const auto aUsedFound
= std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(),
[&rDevice](const Entry& el) { return el.buf == &rDevice; });
SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer",
"OOps, non-registered buffer freed (!)");
if (aUsedFound != maUsedBuffers.end())
{
maFreeBuffers.emplace_back(*aUsedFound);
maUsedBuffers.erase(aUsedFound);
SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
"excessive cached buffers, " << maFreeBuffers.size() << " entries!");
}
Start();
}
void VDevBuffer::Invoke()
{
::osl::MutexGuard aGuard(m_aMutex);
while (!maFreeBuffers.empty())
{
auto aLastOne = maFreeBuffers.back();
maDeviceTemplates.erase(aLastOne.buf);
aLastOne.buf.disposeAndClear();
maFreeBuffers.pop_back();
}
}
}
// support for rendering Bitmap and BitmapEx contents
namespace drawinglayer
{
// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D)
VDevBuffer& getVDevBuffer()
{
// secure global instance with Vcl's safe destroyer of external (seen by
// library base) stuff, the remembered VDevs need to be deleted before
// Vcl's deinit
static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{};
return *aVDevBuffer.get();
}
impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange)
: mrOutDev(rOutDev)
, mpContent(nullptr)
, mpAlpha(nullptr)
{
basegfx::B2DRange aRangePixel(rRange);
aRangePixel.transform(mrOutDev.GetViewTransformation());
const ::tools::Rectangle aRectPixel(static_cast<sal_Int32>(floor(aRangePixel.getMinX())),
static_cast<sal_Int32>(floor(aRangePixel.getMinY())),
static_cast<sal_Int32>(ceil(aRangePixel.getMaxX())),
static_cast<sal_Int32>(ceil(aRangePixel.getMaxY())));
const Point aEmptyPoint;
maDestPixel = ::tools::Rectangle(aEmptyPoint, mrOutDev.GetOutputSizePixel());
maDestPixel.Intersection(aRectPixel);
if (!isVisible())
return;
mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true);
// #i93485# assert when copying from window to VDev is used
SAL_WARN_IF(
mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer",
"impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)");
MapMode aNewMapMode(mrOutDev.GetMapMode());
const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
mpContent->SetMapMode(aNewMapMode);
// copy AA flag for new target
mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
// copy RasterOp (e.g. may be RasterOp::Xor on destination)
mpContent->SetRasterOp(mrOutDev.GetRasterOp());
}
impBufferDevice::~impBufferDevice()
{
if (mpContent)
{
getVDevBuffer().free(*mpContent);
}
if (mpAlpha)
{
getVDevBuffer().free(*mpAlpha);
}
}
void impBufferDevice::paint(double fTrans)
{
if (!isVisible())
return;
const Point aEmptyPoint;
const Size aSizePixel(maDestPixel.GetSize());
const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
#ifdef DBG_UTIL
static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
#endif
mrOutDev.EnableMapMode(false);
mpContent->EnableMapMode(false);
#ifdef DBG_UTIL
if (bDoSaveForVisualControl)
{
SvFileStream aNew(
#ifdef _WIN32
"c:\\content.bmp",
#else
"~/content.bmp",
#endif
StreamMode::WRITE | StreamMode::TRUNC);
Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
WriteDIB(aContent, aNew, false, true);
}
#endif
// during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
mrOutDev.SetRasterOp(RasterOp::OverPaint);
if (mpAlpha)
{
mpAlpha->EnableMapMode(false);
AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
#ifdef DBG_UTIL
if (bDoSaveForVisualControl)
{
SvFileStream aNew(
#ifdef _WIN32
"c:\\transparence.bmp",
#else
"~/transparence.bmp",
#endif
StreamMode::WRITE | StreamMode::TRUNC);
WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
}
#endif
BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel));
aAlphaMask.BlendWith(aContent.GetAlpha());
mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask));
}
else if (0.0 != fTrans)
{
basegfx::B2DHomMatrix trans, scale;
trans.translate(maDestPixel.TopLeft().X(), maDestPixel.TopLeft().Y());
scale.scale(aSizePixel.Width(), aSizePixel.Height());
mrOutDev.DrawTransformedBitmapEx(
trans * scale, mpContent->GetBitmapEx(aEmptyPoint, aSizePixel), 1 - fTrans);
}
else
{
mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
}
mrOutDev.SetRasterOp(aOrigRasterOp);
mrOutDev.EnableMapMode(bWasEnabledDst);
}
VirtualDevice& impBufferDevice::getContent()
{
SAL_WARN_IF(!mpContent, "drawinglayer",
"impBufferDevice: No content, check isVisible() before accessing (!)");
return *mpContent;
}
VirtualDevice& impBufferDevice::getTransparence()
{
SAL_WARN_IF(!mpContent, "drawinglayer",
"impBufferDevice: No content, check isVisible() before accessing (!)");
if (!mpAlpha)
{
mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false);
mpAlpha->SetMapMode(mpContent->GetMapMode());
// copy AA flag for new target; masking needs to be smooth
mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
}
return *mpAlpha;
}
} // end of namespace drawinglayer
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */