office-gobmx/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx
Mike Kaganski 8bd57009d3 Fix wrong / misleading uses of fTools::(more|less|equal) with literal 0
These will never compare with tolerance (the zero is a special case
in rtl_math_approxEqual used internally), so the calls like those
don't do what they appear to do in these cases.

Change-Id: I495ac92b7f45637e118e4fdc04bb6fad6fff31ab
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171391
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Jenkins
2024-08-02 11:31:00 +02:00

851 lines
35 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 <algorithm>
#include <drawinglayer/primitive2d/graphicprimitivehelper2d.hxx>
#include <drawinglayer/animation/animationtiming.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/BitmapAlphaPrimitive2D.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/numeric/ftools.hxx>
// helper class for animated graphics
#include <utility>
#include <vcl/animate/Animation.hxx>
#include <vcl/graph.hxx>
#include <vcl/virdev.hxx>
#include <vcl/svapp.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/skia/SkiaHelper.hxx>
namespace drawinglayer::primitive2d
{
namespace {
class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D
{
private:
/// the geometric definition
basegfx::B2DHomMatrix maTransform;
/** the Graphic with all its content possibilities, here only
animated is allowed and gets checked by isValidData().
an instance of Graphic is used here since it's ref-counted
and thus a safe copy for now
*/
Graphic maGraphic;
/** defines parameters for tiling if this AnimatedGraphicPrimitive2D
is to be used for a FillGraphicPrimitive2D. In that case,
maFillGraphicAttribute.isDefault() will be false
*/
drawinglayer::attribute::FillGraphicAttribute maFillGraphicAttribute;
/// local animation processing data, excerpt from maGraphic
::Animation maAnimation;
/// the transparency in range [0.0 .. 1.0]
double mfTransparency;
/// the on-demand created VirtualDevices for frame creation
ScopedVclPtrInstance< VirtualDevice > maVirtualDevice;
ScopedVclPtrInstance< VirtualDevice > maVirtualDeviceMask;
// index of the next frame that would be regularly prepared
sal_uInt32 mnNextFrameToPrepare;
/// buffering of 1st frame (always active)
Primitive2DReference maBufferedFirstFrame;
/// buffering of all frames
std::vector<Primitive2DReference> maBufferedPrimitives;
bool mbBufferingAllowed;
/// set if the animation is huge so that just always the next frame
/// is used instead of using timing
bool mbHugeSize;
/// helper methods
bool isValidData() const
{
return (GraphicType::Bitmap == maGraphic.GetType()
&& maGraphic.IsAnimated()
&& maAnimation.Count()
&& !basegfx::fTools::equal(getTransparency(), 1.0));
}
void ensureVirtualDeviceSizeAndState()
{
if (!isValidData())
return;
const Size aCurrent(maVirtualDevice->GetOutputSizePixel());
const Size aTarget(maAnimation.GetDisplaySizePixel());
if (aCurrent != aTarget)
{
maVirtualDevice->EnableMapMode(false);
maVirtualDeviceMask->EnableMapMode(false);
maVirtualDevice->SetOutputSizePixel(aTarget);
maVirtualDeviceMask->SetOutputSizePixel(aTarget);
// tdf#156630 make erase calls fill with transparency
maVirtualDevice->SetBackground(COL_BLACK);
maVirtualDeviceMask->SetBackground(COL_ALPHA_TRANSPARENT);
}
maVirtualDevice->Erase();
maVirtualDeviceMask->Erase();
const ::tools::Rectangle aRect(Point(0, 0), aTarget);
maVirtualDeviceMask->SetFillColor(COL_BLACK);
maVirtualDeviceMask->SetLineColor();
maVirtualDeviceMask->DrawRect(aRect);
}
sal_uInt32 generateStepTime(sal_uInt32 nIndex) const
{
const AnimationFrame& rAnimationFrame = maAnimation.Get(sal_uInt16(nIndex));
sal_uInt32 nWaitTime(rAnimationFrame.mnWait * 10);
// Take care of special value for MultiPage TIFFs. ATM these shall just
// show their first page. Later we will offer some switching when object
// is selected.
if (ANIMATION_TIMEOUT_ON_CLICK == rAnimationFrame.mnWait)
{
// ATM the huge value would block the timer, so
// use a long time to show first page (whole day)
nWaitTime = 100 * 60 * 60 * 24;
}
// Bad trap: There are animated gifs with no set WaitTime (!).
// In that case use a default value.
if (0 == nWaitTime)
{
nWaitTime = 100;
}
return nWaitTime;
}
void createAndSetAnimationTiming()
{
if (!isValidData())
return;
animation::AnimationEntryLoop aAnimationLoop(maAnimation.GetLoopCount() ? maAnimation.GetLoopCount() : 0xffff);
const sal_uInt32 nCount(maAnimation.Count());
for (sal_uInt32 a(0); a < nCount; a++)
{
const sal_uInt32 aStepTime(generateStepTime(a));
const animation::AnimationEntryFixed aTime(static_cast<double>(aStepTime), static_cast<double>(a) / static_cast<double>(nCount));
aAnimationLoop.append(aTime);
}
animation::AnimationEntryList aAnimationEntryList;
aAnimationEntryList.append(aAnimationLoop);
setAnimationEntry(aAnimationEntryList);
}
Primitive2DReference createFromBuffer() const
{
// create BitmapEx by extracting from VirtualDevices
const Bitmap aMainBitmap(maVirtualDevice->GetBitmap(Point(), maVirtualDevice->GetOutputSizePixel()));
bool useAlphaMask = false;
#if defined(MACOSX) || defined(IOS)
useAlphaMask = true;
#else
// GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not.
if( SkiaHelper::isVCLSkiaEnabled())
useAlphaMask = true;
#endif
BitmapEx bitmap;
if( useAlphaMask )
{
const AlphaMask aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel()));
bitmap = BitmapEx(aMainBitmap, aMaskBitmap);
}
else
{
Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel()));
// tdf#156630 invert the alpha mask
aMaskBitmap.Invert(); // convert from transparency to alpha
bitmap = BitmapEx(aMainBitmap, aMaskBitmap);
}
if (!maFillGraphicAttribute.isDefault())
{
// need to create FillGraphicPrimitive2D
const drawinglayer::attribute::FillGraphicAttribute aAttribute(
Graphic(bitmap),
maFillGraphicAttribute.getGraphicRange(),
maFillGraphicAttribute.getTiling(),
maFillGraphicAttribute.getOffsetX(),
maFillGraphicAttribute.getOffsetY());
return new FillGraphicPrimitive2D(
getTransform(),
aAttribute,
getTransparency());
}
// need to create BitmapAlphaPrimitive2D/BitmapPrimitive2D
if (basegfx::fTools::equal(getTransparency(), 0.0))
return new BitmapPrimitive2D(bitmap, getTransform());
return new BitmapAlphaPrimitive2D(bitmap, getTransform(), getTransparency());
}
void checkSafeToBuffer(sal_uInt32 nIndex)
{
if (mbBufferingAllowed)
{
// all frames buffered
if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size())
{
if (!maBufferedPrimitives[nIndex].is())
{
maBufferedPrimitives[nIndex] = createFromBuffer();
// check if buffering is complete
bool bBufferingComplete(true);
for (auto const & a: maBufferedPrimitives)
{
if (!a.is())
{
bBufferingComplete = false;
break;
}
}
if (bBufferingComplete)
{
maVirtualDevice.disposeAndClear();
maVirtualDeviceMask.disposeAndClear();
}
}
}
}
else
{
// always buffer first frame
if (0 == nIndex && !maBufferedFirstFrame.is())
{
maBufferedFirstFrame = createFromBuffer();
}
}
}
void createFrame(sal_uInt32 nTarget)
{
// mnNextFrameToPrepare is the target frame to create next (which implies that
// mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when
// 0 != mnNextFrameToPrepare. nTarget is the target frame.
if (!isValidData())
return;
if (mnNextFrameToPrepare > nTarget)
{
// we are ahead request, reset mechanism to start at frame zero
ensureVirtualDeviceSizeAndState();
mnNextFrameToPrepare = 0;
}
while (mnNextFrameToPrepare <= nTarget)
{
// prepare step
const AnimationFrame& rAnimationFrame = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare));
bool bSourceBlending = rAnimationFrame.meBlend == Blend::Source;
if (bSourceBlending)
{
tools::Rectangle aArea(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx.GetSizePixel());
maVirtualDevice->Erase(aArea);
maVirtualDeviceMask->Erase(aArea);
}
switch (rAnimationFrame.meDisposal)
{
case Disposal::Not:
{
maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx);
AlphaMask aAlphaMask = rAnimationFrame.maBitmapEx.GetAlphaMask();
if (aAlphaMask.IsEmpty())
{
const Point aEmpty;
const ::tools::Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel());
const Wallpaper aWallpaper(COL_BLACK);
maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper);
}
else
{
BitmapEx aExpandVisibilityMask(aAlphaMask.GetBitmap(), aAlphaMask);
maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask);
}
break;
}
case Disposal::Back:
{
// #i70772# react on no mask, for primitives, too.
const AlphaMask & rMask(rAnimationFrame.maBitmapEx.GetAlphaMask());
maVirtualDeviceMask->Erase();
maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx);
if (rMask.IsEmpty())
{
const ::tools::Rectangle aRect(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx.GetSizePixel());
maVirtualDeviceMask->SetFillColor(COL_BLACK);
maVirtualDeviceMask->SetLineColor();
maVirtualDeviceMask->DrawRect(aRect);
}
else
{
BitmapEx aExpandVisibilityMask(rMask.GetBitmap(), rMask);
maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask);
}
break;
}
case Disposal::Previous:
{
maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx);
BitmapEx aExpandVisibilityMask(rAnimationFrame.maBitmapEx.GetAlphaMask().GetBitmap(), rAnimationFrame.maBitmapEx.GetAlphaMask());
maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask);
break;
}
}
// to not waste created data, check adding to buffers
checkSafeToBuffer(mnNextFrameToPrepare);
mnNextFrameToPrepare++;
}
}
Primitive2DReference tryTogetFromBuffer(sal_uInt32 nIndex) const
{
if (mbBufferingAllowed)
{
// all frames buffered, check if available
if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size())
{
if (maBufferedPrimitives[nIndex].is())
{
return maBufferedPrimitives[nIndex];
}
}
}
else
{
// always buffer first frame, it's sometimes requested out-of-order
if (0 == nIndex && maBufferedFirstFrame.is())
{
return maBufferedFirstFrame;
}
}
return Primitive2DReference();
}
public:
/// constructor
AnimatedGraphicPrimitive2D(
const Graphic& rGraphic,
const drawinglayer::attribute::FillGraphicAttribute* pFillGraphicAttribute,
basegfx::B2DHomMatrix aTransform,
double fTransparency = 0.0);
virtual ~AnimatedGraphicPrimitive2D();
/// data read access
const basegfx::B2DHomMatrix& getTransform() const { return maTransform; }
double getTransparency() const { return mfTransparency; }
/// provide unique ID
virtual sal_uInt32 getPrimitive2DID() const override { return PRIMITIVE2D_ID_ANIMATEDGRAPHICPRIMITIVE2D; }
/// compare operator
virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
/// override to deliver the correct expected frame dependent of timing
virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override;
/// get range
virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
};
}
AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D(
const Graphic& rGraphic,
const drawinglayer::attribute::FillGraphicAttribute* pFillGraphicAttribute,
basegfx::B2DHomMatrix aTransform,
double fTransparency)
: AnimatedSwitchPrimitive2D(
animation::AnimationEntryList(),
Primitive2DContainer(),
false),
maTransform(std::move(aTransform)),
maGraphic(rGraphic),
maFillGraphicAttribute(),
maAnimation(rGraphic.GetAnimation()),
mfTransparency(std::max(0.0, std::min(1.0, fTransparency))),
maVirtualDevice(*Application::GetDefaultDevice()),
maVirtualDeviceMask(*Application::GetDefaultDevice()),
mnNextFrameToPrepare(SAL_MAX_UINT32),
mbBufferingAllowed(false),
mbHugeSize(false)
{
// if FillGraphicAttribute copy it -> FillGraphicPrimitive2D is intended
if (nullptr != pFillGraphicAttribute)
maFillGraphicAttribute = *pFillGraphicAttribute;
// initialize AnimationTiming, needed to detect which frame is requested
// in get2DDecomposition
createAndSetAnimationTiming();
// check if we allow buffering
if (isValidData())
{
// allow buffering up to a size of:
// - 64 frames
// - sizes of 256x256 pixels
// This may be offered in option values if needed
static const sal_uInt64 nAllowedSize(64 * 256 * 256);
static const sal_uInt64 nHugeSize(10000000);
const Size aTarget(maAnimation.GetDisplaySizePixel());
const sal_uInt64 nUsedSize(static_cast<sal_uInt64>(maAnimation.Count()) * aTarget.Width() * aTarget.Height());
if (nUsedSize < nAllowedSize)
{
mbBufferingAllowed = true;
}
if (nUsedSize > nHugeSize)
{
mbHugeSize = true;
}
}
// prepare buffer space
if (mbBufferingAllowed && isValidData())
{
maBufferedPrimitives.resize(maAnimation.Count());
}
}
AnimatedGraphicPrimitive2D::~AnimatedGraphicPrimitive2D()
{
// Related: tdf#158807 mutex must be locked when disposing a VirtualDevice
// If the following .ppt document is opened in a debug build
// and the document is left open for a minute or two without
// changing any content, this destructor will be called on a
// non-main thread with the mutex unlocked:
// https://bugs.documentfoundation.org/attachment.cgi?id=46801
// This hits an assert in VirtualDevice::ReleaseGraphics() so
// explicitly lock the mutex and explicitly dispose and clear
// the VirtualDevice instances variables.
const SolarMutexGuard aSolarGuard;
maVirtualDevice.disposeAndClear();
maVirtualDeviceMask.disposeAndClear();
maAnimation.Clear();
maGraphic.Clear();
}
bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
{
// do not use 'GroupPrimitive2D::operator==' here, that would compare
// the children. Also do not use 'BasePrimitive2D::operator==', that would
// check the ID-Type. Since we are a simple derivation without own ID,
// use the dynamic_cast RTTI directly
const AnimatedGraphicPrimitive2D* pCompare = dynamic_cast<const AnimatedGraphicPrimitive2D*>(&rPrimitive);
// use operator== of Graphic - if that is equal, the basic definition is equal
return (nullptr != pCompare
&& getTransform() == pCompare->getTransform()
&& maGraphic == pCompare->maGraphic);
}
void AnimatedGraphicPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const
{
if (!isValidData())
return;
Primitive2DReference aRetval;
const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime()));
const sal_uInt32 nLen(maAnimation.Count());
sal_uInt32 nIndex(basegfx::fround(fState * static_cast<double>(nLen)));
// nIndex is the requested frame - it is in range [0..nLen[
// create frame representation in VirtualDevices
if (nIndex >= nLen)
{
nIndex = nLen - 1;
}
// check buffering shortcuts, may already be created
aRetval = tryTogetFromBuffer(nIndex);
if (aRetval.is())
{
rVisitor.visit(aRetval);
return;
}
// if huge size (and not the buffered 1st frame) simply
// create next frame
if (mbHugeSize && 0 != nIndex && mnNextFrameToPrepare <= nIndex)
{
nIndex = mnNextFrameToPrepare % nLen;
}
// frame not (yet) buffered or no buffering allowed, create it
const_cast<AnimatedGraphicPrimitive2D*>(this)->createFrame(nIndex);
// try to get from buffer again, may have been added from createFrame
aRetval = tryTogetFromBuffer(nIndex);
if (aRetval.is())
{
rVisitor.visit(aRetval);
return;
}
// did not work (not buffered and not 1st frame), create from buffer
aRetval = createFromBuffer();
rVisitor.visit(aRetval);
}
basegfx::B2DRange AnimatedGraphicPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
{
// get object's range
basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
aUnitRange.transform(getTransform());
// intersect with visible part
aUnitRange.intersect(rViewInformation.getViewport());
return aUnitRange;
}
} // end of namespace
namespace drawinglayer::primitive2d
{
Primitive2DReference createFillGraphicPrimitive2D(
const basegfx::B2DHomMatrix& rTransform,
const drawinglayer::attribute::FillGraphicAttribute& rFillGraphicAttribute,
double fTransparency)
{
if (basegfx::fTools::equal(fTransparency, 1.0))
{
// completely transparent, done
return nullptr;
}
const Graphic& rGraphic(rFillGraphicAttribute.getGraphic());
const GraphicType aType(rGraphic.GetType());
if (GraphicType::Bitmap == aType && rGraphic.IsAnimated())
{
return new AnimatedGraphicPrimitive2D(
rGraphic,
&rFillGraphicAttribute,
rTransform,
fTransparency);
}
return new FillGraphicPrimitive2D(
rTransform,
rFillGraphicAttribute,
fTransparency);
}
void create2DDecompositionOfGraphic(
Primitive2DContainer& rContainer,
const Graphic& rGraphic,
const basegfx::B2DHomMatrix& rTransform,
double fTransparency)
{
if (basegfx::fTools::equal(fTransparency, 1.0))
{
// completely transparent, done
return;
}
switch(rGraphic.GetType())
{
case GraphicType::Bitmap :
{
if(rGraphic.IsAnimated())
{
// prepare specialized AnimatedGraphicPrimitive2D, now with
// support for alpha
rContainer.append(new AnimatedGraphicPrimitive2D(
rGraphic,
nullptr,
rTransform,
fTransparency));
}
else if(rGraphic.getVectorGraphicData())
{
// embedded Vector Graphic Data fill, create embed transform
const basegfx::B2DRange& rSvgRange(rGraphic.getVectorGraphicData()->getRange());
if(rSvgRange.getWidth() > 0.0 && rSvgRange.getHeight() > 0.0)
{
// translate back to origin, scale to unit coordinates
basegfx::B2DHomMatrix aEmbedVectorGraphic(
basegfx::utils::createTranslateB2DHomMatrix(
-rSvgRange.getMinX(),
-rSvgRange.getMinY()));
aEmbedVectorGraphic.scale(
1.0 / rSvgRange.getWidth(),
1.0 / rSvgRange.getHeight());
// apply created object transformation
aEmbedVectorGraphic = rTransform * aEmbedVectorGraphic;
// add Vector Graphic Data primitives embedded
rtl::Reference<BasePrimitive2D> aPrimitive(
new TransformPrimitive2D(
aEmbedVectorGraphic,
Primitive2DContainer(rGraphic.getVectorGraphicData()->getPrimitive2DSequence())));
// if needed embed to UnifiedTransparencePrimitive2D
if (!basegfx::fTools::equalZero(fTransparency, 0.0))
aPrimitive = new UnifiedTransparencePrimitive2D(
Primitive2DContainer { aPrimitive }, fTransparency);
rContainer.append(aPrimitive);
}
}
else
{
// dependent of transparency used create the needed bitmap primitive
if (basegfx::fTools::equalZero(fTransparency))
{
rContainer.append(
new BitmapPrimitive2D(
rGraphic.GetBitmapEx(),
rTransform));
}
else
{
rContainer.append(
new BitmapAlphaPrimitive2D(
rGraphic.GetBitmapEx(),
rTransform,
fTransparency));
}
}
break;
}
case GraphicType::GdiMetafile :
{
// create MetafilePrimitive2D
const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile();
rtl::Reference<BasePrimitive2D> aPrimitive(
new MetafilePrimitive2D(
rTransform,
rMetafile));
// #i100357# find out if clipping is needed for this primitive. Unfortunately,
// there exist Metafiles who's content is bigger than the proposed PrefSize set
// at them. This is an error, but we need to work around this
const Size aMetaFilePrefSize(rMetafile.GetPrefSize());
const Size aMetaFileRealSize(
rMetafile.GetBoundRect(
*Application::GetDefaultDevice()).GetSize());
if(aMetaFileRealSize.getWidth() > aMetaFilePrefSize.getWidth()
|| aMetaFileRealSize.getHeight() > aMetaFilePrefSize.getHeight())
{
// clipping needed. Embed to MaskPrimitive2D. Create children and mask polygon
basegfx::B2DPolygon aMaskPolygon(basegfx::utils::createUnitPolygon());
aMaskPolygon.transform(rTransform);
aPrimitive = new MaskPrimitive2D(
basegfx::B2DPolyPolygon(aMaskPolygon),
Primitive2DContainer { aPrimitive });
}
// if needed embed to UnifiedTransparencePrimitive2D
if (!basegfx::fTools::equalZero(fTransparency, 0.0))
aPrimitive = new UnifiedTransparencePrimitive2D(
Primitive2DContainer { aPrimitive }, fTransparency);
rContainer.append(aPrimitive);
break;
}
default:
{
// nothing to create
break;
}
}
}
Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded(
Primitive2DContainer&& rChildren,
GraphicDrawMode aGraphicDrawMode,
double fLuminance,
double fContrast,
double fRed,
double fGreen,
double fBlue,
double fGamma,
bool bInvert)
{
Primitive2DContainer aRetval;
if(rChildren.empty())
{
// no child content, done
return aRetval;
}
// set child content as retval; that is what will be used as child content in all
// embeddings from here
aRetval = std::move(rChildren);
if(GraphicDrawMode::Watermark == aGraphicDrawMode)
{
// this is solved by applying fixed values additionally to luminance
// and contrast, do it here and reset DrawMode to GraphicDrawMode::Standard
// original in svtools uses:
// #define WATERMARK_LUM_OFFSET 50
// #define WATERMARK_CON_OFFSET -70
fLuminance = std::clamp(fLuminance + 0.5, -1.0, 1.0);
fContrast = std::clamp(fContrast - 0.7, -1.0, 1.0);
aGraphicDrawMode = GraphicDrawMode::Standard;
}
// DrawMode (GraphicDrawMode::Watermark already handled)
switch(aGraphicDrawMode)
{
case GraphicDrawMode::Greys:
{
// convert to grey
const Primitive2DReference aPrimitiveGrey(
new ModifiedColorPrimitive2D(
std::move(aRetval),
std::make_shared<basegfx::BColorModifier_gray>()));
aRetval = Primitive2DContainer { aPrimitiveGrey };
break;
}
case GraphicDrawMode::Mono:
{
// convert to mono (black/white with threshold 0.5)
const Primitive2DReference aPrimitiveBlackAndWhite(
new ModifiedColorPrimitive2D(
std::move(aRetval),
std::make_shared<basegfx::BColorModifier_black_and_white>(0.5)));
aRetval = Primitive2DContainer { aPrimitiveBlackAndWhite };
break;
}
default: // case GraphicDrawMode::Standard:
{
assert(
aGraphicDrawMode != GraphicDrawMode::Watermark
&& "OOps, GraphicDrawMode::Watermark should already be handled (see above)");
// nothing to do
break;
}
}
// mnContPercent, mnLumPercent, mnRPercent, mnGPercent, mnBPercent
// handled in a single call
if(!basegfx::fTools::equalZero(fLuminance)
|| !basegfx::fTools::equalZero(fContrast)
|| !basegfx::fTools::equalZero(fRed)
|| !basegfx::fTools::equalZero(fGreen)
|| !basegfx::fTools::equalZero(fBlue))
{
const Primitive2DReference aPrimitiveRGBLuminannceContrast(
new ModifiedColorPrimitive2D(
std::move(aRetval),
std::make_shared<basegfx::BColorModifier_RGBLuminanceContrast>(
fRed,
fGreen,
fBlue,
fLuminance,
fContrast)));
aRetval = Primitive2DContainer { aPrimitiveRGBLuminannceContrast };
}
// gamma (boolean)
if(!basegfx::fTools::equal(fGamma, 1.0))
{
const Primitive2DReference aPrimitiveGamma(
new ModifiedColorPrimitive2D(
std::move(aRetval),
std::make_shared<basegfx::BColorModifier_gamma>(
fGamma)));
aRetval = Primitive2DContainer { aPrimitiveGamma };
}
// invert (boolean)
if(bInvert)
{
const Primitive2DReference aPrimitiveInvert(
new ModifiedColorPrimitive2D(
std::move(aRetval),
std::make_shared<basegfx::BColorModifier_invert>()));
aRetval = Primitive2DContainer { aPrimitiveInvert };
}
return aRetval;
}
} // end of namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */