diff options
Diffstat (limited to 'drawinglayer/qa/unit')
-rw-r--r-- | drawinglayer/qa/unit/border.cxx | 181 | ||||
-rw-r--r-- | drawinglayer/qa/unit/vclmetafileprocessor2d.cxx | 156 | ||||
-rw-r--r-- | drawinglayer/qa/unit/vclpixelprocessor2d.cxx | 138 |
3 files changed, 475 insertions, 0 deletions
diff --git a/drawinglayer/qa/unit/border.cxx b/drawinglayer/qa/unit/border.cxx new file mode 100644 index 0000000000..f6f9feba54 --- /dev/null +++ b/drawinglayer/qa/unit/border.cxx @@ -0,0 +1,181 @@ +/* -*- 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/. + */ + +#include <memory> +#include <cppunit/TestAssert.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <rtl/ref.hxx> +#include <test/bootstrapfixture.hxx> +#include <vcl/metaact.hxx> +#include <vcl/vclptr.hxx> +#include <vcl/virdev.hxx> +#include <editeng/borderline.hxx> +#include <svtools/borderhelper.hxx> + +using namespace com::sun::star; + +namespace +{ +class DrawinglayerBorderTest : public test::BootstrapFixture +{ +}; + +CPPUNIT_TEST_FIXTURE(DrawinglayerBorderTest, testDoubleDecompositionSolid) +{ + // Create a border line primitive that's similar to the one from the bugdoc: + // 1.47 pixels is 0.03cm at 130% zoom and 96 DPI. + basegfx::B2DPoint aStart(0, 20); + basegfx::B2DPoint aEnd(100, 20); + double const fLeftWidth = 1.47; + double const fDistance = 1.47; + double const fRightWidth = 1.47; + double const fExtendLeftStart = 0; + double const fExtendLeftEnd = 0; + double const fExtendRightStart = 0; + double const fExtendRightEnd = 0; + basegfx::BColor aColorRight; + basegfx::BColor aColorLeft; + std::vector<double> aDashing(svtools::GetLineDashing(SvxBorderLineStyle::DOUBLE, 10.0)); + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing)); + std::vector<drawinglayer::primitive2d::BorderLine> aBorderlines{ + + drawinglayer::primitive2d::BorderLine( + drawinglayer::attribute::LineAttribute(aColorLeft, fLeftWidth), fExtendLeftStart, + fExtendLeftStart, fExtendLeftEnd, fExtendLeftEnd), + + drawinglayer::primitive2d::BorderLine(fDistance), + + drawinglayer::primitive2d::BorderLine( + drawinglayer::attribute::LineAttribute(aColorRight, fRightWidth), fExtendRightStart, + fExtendRightStart, fExtendRightEnd, fExtendRightEnd) + }; + + rtl::Reference<drawinglayer::primitive2d::BorderLinePrimitive2D> aBorder( + new drawinglayer::primitive2d::BorderLinePrimitive2D(aStart, aEnd, std::move(aBorderlines), + aStrokeAttribute)); + + // Decompose it into polygons. + drawinglayer::geometry::ViewInformation2D aView; + drawinglayer::primitive2d::Primitive2DContainer aContainer; + aBorder->get2DDecomposition(aContainer, aView); + + // Make sure it results in two borders as it's a double one. + CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(2), aContainer.size()); + + // Get the inside line, now a PolygonStrokePrimitive2D + auto pInside = dynamic_cast<const drawinglayer::primitive2d::PolygonStrokePrimitive2D*>( + aContainer[0].get()); + CPPUNIT_ASSERT(pInside); + + // Make sure the inside line's height is fLeftWidth. + const double fLineWidthFromDecompose = pInside->getLineAttribute().getWidth(); + + // This was 2.47, i.e. the width of the inner line was 1 unit (in the bugdoc's case: 1 pixel) wider than expected. + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeftWidth, fLineWidthFromDecompose, + basegfx::fTools::getSmallValue()); +} + +CPPUNIT_TEST_FIXTURE(DrawinglayerBorderTest, testDoublePixelProcessing) +{ + // Create a pixel processor. + ScopedVclPtrInstance<VirtualDevice> pDev; + drawinglayer::geometry::ViewInformation2D aView; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createProcessor2DFromOutputDevice(*pDev, aView)); + CPPUNIT_ASSERT(pProcessor); + GDIMetaFile aMetaFile; + // Start recording after the processor is created, so we can test the pixel processor. + aMetaFile.Record(pDev); + + // Create a border line primitive that's similar to the one from the bugdoc: + // 1.47 pixels is 0.03cm at 130% zoom and 96 DPI. + basegfx::B2DPoint aStart(0, 20); + basegfx::B2DPoint aEnd(100, 20); + double const fLeftWidth = 1.47; + double const fDistance = 1.47; + double const fRightWidth = 1.47; + double const fExtendLeftStart = 0; + double const fExtendLeftEnd = 0; + double const fExtendRightStart = 0; + double const fExtendRightEnd = 0; + basegfx::BColor aColorRight; + basegfx::BColor aColorLeft; + std::vector<double> aDashing(svtools::GetLineDashing(SvxBorderLineStyle::DOUBLE, 10.0)); + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing)); + std::vector<drawinglayer::primitive2d::BorderLine> aBorderlines{ + drawinglayer::primitive2d::BorderLine( + drawinglayer::attribute::LineAttribute(aColorLeft, fLeftWidth), fExtendLeftStart, + fExtendLeftStart, fExtendLeftEnd, fExtendLeftEnd), + + drawinglayer::primitive2d::BorderLine(fDistance), + + drawinglayer::primitive2d::BorderLine( + drawinglayer::attribute::LineAttribute(aColorRight, fRightWidth), fExtendRightStart, + fExtendRightStart, fExtendRightEnd, fExtendRightEnd) + }; + + rtl::Reference<drawinglayer::primitive2d::BorderLinePrimitive2D> aBorder( + new drawinglayer::primitive2d::BorderLinePrimitive2D(aStart, aEnd, std::move(aBorderlines), + aStrokeAttribute)); + + drawinglayer::primitive2d::Primitive2DContainer aPrimitives; + aPrimitives.push_back(drawinglayer::primitive2d::Primitive2DReference(aBorder)); + + // Process the primitives. + pProcessor->process(aPrimitives); + + // Double line now gets decomposed in Metafile to painting four lines + // with width == 0 in a cross pattern due to real line width being between + // 1.0 and 2.0. Count created lines + aMetaFile.Stop(); + aMetaFile.WindStart(); + sal_uInt32 nPolyLineActionCount = 0; + + for (std::size_t nAction = 0; nAction < aMetaFile.GetActionSize(); ++nAction) + { + MetaAction* pAction = aMetaFile.GetAction(nAction); + + if (MetaActionType::POLYLINE == pAction->GetType()) + { + auto pMPLAction = static_cast<MetaPolyLineAction*>(pAction); + + if (0 != pMPLAction->GetLineInfo().GetWidth() + && LineStyle::Solid == pMPLAction->GetLineInfo().GetStyle()) + { + nPolyLineActionCount++; + } + } + } + + // Check if all eight (2x four) simple lines with width == 0 and + // solid were created + // + // This has changed: Now, just the needed 'real' lines get created + // which have a width of 1. This are two lines. The former multiple + // lines were a combination of view-dependent force to a single-pixel + // line width (0 == lineWidth -> hairline) and vcl rendering this + // using a (insane) combination of single non-AAed lines. All the + // system-dependent part of the BorderLine stuff is now done in + // SdrFrameBorderPrimitive2D and svx. + // Adapted this test - still useful, breaking it may be a hint :-) + const sal_uInt32 nExpectedNumPolyLineActions = 2; + + CPPUNIT_ASSERT_EQUAL(nExpectedNumPolyLineActions, nPolyLineActionCount); +} +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/qa/unit/vclmetafileprocessor2d.cxx b/drawinglayer/qa/unit/vclmetafileprocessor2d.cxx new file mode 100644 index 0000000000..f34ef8a1bb --- /dev/null +++ b/drawinglayer/qa/unit/vclmetafileprocessor2d.cxx @@ -0,0 +1,156 @@ +/* -*- 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/. + * + */ + +#include <test/bootstrapfixture.hxx> + +#include <vcl/virdev.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <tools/stream.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <cppcanvas/vclfactory.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> + +using namespace drawinglayer; +using namespace com::sun::star; + +class VclMetaFileProcessor2DTest : public test::BootstrapFixture +{ + VclPtr<VirtualDevice> mVclDevice; + uno::Reference<rendering::XCanvas> mCanvas; + + // if enabled - check the result images with: + // "xdg-open ./workdir/CppunitTest/drawinglayer_processors.test.core/" + static constexpr const bool mbExportBitmap = false; + + void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device) + { + if (mbExportBitmap) + { + BitmapEx aBitmapEx(device->GetBitmapEx(Point(0, 0), device->GetOutputSizePixel())); + SvFileStream aStream(filename, StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream); + } + } + +public: + VclMetaFileProcessor2DTest() + : BootstrapFixture(true, false) + { + } + + virtual void tearDown() override + { + mVclDevice.clear(); + mCanvas = uno::Reference<rendering::XCanvas>(); + BootstrapFixture::tearDown(); + } + + void setupCanvas(const Size& size, Color backgroundColor = COL_WHITE, bool alpha = false) + { + mVclDevice = alpha ? VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA) + : VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + mVclDevice->SetOutputSizePixel(size); + mVclDevice->SetBackground(Wallpaper(backgroundColor)); + mVclDevice->Erase(); + mCanvas = mVclDevice->GetCanvas(); + CPPUNIT_ASSERT(mCanvas.is()); + } + + // Test drawing a dotted line in Impress presentation mode. + void testTdf136957() + { + // Impress presentation mode first draws the slide to a metafile. + GDIMetaFile metafile; + // I got these values by adding debug output to cppcanvas::internal::ImplRenderer::ImplRenderer(). + metafile.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + metafile.SetPrefSize(Size(14548, 3350)); + ScopedVclPtrInstance<VirtualDevice> metadevice; + metafile.Record(metadevice); + drawinglayer::geometry::ViewInformation2D view; + std::unique_ptr<processor2d::BaseProcessor2D> processor( + processor2d::createProcessor2DFromOutputDevice(*metadevice, view)); + CPPUNIT_ASSERT(processor); + // Match the values Impress uses. + basegfx::B2DPolygon polygon = { { 15601, 0 }, { 15602, 5832 } }; + attribute::LineAttribute lineAttributes( + basegfx::BColor(0.047058823529411764, 0.19607843137254902, 0.17254901960784313), 35, + basegfx::B2DLineJoin::Miter, css::drawing::LineCap_ROUND); + attribute::StrokeAttribute strokeAttributes({ 0.35, 69.65 }); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> strokePrimitive( + new primitive2d::PolygonStrokePrimitive2D(polygon, lineAttributes, strokeAttributes)); + primitive2d::Primitive2DContainer primitives; + primitives.push_back(primitive2d::Primitive2DReference(strokePrimitive)); + processor->process(primitives); + metafile.Stop(); + metafile.WindStart(); + + // Now verify that the metafile has the one PolyLine action with the right dashing. + int lineActionCount = 0; + for (std::size_t i = 0; i < metafile.GetActionSize(); ++i) + { + const MetaAction* metaAction = metafile.GetAction(i); + if (metaAction->GetType() == MetaActionType::POLYLINE) + { + const MetaPolyLineAction* action + = static_cast<const MetaPolyLineAction*>(metaAction); + + CPPUNIT_ASSERT_EQUAL(35.0, action->GetLineInfo().GetWidth()); + CPPUNIT_ASSERT_EQUAL(LineStyle::Dash, action->GetLineInfo().GetStyle()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), action->GetLineInfo().GetDashCount()); + CPPUNIT_ASSERT_EQUAL(0.35, action->GetLineInfo().GetDashLen()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), action->GetLineInfo().GetDotCount()); + CPPUNIT_ASSERT_EQUAL(0.0, action->GetLineInfo().GetDotLen()); + CPPUNIT_ASSERT_EQUAL(69.65, action->GetLineInfo().GetDistance()); + lineActionCount++; + } + } + CPPUNIT_ASSERT_EQUAL(1, lineActionCount); + + // Now draw the metafile using canvas and verify that the line is drawn. + setupCanvas(Size(1920, 1080)); + cppcanvas::CanvasSharedPtr cppCanvas = cppcanvas::VCLFactory::createCanvas(mCanvas); + // I got these matrices from a breakpoint in drawing the polyline, and walking up + // the stack to the canvas code. + cppCanvas->setTransformation( + basegfx::B2DHomMatrix(0.056662828121770453, 0, 0, 0, 0.056640419947506564, 0)); + cppcanvas::RendererSharedPtr renderer = cppcanvas::VCLFactory::createRenderer( + cppCanvas, metafile, cppcanvas::Renderer::Parameters()); + renderer->setTransformation(basegfx::B2DHomMatrix(14548, 0, -2, 0, 3350, 3431)); + CPPUNIT_ASSERT(renderer->draw()); + exportDevice("test-tdf136957", mVclDevice); + Bitmap bitmap = mVclDevice->GetBitmap(Point(), Size(1920, 1080)); + BitmapScopedReadAccess access(bitmap); + // There should be a dotted line, without the fix it wouldn't be there, so check + // there's a sufficient amount of non-white pixels and that's the line. + int nonWhiteCount = 0; + for (tools::Long y = 193; y <= 524; ++y) + for (tools::Long x = 883; x <= 885; ++x) + if (access->GetColor(y, x) != COL_WHITE) + ++nonWhiteCount; + CPPUNIT_ASSERT_GREATER(100, nonWhiteCount); + } + + CPPUNIT_TEST_SUITE(VclMetaFileProcessor2DTest); + CPPUNIT_TEST(testTdf136957); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(VclMetaFileProcessor2DTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/qa/unit/vclpixelprocessor2d.cxx b/drawinglayer/qa/unit/vclpixelprocessor2d.cxx new file mode 100644 index 0000000000..c132d09274 --- /dev/null +++ b/drawinglayer/qa/unit/vclpixelprocessor2d.cxx @@ -0,0 +1,138 @@ +/* -*- 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/. + * + */ + +#include <test/bootstrapfixture.hxx> + +#include <vcl/virdev.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graphicfilter.hxx> +#include <tools/stream.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/utils/gradienttools.hxx> + +using namespace drawinglayer; + +class VclPixelProcessor2DTest : public test::BootstrapFixture +{ + // if enabled - check the result images with: + // "xdg-open ./workdir/CppunitTest/drawinglayer_processors.test.core/" + static constexpr const bool mbExportBitmap = false; + + void exportDevice(const OUString& filename, const VclPtr<VirtualDevice>& device) + { + if (mbExportBitmap) + { + BitmapEx aBitmapEx(device->GetBitmapEx(Point(0, 0), device->GetOutputSizePixel())); + SvFileStream aStream(filename, StreamMode::WRITE | StreamMode::TRUNC); + GraphicFilter::GetGraphicFilter().compressAsPNG(aBitmapEx, aStream); + } + } + +public: + VclPixelProcessor2DTest() + : BootstrapFixture(true, false) + { + } + + // Test that drawing only a part of a gradient draws the proper part of it. + void testTdf139000() + { + ScopedVclPtr<VirtualDevice> device + = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + device->SetOutputSizePixel(Size(100, 200)); + device->SetBackground(Wallpaper(COL_RED)); + device->Erase(); + + drawinglayer::geometry::ViewInformation2D view; + std::unique_ptr<processor2d::BaseProcessor2D> processor( + processor2d::createProcessor2DFromOutputDevice(*device, view)); + CPPUNIT_ASSERT(processor); + + // I stumbled over this when hunting another problem, but I have to correct + // this: This test does test something that is not supported. It seems to be + // based on the *misunderstanding* that in the version of the constructor of + // FillGradientPrimitive2D (and similar others) with two ranges the 2nd + // B2DRange parameter 'OutputRange' is a 'clipping' parameter. This is *not* + // the case --- it is in fact the *contrary*, it is there to *extend* the + // usual definition/paintRange of a gradient: + // It was originally needed to correctly display TextFrames (TF) in Writer: If you + // have a TF in SW filled with a gradient and that TF has sub-frames, it inherits + // the gradient fill. Since you can freely move those sub-TFs even outside the + // parent TF there has to be a way to not only paint gradients in their definition + // range (classical, all DrawObjects do that), but extended from that. This is + // needed e.g. for linear gradients, but - dependent of e.g. the center settings - + // also for all other ones, all can have geometry 'outside' the DefinitionRange. + // This is now also used in various other locations which is proof that this is + // useful and needed. It is possible to see that basic history/reason for this + // parameter by following the git history and why and under which circumstances + // that parameter was originally added. Other hints are: It is *not* named + // 'ClipRange'. Using a B2DRange to define a ClipRange topology would be bad due + // to not being transformable, a PolyPolygon would be used in that case. Using as + // clipping mechanism would offer a 2nd principle to add clipping for primitives + // besides MaskPrimitive2D - always bad style in a sub-system. A quick look + // on it's usages gives hints, too. + // This means that when defining an outputRange that resides completely *inside* + // the definitionRange *no change* at all is done by definition since this does + // not *extend* the target area of the gradient paint region at all. If an + // implementation does clip and limit output to 'outputRange' that should do no + // harm, but is not the expected/reliable way to paint primitives clipped. + // That's why all DrawObjects with gradient fill (and other fills do the same) + // embed the fill that is defined for a range (usually the BoundRange of a + // PolyPolygon) in a MaskPrimitive2D defined by the outline PolyPolygon of the + // shape. Nothing speaks against renderers detecting that combination and do + // something optimized if they want to, especially SDPRs, but this is not + // required. The standard embedded clipping of the implementations of the + // MaskPrimitive2D do the right thing. + // This test intends to paint the lower part of a gradient, so define the + // gradient for the full target range and embed it to a MaskPrimitive2D + // defining the lower part of that area to do that. + + basegfx::B2DRange definitionRange(0, 0, 100, 200); + basegfx::B2DRange outputRange(0, 100, 100, 200); // Paint only lower half of the gradient. + + const primitive2d::Primitive2DContainer primitives{ + rtl::Reference<primitive2d::MaskPrimitive2D>(new primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(outputRange)), + primitive2d::Primitive2DContainer{ + rtl::Reference<primitive2d::FillGradientPrimitive2D>( + new primitive2d::FillGradientPrimitive2D( + definitionRange, attribute::FillGradientAttribute( + css::awt::GradientStyle_LINEAR, 0, 0, 0, 0, + basegfx::BColorStops(COL_WHITE.getBColor(), + COL_BLACK.getBColor())))) })) + }; + processor->process(primitives); + + exportDevice("test-tdf139000.png", device); + Bitmap bitmap = device->GetBitmap(Point(), device->GetOutputSizePixel()); + BitmapScopedReadAccess access(bitmap); + // The upper half should keep its red background color. + CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), access->GetColor(Point(0, 99))); + // First line of the gradient should not be the start color, but something halfway. + CPPUNIT_ASSERT_LESS(static_cast<sal_uInt16>(16), + access->GetColor(Point(0, 100)).GetColorError(COL_GRAY)); + // Last line of the gradient should be the end color, or close. + CPPUNIT_ASSERT_LESS(static_cast<sal_uInt16>(16), + access->GetColor(Point(0, 199)).GetColorError(COL_BLACK)); + } + + CPPUNIT_TEST_SUITE(VclPixelProcessor2DTest); + CPPUNIT_TEST(testTdf139000); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(VclPixelProcessor2DTest); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |