/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(rCandidate)); break; } case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: { processTextSimplePortionPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: { processTextDecoratedPortionPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: { processPolygonHairlinePrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: { // direct draw of transformed BitmapEx primitive processBitmapPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: { // direct draw of fillBitmapPrimitive RenderFillGraphicPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: { processPolyPolygonGradientPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: { // direct draw of bitmap RenderPolyPolygonGraphicPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: { processPolyPolygonColorPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: { processMetaFilePrimitive2D(rCandidate); break; } case PRIMITIVE2D_ID_MASKPRIMITIVE2D: { // mask group. RenderMaskPrimitive2DPixel( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: { // modified color group. Force output to unified color. RenderModifiedColorPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: { processUnifiedTransparencePrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: { // sub-transparence group. Draw to VDev first. RenderTransparencePrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: { // transform group. RenderTransformPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: { // new XDrawPage for ViewInformation2D RenderPagePreviewPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: { // marker array RenderMarkerArrayPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: { // point array RenderPointArrayPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: { processControlPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: { processPolygonStrokePrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D: { processFillHatchPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: { processBackgroundColorPrimitive2D( static_cast(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(rCandidate)); break; } case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D: { RenderSvgLinearAtomPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D: { RenderSvgRadialAtomPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D: { processBorderLinePrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: { processGlowPrimitive2D( static_cast(rCandidate)); break; } case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: { processSoftEdgePrimitive2D( static_cast(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(xReference.get()); if (pBasePrimitive) { switch (pBasePrimitive->getPrimitive2DID()) { case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: { // single transparent tools::PolyPolygon identified, use directly const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor = static_cast( 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( 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( 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& rXControl(rControlPrimitive.getXControl()); try { // remember old graphics and create new uno::Reference xControlView(rXControl, uno::UNO_QUERY_THROW); const uno::Reference xOriginalGraphics(xControlView->getGraphics()); const uno::Reference 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 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(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(floor(aViewport.getMinX())), static_cast(floor(aViewport.getMinY())), static_cast(ceil(aViewport.getMaxX())), static_cast(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); 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); process(rCandidate); const tools::Rectangle aRect(static_cast(std::floor(aRange.getMinX())), static_cast(std::floor(aRange.getMinY())), static_cast(std::ceil(aRange.getMaxX())), static_cast(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) { 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); if (aBufferDevice.isVisible()) { // remember last OutDev and set to content OutputDevice* pLastOutputDevice = mpOutputDevice; mpOutputDevice = &aBufferDevice.getContent(); // 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(std::floor(aRange.getMinX())), static_cast(std::floor(aRange.getMinY())), static_cast(std::ceil(aRange.getMaxX())), static_cast(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"); } } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */