office-gobmx/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
Armin Le Grand (allotropia) 0acab5124c Remove unused BColorModifierStack from VclProcessor constructor
Was not used and - if needed - should be done by embedding
the sequence of Primitives sent to it to a
ModifiedColorPrimitive2D containing the intended
BColorModifier. Keep clases as simple as possible.

Change-Id: I99de18cc971d6b396ad291ef88b59bd7599f9196
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174314
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Tested-by: Jenkins
2024-10-01 15:23:33 +02:00

1132 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 <comphelper/lok.hxx>
#include <sal/log.hxx>
#include <vcl/outdev.hxx>
#include <vcl/hatch.hxx>
#include <vcl/canvastools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/utils/gradienttools.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/Tools.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.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/controlprimitive2d.hxx>
#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
#include <drawinglayer/primitive2d/fillgradientprimitive2d.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/shadowprimitive2d.hxx>
#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/XControl.hpp>
#include <officecfg/Office/Common.hxx>
#include <vcl/gradient.hxx>
using namespace com::sun::star;
namespace drawinglayer::processor2d
{
VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
OutputDevice& rOutDev)
: VclProcessor2D(rViewInformation, rOutDev)
, m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
, m_bRenderSimpleTextDirect(
officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get())
, m_bRenderDecoratedTextDirect(
officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
{
// prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels
maCurrentTransformation = rViewInformation.getObjectToViewTransformation();
// prepare output directly to pixels
mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
mpOutputDevice->SetMapMode();
// react on AntiAliasing settings
if (rViewInformation.getUseAntiAliasing())
{
mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing | AntialiasingFlags::Enable);
}
else
{
mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing & ~AntialiasingFlags::Enable);
}
}
VclPixelProcessor2D::~VclPixelProcessor2D()
{
// restore MapMode
mpOutputDevice->Pop();
// restore AntiAliasing
mpOutputDevice->SetAntialiasing(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()));
if (comphelper::LibreOfficeKit::isActive() && aPolygonColor.isAutomatic())
mpOutputDevice->SetFillColor(getViewInformation2D().getAutoColor());
else
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());
}
void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
{
switch (rCandidate.getPrimitive2DID())
{
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_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_FILLGRADIENTPRIMITIVE2D:
{
processFillGradientPrimitive2D(
static_cast<const drawinglayer::primitive2d::FillGradientPrimitive2D&>(rCandidate));
break;
}
case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D:
{
processPatternFillPrimitive2D(
static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate));
break;
}
default:
{
SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
rCandidate.getPrimitive2DID()));
// process recursively
process(rCandidate);
break;
}
}
}
void VclPixelProcessor2D::processTextSimplePortionPrimitive2D(
const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
{
// Adapt evtl. used special DrawMode
const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
adaptTextToFillDrawMode();
if (SAL_LIKELY(m_bRenderSimpleTextDirect))
{
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 (SAL_LIKELY(m_bRenderDecoratedTextDirect))
{
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)
{
basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
// no geometry, no need to render, done
if (!aLocalPolyPolygon.count())
return;
// *try* direct draw (AKA using old VCL stuff) to render gradient
const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
// MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
// so use decomposition
// NOTE: There may be even more reasons to detect, e.g. a ViewTransformation
// that uses shear/rotate/mirror (what VCL cannot handle at all), see
// other checks already in processFillGradientPrimitive2D
if (rGradient.cannotBeHandledByVCL())
{
process(rPolygonCandidate);
return;
}
basegfx::BColor aStartColor(
maBColorModifierStack.getModifiedColor(rGradient.getColorStops().front().getStopColor()));
basegfx::BColor aEndColor(
maBColorModifierStack.getModifiedColor(rGradient.getColorStops().back().getStopColor()));
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);
return;
}
// use the primitive decomposition
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 && getViewInformation2D().getUseAntiAliasing()
&& (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable)))
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::BasePrimitive2D* pBasePrimitive = rContent.front().get();
switch (pBasePrimitive->getPrimitive2DID())
{
case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
{
// single transparent tools::PolyPolygon identified, use directly
const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor
= static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
pBasePrimitive);
assert(pPoPoColor && "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())
{
// 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);
bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is()
&& xControlWindow->isVisible());
// tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple
// reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the
// office is in non-layout mode (default for controls at startup). For the common office
// this means that there exists a real VCL-System-Window for the control, so it is *not*
// painted here due to being exactly obscured by that real Window (and creates danger of
// flickering, too).
// Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but
// exactly that would be needed on each client displaying the tiles (what would be hard
// to do but also would have advantages - the clients would have real controls in the
// shape of their target system which could be interacted with...). It is also what the
// office does.
// For now, fallback to just render these controls when Tiled Rendering is active to just
// have them displayed on all clients.
if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive())
{
// Do force paint when we are in Tiled Renderer and FormControl is 'visible'
bControlIsVisibleAsChildWindow = false;
}
if (!bControlIsVisibleAsChildWindow)
{
// Needs to be drawn. Link new 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));
// 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 (getViewInformation2D().getUseAntiAliasing())
{
// 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_uInt32 nAngle10(
basegfx::rad2deg<10>(basegfx::fround(rFillHatchAttributes.getAngle())));
::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance,
Degree10(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::Enable);
// create color for fill
const basegfx::BColor aPolygonColor(
maBColorModifierStack.getModifiedColor(rPrimitive.getBColor()));
Color aFillColor(aPolygonColor);
aFillColor.SetAlpha(255 - 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::Enable);
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::Enable);
// process content recursively
process(rCandidate);
// restore OutDev
mpOutputDevice->Pop();
mpOutputDevice->SetAntialiasing(nAntiAliasing);
}
void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
{
// #i98289#
const bool bForceLineSnap(getViewInformation2D().getPixelSnapHairline());
const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
if (bForceLineSnap)
{
mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
}
process(rCandidate);
if (bForceLineSnap)
{
mpOutputDevice->SetAntialiasing(nOldAntiAliase);
}
}
void VclPixelProcessor2D::processFillGradientPrimitive2D(
const primitive2d::FillGradientPrimitive2D& rPrimitive)
{
if (rPrimitive.hasAlphaGradient() || rPrimitive.hasTransparency())
{
// SDPR: As long as direct alpha is not supported by this
// renderer we need to work on the decomposition, so call it
process(rPrimitive);
return;
}
const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient();
bool useDecompose(false);
// MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
// so use decomposition
if (rFillGradient.cannotBeHandledByVCL())
{
useDecompose = true;
}
// tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions,
// what should be clear due to being developed to extend/replace them in
// capabilities & precision.
// It is e.g. not capable to correctly paint if the OutputRange is not completely
// inside the DefinitionRange, thus forcing to paint gradient parts *outside* the
// DefinitionRange.
// This happens for Writer with Frames anchored in Frames (and was broken due to
// falling back to VCL Gradient paint here), and for the new SlideBackgroundFill
// Fill mode for objects using it that reach outside the page (which is the
// DefinitionRange in that case).
// I see no real reason to fallback here to OutputDevice::DrawGradient and VCL
// gradient paint at all (system-dependent renderers wouldn't in the future), but
// will for convenience only add that needed additional correcting case
if (!useDecompose && !rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange()))
{
useDecompose = true;
}
// tdf#151081 need to use regular primitive decomposition when the gradient
// is transformed in any other way then just translate & scale
if (!useDecompose)
{
basegfx::B2DVector aScale, aTranslate;
double fRotate, fShearX;
maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
// detect if transformation is rotated, sheared or mirrored in X and/or Y
if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)
|| aScale.getX() < 0.0 || aScale.getY() < 0.0)
{
useDecompose = true;
}
}
if (useDecompose)
{
// default is to use the direct render below. For security,
// keep the (simple) fallback to decompose in place here
static bool bTryDirectRender(true);
if (bTryDirectRender)
{
// MCGR: Avoid one level of primitive creation, use FillGradientPrimitive2D
// tooling to directly create needed geometry & color for getting better
// performance (partially compensate for potentially more expensive multi
// color gradients).
// To handle a primitive that needs paint, either use decompose, or - when you
// do not want that for any reason, e.g. extra primitives created - implement
// a direct handling in your primitive renderer. This is always possible
// since primitives by definition are self-contained what means they have all
// needed data locally available to do so.
// The question is the complexity to invest - the implemented decompose
// is always a good hint of what is needed to do this. In this case I decided
// to add some tooling methods to the primitive itself to support this. These
// are used in decompose and can be used - as here now - for direct handling,
// too. This is always a possibility in primitive handling - you can, but do not
// have to.
mpOutputDevice->SetFillColor(
Color(maBColorModifierStack.getModifiedColor(rPrimitive.getOuterColor())));
mpOutputDevice->SetLineColor();
mpOutputDevice->DrawTransparent(
maCurrentTransformation,
basegfx::B2DPolyPolygon(
basegfx::utils::createPolygonFromRect(rPrimitive.getOutputRange())),
0.0);
// paint solid fill steps by providing callback as lambda
auto aCallback([&rPrimitive, this](const basegfx::B2DHomMatrix& rMatrix,
const basegfx::BColor& rColor) {
// create part polygon
basegfx::B2DPolygon aNewPoly(rPrimitive.getUnitPolygon());
aNewPoly.transform(rMatrix);
// create solid fill
mpOutputDevice->SetFillColor(Color(maBColorModifierStack.getModifiedColor(rColor)));
mpOutputDevice->DrawTransparent(maCurrentTransformation,
basegfx::B2DPolyPolygon(aNewPoly), 0.0);
});
// call value generator to trigger callbacks
rPrimitive.generateMatricesAndColors(aCallback);
}
else
{
// use the decompose
process(rPrimitive);
}
return;
}
// try to use vcl - since vcl uses the old gradient paint mechanisms this may
// create wrong geometries. If so, add another case above for useDecompose
Gradient aGradient(rFillGradient.getStyle(),
Color(rFillGradient.getColorStops().front().getStopColor()),
Color(rFillGradient.getColorStops().back().getStopColor()));
aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle()))));
aGradient.SetBorder(rFillGradient.getBorder() * 100);
aGradient.SetOfsX(rFillGradient.getOffsetX() * 100.0);
aGradient.SetOfsY(rFillGradient.getOffsetY() * 100.0);
aGradient.SetSteps(rFillGradient.getSteps());
basegfx::B2DRange aOutputRange(rPrimitive.getOutputRange());
aOutputRange.transform(maCurrentTransformation);
basegfx::B2DRange aFullRange(rPrimitive.getDefinitionRange());
aFullRange.transform(maCurrentTransformation);
const tools::Rectangle aOutputRectangle(
std::floor(aOutputRange.getMinX()), std::floor(aOutputRange.getMinY()),
std::ceil(aOutputRange.getMaxX()), std::ceil(aOutputRange.getMaxY()));
const tools::Rectangle aFullRectangle(
std::floor(aFullRange.getMinX()), std::floor(aFullRange.getMinY()),
std::ceil(aFullRange.getMaxX()), std::ceil(aFullRange.getMaxY()));
mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
mpOutputDevice->IntersectClipRegion(aOutputRectangle);
mpOutputDevice->DrawGradient(aFullRectangle, aGradient);
mpOutputDevice->Pop();
}
void VclPixelProcessor2D::processPatternFillPrimitive2D(
const primitive2d::PatternFillPrimitive2D& rPrimitive)
{
const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange();
if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0
|| rReferenceRange.getHeight() <= 0.0)
return;
basegfx::B2DPolyPolygon aMask = rPrimitive.getMask();
aMask.transform(maCurrentTransformation);
const basegfx::B2DRange aMaskRange(aMask.getB2DRange());
if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0)
return;
sal_uInt32 nTileWidth, nTileHeight;
rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D());
if (nTileWidth == 0 || nTileHeight == 0)
return;
BitmapEx aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight);
tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange);
// Unless smooth edges are needed, simply use clipping.
if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
{
mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
Wallpaper aWallpaper(aTileImage);
aWallpaper.SetColor(COL_TRANSPARENT);
Point aPaperPt(aMaskRect.getX() % nTileWidth, aMaskRect.getY() % nTileHeight);
tools::Rectangle aPaperRect(aPaperPt, aTileImage.GetSizePixel());
aWallpaper.SetRect(aPaperRect);
mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
mpOutputDevice->Pop();
return;
}
impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRange);
if (!aBufferDevice.isVisible())
return;
// remember last OutDev and set to content
OutputDevice* pLastOutputDevice = mpOutputDevice;
mpOutputDevice = &aBufferDevice.getContent();
// if the tile is a single pixel big, just flood fill with that pixel color
if (nTileWidth == 1 && nTileHeight == 1)
{
Color col = aTileImage.GetPixelColor(0, 0);
mpOutputDevice->SetLineColor(col);
mpOutputDevice->SetFillColor(col);
mpOutputDevice->DrawRect(aMaskRect);
}
else
{
Wallpaper aWallpaper(aTileImage);
aWallpaper.SetColor(COL_TRANSPARENT);
Point aPaperPt(aMaskRect.getX() % nTileWidth, aMaskRect.getY() % nTileHeight);
tools::Rectangle aPaperRect(aPaperPt, aTileImage.GetSizePixel());
aWallpaper.SetRect(aPaperRect);
mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
}
// back to old OutDev
mpOutputDevice = pLastOutputDevice;
// draw mask
VirtualDevice& rMask = aBufferDevice.getTransparence();
rMask.SetLineColor();
rMask.SetFillColor(COL_BLACK);
rMask.DrawPolyPolygon(aMask);
// dump buffer to outdev
aBufferDevice.paint();
}
} // end of namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */