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 <Armin.Le.Grand@me.com>
This commit is contained in:
Armin Le Grand (Collabora)
2024-12-19 19:04:05 +01:00
committed by Armin Le Grand
parent de29bec27e
commit f3f70ef02f
5 changed files with 137 additions and 88 deletions
@@ -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,
@@ -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);
// 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));
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);
}
}
// 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:
@@ -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());
// Needs to be drawn. Link new graphics and view
xControlView->setGraphics(xNewGraphics);
// 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;
}
// get position
const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation
* rControlPrimitive.getTransform());
const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
if (!bControlIsVisibleAsChildWindow)
{
// Needs to be drawn. Link new graphics and view
xControlView->setGraphics(xNewGraphics);
// 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()));
// 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);
}
// 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);
}
@@ -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
@@ -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);