diff options
author | Armin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de> | 2023-06-05 17:15:34 +0200 |
---|---|---|
committer | Andras Timar <andras.timar@collabora.com> | 2023-06-09 12:25:11 +0200 |
commit | 0f948fc383c716cd6583969b258805c3501589ba (patch) | |
tree | 1ef4bd2a717b0b6f2c57c1587336b3838136d5f2 /filter | |
parent | 2c8002555a9b288b756d1af0f69656bbace36e6c (diff) |
MCGR: tdf#155479 repair gradient SVG export for MCGR
Unfortunately SVG export is based on metafiles and thus there
is (in principle) no way to get the BGradient/ColorStop/MCGR
data transfered as needed. For that, using UNO API to read the
model or using B2DPrimitives would help - as is better for the
export respectively.
Since there is not the time to re-design SVG export I added
this 'compromize' as a fix. It gets the needed data transported
over the metafile (that part is the compromize). It then
exports the MCGR data to SVG (at least - as was already there -
if it's a linear/axial gradient). This happens now with all
Gradient Stops when there is a MCGR gradient. That part is/will
hopefully be re-usable if SVG export gets redesigned.
I also added a handling for StepCount feature, so when used (in
LO, others do not have that) 'hard' color stops get generated
to make the gradient look identical for SVG export.
Had to make adding of that extra-information in metafiles
dependent on exporting really to SVG. There are 51 cases which
use 'MetaActionType::COMMENT' which would potentially have
to be adapted.
Also added code to solve the problem for TransparencePrimitive2D
at VclMetafileProcessor2D::processTransparencePrimitive2D. This
will now - also only for SVG export - directly create the needed
MetaFloatTransparentAction and add additional MCGR information.
This will be used on SVG export to write a 'Mask' as was done
before. This is now capable of creating fill MCGR-Masks in
the sense that any number of TransparencyStops will be supported.
Change-Id: Ic6d022714eae96b8fbc09e60652851ac5799b757
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152623
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Diffstat (limited to 'filter')
-rw-r--r-- | filter/source/svg/svgexport.cxx | 3 | ||||
-rw-r--r-- | filter/source/svg/svgwriter.cxx | 198 | ||||
-rw-r--r-- | filter/source/svg/svgwriter.hxx | 10 |
3 files changed, 124 insertions, 87 deletions
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index c27481baf79b..3114d80b592a 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -2380,7 +2380,8 @@ bool SVGFilter::implCreateObjectsFromShape( const Reference< css::drawing::XDraw if( pObj ) { - Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj)); + // tdf#155479 need to signal SVG export + Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj, true)); // Writer graphic shapes are handled differently if( mbWriterFilter && aGraphic.GetType() == GraphicType::NONE ) diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index 8c32dd7cfb0f..97e1c74c082b 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -39,6 +39,8 @@ #include <i18nlangtag/languagetag.hxx> #include <o3tl/string_view.hxx> #include <svx/svdomedia.hxx> +#include <basegfx/utils/bgradient.hxx> +#include <tools/vcompat.hxx> #include <com/sun/star/container/XEnumerationAccess.hpp> #include <com/sun/star/container/XIndexReplace.hpp> @@ -640,7 +642,7 @@ sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAc bEmpty = false; mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), pA->GetGradient(), - nWriteFlags, &maTextOpacity); + nWriteFlags, pA->getSVGTransparencyColorStops(), &maTextOpacity); } } break; @@ -2312,12 +2314,12 @@ void SVGActionWriter::ImplWritePattern( const tools::PolyPolygon& rPolyPoly, void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, - sal_uInt32 nWriteFlags) + sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { if ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { - ImplWriteGradientLinear( rPolyPoly, rGradient ); + ImplWriteGradientLinear( rPolyPoly, rGradient, pColorStops ); } else { @@ -2327,7 +2329,7 @@ void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, - const Gradient& rGradient ) + const Gradient& rGradient, const basegfx::BColorStops* pColorStops ) { if( !rPolyPoly.Count() ) return; @@ -2370,60 +2372,53 @@ void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPo { SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ); + basegfx::BColorStops aColorStops; - const Color aStartColor = ImplGetColorWithIntensity( rGradient.GetStartColor(), rGradient.GetStartIntensity() ); - const Color aEndColor = ImplGetColorWithIntensity( rGradient.GetEndColor(), rGradient.GetEndIntensity() ); - double fBorderOffset = rGradient.GetBorder() / 100.0; - const sal_uInt16 nSteps = rGradient.GetSteps(); - if( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR ) + if (nullptr != pColorStops && pColorStops->size() > 1) { - // Emulate non-smooth gradient - if( 0 < nSteps && nSteps < 100 ) - { - double fOffsetStep = ( 1.0 - fBorderOffset ) / static_cast<double>(nSteps); - for( sal_uInt16 i = 0; i < nSteps; i++ ) { - Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep ); - aColor = ImplGetGradientColor( aStartColor, aEndColor, ( i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep ); - } - } - else - { - ImplWriteGradientStop( aStartColor, fBorderOffset ); - ImplWriteGradientStop( aEndColor, 1.0 ); - } + // if we got the real colr stops, use them. That way we are + // now capable in the SVG export to export real multi color gradients + aColorStops = *pColorStops; } else { - fBorderOffset /= 2; - // Emulate non-smooth gradient - if( 0 < nSteps && nSteps < 100 ) - { - double fOffsetStep = ( 0.5 - fBorderOffset ) / static_cast<double>(nSteps); - // Upper half - for( sal_uInt16 i = 0; i < nSteps; i++ ) - { - Color aColor = ImplGetGradientColor( aEndColor, aStartColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep ); - aColor = ImplGetGradientColor( aEndColor, aStartColor, (i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep ); - } - // Lower half - for( sal_uInt16 i = 0; i < nSteps; i++ ) - { - Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep ); - aColor = ImplGetGradientColor( aStartColor, aEndColor, (i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep ); - } - } - else - { - ImplWriteGradientStop( aEndColor, fBorderOffset ); - ImplWriteGradientStop( aStartColor, 0.5 ); - ImplWriteGradientStop( aEndColor, 1.0 - fBorderOffset ); - } + // else create color stops with 'old' start/endColor + aColorStops.emplace_back(0.0, rGradient.GetStartColor().getBColor()); + aColorStops.emplace_back(1.0, rGradient.GetEndColor().getBColor()); + } + + // create a basegfx::BGradient with the info to be able to directly + // use the tooling it offers + basegfx::BGradient aGradient( + aColorStops, + rGradient.GetStyle(), + rGradient.GetAngle(), + rGradient.GetOfsX(), + rGradient.GetOfsY(), + rGradient.GetBorder(), + rGradient.GetStartIntensity(), + rGradient.GetEndIntensity(), + rGradient.GetSteps()); + + // apply Start/EndIntensity to the whole color stops - if used + aGradient.tryToApplyStartEndIntensity(); + + // apply border to color stops - if used + aGradient.tryToApplyBorder(); + + // convert from 'axial' to linear - if needed and used + aGradient.tryToApplyAxial(); + + // apply 'Steps' as hard gradient stops - if used + aGradient.tryToApplySteps(); + + // write prepared gradient stops + for (const auto& rCand : aGradient.GetColorStops()) + { + ImplWriteGradientStop( + Color(rCand.getStopColor()), + rCand.getStopOffset()); + // aStartColor, fBorderOffset ); } } } @@ -2461,28 +2456,9 @@ Color SVGActionWriter::ImplGetColorWithIntensity( const Color& rColor, } -Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor, - const Color& rEndColor, - double fOffset ) -{ - tools::Long nRedStep = rEndColor.GetRed() - rStartColor.GetRed(); - tools::Long nNewRed = rStartColor.GetRed() + static_cast<tools::Long>( nRedStep * fOffset ); - nNewRed = ( nNewRed < 0 ) ? 0 : ( nNewRed > 0xFF) ? 0xFF : nNewRed; - - tools::Long nGreenStep = rEndColor.GetGreen() - rStartColor.GetGreen(); - tools::Long nNewGreen = rStartColor.GetGreen() + static_cast<tools::Long>( nGreenStep * fOffset ); - nNewGreen = ( nNewGreen < 0 ) ? 0 : ( nNewGreen > 0xFF) ? 0xFF : nNewGreen; - - tools::Long nBlueStep = rEndColor.GetBlue() - rStartColor.GetBlue(); - tools::Long nNewBlue = rStartColor.GetBlue() + static_cast<tools::Long>( nBlueStep * fOffset ); - nNewBlue = ( nNewBlue < 0 ) ? 0 : ( nNewBlue > 0xFF) ? 0xFF : nNewBlue; - - return Color( static_cast<sal_uInt8>(nNewRed), static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) ); -} - void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags, - OUString* pTextFillOpacity) + const basegfx::BColorStops* pColorStops, OUString* pTextFillOpacity) { OUString aStyle; if (rGradient.GetStartColor() == rGradient.GetEndColor()) @@ -2523,7 +2499,19 @@ void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, aGradient.SetEndColor(aTmpColor); aGradient.SetEndIntensity(nTmpIntensity); - ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags); + // tdf#155479 prep local ColorStops. The code above + // implies that the ColorStops need to be reversed, + // so do so & use change of local ptr to represent this + basegfx::BColorStops aLocalColorStops; + + if (nullptr != pColorStops) + { + aLocalColorStops = *pColorStops; + aLocalColorStops.reverseColorStops(); + pColorStops = &aLocalColorStops; + } + + ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags, pColorStops); } } @@ -2533,7 +2521,7 @@ void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, } void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, - const Gradient& rGradient, sal_uInt32 nWriteFlags) + const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin()); const Size aSrcSize(rMtf.GetPrefSize()); @@ -2560,7 +2548,7 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con std::unique_ptr<SvXMLElementExport> pElemG; if (!maTextWriter.hasTextOpacity()) { - StartMask(rDestPt, rDestSize, rGradient, nWriteFlags); + StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops); pElemG.reset( new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true)); } @@ -3283,7 +3271,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, const tools::Polygon aRectPoly( pA->GetRect() ); const tools::PolyPolygon aRectPolyPoly( aRectPoly ); - ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags, nullptr ); } } break; @@ -3293,7 +3281,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction); - ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags, nullptr ); } } break; @@ -3340,7 +3328,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(), - pA->GetGradient(), nWriteFlags ); + pA->GetGradient(), nWriteFlags, pA->getSVGTransparencyColorStops() ); } } break; @@ -3374,7 +3362,55 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, { const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); - if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) && + if (pA->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_BEGIN")) + { + // detect and use the new BGRAD_SEQ_* metafile comment actions + const MetaGradientExAction* pGradAction(nullptr); + bool bDone(false); + + while (!bDone && (++nCurAction < nCount)) + { + pAction = rMtf.GetAction(nCurAction); + + if (MetaActionType::GRADIENTEX == pAction->GetType()) + { + // remember the 'paint' data action + pGradAction = static_cast<const MetaGradientExAction*>(pAction); + } + else if (MetaActionType::COMMENT == pAction->GetType() + && static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_END")) + { + // end action found + bDone = true; + } + } + + if (nullptr != pGradAction) + { + // we have a complete actions sequence of BGRAD_SEQ_*, so we can now + // read the correct color stops here + basegfx::BColorStops aColorStops; + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + VersionCompatRead aCompat(aMemStm); + sal_uInt16 nTmp; + double fOff, fR, fG, fB; + aMemStm.ReadUInt16( nTmp ); + + for (sal_uInt16 a(0); a < nTmp; a++) + { + aMemStm.ReadDouble(fOff); + aMemStm.ReadDouble(fR); + aMemStm.ReadDouble(fG); + aMemStm.ReadDouble(fB); + + aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB)); + } + + // export with real Color Stops + ImplWriteGradientEx(pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, &aColorStops); + } + } + else if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) ) { const MetaGradientExAction* pGradAction = nullptr; @@ -3395,7 +3431,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, } if( pGradAction ) - ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, nullptr ); } else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) && diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx index 022c2585c5ab..4a84f3ae4d9f 100644 --- a/filter/source/svg/svgwriter.hxx +++ b/filter/source/svg/svgwriter.hxx @@ -41,6 +41,7 @@ #include <stack> #include <unordered_map> +namespace basegfx { class BColorStops; } using namespace ::com::sun::star::uno; using namespace ::com::sun::star::container; @@ -340,12 +341,11 @@ private: void ImplStartClipRegion(sal_Int32 nClipPathId); void ImplEndClipRegion(); void ImplWriteClipPath( const tools::PolyPolygon& rPolyPoly ); - void ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags); - void ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient ); + void ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops); + void ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, const basegfx::BColorStops* pColorStops ); void ImplWriteGradientStop( const Color& rColor, double fOffset ); static Color ImplGetColorWithIntensity( const Color& rColor, sal_uInt16 nIntensity ); - static Color ImplGetGradientColor( const Color& rStartColor, const Color& rEndColor, double fOffset ); - void ImplWriteMask( GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags ); + void ImplWriteMask( GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops); void ImplWriteText( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, tools::Long nWidth ); void ImplWriteText( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, tools::Long nWidth, Color aTextColor ); void ImplWriteBmp( const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, const Point& rSrcPt, const Size& rSrcSz, const css::uno::Reference<css::drawing::XShape>* pShape); @@ -377,7 +377,7 @@ public: void SetEmbeddedBitmapRefs( const MetaBitmapActionMap* pEmbeddedBitmapsMap ); void StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, - sal_uInt32 nWriteFlags, OUString* pTextStyle = nullptr); + sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops, OUString* pTextStyle = nullptr); void SetPreviewMode(bool bState = true) { mbIsPreview = bState; } }; |