drawinglayer/source/primitive2d/controlprimitive2d.cxx | 38 +++-- drawinglayer/source/processor2d/cairopixelprocessor2d.cxx | 94 +++++++----- drawinglayer/source/processor2d/vclpixelprocessor2d.cxx | 96 ++++++------- include/drawinglayer/primitive2d/controlprimitive2d.hxx | 3 include/drawinglayer/processor2d/cairopixelprocessor2d.hxx | 2 5 files changed, 141 insertions(+), 92 deletions(-)
New commits: commit f3f70ef02f04d876abb768bb3ec65645ddb8d89d Author: Armin Le Grand (Collabora) <[email protected]> AuthorDate: Wed Dec 18 15:52:12 2024 +0100 Commit: Armin Le Grand <[email protected]> CommitDate: Thu Dec 19 19:04:05 2024 +0100 tdf#164270 CairoSDPR: Correct Control rendering Controls need not to be rendered when they are visible as Windows (they then render themselves). Also added some needed cleanups for better display of control primitive decomposition as Bitmap. Change-Id: I976cd791f9f080a7ab567232303d5d4f0b6dff05 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/178725 Tested-by: Jenkins Reviewed-by: Armin Le Grand <[email protected]> diff --git a/drawinglayer/source/primitive2d/controlprimitive2d.cxx b/drawinglayer/source/primitive2d/controlprimitive2d.cxx index 28d5b5d1d1c2..eb8759bb2169 100644 --- a/drawinglayer/source/primitive2d/controlprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/controlprimitive2d.cxx @@ -30,6 +30,7 @@ #include <vcl/virdev.hxx> #include <vcl/svapp.hxx> #include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XWindow2.hpp> #include <vcl/bitmapex.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <comphelper/diagnose_ex.hxx> @@ -177,21 +178,15 @@ namespace drawinglayer::primitive2d // get bitmap const BitmapEx aContent(aVirtualDevice->GetBitmapEx(Point(), aSizePixel)); - // to avoid scaling, use the Bitmap pixel size as primitive size - const Size aBitmapSize(aContent.GetSizePixel()); - basegfx::B2DVector aBitmapSizeLogic( - rViewInformation.getInverseObjectToViewTransformation() * - basegfx::B2DVector(aBitmapSize.getWidth() - 1, aBitmapSize.getHeight() - 1)); - - if(bScaleUsed) - { - // if scaled adapt to scaled size - aBitmapSizeLogic /= fFactor; - } + // snap translate and scale to discrete position (pixel) to avoid sub-pixel offset and blurring further + basegfx::B2DVector aSnappedTranslate(basegfx::fround(rViewInformation.getObjectToViewTransformation() * aTranslate)); + aSnappedTranslate = rViewInformation.getInverseObjectToViewTransformation() * aSnappedTranslate; + basegfx::B2DVector aSnappedScale(basegfx::fround(rViewInformation.getObjectToViewTransformation() * aScale)); + aSnappedScale = rViewInformation.getInverseObjectToViewTransformation() * aSnappedScale; // short form for scale and translate transformation const basegfx::B2DHomMatrix aBitmapTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( - aBitmapSizeLogic.getX(), aBitmapSizeLogic.getY(), aTranslate.getX(), aTranslate.getY())); + aSnappedScale.getX(), aSnappedScale.getY(), aSnappedTranslate.getX(), aSnappedTranslate.getY())); // create primitive xRetval = new BitmapPrimitive2D( @@ -313,6 +308,25 @@ namespace drawinglayer::primitive2d return aRetval; } + bool ControlPrimitive2D::isVisibleAsChildWindow() const + { + // find out if the control is already visualized as a VCL-ChildWindow + const uno::Reference<awt::XControl>& rXControl(getXControl()); + + try + { + uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW); + return rXControl->getPeer().is() && xControlWindow->isVisible(); + } + catch (const uno::Exception&) + { + // #i116763# since there is a good alternative when the xControlView + // is not found and it is allowed to happen + } + + return false; + } + void ControlPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { // this primitive is view-dependent related to the scaling. If scaling has changed, diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx index 43f0856fd2f7..588afe4b158f 100644 --- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx @@ -18,6 +18,7 @@ #include <vcl/cairo.hxx> #include <vcl/outdev.hxx> #include <vcl/svapp.hxx> +#include <comphelper/lok.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> @@ -46,6 +47,7 @@ #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> #include <drawinglayer/primitive2d/shadowprimitive2d.hxx> #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/controlprimitive2d.hxx> #include <drawinglayer/converters.hxx> #include <drawinglayer/primitive2d/textlayoutdevice.hxx> #include <basegfx/curve/b2dcubicbezier.hxx> @@ -1199,9 +1201,6 @@ void CairoPixelProcessor2D::paintBitmapAlpha(const BitmapEx& rBitmapEx, cairo_stroke(mpRT); } - const sal_uInt32 nWidth(cairo_image_surface_get_width(pTarget)); - const sal_uInt32 nHeight(cairo_image_surface_get_height(pTarget)); - cairo_set_source_surface(mpRT, pTarget, 0, 0); // get the pattern created by cairo_set_source_surface and @@ -1223,38 +1222,21 @@ void CairoPixelProcessor2D::paintBitmapAlpha(const BitmapEx& rBitmapEx, // page shadows, these DO use 8x1/1x8 images which led me to // that problem. I double-checked that these *are* correctly // defined, that is not the problem. - // I see two solutions: - static bool bRenderMasked(true); - - if (bRenderMasked) - { - // Consequence is that these need clipping. That again is - // simple (we are in unit coordinates). Only do for RGBA, - // for RGB this effect does not happen - if (CAIRO_FORMAT_ARGB32 == cairo_image_surface_get_format(pTarget)) - { - cairo_rectangle(mpRT, 0, 0, 1, 1); - cairo_clip(mpRT); - } - - cairo_matrix_scale(&aMatrix, nWidth, nHeight); - } - else - { - // Alternative: for RGBA, resize/scale it SLIGHTLY to force - // that half pixel overlap to be inside the unit range. - // That makes the error disappear, so no clip needed, but - // SLIGHTLY smaller. - if (CAIRO_FORMAT_ARGB32 == cairo_image_surface_get_format(pTarget)) - { - cairo_matrix_init_scale(&aMatrix, nWidth + 1, nHeight + 1); - cairo_matrix_translate(&aMatrix, -0.5 / (nWidth + 1), -0.5 / (nHeight + 1)); - } - else - { - cairo_matrix_scale(&aMatrix, nWidth, nHeight); - } - } + // Decided now to use clipping always. That again is + // simple (we are in unit coordinates) + cairo_rectangle(mpRT, 0, 0, 1, 1); + cairo_clip(mpRT); + cairo_matrix_scale(&aMatrix, cairo_image_surface_get_width(pTarget), + cairo_image_surface_get_height(pTarget)); + + // The alternative wpuld be: resize/scale it SLIGHTLY to force + // that half pixel overlap to be inside the unit range. + // That makes the error disappear, so no clip needed, but + // SLIGHTLY smaller. Keeping this code if someone might have + // to finetune this later for reference. + // + // cairo_matrix_init_scale(&aMatrix, nWidth + 1, nHeight + 1); + // cairo_matrix_translate(&aMatrix, -0.5 / (nWidth + 1), -0.5 / (nHeight + 1)); // The error/effect described above also is connected to the // filter used, so I checked the filter modes available @@ -1272,6 +1254,14 @@ void CairoPixelProcessor2D::paintBitmapAlpha(const BitmapEx& rBitmapEx, // to be on the safe side cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_GOOD); + // also set extend to CAIRO_EXTEND_PAD, else the outside of the + // bitmap is guessed as COL_BLACK and the filtering would blend + // against COL_BLACK what might give strange gray lines at borders + // of white-on-white bitmaps (used e.g. when painting controls). + // NOTE: CAIRO_EXTEND_REPEAT also works with clipping and might be + // broader supported by CVairo implementations + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD); + cairo_pattern_set_matrix(sourcepattern, &aMatrix); // paint bitmap data, evtl. with additional alpha channel @@ -3699,6 +3689,34 @@ void CairoPixelProcessor2D::processSvgRadialGradientPrimitive2D( cairo_restore(mpRT); } +void CairoPixelProcessor2D::processControlPrimitive2D( + const primitive2d::ControlPrimitive2D& rControlPrimitive) +{ + // find out if the control is already visualized as a VCL-ChildWindow + bool bControlIsVisibleAsChildWindow(rControlPrimitive.isVisibleAsChildWindow()); + + // tdf#131281 FormControl rendering for Tiled Rendering + if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive()) + { + // Do force paint when we are in Tiled Renderer and FormControl is 'visible' + bControlIsVisibleAsChildWindow = false; + } + + if (bControlIsVisibleAsChildWindow) + { + // f the control is already visualized as a VCL-ChildWindow it + // does not need to be painted at all + return; + } + + // process recursively and use the decomposition as Bitmap + // NOTE: The VclPixelProcessor2D tries to paint it using + // UNO API and awt::XView/awt::XGraphics to directly paint the + // control. To do so would need the target OutDev which we + // want to avoid here + process(rControlPrimitive); +} + void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) { const cairo_status_t aStart(cairo_status(mpRT)); @@ -3859,6 +3877,12 @@ void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimit static_cast<const primitive2d::SvgRadialGradientPrimitive2D&>(rCandidate)); break; } + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + { + processControlPrimitive2D( + static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); + break; + } // continue with decompose default: diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 1581be4a7c42..0ff0ca53c560 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -624,8 +624,38 @@ void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D( void VclPixelProcessor2D::processControlPrimitive2D( const primitive2d::ControlPrimitive2D& rControlPrimitive) { - // control primitive + // find out if the control is already visualized as a VCL-ChildWindow + bool bControlIsVisibleAsChildWindow(rControlPrimitive.isVisibleAsChildWindow()); + + // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple + // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the + // office is in non-layout mode (default for controls at startup). For the common office + // this means that there exists a real VCL-System-Window for the control, so it is *not* + // painted here due to being exactly obscured by that real Window (and creates danger of + // flickering, too). + // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but + // exactly that would be needed on each client displaying the tiles (what would be hard + // to do but also would have advantages - the clients would have real controls in the + // shape of their target system which could be interacted with...). It is also what the + // office does. + // For now, fallback to just render these controls when Tiled Rendering is active to just + // have them displayed on all clients. + if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive()) + { + // Do force paint when we are in Tiled Renderer and FormControl is 'visible' + bControlIsVisibleAsChildWindow = false; + } + + if (bControlIsVisibleAsChildWindow) + { + // f the control is already visualized as a VCL-ChildWindow it + // does not need to be painted at all + return; + } + + // get awt::XControl from control primitive const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); + bool bDone(false); try { @@ -636,50 +666,23 @@ void VclPixelProcessor2D::processControlPrimitive2D( if (xNewGraphics.is()) { - // find out if the control is already visualized as a VCL-ChildWindow. If yes, - // it does not need to be painted at all. - uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW); - bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is() - && xControlWindow->isVisible()); - - // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple - // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the - // office is in non-layout mode (default for controls at startup). For the common office - // this means that there exists a real VCL-System-Window for the control, so it is *not* - // painted here due to being exactly obscured by that real Window (and creates danger of - // flickering, too). - // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but - // exactly that would be needed on each client displaying the tiles (what would be hard - // to do but also would have advantages - the clients would have real controls in the - // shape of their target system which could be interacted with...). It is also what the - // office does. - // For now, fallback to just render these controls when Tiled Rendering is active to just - // have them displayed on all clients. - if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive()) - { - // Do force paint when we are in Tiled Renderer and FormControl is 'visible' - bControlIsVisibleAsChildWindow = false; - } - - if (!bControlIsVisibleAsChildWindow) - { - // Needs to be drawn. Link new graphics and view - xControlView->setGraphics(xNewGraphics); - - // get position - const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation - * rControlPrimitive.getTransform()); - const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0)); - - // Do not forget to use the evtl. offsetted origin of the target device, - // e.g. when used with mask/transparence buffer device - const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); - xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()), - aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY())); - - // restore original graphics - xControlView->setGraphics(xOriginalGraphics); - } + // Needs to be drawn. Link new graphics and view + xControlView->setGraphics(xNewGraphics); + + // get position + const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation + * rControlPrimitive.getTransform()); + const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0)); + + // Do not forget to use the evtl. offsetted origin of the target device, + // e.g. when used with mask/transparence buffer device + const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); + xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()), + aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY())); + + // restore original graphics + xControlView->setGraphics(xOriginalGraphics); + bDone = true; } } catch (const uno::Exception&) @@ -687,7 +690,10 @@ void VclPixelProcessor2D::processControlPrimitive2D( // #i116763# removing since there is a good alternative when the xControlView // is not found and it is allowed to happen // DBG_UNHANDLED_EXCEPTION(); + } + if (!bDone) + { // process recursively and use the decomposition as Bitmap process(rControlPrimitive); } diff --git a/include/drawinglayer/primitive2d/controlprimitive2d.hxx b/include/drawinglayer/primitive2d/controlprimitive2d.hxx index 7e1184dae3ae..19226797d659 100644 --- a/include/drawinglayer/primitive2d/controlprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/controlprimitive2d.hxx @@ -93,6 +93,9 @@ public: return mxControlModel; } + /// check if this control is visible as ChildWindow + bool isVisibleAsChildWindow() const; + /** mxControl access. This will on demand create the awt::XControl using createXControl() if it does not exist. It may already have been created or even handed over at incarnation diff --git a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx index 3be88707dd4d..fc67ed78932b 100644 --- a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx +++ b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx @@ -41,6 +41,7 @@ class FillGraphicPrimitive2D; class PolyPolygonRGBAPrimitive2D; class PolyPolygonAlphaGradientPrimitive2D; class BitmapAlphaPrimitive2D; +class ControlPrimitive2D; class TextSimplePortionPrimitive2D; class TextDecoratedPortionPrimitive2D; class TextLayouterDevice; @@ -129,6 +130,7 @@ class UNLESS_MERGELIBS(DRAWINGLAYER_DLLPUBLIC) CairoPixelProcessor2D final : pub double fTransparency = 0.0); void processBitmapAlphaPrimitive2D( const primitive2d::BitmapAlphaPrimitive2D& rBitmapAlphaPrimitive2D); + void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive); void processTextSimplePortionPrimitive2D( const primitive2d::TextSimplePortionPrimitive2D& rCandidate);
