/**************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_slideshow.hxx"

#include <hslcolor.hxx>
#include <rgbcolor.hxx>

#include <basegfx/numeric/ftools.hxx>

#include <cmath> // for fmod
#include <algorithm>

namespace slideshow
{
	namespace internal
	{
		namespace
		{
			// helper functions
			// ================

			double getMagic( double nLuminance, double nSaturation )
			{
				if( nLuminance <= 0.5 )
					return nLuminance*(1.0 + nSaturation);
				else
					return nLuminance + nSaturation - nLuminance*nSaturation;
			}

			HSLColor::HSLTriple rgb2hsl( double nRed, double nGreen, double nBlue )
			{
				// r,g,b in [0,1], h in [0,360] and s,l in [0,1]
				HSLColor::HSLTriple aRes;

				const double nMax( ::std::max(nRed,::std::max(nGreen, nBlue)) );
				const double nMin( ::std::min(nRed,::std::min(nGreen, nBlue)) );

				const double nDelta( nMax - nMin );

				aRes.mnLuminance = (nMax + nMin) / 2.0;

				if( ::basegfx::fTools::equalZero( nDelta ) )
				{
					aRes.mnSaturation = 0.0;

					// hue undefined (achromatic case)
					aRes.mnHue = 0.0;
				}
				else
				{
					aRes.mnSaturation = aRes.mnLuminance > 0.5 ?
						nDelta/(2.0-nMax-nMin) :
						nDelta/(nMax + nMin);

					if( nRed == nMax )
						aRes.mnHue = (nGreen - nBlue)/nDelta;
					else if( nGreen == nMax )
						aRes.mnHue = 2.0 + (nBlue - nRed)/nDelta;
					else if( nBlue == nMax )
						aRes.mnHue = 4.0 + (nRed - nGreen)/nDelta;

					aRes.mnHue *= 60.0;

					if( aRes.mnHue < 0.0 )
						aRes.mnHue += 360.0;
				}

				return aRes;
			}

			double hsl2rgbHelper( double nValue1, double nValue2, double nHue )
			{
				// clamp hue to [0,360]
				nHue = fmod( nHue, 360.0 );

				// cope with wrap-arounds
				if( nHue < 0.0 )
					nHue += 360.0;

				if( nHue < 60.0 )
					return nValue1 + (nValue2 - nValue1)*nHue/60.0;
				else if( nHue < 180.0 )
					return nValue2;
				else if( nHue < 240.0 )
					return nValue1 + (nValue2 - nValue1)*(240.0 - nHue)/60.0;
				else
					return nValue1;
			}

			RGBColor::RGBTriple hsl2rgb( double nHue, double nSaturation, double nLuminance )
			{
				if( ::basegfx::fTools::equalZero( nSaturation ) )
					return RGBColor::RGBTriple(0.0, 0.0, nLuminance );

				const double nVal1( getMagic(nLuminance, nSaturation) );
				const double nVal2( 2.0*nLuminance - nVal1 );

				RGBColor::RGBTriple aRes;

				aRes.mnRed = hsl2rgbHelper( nVal2,
											nVal1,
											nHue + 120.0 );
				aRes.mnGreen = hsl2rgbHelper( nVal2,
											  nVal1,
											  nHue );
				aRes.mnBlue = hsl2rgbHelper( nVal2,
											 nVal1,
											 nHue - 120.0 );

				return aRes;
			}

			// Truncate range of value to [0,1]
			double truncateRangeStd( double nVal )
			{
				return ::std::max( 0.0,
								   ::std::min( 1.0,
											   nVal ) );
			}

			// Truncate range of value to [0,360]
			double truncateRangeHue( double nVal )
			{
				return ::std::max( 0.0,
								   ::std::min( 360.0,
											   nVal ) );
			}

			// convert RGB color to sal_uInt8, truncate range appropriately before
			sal_uInt8 colorToInt( double nCol )
			{
				return static_cast< sal_uInt8 >(
					::basegfx::fround( truncateRangeStd( nCol ) * 255.0 ) );
			}
		}


		// HSLColor
		// ===============================================

		HSLColor::HSLTriple::HSLTriple() :
			mnHue(),
			mnSaturation(),
			mnLuminance()
		{
		}

		HSLColor::HSLTriple::HSLTriple( double nHue, double nSaturation, double nLuminance ) :
			mnHue( nHue ),
			mnSaturation( nSaturation ),
			mnLuminance( nLuminance )
		{
		}

		HSLColor::HSLColor() :
			maHSLTriple( 0.0, 0.0, 0.0 ),
			mnMagicValue( getMagic( maHSLTriple.mnLuminance,
									maHSLTriple.mnSaturation ) )
		{
		}

		HSLColor::HSLColor( ::cppcanvas::Color::IntSRGBA nRGBColor ) :
			maHSLTriple( rgb2hsl( ::cppcanvas::getRed( nRGBColor ) / 255.0,
								  ::cppcanvas::getGreen( nRGBColor ) / 255.0,
								  ::cppcanvas::getBlue( nRGBColor ) / 255.0 ) ),
			mnMagicValue( getMagic( maHSLTriple.mnLuminance,
									maHSLTriple.mnSaturation ) )
		{
		}

		HSLColor::HSLColor( double nHue, double nSaturation, double nLuminance ) :
			maHSLTriple( nHue, nSaturation, nLuminance ),
			mnMagicValue( getMagic( maHSLTriple.mnLuminance,
									maHSLTriple.mnSaturation ) )
		{
		}

		HSLColor::HSLColor( const RGBColor& rColor ) :
			maHSLTriple( rgb2hsl( truncateRangeStd( rColor.getRed() ),
								  truncateRangeStd( rColor.getGreen() ),
								  truncateRangeStd( rColor.getBlue() ) ) ),
			mnMagicValue( getMagic( maHSLTriple.mnLuminance,
									maHSLTriple.mnSaturation ) )
		{
		}

		double HSLColor::getHue() const
		{
			return maHSLTriple.mnHue;
		}

		double HSLColor::getSaturation() const
		{
			return maHSLTriple.mnSaturation;
		}

		double HSLColor::getLuminance() const
		{
			return maHSLTriple.mnLuminance;
		}

		double HSLColor::getRed() const
		{
			if( ::basegfx::fTools::equalZero( getSaturation() ) )
				return getLuminance();

			return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue,
								  mnMagicValue,
								  getHue() + 120.0 );
		}

		double HSLColor::getGreen() const
		{
			if( ::basegfx::fTools::equalZero( getSaturation() ) )
				return getLuminance();

			return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue,
								  mnMagicValue,
								  getHue() );
		}

		double HSLColor::getBlue() const
		{
			if( ::basegfx::fTools::equalZero( getSaturation() ) )
				return getLuminance();

			return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue,
								  mnMagicValue,
								  getHue() - 120.0 );
		}

		RGBColor HSLColor::getRGBColor() const
		{
			RGBColor::RGBTriple aColor( hsl2rgb( getHue(),
												 getSaturation(),
												 getLuminance() ) );
			return RGBColor( aColor.mnRed, aColor.mnGreen, aColor.mnBlue );
		}

		RGBColor::RGBColor(const RGBColor& rLHS)
		{
			maRGBTriple.mnRed = rLHS.getRed();
			maRGBTriple.mnGreen = rLHS.getGreen();
			maRGBTriple.mnBlue = rLHS.getBlue();
		}

		RGBColor& RGBColor::operator=( const RGBColor& rLHS ){

			maRGBTriple.mnRed = rLHS.getRed();
			maRGBTriple.mnGreen = rLHS.getGreen();
			maRGBTriple.mnBlue = rLHS.getBlue();
			return *this;
		}

		HSLColor operator+( const HSLColor& rLHS, const HSLColor& rRHS )
		{
			return HSLColor( rLHS.getHue() + rRHS.getHue(),
							 rLHS.getSaturation() + rRHS.getSaturation(),
							 rLHS.getLuminance() + rRHS.getLuminance() );
		}

		HSLColor operator*( const HSLColor& rLHS, const HSLColor& rRHS )
		{
			return HSLColor( rLHS.getHue() * rRHS.getHue(),
							 rLHS.getSaturation() * rRHS.getSaturation(),
							 rLHS.getLuminance() * rRHS.getLuminance() );
		}

		HSLColor operator*( double nFactor, const HSLColor& rRHS )
		{
			return HSLColor( nFactor * rRHS.getHue(),
							 nFactor * rRHS.getSaturation(),
							 nFactor * rRHS.getLuminance() );
		}

		HSLColor interpolate( const HSLColor& rFrom, const HSLColor& rTo, double t, bool bCCW )
		{
			const double nFromHue( rFrom.getHue() );
			const double nToHue	 ( rTo.getHue()   );

			double nHue=0.0;

			if( nFromHue <= nToHue && !bCCW )
			{
				// interpolate hue clockwise. That is, hue starts at
				// high values and ends at low ones. Therefore, we
				// must 'cross' the 360 degrees and start at low
				// values again (imagine the hues to lie on the
				// circle, where values above 360 degrees are mapped
				// back to [0,360)).
				nHue = (1.0-t)*(nFromHue + 360.0) + t*nToHue;
			}
			else if( nFromHue > nToHue && bCCW )
			{
				// interpolate hue counter-clockwise. That is, hue
				// starts at high values and ends at low
				// ones. Therefore, we must 'cross' the 360 degrees
				// and start at low values again (imagine the hues to
				// lie on the circle, where values above 360 degrees
				// are mapped back to [0,360)).
				nHue = (1.0-t)*nFromHue + t*(nToHue + 360.0);
			}
			else
			{
				// interpolate hue counter-clockwise. That is, hue
				// starts at low values and ends at high ones (imagine
				// the hue value as degrees on a circle, with
				// increasing values going counter-clockwise)
				nHue = (1.0-t)*nFromHue + t*nToHue;
			}

			return HSLColor( nHue,
							 (1.0-t)*rFrom.getSaturation() + t*rTo.getSaturation(),
							 (1.0-t)*rFrom.getLuminance() + t*rTo.getLuminance() );
		}


		// RGBColor
		// ===============================================


		RGBColor::RGBTriple::RGBTriple() :
			mnRed(),
			mnGreen(),
			mnBlue()
		{
		}

		RGBColor::RGBTriple::RGBTriple( double nRed, double nGreen, double nBlue ) :
			mnRed( nRed ),
			mnGreen( nGreen ),
			mnBlue( nBlue )
		{
		}

		RGBColor::RGBColor() :
			maRGBTriple( 0.0, 0.0, 0.0 )
		{
		}

		RGBColor::RGBColor( ::cppcanvas::Color::IntSRGBA nRGBColor ) :
			maRGBTriple( ::cppcanvas::getRed( nRGBColor ) / 255.0,
						 ::cppcanvas::getGreen( nRGBColor ) / 255.0,
						 ::cppcanvas::getBlue( nRGBColor ) / 255.0 )
		{
		}

		RGBColor::RGBColor( double nRed, double nGreen, double nBlue ) :
			maRGBTriple( nRed, nGreen, nBlue )
		{
		}

		RGBColor::RGBColor( const HSLColor& rColor ) :
			maRGBTriple( hsl2rgb( truncateRangeHue( rColor.getHue() ),
								  truncateRangeStd( rColor.getSaturation() ),
								  truncateRangeStd( rColor.getLuminance() ) ) )
		{
		}

		double RGBColor::getHue() const
		{
			return rgb2hsl( getRed(),
							getGreen(),
							getBlue() ).mnHue;
		}

		double RGBColor::getSaturation() const
		{
			return rgb2hsl( getRed(),
							getGreen(),
							getBlue() ).mnSaturation;
		}

		double RGBColor::getLuminance() const
		{
			return rgb2hsl( getRed(),
							getGreen(),
							getBlue() ).mnLuminance;
		}

		double RGBColor::getRed() const
		{
			return maRGBTriple.mnRed;
		}

		double RGBColor::getGreen() const
		{
			return maRGBTriple.mnGreen;
		}

		double RGBColor::getBlue() const
		{
			return maRGBTriple.mnBlue;
		}

		HSLColor RGBColor::getHSLColor() const
		{
			HSLColor::HSLTriple aColor( rgb2hsl( getRed(),
												 getGreen(),
												 getBlue() ) );
			return HSLColor( aColor.mnHue, aColor.mnSaturation, aColor.mnLuminance );
		}

		::cppcanvas::Color::IntSRGBA RGBColor::getIntegerColor() const
		{
			return ::cppcanvas::makeColor( colorToInt( getRed() ),
										   colorToInt( getGreen() ),
										   colorToInt( getBlue() ),
										   255 );
		}

		RGBColor operator+( const RGBColor& rLHS, const RGBColor& rRHS )
		{
			return RGBColor( rLHS.getRed() + rRHS.getRed(),
							 rLHS.getGreen() + rRHS.getGreen(),
							 rLHS.getBlue() + rRHS.getBlue() );
		}

		RGBColor operator*( const RGBColor& rLHS, const RGBColor& rRHS )
		{
			return RGBColor( rLHS.getRed() * rRHS.getRed(),
							 rLHS.getGreen() * rRHS.getGreen(),
							 rLHS.getBlue() * rRHS.getBlue() );
		}

		RGBColor operator*( double nFactor, const RGBColor& rRHS )
		{
			return RGBColor( nFactor * rRHS.getRed(),
							 nFactor * rRHS.getGreen(),
							 nFactor * rRHS.getBlue() );
		}

		RGBColor interpolate( const RGBColor& rFrom, const RGBColor& rTo, double t )
		{
			return RGBColor( (1.0-t)*rFrom.getRed() + t*rTo.getRed(),
							 (1.0-t)*rFrom.getGreen() + t*rTo.getGreen(),
							 (1.0-t)*rFrom.getBlue() + t*rTo.getBlue() );
		}
	}
}

/* vim: set noet sw=4 ts=4: */
