basegfx/source/tools/bgradient.cxx | 137 ++++++++ drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 188 +++++++++--- filter/source/svg/svgexport.cxx | 3 filter/source/svg/svgwriter.cxx | 198 +++++++------ filter/source/svg/svgwriter.hxx | 10 include/basegfx/utils/bgradient.hxx | 17 + include/svx/svdxcgv.hxx | 6 include/vcl/bitmapex.hxx | 1 include/vcl/gdimtf.hxx | 7 include/vcl/metaact.hxx | 11 oox/source/export/drawingml.cxx | 56 --- svx/source/svdraw/svdxcgv.cxx | 4 vcl/source/bitmap/BitmapEx.cxx | 5 vcl/source/control/fmtfield.cxx | 2 vcl/source/filter/png/PngImageWriter.cxx | 8 vcl/source/filter/svm/SvmReader.cxx | 21 + vcl/source/filter/svm/SvmWriter.cxx | 23 + vcl/source/gdi/gdimtf.cxx | 9 vcl/source/gdi/metaact.cxx | 5 19 files changed, 535 insertions(+), 176 deletions(-)
New commits: commit 9b8c21acd31f08a0c3f8d88ddac57c80ef5997a1 Author: Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de> AuthorDate: Mon Jun 5 17:15:34 2023 +0200 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Jun 13 23:20:41 2023 +0200 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.gr...@me.com> (cherry picked from commit a6e72e2b314e64f3199f3eaf1ecf78157446f6dd) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152882 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/basegfx/source/tools/bgradient.cxx b/basegfx/source/tools/bgradient.cxx index b56ef0540d17..c96cd0b673cb 100644 --- a/basegfx/source/tools/bgradient.cxx +++ b/basegfx/source/tools/bgradient.cxx @@ -679,6 +679,117 @@ bool BColorStops::isSymmetrical() const return aIter > aRIter; } +void BColorStops::doApplyAxial() +{ + // preapare new ColorStops + basegfx::BColorStops aNewColorStops; + + // add gradient stops in reverse order, scaled to [0.0 .. 0.5] + basegfx::BColorStops::const_reverse_iterator aRevCurrColor(rbegin()); + + while (aRevCurrColor != rend()) + { + aNewColorStops.emplace_back((1.0 - aRevCurrColor->getStopOffset()) * 0.5, + aRevCurrColor->getStopColor()); + aRevCurrColor++; + } + + // prepare non-reverse run + basegfx::BColorStops::const_iterator aCurrColor(begin()); + + if (basegfx::fTools::equalZero(aCurrColor->getStopOffset())) + { + // Caution: do not add 1st entry again, that would be double since it was + // already added as last element of the inverse run above. But only if + // the gradient has a start entry for 0.0 aka StartColor, else it is correct. + aCurrColor++; + } + + // add gradient stops in non-reverse order, translated and scaled to [0.5 .. 1.0] + while (aCurrColor != end()) + { + aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 0.5) + 0.5, + aCurrColor->getStopColor()); + aCurrColor++; + } + + // apply color stops + *this = aNewColorStops; +} + +void BColorStops::doApplySteps(sal_uInt16 nStepCount) +{ + // check for zero or invalid steps setting -> done + if (0 == nStepCount || nStepCount > 100) + return; + + // no change needed if single color + BColor aSingleColor; + if (isSingleColor(aSingleColor)) + return; + + // prepare new color stops, get L/R iterators for segments + basegfx::BColorStops aNewColorStops; + basegfx::BColorStops::const_iterator aColorR(begin()); + basegfx::BColorStops::const_iterator aColorL(aColorR++); + + while (aColorR != end()) + { + // get start/end color for segment + const double fStart(aColorL->getStopOffset()); + const double fDelta(aColorR->getStopOffset() - fStart); + + if (aNewColorStops.empty() || aNewColorStops.back() != *aColorL) + { + // add start color, but check if it is already there - which is the + // case from the 2nd segment on due to a new segment starting with + // the same color as the previous one ended + aNewColorStops.push_back(*aColorL); + } + + if (!basegfx::fTools::equalZero(fDelta)) + { + // create in-between steps, always two at the same positon to + // define a 'hard' color stop. Get start/end color for the segment + const basegfx::BColor& rStartColor(aColorL->getStopColor()); + const basegfx::BColor& rEndColor(aColorR->getStopColor()); + + if (rStartColor != rEndColor) + { + // get relative single-step width + const double fSingleStep(1.0 / static_cast<double>(nStepCount)); + + for (sal_uInt16 a(1); a < nStepCount; a++) + { + // calculate position since being used twice + const double fPosition(fStart + + (fDelta * (static_cast<double>(a) * fSingleStep))); + + // add start color of sub-segment + aNewColorStops.emplace_back( + fPosition, basegfx::interpolate(rStartColor, rEndColor, + static_cast<double>(a - 1) * fSingleStep)); + + // add end color of sub-segment + aNewColorStops.emplace_back( + fPosition, basegfx::interpolate(rStartColor, rEndColor, + static_cast<double>(a) * fSingleStep)); + } + } + } + + // always add end color of segment + aNewColorStops.push_back(*aColorR); + + // next segment + aColorL++; + aColorR++; + } + + // apply the change to color stops + *this = aNewColorStops; +} + std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle) { switch (eStyle) @@ -1022,6 +1133,32 @@ void BGradient::tryToConvertToAxial() SetColorStops(aAxialColorStops); } + +void BGradient::tryToApplyAxial() +{ + // only need to do something if css::awt::GradientStyle_AXIAL, else done + if (GetGradientStyle() != css::awt::GradientStyle_AXIAL) + return; + + // apply the change to color stops + aColorStops.doApplyAxial(); + + // set style to GradientStyle_LINEAR + SetGradientStyle(css::awt::GradientStyle_LINEAR); +} + +void BGradient::tryToApplySteps() +{ + // check for zero or invalid steps setting -> done + if (0 == GetSteps() || GetSteps() > 100) + return; + + // do the action + aColorStops.doApplySteps(GetSteps()); + + // set value to default + SetSteps(0); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index 9b536bcbd3d7..feb30d33e1ed 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -66,6 +66,7 @@ #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata #include <drawinglayer/converters.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/vcompat.hxx> #include <com/sun/star/awt/XControl.hpp> #include <com/sun/star/i18n/BreakIterator.hpp> @@ -1962,58 +1963,126 @@ void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) { - basegfx::B2DVector aScale, aTranslate; - double fRotate, fShearX; + bool useDecompose(false); - maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); - - if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + if (!useDecompose) { - // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. - // This is because VCL Gradient mechanism does *not* support to rotate the gradient - // with objects and this case is not expressible in a Metafile (and cannot be added - // since the FileFormats used, e.g. *.wmf, do not support it either). - // Such cases happen when a graphic object uses a Metafile as graphic information or - // a fill style definition uses a Metafile. In this cases the graphic content is - // rotated with the graphic or filled object; this is not supported by the target - // format of this conversion renderer - Metafiles. - // To solve this, not a Gradient is written, but the decomposition of this object - // is written to the Metafile. This is the PolyPolygons building the gradient fill. - // These will need more space and time, but the result will be as if the Gradient - // was rotated with the object. - // This mechanism is used by all exporters still not using Primitives (e.g. Print, - // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile - // transfers. One more reason to *change* these to primitives. - // BTW: One more example how useful the principles of primitives are; the decomposition - // is by definition a simpler, maybe more expensive representation of the same content. - process(rGradientCandidate); - return; + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) + { + // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. + // This is because VCL Gradient mechanism does *not* support to rotate the gradient + // with objects and this case is not expressible in a Metafile (and cannot be added + // since the FileFormats used, e.g. *.wmf, do not support it either). + // Such cases happen when a graphic object uses a Metafile as graphic information or + // a fill style definition uses a Metafile. In this cases the graphic content is + // rotated with the graphic or filled object; this is not supported by the target + // format of this conversion renderer - Metafiles. + // To solve this, not a Gradient is written, but the decomposition of this object + // is written to the Metafile. This is the PolyPolygons building the gradient fill. + // These will need more space and time, but the result will be as if the Gradient + // was rotated with the object. + // This mechanism is used by all exporters still not using Primitives (e.g. Print, + // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile + // transfers. One more reason to *change* these to primitives. + // BTW: One more example how useful the principles of primitives are; the decomposition + // is by definition a simpler, maybe more expensive representation of the same content. + useDecompose = true; + } } // tdf#150551 for PDF export, use the decomposition for better gradient visualization - if (nullptr != mpPDFExtOutDevData) + if (!useDecompose && nullptr != mpPDFExtOutDevData) { - process(rGradientCandidate); - return; + useDecompose = true; } basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); - if (aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) + if (!useDecompose && aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) { // the range which defines the gradient is different from the range of the // geometry (used for writer frames). This cannot be done calling vcl, thus use // decomposition here - process(rGradientCandidate); - return; + useDecompose = true; } - if (!rGradientCandidate.getFillGradient().getColorStops().empty()) + const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient()); + + if (!useDecompose && rFillGradient.cannotBeHandledByVCL()) { // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient, // that will *not* be capable of representing this properly. Use the // correct decomposition instead + useDecompose = true; + } + + if (useDecompose) + { + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + + // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // write the color stops to a memory stream + SvMemoryStream aMemStm; + VersionCompatWrite aCompat(aMemStm, 1); + + const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops()); + sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size())); + aMemStm.WriteUInt16(nTmp); + + for (auto const& rCand : rColorStops) + { + aMemStm.WriteDouble(rCand.getStopOffset()); + const basegfx::BColor& rColor(rCand.getStopColor()); + aMemStm.WriteDouble(rColor.getRed()); + aMemStm.WriteDouble(rColor.getGreen()); + aMemStm.WriteDouble(rColor.getBlue()); + } + + // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END' + // that is capable of holding the new color step information, plus the + // already used MetaActionType::GRADIENTEX. + // With that combination only places that know about that new BGRAD_SEQ_* will + // use it while all others will work on the created decomposition of the + // gradient for compatibility - which are single-color filled polygons + pMetaFile->AddAction(new MetaCommentAction( + "BGRAD_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + + // create MetaActionType::GRADIENTEX + // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and + // basegfx::BGradient here directly, but may have to add streaming OPs + // for these, so for now just go with what we use all the time. The real + // work for improvement should not go to this 'compromize' but to a real + // re-work of the SVG export (or/and others) to no longer work on metafiles + // but on UNO API or primitives (whatever fits best to the specific export) + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); + aLocalPolyPolygon.transform(maCurrentTransformation); + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + } + + // use decompose to draw, will create PolyPolygon ColorFill actions process(rGradientCandidate); + + // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // close the BGRAD_SEQ_* actions range + pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END")); + } + return; } @@ -2025,8 +2094,7 @@ void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( // it is safest to use the VCL OutputDevice::DrawGradient method which creates those. // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon Gradient aVCLGradient; - impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rGradientCandidate.getFillGradient(), - false); + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); aLocalPolyPolygon.transform(maCurrentTransformation); // #i82145# ATM VCL printing of gradients using curved shapes does not work, @@ -2296,6 +2364,10 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( pFiGradient = nullptr; } + // tdf#155479 preps for holding extra-MCGR infos + bool bSVGTransparencyColorStops(false); + basegfx::BColorStops aSVGTransparencyColorStops; + // MCGR: tdf#155437 If we have identified a transparency gradient, // check if VCL is able to handle it at all if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL()) @@ -2314,7 +2386,21 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( // available in metafiles, with the known limitations (not backward comp, all // places using it would need adaption, ...), but combined with knowing that nearly // all usages ignore or render it locally anyways makes that a non-option. - pFiGradient = nullptr; + + // tdf#155479 Yepp, as already mentionmed above we need to add + // some MCGR infos in case of SVG export, prepare that here + if (nullptr != mpOutputDevice->GetConnectMetaFile() + && mpOutputDevice->GetConnectMetaFile()->getSVG()) + { + // for SVG, do not use decompose & prep extra data + bSVGTransparencyColorStops = true; + aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops(); + } + else + { + // use decomposition + pFiGradient = nullptr; + } } if (nullptr != pFiGradient) @@ -2322,6 +2408,13 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( // this combination of Gradient can be expressed/handled by // vcl/metafile, so add it directly. various content, create content-metafile GDIMetaFile aContentMetafile; + + // tdf#155479 do not forget to forward SVG flag for sub-content + if (bSVGTransparencyColorStops) + { + aContentMetafile.setSVG(true); + } + const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); // re-create a VCL-gradient from FillGradientPrimitive2D @@ -2329,9 +2422,30 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(), true); - // render it to VCL (creates MetaFloatTransparentAction) - mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), - aPrimitiveRectangle.GetSize(), aVCLGradient); + if (bSVGTransparencyColorStops) + { + // tdf#155479 create action directly & add extra + // MCGR infos to the metafile, do that by adding - ONLY in + // case of SVG export - to the MetaFileAction. For that + // reason, do what OutputDevice::DrawTransparent will do, + // but locally. + // NOTE: That would be good for this whole + // VclMetafileProcessor2D anyways to allow to get it + // completely independent from OutputDevice in the long run + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + rtl::Reference<::MetaFloatTransparentAction> pAction( + new MetaFloatTransparentAction(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient)); + + pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops); + pMetaFile->AddAction(pAction); + } + else + { + // render it to VCL (creates MetaFloatTransparentAction) + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } return; } diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index 776643a9740d..a8afae2c6461 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -2381,7 +2381,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 e5f918afb0aa..bb9f39dd8d19 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; @@ -2313,12 +2315,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 { @@ -2328,7 +2330,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; @@ -2371,60 +2373,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 ); } } } @@ -2462,28 +2457,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()) @@ -2524,7 +2500,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); } } @@ -2534,7 +2522,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()); @@ -2561,7 +2549,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)); } @@ -3284,7 +3272,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; @@ -3294,7 +3282,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; @@ -3341,7 +3329,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; @@ -3375,7 +3363,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; @@ -3396,7 +3432,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 16ac77a20026..b0f65f319262 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; } }; diff --git a/include/basegfx/utils/bgradient.hxx b/include/basegfx/utils/bgradient.hxx index 1f42b23c6321..3e2acf9a4b67 100644 --- a/include/basegfx/utils/bgradient.hxx +++ b/include/basegfx/utils/bgradient.hxx @@ -96,6 +96,8 @@ public: return getStopOffset() == rCandidate.getStopOffset() && getStopColor() == rCandidate.getStopColor(); } + + bool operator!=(const BColorStop& rCandidate) const { return !(*this == rCandidate); } }; /* MCGR: Provide ColorStops definition to the FillGradientAttribute @@ -276,6 +278,13 @@ public: // returns true if the color stops are symmetrical in color and offset, otherwise false. bool isSymmetrical() const; + // assume that the color stops represent an Axial gradient + // and replace with gradient stops to represent the same + // gradient as linear gradient + void doApplyAxial(); + + // apply Steps as 'hard' color stops + void doApplySteps(sal_uInt16 nStepCount); }; class BASEGFX_DLLPUBLIC BGradient final @@ -337,7 +346,11 @@ public: /// Tooling method to fill awt::Gradient2 from data contained in the given basegfx::BGradient css::awt::Gradient2 getAsGradient2() const; - /// Tooling to handle border correction/integration and StartStopIntensity + // Tooling to handle + // - border correction/integration + // - apply StartStopIntensity to color stops + // - convert type from 'axial' to linear + // - apply Steps as 'hard' color stops void tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops = nullptr); void tryToApplyBorder(); void tryToApplyStartEndIntensity(); @@ -345,6 +358,8 @@ public: // If a linear gradient is symmetrical it is converted to an axial gradient. // Does nothing in other cases and for other gradient types. void tryToConvertToAxial(); + void tryToApplyAxial(); + void tryToApplySteps(); }; } diff --git a/include/svx/svdxcgv.hxx b/include/svx/svdxcgv.hxx index 11e16b2ebcdb..2d7f04455913 100644 --- a/include/svx/svdxcgv.hxx +++ b/include/svx/svdxcgv.hxx @@ -79,10 +79,14 @@ public: The object (can also be a group object) to retrieve a Graphic for. + @param bSVG + tdf#155479 need to know it's SVG export, default is false + + @return a graphical representation of the given object, as it appears on screen (e.g. with rotation, if any, applied). */ - static Graphic GetObjGraphic(const SdrObject& rSdrObject); + static Graphic GetObjGraphic(const SdrObject& rSdrObject, bool bSVG = false); // The new Draw objects are marked for all paste methods. // If bAddMark is true, the new Draw objects are added to an existing diff --git a/include/vcl/gdimtf.hxx b/include/vcl/gdimtf.hxx index f8637675482d..d62cd899623b 100644 --- a/include/vcl/gdimtf.hxx +++ b/include/vcl/gdimtf.hxx @@ -67,6 +67,9 @@ private: bool m_bRecord; bool m_bUseCanvas; + // tdf#155479 need to know if it's SVG export + bool m_bSVG; + SAL_DLLPRIVATE static Color ImplColAdjustFnc( const Color& rColor, const void* pColParam ); SAL_DLLPRIVATE static BitmapEx ImplBmpAdjustFnc( const BitmapEx& rBmpEx, const void* pBmpParam ); @@ -186,6 +189,10 @@ public: void UseCanvas( bool _bUseCanvas ); bool GetUseCanvas() const { return m_bUseCanvas; } + // tdf#155479 + bool getSVG() const { return m_bSVG; } + void setSVG(bool bNew) { m_bSVG = bNew; } + /// Dumps the meta actions as XML in metafile.xml. void dumpAsXml(const char* pFileName = nullptr) const; }; diff --git a/include/vcl/metaact.hxx b/include/vcl/metaact.hxx index 06357c74a7eb..3f2e85dff6b4 100644 --- a/include/vcl/metaact.hxx +++ b/include/vcl/metaact.hxx @@ -43,6 +43,7 @@ #include <vcl/region.hxx> #include <vcl/rendercontext/RasterOp.hxx> #include <vcl/wall.hxx> +#include <basegfx/utils/bgradient.hxx> #include <memory> @@ -1569,7 +1570,7 @@ public: bool IsTransparent() const override { return true; } }; -class SAL_DLLPUBLIC_RTTI MetaFloatTransparentAction final : public MetaAction +class VCL_DLLPUBLIC MetaFloatTransparentAction final : public MetaAction { private: @@ -1578,6 +1579,9 @@ private: Size maSize; Gradient maGradient; + // tdf#155479 allow holding MCGR infos + std::optional<basegfx::BColorStops> maSVGTransparencyColorStops; + public: MetaFloatTransparentAction(); MetaFloatTransparentAction(MetaFloatTransparentAction const &) = default; @@ -1605,6 +1609,11 @@ public: void SetSize(const Size& rSize) { maSize = rSize; } void SetGradient(const Gradient& rGradient) { maGradient = rGradient; } bool IsTransparent() const override { return true; } + + // tdf#155479 allow holding MCGR infos + const basegfx::BColorStops* getSVGTransparencyColorStops() const + { return !maSVGTransparencyColorStops ? nullptr : &(*maSVGTransparencyColorStops); } + void addSVGTransparencyColorStops(const basegfx::BColorStops& rSVGTransparencyColorStops); }; class VCL_DLLPUBLIC MetaEPSAction final : public MetaAction diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index bd58cbf21249..5b0772550471 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -761,6 +761,15 @@ void DrawingML::WriteGradientFill( basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha); } + // apply steps if used. Need to do that before synchronizeColorStops + // since that may add e.g. for AlphaStops all-the-same no-data entries, + // so the number of entries might change + if (pGradient->GetSteps()) + { + aColorStops.doApplySteps(pGradient->GetSteps()); + aAlphaStops.doApplySteps(pGradient->GetSteps()); + } + // synchronize ColorStops and AlphaStops as preparation to export // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient' // method (at import time) will be exported again @@ -790,48 +799,11 @@ void DrawingML::WriteGradientFill( } case awt::GradientStyle_AXIAL: { - // we need to 'double' the gradient to make it appear as what we call - // 'axial', but also scale and mirror in doing so - basegfx::BColorStops aNewColorStops; - basegfx::BColorStops aNewAlphaStops; - - // add mirrored gradients, scaled to [0.0 .. 0.5] - basegfx::BColorStops::const_reverse_iterator aRevCurrColor(aColorStops.rbegin()); - basegfx::BColorStops::const_reverse_iterator aRevCurrAlpha(aAlphaStops.rbegin()); - - while (aRevCurrColor != aColorStops.rend() && aRevCurrAlpha != aAlphaStops.rend()) - { - aNewColorStops.emplace_back((1.0 - aRevCurrColor->getStopOffset()) * 0.5, aRevCurrColor->getStopColor()); - aNewAlphaStops.emplace_back((1.0 - aRevCurrAlpha->getStopOffset()) * 0.5, aRevCurrAlpha->getStopColor()); - aRevCurrColor++; - aRevCurrAlpha++; - } - - basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin()); - basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin()); - - if (basegfx::fTools::equalZero(aCurrColor->getStopOffset())) - { - // Caution: do not add 1st entry again, that would be double since it was - // already added as last element of the inverse run above. But only if - // the gradient has a start entry for 0.0 aka StartColor, else it is correct. - // Since aColorStops and aAlphaStops are already synched (see - // synchronizeColorStops above), testing one of them is sufficient here. - aCurrColor++; - aCurrAlpha++; - } - - // add non-mirrored gradients, translated and scaled to [0.5 .. 1.0] - while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end()) - { - aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 0.5) + 0.5, aCurrColor->getStopColor()); - aNewAlphaStops.emplace_back((aCurrAlpha->getStopOffset() * 0.5) + 0.5, aCurrAlpha->getStopColor()); - aCurrColor++; - aCurrAlpha++; - } - - aColorStops = aNewColorStops; - aAlphaStops = aNewAlphaStops; + // use tooling to convert from GradientStyle_AXIAL to GradientStyle_LINEAR + // NOTE: Since aColorStops and aAlphaStops are already synched (see + // synchronizeColorStops above) this can be done directly here + aColorStops.doApplyAxial(); + aAlphaStops.doApplyAxial(); // remember being axial bAxial = true; diff --git a/svx/source/svdraw/svdxcgv.cxx b/svx/source/svdraw/svdxcgv.cxx index bbf100463fc6..050a99707740 100644 --- a/svx/source/svdraw/svdxcgv.cxx +++ b/svx/source/svdraw/svdxcgv.cxx @@ -594,7 +594,8 @@ Graphic SdrExchangeView::GetAllMarkedGraphic() const } -Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject) +// tdf#155479 bSVG: need to know it's SVG export, default is false +Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject, bool bSVG) { Graphic aRet; @@ -648,6 +649,7 @@ Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject) pOut->EnableOutput(false); pOut->SetMapMode(aMap); aMtf.Record(pOut); + aMtf.setSVG(bSVG); rSdrObject.SingleObjectPainter(*pOut); aMtf.Stop(); aMtf.WindStart(); diff --git a/vcl/source/control/fmtfield.cxx b/vcl/source/control/fmtfield.cxx index 69497dec2c1b..fc7bdfee6f58 100644 --- a/vcl/source/control/fmtfield.cxx +++ b/vcl/source/control/fmtfield.cxx @@ -910,7 +910,7 @@ namespace m_rSpinButton.SpinField::SetText(rText, rSel); } - virtual void SetEntryTextColor(const Color* pColor) override + virtual void SetEntryTextColor(const ::Color* pColor) override { if (pColor) m_rSpinButton.SetControlForeground(*pColor); diff --git a/vcl/source/filter/svm/SvmReader.cxx b/vcl/source/filter/svm/SvmReader.cxx index 5cb9009a5834..4ec9ec0a3d2e 100644 --- a/vcl/source/filter/svm/SvmReader.cxx +++ b/vcl/source/filter/svm/SvmReader.cxx @@ -1336,6 +1336,27 @@ rtl::Reference<MetaAction> SvmReader::FloatTransparentHandler(ImplMetaReadData* pAction->SetSize(aSize); pAction->SetGradient(aGradient); + // tdf#155479 add support for MCGR and SVG export + if (aCompat.GetVersion() > 1) + { + basegfx::BColorStops aColorStops; + sal_uInt16 nTmp; + double fOff, fR, fG, fB; + mrStream.ReadUInt16(nTmp); + + for (sal_uInt16 a(0); a < nTmp; a++) + { + mrStream.ReadDouble(fOff); + mrStream.ReadDouble(fR); + mrStream.ReadDouble(fG); + mrStream.ReadDouble(fB); + + aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB)); + } + + pAction->addSVGTransparencyColorStops(aColorStops); + } + return pAction; } diff --git a/vcl/source/filter/svm/SvmWriter.cxx b/vcl/source/filter/svm/SvmWriter.cxx index c1913b048b27..7eacba78b88e 100644 --- a/vcl/source/filter/svm/SvmWriter.cxx +++ b/vcl/source/filter/svm/SvmWriter.cxx @@ -1364,7 +1364,12 @@ void SvmWriter::TransparentHandler(const MetaTransparentAction* pAction) void SvmWriter::FloatTransparentHandler(const MetaFloatTransparentAction* pAction) { mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); - VersionCompatWrite aCompat(mrStream, 1); + + // tdf#155479 prep vars for MCGR + const basegfx::BColorStops* pSVGTransparencyColorStops(pAction->getSVGTransparencyColorStops()); + const bool bSVG(nullptr != pSVGTransparencyColorStops); + + VersionCompatWrite aCompat(mrStream, bSVG ? 2 : 1); SvmWriter aWriter(mrStream); GDIMetaFile aMtf = pAction->GetGDIMetaFile(); @@ -1373,6 +1378,22 @@ void SvmWriter::FloatTransparentHandler(const MetaFloatTransparentAction* pActio aSerializer.writePoint(pAction->GetPoint()); aSerializer.writeSize(pAction->GetSize()); aSerializer.writeGradient(pAction->GetGradient()); + + // tdf#155479 add support for MCGR and SVG export + if (bSVG) + { + sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(pSVGTransparencyColorStops->size())); + mrStream.WriteUInt16(nTmp); + + for (auto const& rCand : *pSVGTransparencyColorStops) + { + mrStream.WriteDouble(rCand.getStopOffset()); + const basegfx::BColor& rColor(rCand.getStopColor()); + mrStream.WriteDouble(rColor.getRed()); + mrStream.WriteDouble(rColor.getGreen()); + mrStream.WriteDouble(rColor.getBlue()); + } + } } void SvmWriter::EPSHandler(const MetaEPSAction* pAction) diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx index 6b1300e589d6..e8bb8ba7e0bd 100644 --- a/vcl/source/gdi/gdimtf.cxx +++ b/vcl/source/gdi/gdimtf.cxx @@ -117,7 +117,8 @@ GDIMetaFile::GDIMetaFile() : m_pOutDev ( nullptr ), m_bPause ( false ), m_bRecord ( false ), - m_bUseCanvas ( false ) + m_bUseCanvas ( false ), + m_bSVG ( false ) { } @@ -130,7 +131,8 @@ GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) : m_pOutDev ( nullptr ), m_bPause ( false ), m_bRecord ( false ), - m_bUseCanvas ( rMtf.m_bUseCanvas ) + m_bUseCanvas ( rMtf.m_bUseCanvas ), + m_bSVG ( rMtf.m_bSVG ) { for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i ) { @@ -228,6 +230,7 @@ GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf ) m_bPause = false; m_bRecord = false; m_bUseCanvas = rMtf.m_bUseCanvas; + m_bSVG = rMtf.m_bSVG; if( rMtf.m_bRecord ) { @@ -328,6 +331,7 @@ void GDIMetaFile::Play( GDIMetaFile& rMtf ) const size_t nObjCount = m_aList.size(); rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas ); + rMtf.setSVG( rMtf.getSVG() || m_bSVG ); for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ ) { @@ -1809,6 +1813,7 @@ void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pCol aMtf.m_aPrefSize = m_aPrefSize; aMtf.m_aPrefMapMode = m_aPrefMapMode; aMtf.m_bUseCanvas = m_bUseCanvas; + aMtf.m_bSVG = m_bSVG; for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() ) { diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx index 22093a95735e..241940c3b79d 100644 --- a/vcl/source/gdi/metaact.cxx +++ b/vcl/source/gdi/metaact.cxx @@ -1908,6 +1908,11 @@ void MetaFloatTransparentAction::Scale( double fScaleX, double fScaleY ) maSize = aRectangle.GetSize(); } +void MetaFloatTransparentAction::addSVGTransparencyColorStops(const basegfx::BColorStops& rSVGTransparencyColorStops) +{ + maSVGTransparencyColorStops = rSVGTransparencyColorStops; +} + MetaEPSAction::MetaEPSAction() : MetaAction(MetaActionType::EPS) {} commit e691869486cd4ee4ee1dfb8f7a41440f7f6500fd Author: Paris Oplopoios <paris.oplopo...@collabora.com> AuthorDate: Sat Jun 10 21:33:39 2023 +0300 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Jun 13 23:20:31 2023 +0200 tdf#154168 Export no transparency PNGs correctly When bTranslucent was false the function would simply return, causing a 0 byte png. Now we just remove the alpha channel. Change-Id: Ie2578185ac12fb38b6f1b674758e564721e3973f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152840 Tested-by: Jenkins Reviewed-by: Paris Oplopoios <parisop...@gmail.com> (cherry picked from commit 7588c1f33cdaab58a6b84f4f4e75922c5d4a8a7f) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152886 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/include/vcl/bitmapex.hxx b/include/vcl/bitmapex.hxx index 5838ef8d6e40..9d7a104264ef 100644 --- a/include/vcl/bitmapex.hxx +++ b/include/vcl/bitmapex.hxx @@ -56,6 +56,7 @@ public: bool IsEmpty() const; void SetEmpty(); void Clear(); + void ClearAlpha(); void Draw( OutputDevice* pOutDev, const Point& rDestPt ) const; diff --git a/vcl/source/bitmap/BitmapEx.cxx b/vcl/source/bitmap/BitmapEx.cxx index af712a0561a5..ad4adca6319e 100644 --- a/vcl/source/bitmap/BitmapEx.cxx +++ b/vcl/source/bitmap/BitmapEx.cxx @@ -190,6 +190,11 @@ void BitmapEx::Clear() SetEmpty(); } +void BitmapEx::ClearAlpha() +{ + maAlphaMask.SetEmpty(); +} + bool BitmapEx::IsAlpha() const { return !maAlphaMask.IsEmpty(); diff --git a/vcl/source/filter/png/PngImageWriter.cxx b/vcl/source/filter/png/PngImageWriter.cxx index 914302223d6a..e68c6155826b 100644 --- a/vcl/source/filter/png/PngImageWriter.cxx +++ b/vcl/source/filter/png/PngImageWriter.cxx @@ -60,8 +60,6 @@ static bool pngWrite(SvStream& rStream, const BitmapEx& rBitmapEx, int nCompress bool bInterlaced, bool bTranslucent, const std::vector<PngChunk>& aAdditionalChunks) { - if (rBitmapEx.IsAlpha() && !bTranslucent) - return false; if (rBitmapEx.IsEmpty()) return false; @@ -88,6 +86,12 @@ static bool pngWrite(SvStream& rStream, const BitmapEx& rBitmapEx, int nCompress aBitmapEx = rBitmapEx; } + if (!bTranslucent) + { + // Clear alpha channel + aBitmapEx.ClearAlpha(); + } + Bitmap aBitmap; AlphaMask aAlphaMask; Bitmap::ScopedReadAccess pAccess;