drawinglayer/source/tools/emfpbrush.cxx | 18 ++ drawinglayer/source/tools/emfpbrush.hxx | 4 drawinglayer/source/tools/emfphelperdata.cxx | 69 ++++++++-- drawinglayer/source/tools/emfppen.cxx | 4 drawinglayer/source/tools/emfppen.hxx | 2 drawinglayer/source/tools/primitive2dxmldump.cxx | 27 +++ emfio/qa/cppunit/emf/EmfImportTest.cxx | 46 ++++++ emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf |binary 8 files changed, 155 insertions(+), 15 deletions(-)
New commits: commit 6c05a6339c27038b14d2f35f8f4b2b94335d6491 Author: Bartosz Kosiorek <[email protected]> AuthorDate: Tue Feb 24 23:23:28 2026 +0100 Commit: Bartosz Kosiorek <[email protected]> CommitDate: Sat Feb 28 08:04:39 2026 +0100 tdf#129342 tdf#170985 EMF+ Implement BrushTypeTextureFill for bitmaps This patch implements the missing TextureFill brush support for EMF+ records. Previously, texture fills were ignored, resulting in missing backgrounds or shapes. Change-Id: I61f80a9851f3929651f54b68d6d4d21e65720aa6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200547 Tested-by: Jenkins Reviewed-by: Bartosz Kosiorek <[email protected]> diff --git a/drawinglayer/source/tools/emfpbrush.cxx b/drawinglayer/source/tools/emfpbrush.cxx index d64d76085dc5..51fce424b70a 100644 --- a/drawinglayer/source/tools/emfpbrush.cxx +++ b/drawinglayer/source/tools/emfpbrush.cxx @@ -60,7 +60,7 @@ namespace emfplushelper return u""_ustr; } - void EMFPBrush::Read(SvStream& s, EmfPlusHelperData const & rR) + void EMFPBrush::Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 dataSize, bool bUseWholeStream) { sal_uInt32 header; @@ -99,7 +99,21 @@ namespace emfplushelper } case BrushTypeTextureFill: { - SAL_WARN("drawinglayer.emf", "EMF+ TODO: implement BrushTypeTextureFill brush"); + s.ReadUInt32(additionalFlags).ReadInt32(wrapMode); + SAL_INFO("drawinglayer.emf", "EMF+ TextureFill, flags: 0x" + << std::hex << additionalFlags << std::dec + << ", wrapMode: " << wrapMode); + + if (additionalFlags & 0x02) // BrushDataTransform + { + EmfPlusHelperData::readXForm(s, brush_transformation); + hasTransformation = true; + SAL_INFO("drawinglayer.emf", + "EMF+ Use brush transformation: " << brush_transformation); + } + + textureImage = std::make_unique<EMFPImage>(); + textureImage->Read(s, dataSize, bUseWholeStream); break; } case BrushTypePathGradient: diff --git a/drawinglayer/source/tools/emfpbrush.hxx b/drawinglayer/source/tools/emfpbrush.hxx index aee3fe02f60e..d50df7176133 100644 --- a/drawinglayer/source/tools/emfpbrush.hxx +++ b/drawinglayer/source/tools/emfpbrush.hxx @@ -20,6 +20,7 @@ #pragma once #include "emfphelperdata.hxx" +#include "emfpimage.hxx" #include <tools/color.hxx> namespace emfplushelper @@ -114,6 +115,7 @@ namespace emfplushelper std::unique_ptr<::Color[]> surroundColors; std::unique_ptr<EMFPPath> path; EmfPlusHatchStyle hatchStyle; + std::unique_ptr<EMFPImage> textureImage; EMFPBrush(); virtual ~EMFPBrush() override; @@ -121,7 +123,7 @@ namespace emfplushelper sal_uInt32 GetType() const { return type; } const ::Color& GetColor() const { return solidColor; } - void Read(SvStream& s, EmfPlusHelperData const & rR); + void Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 dataSize, bool bUseWholeStream); }; } diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx index e5bc612b4f40..0edcda846828 100644 --- a/drawinglayer/source/tools/emfphelperdata.cxx +++ b/drawinglayer/source/tools/emfphelperdata.cxx @@ -28,11 +28,14 @@ #include "emfpfont.hxx" #include "emfpstringformat.hxx" #include <wmfemfhelper.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> -#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> #include <drawinglayer/primitive2d/textlayoutdevice.hxx> @@ -40,7 +43,6 @@ #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/metafileprimitive2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> -#include <drawinglayer/attribute/fontattribute.hxx> #include <basegfx/color/bcolor.hxx> #include <basegfx/color/bcolormodifier.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> @@ -273,14 +275,14 @@ namespace emfplushelper { EMFPBrush *brush = new EMFPBrush(); maEMFPObjects[index].reset(brush); - brush->Read(rObjectStream, *this); + brush->Read(rObjectStream, *this, dataSize, bUseWholeStream); break; } case EmfPlusObjectTypePen: { EMFPPen *pen = new EMFPPen(); maEMFPObjects[index].reset(pen); - pen->Read(rObjectStream, *this); + pen->Read(rObjectStream, *this, dataSize, bUseWholeStream); pen->penWidth = unitToPixel(pen->penWidth, pen->penUnit, Direction::horizontal); break; } @@ -771,12 +773,65 @@ namespace emfplushelper polygon, fillColor.getBColor())); } - else if (brush->type == BrushTypeTextureFill) + else if (brush->type == BrushTypeTextureFill && brush->textureImage) { - SAL_WARN("drawinglayer.emf", "EMF+ TODO: implement BrushTypeTextureFill brush"); + if (brush->textureImage->type != ImageDataTypeBitmap) + { + SAL_WARN("drawinglayer.emf", "EMF+ TextureFill: Metafiles type is not supported"); + return; + } + + const Size aSizePx = brush->textureImage->graphic.GetSizePixel(); + if (aSizePx.Width() <= 0 || aSizePx.Height() <= 0) + return; + + basegfx::B2DHomMatrix aBrushMatrix; + if (brush->additionalFlags & 0x02) // 0x02 = BrushDataTransform flag + aBrushMatrix = brush->brush_transformation; + + // Create a scaling matrix to map the normalized 1x1 range + // to the actual pixel dimensions of the texture. + + const basegfx::B2DHomMatrix aTextureScale( + /* Row 0, Column 0 */ aSizePx.Width(), + /* Row 0, Column 1 */ 0.0, + /* Row 0, Column 2 */ 0.0, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ aSizePx.Height(), + /* Row 1, Column 2 */ 0.0); + + const basegfx::B2DHomMatrix aTotalMatrix = maMapTransform * aBrushMatrix * aTextureScale; + + // Invert the matrix to transform the fill polygon from document space + // back into the local normalized texture space (0.0 to 1.0). + basegfx::B2DHomMatrix aInvTotalMatrix = aTotalMatrix; + if (!aInvTotalMatrix.invert()) + { + SAL_WARN("drawinglayer.emf", "EMF+ TextureFill: Failed to invert transformation matrix"); + return; + } + basegfx::B2DPolyPolygon aLocalPolygon = polygon; + aLocalPolygon.transform(aInvTotalMatrix); + + const bool bIsTiled = (brush->wrapMode != WrapModeClamp); + + const drawinglayer::attribute::FillGraphicAttribute aFillAttribute( + brush->textureImage->graphic, + basegfx::B2DRange(0.0, 0.0, 1.0, 1.0), + bIsTiled); + + drawinglayer::primitive2d::Primitive2DReference xFillPrimitive = + new drawinglayer::primitive2d::PolyPolygonGraphicPrimitive2D( + aLocalPolygon, + basegfx::B2DRange(0.0, 0.0, 1.0, 1.0), + aFillAttribute); + + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::TransformPrimitive2D( + aTotalMatrix, + drawinglayer::primitive2d::Primitive2DContainer({ xFillPrimitive }))); } else if (brush->type == BrushTypePathGradient || brush->type == BrushTypeLinearGradient) - { if (brush->type == BrushTypePathGradient && !(brush->additionalFlags & 0x1)) { diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx index bef1df4d8480..789239d62e8b 100644 --- a/drawinglayer/source/tools/emfppen.cxx +++ b/drawinglayer/source/tools/emfppen.cxx @@ -178,7 +178,7 @@ namespace emfplushelper return drawinglayer::attribute::StrokeAttribute(); } - void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR) + void EMFPPen::Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 dataSize, bool bUseWholeStream) { sal_Int32 lineJoin = EmfPlusLineJoinTypeMiter; sal_uInt32 graphicsVersion, penType; @@ -373,7 +373,7 @@ namespace emfplushelper customEndCapLen = 0; } - EMFPBrush::Read(s, rR); + EMFPBrush::Read(s, rR, dataSize, bUseWholeStream); } } diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx index bdb5ebf0599b..b6d30f411057 100644 --- a/drawinglayer/source/tools/emfppen.hxx +++ b/drawinglayer/source/tools/emfppen.hxx @@ -122,7 +122,7 @@ namespace emfplushelper virtual ~EMFPPen() override; - void Read(SvStream& s, EmfPlusHelperData const & rR); + void Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 dataSize, bool bUseWholeStream); drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const; }; diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx index 4d113d509c35..a1fffa4cbee9 100644 --- a/drawinglayer/source/tools/primitive2dxmldump.cxx +++ b/drawinglayer/source/tools/primitive2dxmldump.cxx @@ -33,8 +33,9 @@ #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> -#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> @@ -68,6 +69,7 @@ #include <drawinglayer/attribute/sdrfillattribute.hxx> #include <drawinglayer/attribute/fillhatchattribute.hxx> #include <drawinglayer/attribute/fillgradientattribute.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> #include <drawinglayer/attribute/materialattribute3d.hxx> #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> @@ -733,6 +735,15 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.endElement(); } break; + + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + rWriter.startElement("fillgraphic"); + runDecomposeAndRecurse(pBasePrimitive, rWriter); + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D: { const auto& rHiddenGeometryPrimitive2D @@ -852,6 +863,20 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.endElement(); } break; + + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + { + const auto& rPolyPolygonGraphicPrimitive2D + = static_cast<const PolyPolygonGraphicPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("polypolygongraphic"); + rWriter.attribute( + "transparency", + std::lround(100 * rPolyPolygonGraphicPrimitive2D.getTransparency())); + runDecomposeAndRecurse(pBasePrimitive, rWriter); + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D: { const auto& rPolyPolygonStrokePrimitive2D diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx index 144b0356af16..7643e44ce9fa 100644 --- a/emfio/qa/cppunit/emf/EmfImportTest.cxx +++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx @@ -117,7 +117,7 @@ CPPUNIT_TEST_FIXTURE(Test, testPolyPolygon) CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsTypeBitmap) { - // tdf#142941 EMF+ file with ObjectTypeImage, FillRects, DrawImagePoints ,records + // tdf#142941 EMF+ file with ObjectTypeImage, FillRects, DrawImagePoints // The test is checking the position of displaying bitmap with too large SrcRect Primitive2DSequence aSequence @@ -147,6 +147,50 @@ CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsTypeBitmap) "b63539,b53335,ba3236,a2393e,1c0000"); } +CPPUNIT_TEST_FIXTURE(Test, testFillRectsWithTextureBrush) +{ + // tdf#129342 tdf#170985 EMF+ file with ObjectTypeBrush, ObjectTypepen, FillRects + // The test is checking the position of displaying bitmap with too large SrcRect + + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "transform/polypolygongraphic", "transparency", u"0"); + assertXPath(pDocument, aXPathPrefix + "transform/polypolygongraphic/mask/polypolygon", 1); + + assertXPath(pDocument, + aXPathPrefix + "transform/polypolygongraphic/mask/fillgraphic/group/transform", 17); + assertXPath(pDocument, + aXPathPrefix + + "transform/polypolygongraphic/mask/fillgraphic/group/transform/bitmap/data", + 697); + + assertXPath( + pDocument, + aXPathPrefix + + "transform/polypolygongraphic/mask/fillgraphic/group/transform[1]/bitmap/data", + 41); + assertXPath( + pDocument, + aXPathPrefix + + "transform/polypolygongraphic/mask/fillgraphic/group/transform[1]/bitmap/data[30]", + "row", + u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe," + u"fefefe,fefefe,fefefe,fefefe,ffedfe,edf6f5,e6fffd,edfff8,e8f3e3,ffffef,fffbed,ffe8e1," + u"f5f6e6,fff7f3,fff0f6,ffeff4,f6ffff,e9ffff,f2ffff,f4ebfe,fefefe,fcfcfc,fefefe,ffffff," + u"ffffff,fdfdfd,fbfbfb,fdfdfd,fff6fd,fff9fc,fdfcfa,f6fdf6,f7fffb,f7fffa,f9fffa,f2f9f1," + u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe," + u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe"); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/line", "width", u"26"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/polypolygon", "minx", u"397"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/polypolygon", "miny", u"397"); +} + CPPUNIT_TEST_FIXTURE(Test, testDrawString) { #if HAVE_MORE_FONTS diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf new file mode 100644 index 000000000000..e90e2af12a0f Binary files /dev/null and b/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf differ
