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 <gang65@poczta.onet.pl>
This commit is contained in:
Bartosz Kosiorek
2026-02-28 08:04:39 +01:00
parent 321fa22e87
commit 6c05a6339c
8 changed files with 155 additions and 15 deletions
+16 -2
View File
@@ -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+\tTODO: implement BrushTypeTextureFill brush");
s.ReadUInt32(additionalFlags).ReadInt32(wrapMode);
SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tTextureFill, 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+\t\t\t\tUse brush transformation: " << brush_transformation);
}
textureImage = std::make_unique<EMFPImage>();
textureImage->Read(s, dataSize, bUseWholeStream);
break;
}
case BrushTypePathGradient:
+3 -1
View File
@@ -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);
};
}
+62 -7
View File
@@ -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+\tTODO: implement BrushTypeTextureFill brush");
if (brush->textureImage->type != ImageDataTypeBitmap)
{
SAL_WARN("drawinglayer.emf", "EMF+\tTextureFill: 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+\tTextureFill: 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))
{
+2 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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;
};
@@ -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
+45 -1
View File
@@ -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