Files
loongoffice/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
Mike Kaganski 0a618246fc tdf#134134: consider color modifier stack when pixel-processing
Add initial color stack as an optional processor ctor argument,
so that it wouldn't be possibe to modify it after construction
from outside randomly (which could break the stack state).

Change-Id: I8aae4b806531fa61cc67def865297f5de1cf0755
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/96684
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
2020-06-19 13:06:56 +02:00

1127 lines
46 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 "vclpixelprocessor2d.hxx"
#include "vclhelperbufferdevice.hxx"
#include "helperwrongspellrenderer.hxx"
#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <vcl/BitmapBasicMorphologyFilter.hxx>
#include <vcl/BitmapFilterStackBlur.hxx>
#include <vcl/outdev.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/hatch.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/Tools.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonMarkerPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx>
#include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
#include <drawinglayer/primitive2d/glowprimitive2d.hxx>
#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
#include <drawinglayer/primitive2d/controlprimitive2d.hxx>
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/XControl.hpp>
using namespace com::sun::star;
namespace drawinglayer::processor2d
{
struct VclPixelProcessor2D::Impl
{
AntialiasingFlags m_nOrigAntiAliasing;
explicit Impl(OutputDevice const& rOutDev)
: m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
{
}
};
VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
OutputDevice& rOutDev,
const basegfx::BColorModifierStack& rInitStack)
: VclProcessor2D(rViewInformation, rOutDev, rInitStack)
, m_pImpl(new Impl(rOutDev))
{
// prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels
maCurrentTransformation = rViewInformation.getObjectToViewTransformation();
// prepare output directly to pixels
mpOutputDevice->Push(PushFlags::MAPMODE);
mpOutputDevice->SetMapMode();
// react on AntiAliasing settings
if (getOptionsDrawinglayer().IsAntiAliasing())
{
mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing
| AntialiasingFlags::EnableB2dDraw);
}
else
{
mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing
& ~AntialiasingFlags::EnableB2dDraw);
}
}
VclPixelProcessor2D::~VclPixelProcessor2D()
{
// restore MapMode
mpOutputDevice->Pop();
// restore AntiAliasing
mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing);
}
void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect(
const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency)
{
if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0)
{
// no geometry, done
return;
}
const basegfx::BColor aPolygonColor(
maBColorModifierStack.getModifiedColor(rSource.getBColor()));
mpOutputDevice->SetFillColor(Color(aPolygonColor));
mpOutputDevice->SetLineColor();
mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(),
fTransparency);
}
bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(
const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
{
const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
{
// no geometry, done
return true;
}
const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor()));
mpOutputDevice->SetFillColor();
mpOutputDevice->SetLineColor(Color(aLineColor));
//aLocalPolygon.transform(maCurrentTransformation);
// try drawing; if it did not work, use standard fallback
return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0,
fTransparency);
}
bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(
const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
{
const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
{
// no geometry, done
return true;
}
if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin()
&& css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap())
{
// better use decompose to get that combination done for now, see discussion
// at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff
return false;
}
// MM01: Radically change here - no dismantle/applyLineDashing,
// let that happen low-level at DrawPolyLineDirect implementations
// to open up for buffering and evtl. direct draw with sys-dep
// graphic systems. Check for stroke is in use
const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault()
|| 0.0 == rSource.getStrokeAttribute().getFullDotDashLen());
const basegfx::BColor aLineColor(
maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor()));
mpOutputDevice->SetFillColor();
mpOutputDevice->SetLineColor(Color(aLineColor));
// MM01 draw direct, hand over dash data if available
return mpOutputDevice->DrawPolyLineDirect(
maCurrentTransformation, rLocalPolygon,
// tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
rSource.getLineAttribute().getWidth(), fTransparency,
bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(),
rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(),
rSource.getLineAttribute().getMiterMinimumAngle()
/* false bBypassAACheck, default*/);
}
void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
{
switch (rCandidate.getPrimitive2DID())
{
case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D:
{
processWrongSpellPrimitive2D(
static_cast<const primitive2d::WrongSpellPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
{
processTextSimplePortionPrimitive2D(
static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
{
processTextDecoratedPortionPrimitive2D(
static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
{
processPolygonHairlinePrimitive2D(
static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
{
// direct draw of transformed BitmapEx primitive
processBitmapPrimitive2D(
static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
{
// direct draw of fillBitmapPrimitive
RenderFillGraphicPrimitive2D(
static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D:
{
processPolyPolygonGradientPrimitive2D(
static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
{
// direct draw of bitmap
RenderPolyPolygonGraphicPrimitive2D(
static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
{
processPolyPolygonColorPrimitive2D(
static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D:
{
processMetaFilePrimitive2D(rCandidate);
break;
}
case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
{
// mask group.
RenderMaskPrimitive2DPixel(
static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
{
// modified color group. Force output to unified color.
RenderModifiedColorPrimitive2D(
static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
{
processUnifiedTransparencePrimitive2D(
static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
{
// sub-transparence group. Draw to VDev first.
RenderTransparencePrimitive2D(
static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
{
// transform group.
RenderTransformPrimitive2D(
static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D:
{
// new XDrawPage for ViewInformation2D
RenderPagePreviewPrimitive2D(
static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
{
// marker array
RenderMarkerArrayPrimitive2D(
static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
{
// point array
RenderPointArrayPrimitive2D(
static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
{
processControlPrimitive2D(
static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
{
processPolygonStrokePrimitive2D(
static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D:
{
processFillHatchPrimitive2D(
static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
{
processBackgroundColorPrimitive2D(
static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D:
{
// #i97628#
// This primitive means that the content is derived from an active text edit,
// not from model data itself. Some renderers need to suppress this content, e.g.
// the pixel renderer used for displaying the edit view (like this one). It's
// not to be suppressed by the MetaFile renderers, so that the edited text is
// part of the MetaFile, e.g. needed for presentation previews.
// Action: Ignore here, do nothing.
break;
}
case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
{
processInvertPrimitive2D(rCandidate);
break;
}
case PRIMITIVE2D_ID_EPSPRIMITIVE2D:
{
RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D:
{
RenderSvgLinearAtomPrimitive2D(
static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D:
{
RenderSvgRadialAtomPrimitive2D(
static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D:
{
processBorderLinePrimitive2D(
static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
{
processGlowPrimitive2D(
static_cast<const drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
{
processSoftEdgePrimitive2D(
static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
{
processShadowPrimitive2D(
static_cast<const drawinglayer::primitive2d::ShadowPrimitive2D&>(rCandidate));
break;
}
default:
{
SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
rCandidate.getPrimitive2DID()));
// process recursively
process(rCandidate);
break;
}
}
}
void VclPixelProcessor2D::processWrongSpellPrimitive2D(
const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive)
{
if (!renderWrongSpellPrimitive2D(rWrongSpellPrimitive, *mpOutputDevice, maCurrentTransformation,
maBColorModifierStack))
{
// fallback to decomposition (MetaFile)
process(rWrongSpellPrimitive);
}
}
void VclPixelProcessor2D::processTextSimplePortionPrimitive2D(
const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
{
// Adapt evtl. used special DrawMode
const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
adaptTextToFillDrawMode();
if (getOptionsDrawinglayer().IsRenderSimpleTextDirect())
{
RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
}
else
{
process(rCandidate);
}
// restore DrawMode
mpOutputDevice->SetDrawMode(nOriginalDrawMode);
}
void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D(
const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
{
// Adapt evtl. used special DrawMode
const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
adaptTextToFillDrawMode();
if (getOptionsDrawinglayer().IsRenderDecoratedTextDirect())
{
RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
}
else
{
process(rCandidate);
}
// restore DrawMode
mpOutputDevice->SetDrawMode(nOriginalDrawMode);
}
void VclPixelProcessor2D::processPolygonHairlinePrimitive2D(
const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
{
if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0))
{
return;
}
// direct draw of hairline
RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true);
}
void VclPixelProcessor2D::processBitmapPrimitive2D(
const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
{
// check if graphic content is inside discrete local ViewPort
const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
* rBitmapCandidate.getTransform());
if (!rDiscreteViewPort.isEmpty())
{
basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
aUnitRange.transform(aLocalTransform);
if (!aUnitRange.overlaps(rDiscreteViewPort))
{
// content is outside discrete local ViewPort
return;
}
}
RenderBitmapPrimitive2D(rBitmapCandidate);
}
void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D(
const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate)
{
// direct draw of gradient
const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
basegfx::BColor aStartColor(maBColorModifierStack.getModifiedColor(rGradient.getStartColor()));
basegfx::BColor aEndColor(maBColorModifierStack.getModifiedColor(rGradient.getEndColor()));
basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
if (!aLocalPolyPolygon.count())
return;
if (aStartColor == aEndColor)
{
// no gradient at all, draw as polygon in AA and non-AA case
aLocalPolyPolygon.transform(maCurrentTransformation);
mpOutputDevice->SetLineColor();
mpOutputDevice->SetFillColor(Color(aStartColor));
mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
}
else
{
// use the primitive decomposition of the metafile
process(rPolygonCandidate);
}
}
void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D(
const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
{
// try to use directly
basegfx::B2DPolyPolygon aLocalPolyPolygon;
tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0);
// okay, done. In this case no gaps should have to be repaired, too
// when AA is on and this filled polygons are the result of stroked line geometry,
// draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons
// Caution: This is needed in both cases (!)
if (!(mnPolygonStrokePrimitive2D && getOptionsDrawinglayer().IsAntiAliasing()
&& (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::EnableB2dDraw)))
return;
const basegfx::BColor aPolygonColor(
maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
sal_uInt32 nCount(aLocalPolyPolygon.count());
if (!nCount)
{
aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon();
aLocalPolyPolygon.transform(maCurrentTransformation);
nCount = aLocalPolyPolygon.count();
}
mpOutputDevice->SetFillColor();
mpOutputDevice->SetLineColor(Color(aPolygonColor));
for (sal_uInt32 a(0); a < nCount; a++)
{
mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0);
}
}
void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D(
const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate)
{
// Detect if a single PolyPolygonColorPrimitive2D is contained; in that case,
// use the faster OutputDevice::DrawTransparent method
const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren();
if (rContent.empty())
return;
if (0.0 == rUniTransparenceCandidate.getTransparence())
{
// not transparent at all, use content
process(rUniTransparenceCandidate.getChildren());
}
else if (rUniTransparenceCandidate.getTransparence() > 0.0
&& rUniTransparenceCandidate.getTransparence() < 1.0)
{
bool bDrawTransparentUsed(false);
if (1 == rContent.size())
{
const primitive2d::Primitive2DReference xReference(rContent[0]);
const primitive2d::BasePrimitive2D* pBasePrimitive
= dynamic_cast<const primitive2d::BasePrimitive2D*>(xReference.get());
if (pBasePrimitive)
{
switch (pBasePrimitive->getPrimitive2DID())
{
case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
{
// single transparent tools::PolyPolygon identified, use directly
const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor
= static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
pBasePrimitive);
SAL_WARN_IF(!pPoPoColor, "drawinglayer",
"OOps, PrimitiveID and PrimitiveType do not match (!)");
bDrawTransparentUsed = true;
tryDrawPolyPolygonColorPrimitive2DDirect(
*pPoPoColor, rUniTransparenceCandidate.getTransparence());
break;
}
case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
{
// single transparent PolygonHairlinePrimitive2D identified, use directly
const primitive2d::PolygonHairlinePrimitive2D* pPoHair
= static_cast<const primitive2d::PolygonHairlinePrimitive2D*>(
pBasePrimitive);
SAL_WARN_IF(!pPoHair, "drawinglayer",
"OOps, PrimitiveID and PrimitiveType do not match (!)");
// do no tallow by default - problem is that self-overlapping parts of this geometry will
// not be in an all-same transparency but will already alpha-cover themselves with blending.
// This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
// content to be uniformly transparent.
// For hairline the effect is pretty minimal, but still not correct.
bDrawTransparentUsed = false;
break;
}
case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
{
// single transparent PolygonStrokePrimitive2D identified, use directly
const primitive2d::PolygonStrokePrimitive2D* pPoStroke
= static_cast<const primitive2d::PolygonStrokePrimitive2D*>(
pBasePrimitive);
SAL_WARN_IF(!pPoStroke, "drawinglayer",
"OOps, PrimitiveID and PrimitiveType do not match (!)");
// do no tallow by default - problem is that self-overlapping parts of this geometry will
// not be in an all-same transparency but will already alpha-cover themselves with blending.
// This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
// content to be uniformly transparent.
// To check, activate and draw a wide transparent self-crossing line/curve
bDrawTransparentUsed = false;
break;
}
default:
SAL_INFO("drawinglayer",
"default case for " << drawinglayer::primitive2d::idToString(
rUniTransparenceCandidate.getPrimitive2DID()));
break;
}
}
}
if (!bDrawTransparentUsed)
{
// unified sub-transparence. Draw to VDev first.
RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate);
}
}
}
void VclPixelProcessor2D::processControlPrimitive2D(
const primitive2d::ControlPrimitive2D& rControlPrimitive)
{
// control primitive
const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
try
{
// remember old graphics and create new
uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics());
if (xNewGraphics.is())
{
// link graphics and view
xControlView->setGraphics(xNewGraphics);
// get position
const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation
* rControlPrimitive.getTransform());
const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
// find out if the control is already visualized as a VCL-ChildWindow. If yes,
// it does not need to be painted at all.
uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW);
const bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is()
&& xControlWindow->isVisible());
if (!bControlIsVisibleAsChildWindow)
{
// draw it. Do not forget to use the evtl. offsetted origin of the target device,
// e.g. when used with mask/transparence buffer device
const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()),
aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY()));
}
// restore original graphics
xControlView->setGraphics(xOriginalGraphics);
}
}
catch (const uno::Exception&)
{
// #i116763# removing since there is a good alternative when the xControlView
// is not found and it is allowed to happen
// DBG_UNHANDLED_EXCEPTION();
// process recursively and use the decomposition as Bitmap
process(rControlPrimitive);
}
}
void VclPixelProcessor2D::processPolygonStrokePrimitive2D(
const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D)
{
// try to use directly
if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0))
{
return;
}
// the stroke primitive may be decomposed to filled polygons. To keep
// evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine,
// DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine
// working, these need to be copied to the corresponding fill modes
const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
adaptLineToFillDrawMode();
// polygon stroke primitive
// Lines with 1 and 2 pixel width without AA need special treatment since their visualization
// as filled polygons is geometrically correct but looks wrong since polygon filling avoids
// the right and bottom pixels. The used method evaluates that and takes the correct action,
// including calling recursively with decomposition if line is wide enough
RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D);
// restore DrawMode
mpOutputDevice->SetDrawMode(nOriginalDrawMode);
}
void VclPixelProcessor2D::processFillHatchPrimitive2D(
const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive)
{
if (getOptionsDrawinglayer().IsAntiAliasing())
{
// if AA is used (or ignore smoothing is on), there is no need to smooth
// hatch painting, use decomposition
process(rFillHatchPrimitive);
}
else
{
// without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel
// and forces hatch distance to be >= 3 pixels to make the hatch display look smoother.
// This is wrong in principle, but looks nicer. This could also be done here directly
// without VCL usage if needed
const attribute::FillHatchAttribute& rFillHatchAttributes
= rFillHatchPrimitive.getFillHatch();
// create hatch polygon in range size and discrete coordinates
basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange());
aHatchRange.transform(maCurrentTransformation);
const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange));
if (rFillHatchAttributes.isFillBackground())
{
// #i111846# background fill is active; draw fill polygon
const basegfx::BColor aPolygonColor(
maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
mpOutputDevice->SetFillColor(Color(aPolygonColor));
mpOutputDevice->SetLineColor();
mpOutputDevice->DrawPolygon(aHatchPolygon);
}
// set hatch line color
const basegfx::BColor aHatchColor(
maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
mpOutputDevice->SetFillColor();
mpOutputDevice->SetLineColor(Color(aHatchColor));
// get hatch style
HatchStyle eHatchStyle(HatchStyle::Single);
switch (rFillHatchAttributes.getStyle())
{
default: // HatchStyle::Single
{
break;
}
case attribute::HatchStyle::Double:
{
eHatchStyle = HatchStyle::Double;
break;
}
case attribute::HatchStyle::Triple:
{
eHatchStyle = HatchStyle::Triple;
break;
}
}
// create hatch
const basegfx::B2DVector aDiscreteDistance(
maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0));
const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength()));
const sal_uInt16 nAngle10(
static_cast<sal_uInt16>(basegfx::fround(rFillHatchAttributes.getAngle() / F_PI1800)));
::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance, nAngle10);
// draw hatch using VCL
mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch);
}
}
void VclPixelProcessor2D::processBackgroundColorPrimitive2D(
const primitive2d::BackgroundColorPrimitive2D& rPrimitive)
{
// #i98404# Handle directly, especially when AA is active
const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing());
// switch AA off in all cases
mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing()
& ~AntialiasingFlags::EnableB2dDraw);
// create color for fill
const basegfx::BColor aPolygonColor(
maBColorModifierStack.getModifiedColor(rPrimitive.getBColor()));
Color aFillColor(aPolygonColor);
aFillColor.SetTransparency(sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5));
mpOutputDevice->SetFillColor(aFillColor);
mpOutputDevice->SetLineColor();
// create rectangle for fill
const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport());
const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())),
static_cast<sal_Int32>(floor(aViewport.getMinY())),
static_cast<sal_Int32>(ceil(aViewport.getMaxX())),
static_cast<sal_Int32>(ceil(aViewport.getMaxY())));
mpOutputDevice->DrawRect(aRectangle);
// restore AA setting
mpOutputDevice->SetAntialiasing(nOriginalAA);
}
void VclPixelProcessor2D::processBorderLinePrimitive2D(
const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder)
{
// Process recursively, but switch off AntiAliasing for
// horizontal/vertical lines (*not* diagonal lines).
// Checked using AntialiasingFlags::PixelSnapHairline instead,
// but with AntiAliasing on the display really is too 'ghosty' when
// using fine stroking. Correct, but 'ghosty'.
// It has shown that there are quite some problems here:
// - vcl OutDev renderer methods still use fallbacks to paint
// multiple single lines between discrete sizes of < 3.5 what
// looks bad and does not match
// - mix of filled Polygons and Lines is bad when AA switched off
// - Alignment of AA with non-AA may be bad in diverse different
// renderers
//
// Due to these reasons I change the strategy: Always draw AAed, but
// allow fallback to test/check and if needed. The normal case
// where BorderLines will be system-dependently snapped to have at
// least a single discrete width per partial line (there may be up to
// three) works well nowadays due to most renderers moving the AA stuff
// by 0.5 pixels (discrete units) to match well with the non-AAed parts.
//
// Env-Switch for steering this, default is off.
// Enable by setting at all (and to something)
static const char* pSwitchOffAntiAliasingForHorVerBorderlines(
getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES"));
static bool bSwitchOffAntiAliasingForHorVerBorderlines(
nullptr != pSwitchOffAntiAliasingForHorVerBorderlines);
if (bSwitchOffAntiAliasingForHorVerBorderlines
&& rBorder.isHorizontalOrVertical(getViewInformation2D()))
{
AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing();
mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::EnableB2dDraw);
process(rBorder);
mpOutputDevice->SetAntialiasing(nAntiAliasing);
}
else
{
process(rBorder);
}
}
void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
{
// invert primitive (currently only used for HighContrast fallback for selection in SW and SC).
// (Not true, also used at least for the drawing of dragged column and row boundaries in SC.)
// Set OutDev to XOR and switch AA off (XOR does not work with AA)
mpOutputDevice->Push();
mpOutputDevice->SetRasterOp(RasterOp::Xor);
const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing());
mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::EnableB2dDraw);
// process content recursively
process(rCandidate);
// restore OutDev
mpOutputDevice->Pop();
mpOutputDevice->SetAntialiasing(nAntiAliasing);
}
void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
{
// #i98289#
const bool bForceLineSnap(getOptionsDrawinglayer().IsAntiAliasing()
&& getOptionsDrawinglayer().IsSnapHorVerLinesToDiscrete());
const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
if (bForceLineSnap)
{
mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
}
process(rCandidate);
if (bForceLineSnap)
{
mpOutputDevice->SetAntialiasing(nOldAntiAliase);
}
}
namespace
{
/* Returns 8-bit alpha mask created from passed mask.
Negative fErodeDilateRadius values mean erode, positive - dilate.
nTransparency defines minimal transparency level.
*/
AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double fErodeDilateRadius,
double fBlurRadius, sal_uInt8 nTransparency)
{
// Only completely white pixels on the initial mask must be considered for transparency. Any
// other color must be treated as black. This creates 1-bit B&W bitmap.
BitmapEx mask(rMask.CreateMask(COL_WHITE));
// Scaling down increases performance without noticeable quality loss. Additionally,
// current blur implementation can only handle blur radius between 2 and 254.
Size aSize = mask.GetSizePixel();
double fScale = 1.0;
while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
{
fScale /= 2;
fBlurRadius /= 2;
fErodeDilateRadius /= 2;
aSize.setHeight(aSize.Height() / 2);
aSize.setWidth(aSize.Width() / 2);
}
// BmpScaleFlag::Fast is important for following color replacement
mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
if (fErodeDilateRadius > 0)
BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
else if (fErodeDilateRadius < 0)
BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF));
if (nTransparency)
{
const Color aTransparency(nTransparency, nTransparency, nTransparency);
mask.Replace(COL_BLACK, aTransparency);
}
// We need 8-bit grey mask for blurring
mask.Convert(BmpConversion::N8BitGreys);
// calculate blurry effect
BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
mask.Scale(rMask.GetSizePixel());
return AlphaMask(mask.GetBitmap());
}
}
void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate)
{
basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
aRange.transform(maCurrentTransformation);
basegfx::B2DVector aGlowRadiusVector(rCandidate.getGlowRadius(), 0);
// Calculate the pixel size of glow radius in current transformation
aGlowRadiusVector *= maCurrentTransformation;
// Glow radius is the size of the halo from each side of the object. The halo is the
// border of glow color that fades from glow transparency level to fully transparent
// When blurring a sharp boundary (our case), it gets 50% of original intensity, and
// fades to both sides by the blur radius; thus blur radius is half of glow radius.
const double fBlurRadius = aGlowRadiusVector.getLength() / 2;
// Consider glow transparency (initial transparency near the object edge)
const sal_uInt8 nTransparency = rCandidate.getGlowColor().GetTransparency();
impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
if (aBufferDevice.isVisible())
{
// remember last OutDev and set to content
OutputDevice* pLastOutputDevice = mpOutputDevice;
mpOutputDevice = &aBufferDevice.getContent();
// We don't need antialiased mask here, which would only make effect thicker
const auto aPrevAA = mpOutputDevice->GetAntialiasing();
mpOutputDevice->SetAntialiasing(AntialiasingFlags::NONE);
mpOutputDevice->Erase();
process(rCandidate);
const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
static_cast<long>(std::floor(aRange.getMinY())),
static_cast<long>(std::ceil(aRange.getMaxX())),
static_cast<long>(std::ceil(aRange.getMaxY())));
BitmapEx bmpEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
mpOutputDevice->SetAntialiasing(aPrevAA);
AlphaMask mask
= ProcessAndBlurAlphaMask(bmpEx.GetAlpha(), fBlurRadius, fBlurRadius, nTransparency);
// The end result is the bitmap filled with glow color and blurred 8-bit alpha mask
const basegfx::BColor aGlowColor(
maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor()));
Bitmap bmp = bmpEx.GetBitmap();
bmp.Erase(Color(aGlowColor));
BitmapEx result(bmp, mask);
// back to old OutDev
mpOutputDevice = pLastOutputDevice;
mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
}
else
SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
}
void VclPixelProcessor2D::processSoftEdgePrimitive2D(
const primitive2d::SoftEdgePrimitive2D& rCandidate)
{
// TODO: don't limit the object at view range. This is needed to not blur objects at window
// borders, where they don't end. Ideally, process the full object once at maximal reasonable
// resolution, and store the resulting alpha mask in primitive's cache; then reuse it later,
// applying the transform.
basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
aRange.transform(maCurrentTransformation);
basegfx::B2DVector aRadiusVector(rCandidate.getRadius(), 0);
// Calculate the pixel size of soft edge radius in current transformation
aRadiusVector *= maCurrentTransformation;
// Blur radius is equal to soft edge radius
const double fBlurRadius = aRadiusVector.getLength();
impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
if (aBufferDevice.isVisible())
{
// remember last OutDev and set to content
OutputDevice* pLastOutputDevice = mpOutputDevice;
mpOutputDevice = &aBufferDevice.getContent();
mpOutputDevice->Erase();
// Since the effect converts all children to bitmap, we can't disable antialiasing here,
// because it would result in poor quality in areas not affected by the effect
process(rCandidate);
const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
static_cast<long>(std::floor(aRange.getMinY())),
static_cast<long>(std::ceil(aRange.getMaxX())),
static_cast<long>(std::ceil(aRange.getMaxY())));
BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
AlphaMask aMask = bitmap.GetAlpha();
AlphaMask blurMask = ProcessAndBlurAlphaMask(aMask, -fBlurRadius, fBlurRadius, 0);
aMask.BlendWith(blurMask);
// The end result is the original bitmap with blurred 8-bit alpha mask
BitmapEx result(bitmap.GetBitmap(), aMask);
// back to old OutDev
mpOutputDevice = pLastOutputDevice;
mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
}
else
SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
}
void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate)
{
if (rCandidate.getShadowBlur() == 0)
{
process(rCandidate);
return;
}
basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
aRange.transform(maCurrentTransformation);
basegfx::B2DVector aBlurRadiusVector(rCandidate.getShadowBlur(), 0);
aBlurRadiusVector *= maCurrentTransformation;
const double fBlurRadius = aBlurRadiusVector.getLength();
impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
if (aBufferDevice.isVisible())
{
OutputDevice* pLastOutputDevice = mpOutputDevice;
mpOutputDevice = &aBufferDevice.getContent();
mpOutputDevice->Erase();
process(rCandidate);
const tools::Rectangle aRect(static_cast<long>(std::floor(aRange.getMinX())),
static_cast<long>(std::floor(aRange.getMinY())),
static_cast<long>(std::ceil(aRange.getMaxX())),
static_cast<long>(std::ceil(aRange.getMaxY())));
BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
AlphaMask mask = ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0, fBlurRadius, 0);
const basegfx::BColor aShadowColor(
maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor()));
Bitmap bitmap = bitmapEx.GetBitmap();
bitmap.Erase(Color(aShadowColor));
BitmapEx result(bitmap, mask);
mpOutputDevice = pLastOutputDevice;
mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
}
else
SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
}
} // end of namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */