tdf166084 tdf138087 tdf59814 EMF+ Fix scale and trans DrawImagePoints Metafile

This commit fixes the incorrect positioning and scaling of Metafiles
rendered via EMF+ DrawImagePoints.

Key changes:
- Implemented a normalization matrix for Metafiles to properly map
  source coordinates (SrcRect) to the destination.
- Updated EMFPImage to parse and store bounds from nested EMF headers,
  ensuring correct dimensions (x, y, width, height) are available.

Change-Id: I07b4d99c334611a10e7faff42b76699f04fa1520
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201084
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
(cherry picked from commit 4dccec254c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201163
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
This commit is contained in:
Bartosz Kosiorek
2026-03-12 15:00:12 +01:00
committed by Tomaž Vajngerl
parent 292c654185
commit 0afed6774e
7 changed files with 155 additions and 37 deletions
+46 -27
View File
@@ -30,19 +30,20 @@
#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/PolygonStrokeArrowPrimitive2D.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <basegfx/color/bcolor.hxx>
#include <basegfx/color/bcolormodifier.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
@@ -1542,11 +1543,8 @@ namespace emfplushelper
ReadPoint(rMS, x2, y2, flags); // upper-right
ReadPoint(rMS, x3, y3, flags); // lower-left
SAL_INFO("drawinglayer.emf", "EMF+\t destination points: "
<< x1 << "," << y1 << " " << x2 << ","
<< y2 << " " << x3 << "," << y3);
dx = x1;
dy = y2;
dy = y1;
dw = x2 - x1;
dh = y3 - y1;
fShearX = x3 - x1;
@@ -1556,7 +1554,7 @@ namespace emfplushelper
ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
SAL_INFO("drawinglayer.emf",
"EMF+\t Rectangle: " << dx << "," << dy << " " << dw << "x" << dh);
"EMF+\t Destination rectangle: " << dx << "," << dy << " " << dw << "x" << dh);
Size aSize;
if (image->type == ImageDataTypeBitmap)
{
@@ -1588,22 +1586,15 @@ namespace emfplushelper
else if (sy + sh > aSize.Height())
dh = ((aSize.Height() - sy) / sh) * dh;
}
else
SAL_INFO(
"drawinglayer.emf",
"EMF+\t TODO: Add support for SrcRect to ImageDataTypeMetafile");
const ::basegfx::B2DPoint aDstPoint(dx, dy);
const ::basegfx::B2DSize aDstSize(dw, dh);
const basegfx::B2DHomMatrix aTransformMatrix
= maMapTransform
* basegfx::B2DHomMatrix(
/* Row 0, Column 0 */ aDstSize.getWidth(),
const basegfx::B2DHomMatrix aDestMatrix =
maMapTransform * basegfx::B2DHomMatrix(
/* Row 0, Column 0 */ dw,
/* Row 0, Column 1 */ fShearX,
/* Row 0, Column 2 */ aDstPoint.getX(),
/* Row 0, Column 2 */ dx,
/* Row 1, Column 0 */ fShearY,
/* Row 1, Column 1 */ aDstSize.getHeight(),
/* Row 1, Column 2 */ aDstPoint.getY());
/* Row 1, Column 1 */ dh,
/* Row 1, Column 2 */ dy);
if (image->type == ImageDataTypeBitmap)
{
@@ -1614,18 +1605,46 @@ namespace emfplushelper
{
mrTargetHolders.Current().append(
new drawinglayer::primitive2d::BitmapPrimitive2D(
aBmp, aTransformMatrix));
aBmp, aDestMatrix));
}
else
SAL_WARN("drawinglayer.emf", "EMF+\t warning: empty bitmap");
}
else if (image->type == ImageDataTypeMetafile)
{
GDIMetaFile aGDI(image->graphic.GetGDIMetaFile());
aGDI.Clip(aSource);
SAL_INFO("drawinglayer.emf",
"\t\t Metafile Dimensions: " << image->x << "," << image->y
<< " [" << image->width << "x"
<< image->height);
const GDIMetaFile aGDI(image->graphic.GetGDIMetaFile());
basegfx::B2DHomMatrix aFinalMatrix = aDestMatrix;
if (image->width > 0 && image->height > 0 && sw > 0. && sh > 0.)
{
const double fRelSrcX = sx - static_cast<double>(image->x);
const double fRelSrcY = sy - static_cast<double>(image->y);
const basegfx::B2DHomMatrix aNormalizeMatrix(
/* Row 0, Column 0 */ static_cast<double>(image->width) / sw,
/* Row 0, Column 1 */ 0.0,
/* Row 0, Column 2 */ -fRelSrcX / sw,
/* Row 1, Column 0 */ 0.0,
/* Row 1, Column 1 */ static_cast<double>(image->height) / sh,
/* Row 1, Column 2 */ -fRelSrcY / sh);
aFinalMatrix = aDestMatrix * aNormalizeMatrix;
}
basegfx::B2DPolygon aClipPoly = basegfx::utils::createUnitPolygon();
aClipPoly.transform(aDestMatrix);
mrTargetHolders.Current().append(
new drawinglayer::primitive2d::MetafilePrimitive2D(aTransformMatrix,
aGDI));
new drawinglayer::primitive2d::MaskPrimitive2D(
basegfx::B2DPolyPolygon(aClipPoly),
drawinglayer::primitive2d::Primitive2DContainer {
new drawinglayer::primitive2d::MetafilePrimitive2D(aFinalMatrix, aGDI)
}
)
);
}
}
else
+34
View File
@@ -60,6 +60,40 @@ namespace emfplushelper
SvMemoryStream mfStream(const_cast<char *>(static_cast<char const *>(s.GetData()) + s.Tell()), dataSize, StreamMode::READ);
filter.ImportGraphic(graphic, u"", mfStream);
mfStream.Seek(0);
// 1 = Wmf, 2 = WmfPlaceable, 3 = Emf, 4 = EmfPlusOnly, 5 = EmfPlusDual
if (mfType == 3 || mfType == 4 || mfType == 5)
{
sal_uInt32 dwRecordType, dwRecordSize, dSignature, nVersion;
sal_Int32 rclBoundsLeft, rclBoundsTop, rclBoundsRight, rclBoundsBottom;
mfStream.ReadUInt32(dwRecordType);
mfStream.ReadUInt32(dwRecordSize);
mfStream.ReadInt32(rclBoundsLeft);
mfStream.ReadInt32(rclBoundsTop);
mfStream.ReadInt32(rclBoundsRight);
mfStream.ReadInt32(rclBoundsBottom);
mfStream.SeekRel(16);
mfStream.ReadUInt32(dSignature);
mfStream.ReadUInt32(nVersion);
if (dwRecordType == 1 && dSignature == 0x464D4520)
{
x = rclBoundsLeft;
y = rclBoundsTop;
width = rclBoundsRight - rclBoundsLeft;
height = rclBoundsBottom - rclBoundsTop;
SAL_INFO("drawinglayer.emf", "EMF+\tNested EMF Header detected.");
SAL_INFO("drawinglayer.emf",
"EMF+\tBounds: [" << rclBoundsLeft << "," << rclBoundsTop << ", "
<< rclBoundsRight << ", " << rclBoundsBottom << "]");
SAL_INFO("drawinglayer.emf",
"EMF+\tVersion: 0x" << std::hex << nVersion << std::dec);
}
}
// debug code - write the stream to debug file /tmp/emf-stream.emf
#if OSL_DEBUG_LEVEL > 1
mfStream.Seek(0);
+12
View File
@@ -35,12 +35,24 @@ namespace emfplushelper
struct EMFPImage : public EMFPObject
{
sal_uInt32 type;
sal_Int32 x;
sal_Int32 y;
sal_Int32 width;
sal_Int32 height;
sal_Int32 stride;
sal_uInt32 pixelFormat;
Graphic graphic;
EMFPImage():
type(ImageDataTypeUnknown),
x(0),
y(0),
width(0),
height(0),
stride(0),
pixelFormat(0)
{}
void Read(SvMemoryStream &s, sal_uInt32 dataSize, bool bUseWholeStream);
};
}
@@ -118,12 +118,12 @@ void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon
{
rWriter.startElement("polypolygon");
const basegfx::B2DRange aB2DRange(rB2DPolyPolygon.getB2DRange());
rWriter.attributeDouble("height", aB2DRange.getHeight());
rWriter.attributeDouble("width", aB2DRange.getWidth());
rWriter.attributeDouble("minx", aB2DRange.getMinX());
rWriter.attributeDouble("miny", aB2DRange.getMinY());
rWriter.attributeDouble("maxx", aB2DRange.getMaxX());
rWriter.attributeDouble("maxy", aB2DRange.getMaxY());
rWriter.attribute("height", aB2DRange.getHeight());
rWriter.attribute("width", aB2DRange.getWidth());
rWriter.attribute("minx", aB2DRange.getMinX());
rWriter.attribute("miny", aB2DRange.getMinY());
rWriter.attribute("maxx", aB2DRange.getMaxX());
rWriter.attribute("maxy", aB2DRange.getMaxY());
rWriter.attribute("path", basegfx::utils::exportToSvgD(rB2DPolyPolygon, true, true, false));
for (basegfx::B2DPolygon const& rPolygon : rB2DPolyPolygon)
+53
View File
@@ -146,6 +146,59 @@ CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsTypeBitmap)
"b63539,b53335,ba3236,a2393e,1c0000");
}
CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsWithMetafile)
{
// tdf166084 tdf138087 tdf59814 EMF+ file with ObjectTypeImage, DrawImagePoints
// The test is checking the position and scale of embedded metafile drawed by DrawImagePoints
Primitive2DSequence aSequence
= parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusDrawImagePointsWithMetafile.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 + "mask/polypolygon", 1);
assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "minx", u"0");
assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "miny", u"0");
assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "maxx", u"6921");
assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "maxy", u"20306");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy11", u"1");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy12", u"0");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy13", u"-3279");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy21", u"0");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy22", u"1");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform", "xy23", u"-176");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform/mask/polypolygon", "height",
u"15747");
assertXPath(pDocument, aXPathPrefix + "mask/mask/metafile/transform/mask/polypolygon", "width",
u"10088");
assertXPath(
pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform/mask/group", 17);
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy11",
u"1");
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy12",
u"0");
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy13",
u"3");
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy21",
u"0");
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy22",
u"1");
assertXPath(pDocument,
aXPathPrefix + "mask/mask/metafile/transform/mask/mask/metafile/transform", "xy23",
u"-140");
}
CPPUNIT_TEST_FIXTURE(Test, testFillRectsWithTextureBrush)
{
// tdf#129342 tdf#170985 EMF+ file with ObjectTypeBrush, ObjectTypepen, FillRects
+4 -4
View File
@@ -1424,8 +1424,8 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf158445)
xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf158445.svg");
assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/polypolygoncolor", "color", u"#000000");
assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/polypolygoncolor/polypolygon", "height", u"8.052");
assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/polypolygoncolor/polypolygon", "width", u"5.328");
assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/polypolygoncolor/polypolygon", "height", u"8");
assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/polypolygoncolor/polypolygon", "width", u"5");
}
CPPUNIT_TEST_FIXTURE(Test, testTdf159594)
@@ -1433,8 +1433,8 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf159594)
xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf159594.svg");
assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor", "color", u"#000000");
assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor/polypolygon", "height", u"11.671875");
assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor/polypolygon", "width", u"7.5");
assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor/polypolygon", "height", u"12");
assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor/polypolygon", "width", u"8");
}
CPPUNIT_TEST_FIXTURE(Test, testTdf97663)