summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Cecchetti <marco.cecchetti@collabora.com>2021-02-02 14:05:46 +0100
committerAndras Timar <andras.timar@collabora.com>2021-02-22 15:49:03 +0100
commit7ab136407252014273c9ba193f0bf9ea104c9db2 (patch)
tree7bcb97b54dce92fbe9f126a2af6f17fa80cd21ab
parentfilter: svg: js engine: unit test: slide background: exporting bitmaps (diff)
downloadcore-7ab136407252014273c9ba193f0bf9ea104c9db2.tar.gz
core-7ab136407252014273c9ba193f0bf9ea104c9db2.zip
filter: svg: export tiled background by exploiting svg:pattern element
By exporting a tiled bitmap background by exploiting the <pattern> element we get performance improvement when the background is made of a big number of tiles. The unit test for the tiled background case has been updated. Change-Id: I80a4eebd081d2c59ec7d9906fc9c616692f7e0fa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110319 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Ashod Nakashian <ash@collabora.com>
-rw-r--r--filter/source/svg/svgexport.cxx184
-rw-r--r--filter/source/svg/svgfilter.hxx13
-rw-r--r--filter/source/svg/svgwriter.cxx12
-rw-r--r--sd/qa/unit/SVGExportTests.cxx63
4 files changed, 254 insertions, 18 deletions
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx
index a2357b605b18..a7a08f486df9 100644
--- a/filter/source/svg/svgexport.cxx
+++ b/filter/source/svg/svgexport.cxx
@@ -512,6 +512,32 @@ static void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz )
OSL_FAIL( "MetaBitmapActionGetSize: passed MetaAction pointer is null." );
return;
}
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ rSz = pA->GetSize();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ rSz = pA->GetSize();
+ }
+ break;
+ default: break;
+ }
+}
+
+static void MetaBitmapActionGetOrigSize( const MetaAction* pAction, Size& rSz )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "MetaBitmapActionGetOrigSize: passed MetaAction pointer is null." );
+ return;
+ }
const MetaActionType nType = pAction->GetType();
MapMode aSourceMode( MapUnit::MapPixel );
@@ -538,6 +564,16 @@ static void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz )
rSz = OutputDevice::LogicToLogic( rSz, aSourceMode, aTargetMode );
}
+static OUString getPatternIdForTiledBackground( const OUString& sSlideId, BitmapChecksum nChecksum )
+{
+ return "bg-pattern." + sSlideId + "." + OUString::number( nChecksum );
+}
+
+static OUString getIdForTiledBackground( const OUString& sSlideId, BitmapChecksum nChecksum )
+{
+ return "bg-" + sSlideId + "." + OUString::number( nChecksum );
+}
+
} // end anonymous namespace
size_t HashBitmap::operator()( const ObjectRepresentation& rObjRep ) const
@@ -945,6 +981,7 @@ bool SVGFilter::implExportDocument()
implExportTextEmbeddedBitmaps();
implExportBackgroundBitmaps();
mpSVGWriter->SetEmbeddedBitmapRefs( &maBitmapActionMap );
+ implExportTiledBackground();
}
// #i124608# export a given object selection, so no MasterPage export at all
@@ -1530,6 +1567,77 @@ void SVGFilter::implExportBackgroundBitmaps()
}
}
+void SVGFilter::implExportTiledBackground()
+{
+ if( maPatterProps.empty() )
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundPatterns" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ for( const auto& [ rSlideId, rData ] : maPatterProps )
+ {
+ auto aBitmapActionIt = maBitmapActionMap.find( rData.aBitmapChecksum );
+ if( aBitmapActionIt != maBitmapActionMap.end() )
+ {
+ // pattern element attributes
+ const OUString sPatternId = getPatternIdForTiledBackground( rSlideId, rData.aBitmapChecksum );
+ // <pattern> <use>
+ {
+ // pattern element attributes
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPatternId );
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( rData.aPos.X() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( rData.aPos.Y() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSize.Width() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSize.Height() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "patternUnits", "userSpaceOnUse" );
+
+ SvXMLElementExport aPatternElem( *mpSVGExport, XML_NAMESPACE_NONE, "pattern", true, true );
+
+ // use element attributes
+ const Size& aOrigSize = aBitmapActionIt->second->GetPrefSize();
+ OUString sTransform;
+ Fraction aFractionX( rData.aSize.Width(), aOrigSize.Width() );
+ Fraction aFractionY( rData.aSize.Height(), aOrigSize.Height() );
+ double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) )
+ sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")";
+
+ if( !sTransform.isEmpty() )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform );
+
+ // referenced bitmap
+ OUString sRefId = "#bitmap(" + OUString::number( rData.aBitmapChecksum ) + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", sRefId );
+
+ SvXMLElementExport aUseElem( *mpSVGExport, XML_NAMESPACE_NONE, "use", true, true );
+ } // </use> </pattern>
+
+ // <g> <rect>
+ {
+ // group
+ const OUString sBgId = getIdForTiledBackground( rSlideId, rData.aBitmapChecksum );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBgId );
+
+ SvXMLElementExport aGroupElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // rectangle
+ const OUString sUrl = "url(#" + sPatternId + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", "0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", "0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSlideSize.Width() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSlideSize.Height() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", sUrl );
+
+ SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true );
+ } // </g> </rect>
+ }
+ }
+}
+
/** SVGFilter::implExportTextEmbeddedBitmaps
We export bitmaps embedded into text shapes, such as those used by list
items with image style, only once in a specific defs element.
@@ -2379,21 +2487,63 @@ void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing::
xExporter->filter( aDescriptor );
aMtf.Read( *aFile.GetStream( StreamMode::READ ) );
- MetaAction* pAction;
+ bool bIsBitmap = false;
+ bool bIsTiled = false;
+
+ // look for background type
+ Reference< XPropertySet > xPropSet( rxDrawPage, UNO_QUERY );
+ if( xPropSet.is() )
+ {
+ Reference< XPropertySet > xBackground;
+ xPropSet->getPropertyValue( "Background" ) >>= xBackground;
+ if( xBackground.is() )
+ {
+ drawing::FillStyle aFillStyle;
+ if( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle )
+ {
+ if( aFillStyle == drawing::FillStyle::FillStyle_BITMAP )
+ {
+ bIsBitmap = true;
+ xBackground->getPropertyValue( "FillBitmapTile" ) >>= bIsTiled;
+
+ // we do not handle tiled background with a row or column offset
+ sal_Int32 nFillBitmapOffsetX = 0, nFillBitmapOffsetY = 0;
+ xBackground->getPropertyValue( "FillBitmapOffsetX" ) >>= nFillBitmapOffsetX;
+ xBackground->getPropertyValue( "FillBitmapOffsetY" ) >>= nFillBitmapOffsetY;
+ bIsTiled = bIsTiled && ( nFillBitmapOffsetX == 0 && nFillBitmapOffsetY == 0 );
+ }
+ }
+ }
+ }
+
+ if( !bIsBitmap )
+ {
+ (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf );
+ return;
+ }
+
+ GDIMetaFile aTiledMtf;
+ bool bBitmapFound = false;
+ MetaAction* pAction;
sal_uLong nCount = aMtf.GetActionSize();
for( sal_uLong nCurAction = 0; nCurAction < nCount; ++nCurAction )
{
pAction = aMtf.GetAction( nCurAction );
const MetaActionType nType = pAction->GetType();
+ // collect bitmap
if( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE )
{
+ if( bBitmapFound )
+ continue;
+ bBitmapFound = true; // the subsequent bitmaps are still the same just translated
+
BitmapChecksum nChecksum = GetBitmapChecksum( pAction );
if( maBitmapActionMap.find( nChecksum ) == maBitmapActionMap.end() )
{
Point aPos; // (0, 0)
Size aSize;
- MetaBitmapActionGetSize( pAction, aSize );
+ MetaBitmapActionGetOrigSize( pAction, aSize );
MetaAction* pBitmapAction = CreateMetaBitmapAction( pAction, aPos, aSize );
if( pBitmapAction )
{
@@ -2405,10 +2555,38 @@ void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing::
maBitmapActionMap[ nChecksum ].reset( pEmbeddedBitmapMtf );
}
}
+
+ if( bIsTiled )
+ {
+ // collect data for <pattern> and <rect>
+ const OUString & sPageId = implGetValidIDFromInterface( rxDrawPage );
+ Point aPos;
+ MetaBitmapActionGetPoint( pAction, aPos );
+ Size aSize;
+ MetaBitmapActionGetSize( pAction, aSize );
+
+ sal_Int32 nSlideWidth = 0, nSlideHeight = 0;
+ xPropSet->getPropertyValue( "Width" ) >>= nSlideWidth;
+ xPropSet->getPropertyValue( "Height" ) >>= nSlideHeight;
+
+ maPatterProps[ sPageId ] = { nChecksum, aPos, aSize, { nSlideWidth, nSlideHeight } };
+
+ // create meta comment action that is used to exporting
+ // a <use> element which points to the group element representing the background
+ const OUString sBgId = getIdForTiledBackground( sPageId, nChecksum );
+ OString sComment = sTiledBackgroundTag + " " + sBgId.toUtf8();
+ MetaCommentAction* pCommentAction = new MetaCommentAction( sComment );
+ if( pCommentAction )
+ aTiledMtf.AddAction( pCommentAction );
+ }
+ }
+ else if( bIsTiled && nType != MetaActionType::CLIPREGION )
+ {
+ aTiledMtf.AddAction( pAction );
}
}
- (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf );
+ (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, bIsTiled ? aTiledMtf : aMtf );
}
OUString SVGFilter::implGetClassFromShape( const Reference< css::drawing::XShape >& rxShape )
diff --git a/filter/source/svg/svgfilter.hxx b/filter/source/svg/svgfilter.hxx
index 64dc619dc739..3f7979ecf766 100644
--- a/filter/source/svg/svgfilter.hxx
+++ b/filter/source/svg/svgfilter.hxx
@@ -66,6 +66,8 @@ using namespace ::com::sun::star::xml::sax;
// Placeholder tag used into the ImplWriteActions method to filter text placeholder fields
static const OUString sPlaceholderTag( "<[:isPlaceholder:]>" );
+// This tag is used for exporting a slide background made of tiled bitmaps
+static const OString sTiledBackgroundTag( "SLIDE_BACKGROUND" );
class SVGExport : public SvXMLExport
{
@@ -174,6 +176,15 @@ struct EqualityBitmap
// This must match the same type definition in svgwriter.hxx
typedef std::unordered_map< BitmapChecksum, std::unique_ptr< GDIMetaFile > > MetaBitmapActionMap;
+struct PatternData
+{
+ BitmapChecksum aBitmapChecksum;
+ Point aPos;
+ Size aSize;
+ Size aSlideSize;
+};
+typedef std::map<OUString, PatternData> PatternPropertySet;
+
class SVGFontExport;
class SVGActionWriter;
class EditFieldInfo;
@@ -234,6 +245,7 @@ private:
MetaBitmapActionSet mEmbeddedBitmapActionSet;
ObjectMap mEmbeddedBitmapActionMap;
MetaBitmapActionMap maBitmapActionMap;
+ PatternPropertySet maPatterProps;
std::vector< Reference< css::drawing::XDrawPage > > mMasterPageTargets;
Link<EditFieldInfo*,void> maOldFieldHdl;
@@ -254,6 +266,7 @@ private:
void implEmbedBulletGlyph( sal_Unicode cBullet, const OUString & sPathData );
void implExportTextEmbeddedBitmaps();
void implExportBackgroundBitmaps();
+ void implExportTiledBackground();
void implGenerateScript();
bool implExportDocument();
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index 4f4bea2dc98b..a96efaaec878 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -3568,6 +3568,18 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
}
}
}
+ else if( pA->GetComment().startsWithIgnoreAsciiCase( sTiledBackgroundTag ) )
+ {
+ // In the tile case the background is rendered through a rectangle
+ // filled by exploiting an exported pattern element.
+ // Both the pattern and the rectangle are embedded in a <defs> element.
+ // The comment content has the following format: "SLIDE_BACKGROUND <background-id>"
+ const OString& sComment = pA->GetComment();
+ OUString sRefId = "#" + OUString::fromUtf8( sComment.getToken(1, ' ') );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
}
break;
diff --git a/sd/qa/unit/SVGExportTests.cxx b/sd/qa/unit/SVGExportTests.cxx
index d14f7e146893..4a45cc4edf29 100644
--- a/sd/qa/unit/SVGExportTests.cxx
+++ b/sd/qa/unit/SVGExportTests.cxx
@@ -28,6 +28,8 @@
#define SVG_DEFS *[name()='defs']
#define SVG_IMAGE *[name()='image']
#define SVG_USE *[name()='use']
+#define SVG_PATTERN *[name()='pattern']
+#define SVG_RECT *[name()='rect']
using namespace css;
@@ -47,6 +49,19 @@ static BitmapChecksum getBitmapChecksumFromId(const OUString& sId)
OUString sChecksum = sId.copy( nStart, nCount );
return sChecksum.toUInt64();
}
+
+static bool isValidBackgroundPatternId(const OUString& sId)
+{
+ std::regex aRegEx( R"(bg\-pattern\.id\d+\.\d+)" );
+ return std::regex_match(sId.toUtf8().getStr(), aRegEx);
+}
+
+static bool isValidTiledBackgroundId(const OUString& sId)
+{
+ std::regex aRegEx( R"(bg\-id\d+\.\d+)" );
+ return std::regex_match(sId.toUtf8().getStr(), aRegEx);
+}
+
}
class SdSVGFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
@@ -219,31 +234,49 @@ public:
xmlDocPtr svgDoc = parseXml(maTempFile);
CPPUNIT_ASSERT(svgDoc);
+ // check the bitmap
assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9] ), "class", "BackgroundBitmaps");
assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), 1);
+ // check the pattern and background rectangle
+ assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10] ), "class", "BackgroundPatterns");
+ assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN ), 1);
+ assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN/SVG_USE ), 1);
+ assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G/SVG_RECT ), 1);
+
+
+ // check that <pattern><use> is pointing to the correct <image>
OUString sImageId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), "id");
CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid id: " + sImageId.toUtf8()).getStr(), isValidBitmapId(sImageId));
BitmapChecksum nChecksum = getBitmapChecksumFromId(sImageId);
CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid checksum: " + sImageId.toUtf8()).getStr(), nChecksum != 0);
- // tiles case
- constexpr unsigned int nNumberOfTiles = 37;
+ OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN/SVG_USE ), "href");
+ CPPUNIT_ASSERT_MESSAGE("The <pattern><use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#"));
+ sRef = sRef.copy(1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("The href attribute for <pattern><use> does not match the <image> id attribute: ", sImageId, sRef);
+
+ OUString sPatternId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN ), "id");
+ CPPUNIT_ASSERT_MESSAGE(OString("The exported pattern has not a valid id: " + sPatternId.toUtf8()).getStr(), isValidBackgroundPatternId(sPatternId));
+
+ OUString sFillUrl = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G/SVG_RECT ), "fill");
+ CPPUNIT_ASSERT_MESSAGE("The fill attribute for the <rectangle> element has not a url format .", sFillUrl.startsWith("url(#") && sFillUrl.endsWith(")"));
+ // remove "url(#" and ")"
+ sFillUrl = sFillUrl.copy(5, sFillUrl.getLength() - 6);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("The fill url for <rectangle> does not match the <pattern> id attribute: ", sPatternId, sFillUrl);
+
+ OUString sBackgroundId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G ), "id");
+ CPPUNIT_ASSERT_MESSAGE(OString("The exported tiled background has not a valid id: " + sBackgroundId.toUtf8()).getStr(), isValidTiledBackgroundId(sBackgroundId));
+
+ // check <use> element that point to the tiled background
assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS ), "class", "SlideBackground");
- assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ), nNumberOfTiles);
-
- for (unsigned int i = 1; i <= nNumberOfTiles; ++i)
- {
- OString sIndex = OStringLiteral("[") + OString::number(i) + OStringLiteral("]");
- OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ) + sIndex, "href");
- CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#"));
- sRef = sRef.copy(1);
- CPPUNIT_ASSERT_MESSAGE(OString("The <use> element does not point to a valid bitmap id: " + sRef.toUtf8()).getStr(), isValidBitmapId(sRef));
-
- BitmapChecksum nUseChecksum = getBitmapChecksumFromId(sRef);
- CPPUNIT_ASSERT_EQUAL_MESSAGE("The bitmap checksum used in <use> does not match the expected one: ", nChecksum, nUseChecksum);
- }
+ assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_USE ), 1);
+
+ sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_USE ), "href");
+ CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#"));
+ sRef = sRef.copy(1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("The href attribute for <use> does not match the tiled background id attribute: ", sBackgroundId, sRef);
}
CPPUNIT_TEST_SUITE(SdSVGFilterTest);