diff options
author | Armin Le Grand (Allotropia) <Armin.Le.Grand@me.com> | 2023-01-13 10:42:44 +0100 |
---|---|---|
committer | Armin Le Grand <Armin.Le.Grand@me.com> | 2023-01-13 20:00:19 +0000 |
commit | 9455aae461685809191a3051cf86a208fa5946b4 (patch) | |
tree | 0700fe7f670945732568c63e522af156499fca2a | |
parent | a25eda715591cfa96136bcfd95360156516239d1 (diff) |
SDPR: Direct support for FillGraphicPrimitive2D
Added support to directly paint FillGraphicPrimitive2D, that
means object fill with multiple tiles/tiled fill. Note: This
may use not only bitmap data, but also metafile or svg.
The standard decompose creates one transformed bitmap/vector
data primitive per tile what is correct, but has some limits
(a) These may get many when tiles are small compared to
mask polyPolygon (performance/ressources)
(b) Correctness: when rendering in AA the common edges of
tiles will *not* sum up perfectly, but due to AA
multiplying when blending will leave ugly 'traces'
This direct rendering avoids both. It can use the D2D Brush
functionality to repeat. Additionally to the current
office it can also do that when content is rotated/sheared.
This may not be well known since for those fills the office
always keeps the fill unrotated (for historical reasons,
primitives can do that, but not vcl). To see it you may
convert a SdrObject to metafile and rotate that, so the
content gets rotated and shows that ugly gaps.
Also added a square-pixel step value for vector fills where
the default gets used starting from some size. The argument
is better quality for vector data fills while only a
limited number of tiles will be rendered.
Also added a buffered handling of the (old and ugly)
OffsetX/OffsetY to make that work, we will not get rid of
that soon.
Packed all of this into tooling (so started a tooling
collection for SDPRs target-system independent) to be able
to easily re-use all these preparations/tests in other
SDPR implementations in the future.
Change-Id: Iafacf4181e7c9e2d2b2e3b5cf1f7e4cdd0a43f0a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145466
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
8 files changed, 571 insertions, 17 deletions
diff --git a/drawinglayer/Library_drawinglayer.mk b/drawinglayer/Library_drawinglayer.mk index 884c32b170a8..40abc8d0f0fa 100644 --- a/drawinglayer/Library_drawinglayer.mk +++ b/drawinglayer/Library_drawinglayer.mk @@ -66,6 +66,7 @@ endif ifeq ($(OS),WNT) $(eval $(call gb_Library_add_exception_objects,drawinglayer,\ drawinglayer/source/processor2d/d2dpixelprocessor2d \ + drawinglayer/source/processor2d/SDPRProcessor2dTools \ )) endif diff --git a/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx index be58c77dd390..2eb1373df0ae 100644 --- a/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx +++ b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx @@ -116,7 +116,15 @@ namespace drawinglayer::unorenderer MaximumQuadraticPixels = 500000; } - const auto aViewInformation2D = geometry::createViewInformation2D(aViewInformationSequence); + auto aViewInformation2D = geometry::createViewInformation2D(aViewInformationSequence); + + if(aViewInformation2D.getViewport().isEmpty()) + { + // we have a Viewport since we create a discrete pixel device, use it + // if none is given + aViewInformation2D.setViewport(aRange); + } + const sal_uInt32 nDiscreteWidth(basegfx::fround(o3tl::convert(fWidth, eRangeUnit, o3tl::Length::in) * DPI_X)); const sal_uInt32 nDiscreteHeight(basegfx::fround(o3tl::convert(fHeight, eRangeUnit, o3tl::Length::in) * DPI_Y)); diff --git a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx index 383d199f52b0..8c50993e6033 100644 --- a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx @@ -95,7 +95,8 @@ namespace drawinglayer::primitive2d basegfx::B2DHomMatrix aTransformation, const attribute::FillGraphicAttribute& rFillGraphic) : maTransformation(std::move(aTransformation)), - maFillGraphic(rFillGraphic) + maFillGraphic(rFillGraphic), + maOffsetXYCreatedBitmap() { } diff --git a/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx new file mode 100755 index 000000000000..6133e7a99936 --- /dev/null +++ b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx @@ -0,0 +1,277 @@ +/* -*- 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 <drawinglayer/processor2d/SDPRProcessor2dTools.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <vcl/bitmapex.hxx> +#include <basegfx/range/b2drange.hxx> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif + +namespace drawinglayer::processor2d +{ +void setOffsetXYCreatedBitmap( + drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const BitmapEx& rBitmap) +{ + rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap); +} + +void takeCareOfOffsetXY( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0 + && rFillGraphicAttribute.getOffsetX() < 1.0); + const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0 + && rFillGraphicAttribute.getOffsetY() < 1.0); + + if (bOffsetXIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long w(rSize.Width()); + const tools::Long a(basegfx::fround(w * (1.0 - rFillGraphicAttribute.getOffsetX()))); + + if (0 != a && w != a) + { + const tools::Long h(rSize.Height()); + const tools::Long b(w - a); + BitmapEx aTarget(Size(w, h * 2), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2)); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + &rTarget); + const Size aSizeA(b, h); + aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst + tools::Rectangle(Point(a, 0), aSizeA), // Src + &rTarget); + const Size aSizeB(a, h); + aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + &rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight())); + } + } + else if (bOffsetYIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long h(rSize.Height()); + const tools::Long a(basegfx::fround(h * (1.0 - rFillGraphicAttribute.getOffsetY()))); + + if (0 != a && h != a) + { + const tools::Long w(rSize.Width()); + const tools::Long b(h - a); + BitmapEx aTarget(Size(w * 2, h), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height())); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + &rTarget); + const Size aSizeA(w, b); + aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst + tools::Rectangle(Point(0, a), aSizeA), // Src + &rTarget); + const Size aSizeB(w, a); + aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + &rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY())); + } + } +} + +bool prepareBitmapForDirectRender( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget, + basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const Graphic& rGraphic(rFillGraphicAttribute.getGraphic()); + + if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone()) + { + // default attributes or GraphicType::NONE, so no fill .-> done + return false; + } + + if (!rFillGraphicAttribute.getTiling()) + { + // If no tiling used, the Graphic will need to be painted just once. This + // is perfectly done using the decomposition, so use it. + // What we want to do here is to optimize tiled paint, for two reasons: + // (a) speed: draw one tile, repeat -> obvious + // (b) correctness: not so obvious, but since in AAed paint the same edge + // of touching polygons both AAed do *not* sum up, but get blended by + // multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible, + // not only with filled polygons, but also with bitmaps + // Signal that paint is needed + return true; + } + + if (rFillUnitRange.isEmpty()) + { + // no fill range definition, no fill, done + return false; + } + + const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport()); + + if (!rDiscreteViewPort.isEmpty()) + { + // calculate discrete covered pixel area + basegfx::B2DRange aDiscreteRange(0.0, 0.0, 1.0, 1.0); + aDiscreteRange.transform(aLocalTransform); + + if (!rDiscreteViewPort.overlaps(rDiscreteViewPort)) + { + // we have a Viewport and visible range of geometry is outside -> not visible, done + return false; + } + } + + if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated()) + { + // Need to prepare specialized AnimatedGraphicPrimitive2D, + // cannot handle here. Signal that paint is needed + return true; + } + + if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData()) + { + // bitmap graphic, always handle locally, so get bitmap data independent + // if it'sie or it's discrete display size + rTarget = rGraphic.GetBitmapEx(); + } + else + { + // Vector Graphic Data fill, including metafile: + // We can know about discrete pixel size here, calculate and use it. + // To do so, using Vectors is sufficient to get the lengths. It is + // not necessary to transform the whole target coordinate system. + const basegfx::B2DVector aDiscreteXAxis( + aLocalTransform + * basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0)); + const basegfx::B2DVector aDiscreteYAxis( + aLocalTransform + * basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY())); + + // get and ensure minimal size + const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength())); + const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength())); + + // compare with a big visualization size in discrete pixels + const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight); + + if (fTargetDiscreteArea > fBigDiscreteArea) + { + // When the vector data is visualized big it is better to not handle here + // but use decomposition fallback which then will visualize the vector data + // directly -> better quality, acceptable number of tile repeat(s) + // signal that paint is needed + return true; + } + else + { + // If visualized small, the amount of repeated fills gets expensive, so + // in that case use a Bitmap and the Brush technique below. + // The Bitmap may be created here exactly for the needed target size + // (using local D2DBitmapPixelProcessor2D and the vector data), + // but since we have a HW renderer and re-use of system-dependent data + // at BitmapEx is possible, just get the default fallback Bitmap from the + // vector data to continue. Trust the existing converters for now to + // do something with good quality. + rTarget = rGraphic.GetBitmapEx(); + } + } + + if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty()) + { + // no pixel data, done + return false; + } + + // react if OffsetX/OffsetY of the FillGraphicAttribute is used + takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange); + +#ifdef DBG_UTIL + // allow to check bitmap data, e.g. control OffsetX/OffsetY stuff + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_getreplacement.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(rTarget); + } + } +#endif + + // signal to render it + return true; +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx index 2f3d6e25dced..cedf276e0c3c 100644 --- a/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx @@ -26,6 +26,7 @@ #include <postwin.h> #include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx> +#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx> #include <sal/log.hxx> #include <vcl/outdev.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> @@ -1109,15 +1110,18 @@ void D2DPixelProcessor2D::processTransparencePrimitive2D( basegfx::B2DRange aDiscreteRange( rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation()); - const D2D1_SIZE_U aB2DSizePixel(getRenderTarget()->GetPixelSize()); - const basegfx::B2DRange aViewRange(0.0, 0.0, aB2DSizePixel.width, aB2DSizePixel.height); + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); basegfx::B2DRange aVisibleRange(aDiscreteRange); - aVisibleRange.intersect(aViewRange); - if (aVisibleRange.isEmpty()) + if (!rDiscreteViewPort.isEmpty()) { - // not visible, done - return; + aVisibleRange.intersect(rDiscreteViewPort); + + if (aVisibleRange.isEmpty()) + { + // not visible, done + return; + } } // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1 @@ -1204,12 +1208,11 @@ void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D( basegfx::B2DRange aTransparencyRange( rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); aTransparencyRange.transform(getViewInformation2D().getObjectToViewTransformation()); - const D2D1_SIZE_U aB2DSizePixel(getRenderTarget()->GetPixelSize()); - const basegfx::B2DRange aViewRange(0.0, 0.0, aB2DSizePixel.width, aB2DSizePixel.height); + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); - // not visible, done - if (!aViewRange.overlaps(aTransparencyRange)) + if (!rDiscreteViewPort.isEmpty() && !rDiscreteViewPort.overlaps(aTransparencyRange)) { + // we have a Viewport and visible range of geometry is outside -> not visible, done return; } @@ -1255,12 +1258,11 @@ void D2DPixelProcessor2D::processMaskPrimitive2DPixel( basegfx::B2DRange aMaskRange(aMask.getB2DRange()); aMaskRange.transform(getViewInformation2D().getObjectToViewTransformation()); - const D2D1_SIZE_U aB2DSizePixel(getRenderTarget()->GetPixelSize()); - const basegfx::B2DRange aViewRange(0.0, 0.0, aB2DSizePixel.width, aB2DSizePixel.height); + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); - if (!aViewRange.overlaps(aMaskRange)) + if (!rDiscreteViewPort.isEmpty() && !rDiscreteViewPort.overlaps(aMaskRange)) { - // not in visible range, done + // we have a Viewport and visible range of geometry is outside -> not visible, done return; } @@ -1768,6 +1770,129 @@ void D2DPixelProcessor2D::processSingleLinePrimitive2D( increaseError(); } +void D2DPixelProcessor2D::processFillGraphicPrimitive2D( + const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D) +{ + BitmapEx aPreparedBitmap; + basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange()); + static double fBigDiscreteArea(300.0 * 300.0); + + // use tooling to do various checks and prepare tiled rendering, see + // description of method, parameters and return value there + if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(), + aPreparedBitmap, aFillUnitRange, fBigDiscreteArea)) + { + // no output needed, done + return; + } + + if (aPreparedBitmap.IsEmpty()) + { + // output needed and Bitmap data empty, so no bitmap data based + // tiled rendering is suggested. Use fallback for paint (decompositon) + process(rFillGraphicPrimitive2D); + return; + } + + // render tiled using the prepared Bitmap data + if (maBColorModifierStack.count()) + { + // need to apply ColorModifier to Bitmap data + aPreparedBitmap = aPreparedBitmap.ModifyBitmapEx(maBColorModifierStack); + + if (aPreparedBitmap.IsEmpty()) + { + // color gets completely replaced, get it (any input works) + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + // what we still need to apply is the object transform from the + // local primitive, that is not part of DisplayInfo yet + aPolygon.transform(rFillGraphicPrimitive2D.getTransformation()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + + // draw as colored Polygon, done + processPolyPolygonColorPrimitive2D(*aTemp); + return; + } + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap)); + + if (pD2DBitmap) + { + sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush; + const HRESULT hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap, &pBitmapBrush)); + + if (SUCCEEDED(hr) && pBitmapBrush) + { + // set extended to repeat/wrap AKA tiling + pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP); + pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP); + + // set interpolation mode + // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be + // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs + // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1 + sal::systools::COMReference<ID2D1BitmapBrush1> pBrush1; + pBitmapBrush->QueryInterface(__uuidof(ID2D1BitmapBrush1), + reinterpret_cast<void**>(&pBrush1)); + + if (pBrush1) + { + pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR); + } + else + { + pBitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); + } + + // set BitmapBrush transformation relative to it's PixelSize and + // the used FillUnitRange. Since we use unit coordinates here this + // is pretty simple + const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize()); + const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX()) + / aBMSPixel.width); + const double fScaleY((aFillUnitRange.getMaxY() - aFillUnitRange.getMinY()) + / aBMSPixel.height); + const D2D1_MATRIX_3X2_F aBTrans(D2D1::Matrix3x2F( + fScaleX, 0.0, 0.0, fScaleY, aFillUnitRange.getMinX(), aFillUnitRange.getMinY())); + pBitmapBrush->SetTransform(&aBTrans); + + // set transform to ObjectToWorld to be able to paint in unit coordinates, so + // evtl. shear/rotate in that transform is used and does not influence the + // orthogonal and unit-oriented brush handling + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + + // use unit rectangle, transformation is already set to include ObjectToWorld + const D2D1_RECT_F rect = { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) }; + + // draw as unit rectangle as brush filled rectangle + getRenderTarget()->FillRectangle(&rect, pBitmapBrush); + bDone = true; + } + } + + if (!bDone) + increaseError(); +} + void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) { if (0 == mnRecursionCounter) @@ -1915,6 +2040,12 @@ void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate)); break; } + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + processFillGraphicPrimitive2D( + static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); + break; + } // continue with decompose as fallback default: diff --git a/include/drawinglayer/primitive2d/fillgraphicprimitive2d.hxx b/include/drawinglayer/primitive2d/fillgraphicprimitive2d.hxx index bf8222f23785..899851adc84e 100644 --- a/include/drawinglayer/primitive2d/fillgraphicprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/fillgraphicprimitive2d.hxx @@ -24,10 +24,24 @@ #include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> #include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <vcl/bitmapex.hxx> +namespace drawinglayer::primitive2d +{ +class FillGraphicPrimitive2D; +} -// FillbitmapPrimitive2D class +namespace drawinglayer::processor2d +{ +// define a simple accessor which can be used as friend. That method exists +// only locally at SDPRProcessor2dTools.cxx and is thus only usable/callable +// from there +void setOffsetXYCreatedBitmap( + drawinglayer::primitive2d::FillGraphicPrimitive2D&, + const BitmapEx&); +} +// FillbitmapPrimitive2D class namespace drawinglayer::primitive2d { /** FillGraphicPrimitive2D class @@ -52,9 +66,23 @@ namespace drawinglayer::primitive2d /// the fill attributes attribute::FillGraphicAttribute maFillGraphic; + /// the evtl. buffered OffsetXYCreatedBitmap + BitmapEx maOffsetXYCreatedBitmap; + /// local decomposition. virtual void create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const override; + // allow this single acessor to change it to set buggered data + friend void drawinglayer::processor2d::setOffsetXYCreatedBitmap( + drawinglayer::primitive2d::FillGraphicPrimitive2D&, + const BitmapEx&); + + // private tooling method to be called by setOffsetXYCreatedBitmap + void impSetOffsetXYCreatedBitmap(const BitmapEx& rBitmap) + { + maOffsetXYCreatedBitmap = rBitmap; + } + public: /// constructor FillGraphicPrimitive2D( @@ -64,6 +92,7 @@ namespace drawinglayer::primitive2d /// data read access const basegfx::B2DHomMatrix& getTransformation() const { return maTransformation; } const attribute::FillGraphicAttribute& getFillGraphic() const { return maFillGraphic; } + const BitmapEx& getOffsetXYCreatedBitmap() const { return maOffsetXYCreatedBitmap; } /// compare operator virtual bool operator==( const BasePrimitive2D& rPrimitive ) const override; diff --git a/include/drawinglayer/processor2d/SDPRProcessor2dTools.hxx b/include/drawinglayer/processor2d/SDPRProcessor2dTools.hxx new file mode 100755 index 000000000000..82791ac3eb4e --- /dev/null +++ b/include/drawinglayer/processor2d/SDPRProcessor2dTools.hxx @@ -0,0 +1,104 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +namespace drawinglayer::primitive2d +{ +class FillGraphicPrimitive2D; +} + +namespace geometry +{ +class ViewInformation2D; +} + +namespace basegfx +{ +class B2DRange; +} + +class BitmapEx; + +namespace drawinglayer::processor2d +{ +/** helper to process FillGraphicPrimitive2D: + + In places that want to implement direct rendering of this primitive + e.g. in SDPRs all impls would need to handle the FillGraphicAttribute + settings and the type of Graphic. Unify this by this helper in one place + since this may get complicated (many cases to cover). + It will create and return a BitmapEx when direct tiled rendering is + preferrable and suggested. + Of course every impl may still do what it wants, this is just to make + implementations easier. + + @param rFillGraphicPrimitive2D + The primitive to work on + + @param rViewInformation2D + The ViewInformation to work with (from the processor) + + @param rTarget + The prepared PixelData to use for tiled rendering. If this + is empty on return this means to evtl. use the decompose. + Please hand in an empty one to make this work. + + @param rFillUnitRange + This is a modifyable copy of FillGraphicAttribute.getGraphicRange(). We + need a modifyable one since params since OffsetX/OffsetY in + FillGraphicAttribute may require to change/adapt this if used + + @param fBigDiscreteArea + Defines starting with which number of square pixels a target is seen + to be painted 'big' + + @return + false: rendering is not needed (invalid, outside, ...), done + true: rendering is needed + -> if rTarget is filled, use for tiled rendering + -> if not, use fallback (primitive decomposition) + + For the various reasons/things that get checked/tested/done, please + see the implementation + */ +bool prepareBitmapForDirectRender( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget, + basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea = 300.0 * 300.0); + +/** helper to react/process if OffsetX/OffsetY of the FillGraphicAttribute is used. + + This is old but hard to remove stuff that allows hor/ver offset when + tiled fill is used. To solve that, create pixel data that doubles + resp. in width/height and copies the off-setted version of the bitmap + information to the extra space, so rendering does not need to do that. + + Since this doubles the geometry, an adaption of the used fill range + (here rFillUnitRange in unit coordinates) also needs to be adapted, + refer to usage. + */ +void takeCareOfOffsetXY( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/drawinglayer/processor2d/d2dpixelprocessor2d.hxx b/include/drawinglayer/processor2d/d2dpixelprocessor2d.hxx index 10f9a11cb296..8d1929a2f522 100644 --- a/include/drawinglayer/processor2d/d2dpixelprocessor2d.hxx +++ b/include/drawinglayer/processor2d/d2dpixelprocessor2d.hxx @@ -44,6 +44,7 @@ class PolygonStrokePrimitive2D; class LineRectanglePrimitive2D; class FilledRectanglePrimitive2D; class SingleLinePrimitive2D; +class FillGraphicPrimitive2D; } struct ID2D1RenderTarget; @@ -89,6 +90,8 @@ class DRAWINGLAYER_DLLPUBLIC D2DPixelProcessor2D : public BaseProcessor2D const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D); void processSingleLinePrimitive2D(const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D); + void processFillGraphicPrimitive2D( + const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D); // common helpers sal::systools::COMReference<ID2D1Bitmap> |