summaryrefslogtreecommitdiff
path: root/oox
diff options
context:
space:
mode:
authorTor Lillqvist <tml@collabora.com>2014-07-14 20:23:22 +0300
committerAndras Timar <andras.timar@collabora.com>2014-07-17 11:38:43 +0000
commit7a34e0815d541709e96fcb7162895e90a8d08f30 (patch)
treed39f63bdbba4f8c77c5d67fd18689a2e25a7637f /oox
parent61aabb7d814b634e29003d8b555938f40b6f6fb4 (diff)
bnc#862510: Improve handling of OOXML gradients
OOXML gradients can have an arbitrary number of "stops". LibreOffice gradients have just a start and end colour, plus an optional uniformly coloured border (on the "start" side). In addition, LibreOffice has the "axial" gradient mode, which means the gradient is reflected in the middle. It is thus obviously impossible in general to losslessly map OOXML gradients to LibreOffice ones. But let's try a bit harder than earlier to get visually more similar result, in at least some simple sample cases. We look for the widest gradient segment and use that for the start and end colours of the LibreOffice gradient. Also, map an OOXML gradient to an axial LibreOffice gradient only if it is symmetrical. Also, use the border property when suitable. In general, look for the widest OOXML gradient segment (once a segment corresponding to the LibreOffice gradient border, if any, has been accounted for) and use that as the LibreOffice gradient. Possibly some perceptionally better heuristic should be used... Like, if we have a three-segment gradient, with a wide gradient segment between two visually very similar colours (for example, two shades of red), and a narrower segment ending with a visually very different colour (for example, yellow), it probably would be best to represent that in LibreOffice as a gradient from the first red shade to yellow, instead of as a gradient between the two shades of red. Or even, if a first or last gradient segment is between very similar colours, equalize those start and end colours, thus using a border colour in LibreOffice instead. The possibilities for bikeshedding are endless. I am sure there are instances where the old code (by accident?) produced visually more pleasing results... But hopefully this works more pleasingly and consistently in a larger number of cases. Change-Id: If153e986ad943454307e3ba718479d5ac4cdc7ab Reviewed-on: https://gerrit.libreoffice.org/10359 Reviewed-by: Andras Timar <andras.timar@collabora.com> Tested-by: Andras Timar <andras.timar@collabora.com>
Diffstat (limited to 'oox')
-rw-r--r--oox/Library_oox.mk6
-rw-r--r--oox/source/drawingml/fillproperties.cxx212
2 files changed, 192 insertions, 26 deletions
diff --git a/oox/Library_oox.mk b/oox/Library_oox.mk
index d933b56bdf57..a8b2161ecb0f 100644
--- a/oox/Library_oox.mk
+++ b/oox/Library_oox.mk
@@ -59,16 +59,18 @@ $(eval $(call gb_Library_use_libraries,oox,\
$(gb_UWINAPI) \
))
-ifeq ($(TLS),OPENSSL)
$(eval $(call gb_Library_use_externals,oox,\
boost_headers \
+))
+
+ifeq ($(TLS),OPENSSL)
+$(eval $(call gb_Library_use_externals,oox,\
openssl \
openssl_headers \
))
else
ifeq ($(TLS),NSS)
$(eval $(call gb_Library_use_externals,oox,\
- boost_headers \
plc4 \
nss3 \
))
diff --git a/oox/source/drawingml/fillproperties.cxx b/oox/source/drawingml/fillproperties.cxx
index 99026a96869b..a5eb026054de 100644
--- a/oox/source/drawingml/fillproperties.cxx
+++ b/oox/source/drawingml/fillproperties.cxx
@@ -17,6 +17,9 @@
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
+#include <iterator>
+#include <boost/utility.hpp>
+
#include "oox/drawingml/fillproperties.hxx"
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
@@ -340,8 +343,8 @@ void FillProperties::pushToPropMap( ShapePropertyMap& rPropMap,
aGradient.StartIntensity = 100;
aGradient.EndIntensity = 100;
- size_t nColorCount = maGradientProps.maGradientStops.size();
- if( nColorCount > 1 )
+ // Old code, values in aGradient overwritten in many cases by newer code below
+ if( maGradientProps.maGradientStops.size() > 1 )
{
aGradient.StartColor = maGradientProps.maGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr );
aGradient.EndColor = maGradientProps.maGradientStops.rbegin()->second.getColor( rGraphicHelper, nPhClr );
@@ -377,33 +380,194 @@ void FillProperties::pushToPropMap( ShapePropertyMap& rPropMap,
}
else
{
- /* Try to detect a VML axial gradient. This type of
- gradient is simulated by a 3-point linear gradient.
- Even if it's a multi-color linear gradient, its probably better to assume
- axial gradient, when there are 3 or more points.
- */
- bool bAxial = (nColorCount >= 3);
- aGradient.Style = bAxial ? awt::GradientStyle_AXIAL : awt::GradientStyle_LINEAR;
- nDmlAngle = maGradientProps.moShadeAngle.get( 0 ) - nShapeRotation;
- // convert DrawingML angle (in 1/60000 degrees) to API angle (in 1/10 degrees)
- aGradient.Angle = static_cast< sal_Int16 >( (4500 - (nDmlAngle / (PER_DEGREE / 10))) % 3600 );
- if( bAxial )
+ // A copy of the gradient stops for local modification
+ GradientFillProperties::GradientStopMap aGradientStops(maGradientProps.maGradientStops);
+
+ // Add a fake gradient stop at 0% and 100% if necessary, so that the gradient always starts
+ // at 0% and ends at 100%, to make following logic clearer (?).
+ if( aGradientStops.find(0.0) == aGradientStops.end() )
+ {
+ // temp variable required
+ Color aFirstColor(aGradientStops.begin()->second);
+ aGradientStops[0.0] = aFirstColor;
+ }
+
+ if( aGradientStops.find(1.0) == aGradientStops.end() )
+ {
+ // ditto
+ Color aLastColor(aGradientStops.rbegin()->second);
+ aGradientStops[1.0] = aLastColor;
+ }
+
+ // Check if the gradient is symmetric, which we will emulate with an "axial" gradient.
+ bool bSymmetric(true);
+ {
+ GradientFillProperties::GradientStopMap::const_iterator aItA( aGradientStops.begin() );
+ GradientFillProperties::GradientStopMap::const_iterator aItZ( boost::prior( aGradientStops.end() ) );
+ while( bSymmetric && aItA->first < aItZ->first )
+ {
+ if( aItA->second.getColor( rGraphicHelper, nPhClr ) != aItZ->second.getColor( rGraphicHelper, nPhClr ) ||
+ aItA->second.getTransparency() != aItZ->second.getTransparency() )
+ bSymmetric = false;
+ else
+ {
+ aItA++;
+ aItZ = boost::prior(aItZ);
+ }
+ }
+ // Don't be fooled if the middlemost stop isn't at 0.5.
+ if( bSymmetric && aItA == aItZ && aItA->first != 0.5 )
+ bSymmetric = false;
+
+ // If symmetric, do the rest of the logic for just a half.
+ if( bSymmetric )
+ {
+ // aItZ already points to the colour for the middle, but insert a fake stop at the
+ // exact middle if necessary.
+ if( aItA->first != aItZ->first )
+ {
+ Color aMiddleColor = aItZ->second;
+ aGradientStops[0.5] = aMiddleColor;
+ }
+ // Drop the rest of the stops
+ while( aGradientStops.rbegin()->first > 0.5 )
+ aGradientStops.erase( aGradientStops.rbegin()->first );
+ }
+ }
+
+ SAL_INFO("oox.drawingml.gradient", "symmetric: " << (bSymmetric ? "YES" : "NO") <<
+ ", number of stops: " << aGradientStops.size());
+ for (GradientFillProperties::GradientStopMap::iterator p(aGradientStops.begin());
+ p != aGradientStops.end();
+ p++)
+ SAL_INFO("oox.drawingml.gradient", " " << std::distance(aGradientStops.begin(), p) << ": " <<
+ p->first << ": " <<
+ std::hex << p->second.getColor( rGraphicHelper, nPhClr ) << std::dec <<
+ "@" << (100-p->second.getTransparency()) << "%");
+
+ // Now estimate the simple LO style gradient (only two stops, at n% and 100%, where n ==
+ // the "border") that best emulates the gradient between begin() and prior(end()).
+
+ // First look for the largest segment in the gradient.
+ GradientFillProperties::GradientStopMap::iterator aIt(aGradientStops.begin());
+ double nWidestWidth = -1;
+ GradientFillProperties::GradientStopMap::iterator aWidestSegmentStart;
+ aIt++;
+ while( aIt != aGradientStops.end() )
+ {
+ if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
+ {
+ nWidestWidth = aIt->first - boost::prior(aIt)->first;
+ aWidestSegmentStart = boost::prior(aIt);
+ }
+ aIt++;
+ }
+ assert( nWidestWidth > 0 );
+
+ double nBorder = 0;
+ bool bSwap(false);
+
+ // Do we have just two segments, and either one is of uniform colour, or three or more
+ // segments, and the widest one is the first or last one, and is it of uniform colour? If
+ // so, deduce the border from it, and drop that segment.
+ if( aGradientStops.size() == 3 &&
+ aGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) &&
+ aGradientStops.begin()->second.getTransparency() == boost::next(aGradientStops.begin())->second.getTransparency( ) )
+ {
+ // Two segments, first is uniformly coloured
+ SAL_INFO("oox.drawingml.gradient", "two segments, first is uniformly coloured");
+ nBorder = boost::next(aGradientStops.begin())->first - aGradientStops.begin()->first;
+ aGradientStops.erase(aGradientStops.begin());
+ aWidestSegmentStart = aGradientStops.begin();
+ }
+ else if( !bSymmetric &&
+ aGradientStops.size() == 3 &&
+ boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) == boost::prior(aGradientStops.end())->second.getColor( rGraphicHelper, nPhClr ) &&
+ boost::next(aGradientStops.begin())->second.getTransparency() == boost::prior(aGradientStops.end())->second.getTransparency( ) )
{
- GradientFillProperties::GradientStopMap::const_iterator aIt = maGradientProps.maGradientStops.begin();
- // Try to find the axial median
- for(size_t i=0;i<nColorCount;i+=3)
- ++aIt;
- // API StartColor is inner color in axial gradient
- // aIt->second.hasColor() kind would be better than Color != API_RGB_WHITE
- if( aGradient.StartColor == aGradient.EndColor &&
- ( !aIt->second.hasTransparency() || aIt->second.getColor( rGraphicHelper, nPhClr ) != API_RGB_WHITE ) )
+ // Two segments, second is uniformly coloured
+ SAL_INFO("oox.drawingml.gradient", "two segments, second is uniformly coloured");
+ nBorder = boost::prior(aGradientStops.end())->first - boost::next(aGradientStops.begin())->first;
+ aGradientStops.erase(boost::next(aGradientStops.begin()));
+ aWidestSegmentStart = aGradientStops.begin();
+ bSwap = true;
+ nShapeRotation = 180*60000 - nShapeRotation;
+ }
+ else if( !bSymmetric &&
+ aGradientStops.size() >= 4 &&
+ aWidestSegmentStart->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aWidestSegmentStart)->second.getColor( rGraphicHelper, nPhClr ) &&
+ aWidestSegmentStart->second.getTransparency() == boost::next(aWidestSegmentStart)->second.getTransparency() &&
+ ( aWidestSegmentStart == aGradientStops.begin() ||
+ boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) ) )
+ {
+ // Not symmetric, three or more segments, the widest is first or last and is uniformly coloured
+ SAL_INFO("oox.drawingml.gradient", "first or last segment is widest and is uniformly coloured");
+ nBorder = boost::next(aWidestSegmentStart)->first - aWidestSegmentStart->first;
+
+ // If it's the last segment that is uniformly coloured, rotate the gradient 180
+ // degrees and swap start and end colours
+ if( boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) )
{
- aGradient.StartColor = aIt->second.getColor( rGraphicHelper, nPhClr );
+ bSwap = true;
+ nShapeRotation = 180*60000 - nShapeRotation;
}
- if( nStartTrans == nEndTrans && aIt->second.hasTransparency() )
- nStartTrans = aIt->second.getTransparency()*255/100;
+ aGradientStops.erase( aWidestSegmentStart );
+
+ // Look for which is widest now
+ aIt = boost::next(aGradientStops.begin());
+ nWidestWidth = -1;
+ while( aIt != aGradientStops.end() )
+ {
+ if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
+ {
+ nWidestWidth = aIt->first - boost::prior(aIt)->first;
+ aWidestSegmentStart = boost::prior(aIt);
+ }
+ aIt++;
+ }
+ }
+ SAL_INFO("oox.drawingml.gradient", "widest segment start: " << aWidestSegmentStart->first << ", border: " << nBorder);
+ assert( (!bSymmetric && !bSwap) || !(bSymmetric && bSwap) );
+
+ // Now we have a potential border and a largest segment. Use those.
+
+ aGradient.Style = bSymmetric ? awt::GradientStyle_AXIAL : awt::GradientStyle_LINEAR;
+ nDmlAngle = maGradientProps.moShadeAngle.get( 0 ) - nShapeRotation;
+ // convert DrawingML angle (in 1/60000 degrees) to API angle (in 1/10 degrees)
+ aGradient.Angle = static_cast< sal_Int16 >( (4500 - (nDmlAngle / (PER_DEGREE / 10))) % 3600 );
+ Color aStartColor, aEndColor;
+ if( bSymmetric )
+ {
+ aStartColor = boost::next(aWidestSegmentStart)->second;
+ aEndColor = aWidestSegmentStart->second;
+ nBorder *= 2;
+ }
+ else if( bSwap )
+ {
+ aStartColor = boost::next(aWidestSegmentStart)->second;
+ aEndColor = aWidestSegmentStart->second;
}
+ else
+ {
+ aStartColor = aWidestSegmentStart->second;
+ aEndColor = boost::next(aWidestSegmentStart)->second;
+ }
+
+ SAL_INFO("oox.drawingml.gradient", "start color: " << std::hex << aStartColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
+ "@" << (100-aStartColor.getTransparency()) << "%" <<
+ ", end color: " << std::hex << aEndColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
+ "@" << (100-aEndColor.getTransparency()) << "%");
+
+ aGradient.StartColor = aStartColor.getColor( rGraphicHelper, nPhClr );
+ aGradient.EndColor = aEndColor.getColor( rGraphicHelper, nPhClr );
+
+ if( aStartColor.hasTransparency() )
+ nStartTrans = aStartColor.getTransparency()*255/100;
+ if( aEndColor.hasTransparency() )
+ nEndTrans = aEndColor.getTransparency()*255/100;
+
+ aGradient.Border = 100*nBorder;
}
// push gradient or named gradient to property map