Files
loongoffice/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx
Mike Kaganski d4bfa55209 Allow creation of virtual device with alpha as content of impBufferDevice
impBufferDevice now can create its mpContent with alpha; that is used
when painting, blending mpContent's alpha with mpMask/mpAlpha/fTrans.

That allows to paint to the buffer device and get actual transparency
of the painted objects in one pass, to process further (e.g., modify
transparency separately, or use it to get mask).

Required for subsequent soft edge effect improvement

Change-Id: If19c160581ce9a515d86222dd2fab06f746df2da
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/95030
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
2020-05-29 06:23:57 +02:00

500 lines
16 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 <osl/diagnose.h>
#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 bClear,
bool bMonoChrome, 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 bClear,
bool bMonoChrome, bool bTransparent)
{
::osl::MutexGuard aGuard(m_aMutex);
VclPtr<VirtualDevice> pRetval;
sal_Int32 nBits = bMonoChrome ? 1 : 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)
{
if (bClear)
{
pRetval->Erase(
::tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight()));
}
}
else
{
pRetval->SetOutputSizePixel(rSizePixel, bClear);
}
}
}
// no success yet, create new buffer
if (!pRetval)
{
pRetval = VclPtr<VirtualDevice>::Create(
rOutDev, bMonoChrome ? DeviceFormat::BITMASK : DeviceFormat::DEFAULT,
bTransparent ? DeviceFormat::DEFAULT : DeviceFormat::NONE);
maDeviceTemplates[pRetval] = &rOutDev;
pRetval->SetOutputSizePixel(rSizePixel, bClear);
}
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(new VDevBuffer());
return *aVDevBuffer.get();
}
impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange,
bool bContentTransparent)
: mrOutDev(rOutDev)
, mpContent(nullptr)
, mpMask(nullptr)
, mpAlpha(nullptr)
, mbContentTransparent(bContentTransparent)
{
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;
#ifdef IOS
// Exact mechanism unknown, but for some reason SmartArt
// rendering, especially shadows, is broken on iOS unless
// we pass 'true' here. Are virtual devices always de
// facto cleared when created on other platforms?
mpContent
= getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, false, bContentTransparent);
#else
mpContent
= getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false, false, bContentTransparent);
#endif
// #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 (!)");
const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled());
mrOutDev.EnableMapMode(false);
mpContent->DrawOutDev(aEmptyPoint, maDestPixel.GetSize(), maDestPixel.TopLeft(),
maDestPixel.GetSize(), mrOutDev);
mrOutDev.EnableMapMode(bWasEnabledSrc);
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 (mpMask)
{
getVDevBuffer().free(*mpMask);
}
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));
if (mbContentTransparent)
aAlphaMask.BlendWith(aContent.GetAlpha());
mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask));
}
else if (mpMask)
{
mpMask->EnableMapMode(false);
const Bitmap aMask(mpMask->GetBitmap(aEmptyPoint, aSizePixel));
#ifdef DBG_UTIL
if (bDoSaveForVisualControl)
{
SvFileStream aNew(
#ifdef _WIN32
"c:\\mask.bmp",
#else
"~/mask.bmp",
#endif
StreamMode::WRITE | StreamMode::TRUNC);
WriteDIB(aMask, aNew, false, true);
}
#endif
if (mbContentTransparent)
{
BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel));
AlphaMask aAlpha(aContent.GetAlpha());
aAlpha.BlendWith(aMask);
mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlpha));
}
else
{
Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aMask));
}
}
else if (0.0 != fTrans)
{
sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel));
if (mbContentTransparent)
aAlphaMask.BlendWith(aContent.GetAlpha());
mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask));
}
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::getMask()
{
SAL_WARN_IF(!mpContent, "drawinglayer",
"impBufferDevice: No content, check isVisible() before accessing (!)");
if (!mpMask)
{
mpMask = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, true, false);
mpMask->SetMapMode(mpContent->GetMapMode());
// do NOT copy AA flag for mask!
}
return *mpMask;
}
VirtualDevice& impBufferDevice::getTransparence()
{
SAL_WARN_IF(!mpContent, "drawinglayer",
"impBufferDevice: No content, check isVisible() before accessing (!)");
if (!mpAlpha)
{
mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, false, 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: */