forked from amazingfate/loongoffice
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>
1127 lines
46 KiB
C++
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: */
|