/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



#include "oox/drawingml/color.hxx"
#include <algorithm>
#include <math.h>
#include "oox/helper/containerhelper.hxx"
#include "oox/helper/graphichelper.hxx"
#include "oox/drawingml/drawingmltypes.hxx"
#include "oox/token/namespaces.hxx"
#include "oox/token/tokens.hxx"

using ::rtl::OUString;

namespace oox {
namespace drawingml {

// ============================================================================

namespace {

/** Global storage for predefined color values used in OOXML file formats. */
struct PresetColorsPool
{
    typedef ::std::vector< sal_Int32 > ColorVector;

    ColorVector         maDmlColors;        /// Predefined colors in DrawingML, indexed by XML token.
    ColorVector         maVmlColors;        /// Predefined colors in VML, indexed by XML token.

    explicit            PresetColorsPool();
};

// ----------------------------------------------------------------------------

PresetColorsPool::PresetColorsPool() :
    maDmlColors( static_cast< size_t >( XML_TOKEN_COUNT ), API_RGB_TRANSPARENT ),
    maVmlColors( static_cast< size_t >( XML_TOKEN_COUNT ), API_RGB_TRANSPARENT )
{
    // predefined colors in DrawingML (map XML token identifiers to RGB values)
    static const sal_Int32 spnDmlColors[] =
    {
        XML_aliceBlue,         0xF0F8FF,    XML_antiqueWhite,      0xFAEBD7,
        XML_aqua,              0x00FFFF,    XML_aquamarine,        0x7FFFD4,
        XML_azure,             0xF0FFFF,    XML_beige,             0xF5F5DC,
        XML_bisque,            0xFFE4C4,    XML_black,             0x000000,
        XML_blanchedAlmond,    0xFFEBCD,    XML_blue,              0x0000FF,
        XML_blueViolet,        0x8A2BE2,    XML_brown,             0xA52A2A,
        XML_burlyWood,         0xDEB887,    XML_cadetBlue,         0x5F9EA0,
        XML_chartreuse,        0x7FFF00,    XML_chocolate,         0xD2691E,
        XML_coral,             0xFF7F50,    XML_cornflowerBlue,    0x6495ED,
        XML_cornsilk,          0xFFF8DC,    XML_crimson,           0xDC143C,
        XML_cyan,              0x00FFFF,    XML_deepPink,          0xFF1493,
        XML_deepSkyBlue,       0x00BFFF,    XML_dimGray,           0x696969,
        XML_dkBlue,            0x00008B,    XML_dkCyan,            0x008B8B,
        XML_dkGoldenrod,       0xB8860B,    XML_dkGray,            0xA9A9A9,
        XML_dkGreen,           0x006400,    XML_dkKhaki,           0xBDB76B,
        XML_dkMagenta,         0x8B008B,    XML_dkOliveGreen,      0x556B2F,
        XML_dkOrange,          0xFF8C00,    XML_dkOrchid,          0x9932CC,
        XML_dkRed,             0x8B0000,    XML_dkSalmon,          0xE9967A,
        XML_dkSeaGreen,        0x8FBC8B,    XML_dkSlateBlue,       0x483D8B,
        XML_dkSlateGray,       0x2F4F4F,    XML_dkTurquoise,       0x00CED1,
        XML_dkViolet,          0x9400D3,    XML_dodgerBlue,        0x1E90FF,
        XML_firebrick,         0xB22222,    XML_floralWhite,       0xFFFAF0,
        XML_forestGreen,       0x228B22,    XML_fuchsia,           0xFF00FF,
        XML_gainsboro,         0xDCDCDC,    XML_ghostWhite,        0xF8F8FF,
        XML_gold,              0xFFD700,    XML_goldenrod,         0xDAA520,
        XML_gray,              0x808080,    XML_green,             0x008000,
        XML_greenYellow,       0xADFF2F,    XML_honeydew,          0xF0FFF0,
        XML_hotPink,           0xFF69B4,    XML_indianRed,         0xCD5C5C,
        XML_indigo,            0x4B0082,    XML_ivory,             0xFFFFF0,
        XML_khaki,             0xF0E68C,    XML_lavender,          0xE6E6FA,
        XML_lavenderBlush,     0xFFF0F5,    XML_lawnGreen,         0x7CFC00,
        XML_lemonChiffon,      0xFFFACD,    XML_lime,              0x00FF00,
        XML_limeGreen,         0x32CD32,    XML_linen,             0xFAF0E6,
        XML_ltBlue,            0xADD8E6,    XML_ltCoral,           0xF08080,
        XML_ltCyan,            0xE0FFFF,    XML_ltGoldenrodYellow, 0xFAFA78,
        XML_ltGray,            0xD3D3D3,    XML_ltGreen,           0x90EE90,
        XML_ltPink,            0xFFB6C1,    XML_ltSalmon,          0xFFA07A,
        XML_ltSeaGreen,        0x20B2AA,    XML_ltSkyBlue,         0x87CEFA,
        XML_ltSlateGray,       0x778899,    XML_ltSteelBlue,       0xB0C4DE,
        XML_ltYellow,          0xFFFFE0,    XML_magenta,           0xFF00FF,
        XML_maroon,            0x800000,    XML_medAquamarine,     0x66CDAA,
        XML_medBlue,           0x0000CD,    XML_medOrchid,         0xBA55D3,
        XML_medPurple,         0x9370DB,    XML_medSeaGreen,       0x3CB371,
        XML_medSlateBlue,      0x7B68EE,    XML_medSpringGreen,    0x00FA9A,
        XML_medTurquoise,      0x48D1CC,    XML_medVioletRed,      0xC71585,
        XML_midnightBlue,      0x191970,    XML_mintCream,         0xF5FFFA,
        XML_mistyRose,         0xFFE4E1,    XML_moccasin,          0xFFE4B5,
        XML_navajoWhite,       0xFFDEAD,    XML_navy,              0x000080,
        XML_oldLace,           0xFDF5E6,    XML_olive,             0x808000,
        XML_oliveDrab,         0x6B8E23,    XML_orange,            0xFFA500,
        XML_orangeRed,         0xFF4500,    XML_orchid,            0xDA70D6,
        XML_paleGoldenrod,     0xEEE8AA,    XML_paleGreen,         0x98FB98,
        XML_paleTurquoise,     0xAFEEEE,    XML_paleVioletRed,     0xDB7093,
        XML_papayaWhip,        0xFFEFD5,    XML_peachPuff,         0xFFDAB9,
        XML_peru,              0xCD853F,    XML_pink,              0xFFC0CB,
        XML_plum,              0xDDA0DD,    XML_powderBlue,        0xB0E0E6,
        XML_purple,            0x800080,    XML_red,               0xFF0000,
        XML_rosyBrown,         0xBC8F8F,    XML_royalBlue,         0x4169E1,
        XML_saddleBrown,       0x8B4513,    XML_salmon,            0xFA8072,
        XML_sandyBrown,        0xF4A460,    XML_seaGreen,          0x2E8B57,
        XML_seaShell,          0xFFF5EE,    XML_sienna,            0xA0522D,
        XML_silver,            0xC0C0C0,    XML_skyBlue,           0x87CEEB,
        XML_slateBlue,         0x6A5ACD,    XML_slateGray,         0x708090,
        XML_snow,              0xFFFAFA,    XML_springGreen,       0x00FF7F,
        XML_steelBlue,         0x4682B4,    XML_tan,               0xD2B48C,
        XML_teal,              0x008080,    XML_thistle,           0xD8BFD8,
        XML_tomato,            0xFF6347,    XML_turquoise,         0x40E0D0,
        XML_violet,            0xEE82EE,    XML_wheat,             0xF5DEB3,
        XML_white,             0xFFFFFF,    XML_whiteSmoke,        0xF5F5F5,
        XML_yellow,            0xFFFF00,    XML_yellowGreen,       0x9ACD32
    };
    for( const sal_Int32* pnEntry = spnDmlColors; pnEntry < STATIC_ARRAY_END( spnDmlColors ); pnEntry += 2 )
        maDmlColors[ static_cast< size_t >( pnEntry[ 0 ] ) ] = pnEntry[ 1 ];

    // predefined colors in VML (map XML token identifiers to RGB values)
    static const sal_Int32 spnVmlColors[] =
    {
        XML_aqua,              0x00FFFF,    XML_black,             0x000000,
        XML_blue,              0x0000FF,    XML_fuchsia,           0xFF00FF,
        XML_gray,              0x808080,    XML_green,             0x008000,
        XML_lime,              0x00FF00,    XML_maroon,            0x800000,
        XML_navy,              0x000080,    XML_olive,             0x808000,
        XML_purple,            0x800080,    XML_red,               0xFF0000,
        XML_silver,            0xC0C0C0,    XML_teal,              0x008080,
        XML_white,             0xFFFFFF,    XML_yellow,            0xFFFF00
    };
    for( const sal_Int32* pnEntry = spnVmlColors; pnEntry < STATIC_ARRAY_END( spnVmlColors ); pnEntry += 2 )
        maVmlColors[ static_cast< size_t >( pnEntry[ 0 ] ) ] = pnEntry[ 1 ];
}

// ----------------------------------------------------------------------------

struct StaticPresetColorsPool : public ::rtl::Static< PresetColorsPool, StaticPresetColorsPool > {};

// ----------------------------------------------------------------------------

const double DEC_GAMMA          = 2.3;
const double INC_GAMMA          = 1.0 / DEC_GAMMA;

// ----------------------------------------------------------------------------

inline void lclRgbToRgbComponents( sal_Int32& ornR, sal_Int32& ornG, sal_Int32& ornB, sal_Int32 nRgb )
{
    ornR = (nRgb >> 16) & 0xFF;
    ornG = (nRgb >> 8) & 0xFF;
    ornB = nRgb & 0xFF;
}

inline sal_Int32 lclRgbComponentsToRgb( sal_Int32 nR, sal_Int32 nG, sal_Int32 nB )
{
    return static_cast< sal_Int32 >( (nR << 16) | (nG << 8) | nB );
}

inline sal_Int32 lclRgbCompToCrgbComp( sal_Int32 nRgbComp )
{
    return static_cast< sal_Int32 >( nRgbComp * MAX_PERCENT / 255 );
}

inline sal_Int32 lclCrgbCompToRgbComp( sal_Int32 nCrgbComp )
{
    return static_cast< sal_Int32 >( nCrgbComp * 255 / MAX_PERCENT );
}

inline sal_Int32 lclGamma( sal_Int32 nComp, double fGamma )
{
    return static_cast< sal_Int32 >( pow( static_cast< double >( nComp ) / MAX_PERCENT, fGamma ) * MAX_PERCENT + 0.5 );
}

void lclSetValue( sal_Int32& ornValue, sal_Int32 nNew, sal_Int32 nMax = MAX_PERCENT )
{
    OSL_ENSURE( (0 <= nNew) && (nNew <= nMax), "lclSetValue - invalid value" );
    if( (0 <= nNew) && (nNew <= nMax) )
        ornValue = nNew;
}

void lclModValue( sal_Int32& ornValue, sal_Int32 nMod, sal_Int32 nMax = MAX_PERCENT )
{
    OSL_ENSURE( (0 <= nMod), "lclModValue - invalid modificator" );
    ornValue = getLimitedValue< sal_Int32, double >( static_cast< double >( ornValue ) * nMod / MAX_PERCENT, 0, nMax );
}

void lclOffValue( sal_Int32& ornValue, sal_Int32 nOff, sal_Int32 nMax = MAX_PERCENT )
{
    OSL_ENSURE( (-nMax <= nOff) && (nOff <= nMax), "lclOffValue - invalid offset" );
    ornValue = getLimitedValue< sal_Int32, sal_Int32 >( ornValue + nOff, 0, nMax );
}

} // namespace

// ============================================================================

Color::Color() :
    meMode( COLOR_UNUSED ),
    mnC1( 0 ),
    mnC2( 0 ),
    mnC3( 0 ),
    mnAlpha( MAX_PERCENT )
{
}

Color::~Color()
{
}

/*static*/ sal_Int32 Color::getDmlPresetColor( sal_Int32 nToken, sal_Int32 nDefaultRgb )
{
    /*  Do not pass nDefaultRgb to ContainerHelper::getVectorElement(), to be
        able to catch the existing vector entries without corresponding XML
        token identifier. */
    sal_Int32 nRgbValue = ContainerHelper::getVectorElement( StaticPresetColorsPool::get().maDmlColors, nToken, API_RGB_TRANSPARENT );
    return (nRgbValue >= 0) ? nRgbValue : nDefaultRgb;
}

/*static*/ sal_Int32 Color::getVmlPresetColor( sal_Int32 nToken, sal_Int32 nDefaultRgb )
{
    /*  Do not pass nDefaultRgb to ContainerHelper::getVectorElement(), to be
        able to catch the existing vector entries without corresponding XML
        token identifier. */
    sal_Int32 nRgbValue = ContainerHelper::getVectorElement( StaticPresetColorsPool::get().maVmlColors, nToken, API_RGB_TRANSPARENT );
    return (nRgbValue >= 0) ? nRgbValue : nDefaultRgb;
}

void Color::setUnused()
{
    meMode = COLOR_UNUSED;
}

void Color::setSrgbClr( sal_Int32 nRgb )
{
    OSL_ENSURE( (0 <= nRgb) && (nRgb <= 0xFFFFFF), "Color::setSrgbClr - invalid RGB value" );
    meMode = COLOR_RGB;
    lclRgbToRgbComponents( mnC1, mnC2, mnC3, nRgb );
}

void Color::setScrgbClr( sal_Int32 nR, sal_Int32 nG, sal_Int32 nB )
{
    OSL_ENSURE( (0 <= nR) && (nR <= MAX_PERCENT), "Color::setScrgbClr - invalid red value" );
    OSL_ENSURE( (0 <= nG) && (nG <= MAX_PERCENT), "Color::setScrgbClr - invalid green value" );
    OSL_ENSURE( (0 <= nB) && (nB <= MAX_PERCENT), "Color::setScrgbClr - invalid blue value" );
    meMode = COLOR_CRGB;
    mnC1 = getLimitedValue< sal_Int32, sal_Int32 >( nR, 0, MAX_PERCENT );
    mnC2 = getLimitedValue< sal_Int32, sal_Int32 >( nG, 0, MAX_PERCENT );
    mnC3 = getLimitedValue< sal_Int32, sal_Int32 >( nB, 0, MAX_PERCENT );
}

void Color::setHslClr( sal_Int32 nHue, sal_Int32 nSat, sal_Int32 nLum )
{
    OSL_ENSURE( (0 <= nHue) && (nHue <= MAX_DEGREE), "Color::setHslClr - invalid hue value" );
    OSL_ENSURE( (0 <= nSat) && (nSat <= MAX_PERCENT), "Color::setHslClr - invalid saturation value" );
    OSL_ENSURE( (0 <= nLum) && (nLum <= MAX_PERCENT), "Color::setHslClr - invalid luminance value" );
    meMode = COLOR_HSL;
    mnC1 = getLimitedValue< sal_Int32, sal_Int32 >( nHue, 0, MAX_DEGREE );
    mnC2 = getLimitedValue< sal_Int32, sal_Int32 >( nSat, 0, MAX_PERCENT );
    mnC3 = getLimitedValue< sal_Int32, sal_Int32 >( nLum, 0, MAX_PERCENT );
}

void Color::setPrstClr( sal_Int32 nToken )
{
    sal_Int32 nRgbValue = getDmlPresetColor( nToken, API_RGB_TRANSPARENT );
    OSL_ENSURE( nRgbValue >= 0, "Color::setPrstClr - invalid preset color token" );
    if( nRgbValue >= 0 )
        setSrgbClr( nRgbValue );
}

void Color::setSchemeClr( sal_Int32 nToken )
{
    OSL_ENSURE( nToken != XML_TOKEN_INVALID, "Color::setSchemeClr - invalid color token" );
    meMode = (nToken == XML_phClr) ? COLOR_PH : COLOR_SCHEME;
    mnC1 = nToken;
}

void Color::setPaletteClr( sal_Int32 nPaletteIdx )
{
    OSL_ENSURE( nPaletteIdx >= 0, "Color::setPaletteClr - invalid palette index" );
    meMode = COLOR_PALETTE;
    mnC1 = nPaletteIdx;
}

void Color::setSysClr( sal_Int32 nToken, sal_Int32 nLastRgb )
{
    OSL_ENSURE( (-1 <= nLastRgb) && (nLastRgb <= 0xFFFFFF), "Color::setSysClr - invalid RGB value" );
    meMode = COLOR_SYSTEM;
    mnC1 = nToken;
    mnC2 = nLastRgb;
}

void Color::addTransformation( sal_Int32 nElement, sal_Int32 nValue )
{
    /*  Execute alpha transformations directly, store other transformations in
        a vector, they may depend on a scheme base color which will be resolved
        in Color::getColor(). */
    sal_Int32 nToken = getBaseToken( nElement );
    switch( nToken )
    {
        case XML_alpha:     lclSetValue( mnAlpha, nValue ); break;
        case XML_alphaMod:  lclModValue( mnAlpha, nValue ); break;
        case XML_alphaOff:  lclOffValue( mnAlpha, nValue ); break;
        default:            maTransforms.push_back( Transformation( nToken, nValue ) );
    }
}

void Color::addChartTintTransformation( double fTint )
{
    sal_Int32 nValue = getLimitedValue< sal_Int32, double >( fTint * MAX_PERCENT + 0.5, -MAX_PERCENT, MAX_PERCENT );
    if( nValue < 0 )
        maTransforms.push_back( Transformation( XML_shade, nValue + MAX_PERCENT ) );
    else if( nValue > 0 )
        maTransforms.push_back( Transformation( XML_tint, MAX_PERCENT - nValue ) );
}

void Color::addExcelTintTransformation( double fTint )
{
    sal_Int32 nValue = getLimitedValue< sal_Int32, double >( fTint * MAX_PERCENT + 0.5, -MAX_PERCENT, MAX_PERCENT );
    maTransforms.push_back( Transformation( XLS_TOKEN( tint ), nValue ) );
}

void Color::clearTransformations()
{
    maTransforms.clear();
    clearTransparence();
}

void Color::clearTransparence()
{
    mnAlpha = MAX_PERCENT;
}

sal_Int32 Color::getColor( const GraphicHelper& rGraphicHelper, sal_Int32 nPhClr ) const
{
    /*  Special handling for theme style list placeholder colors (state
        COLOR_PH), Color::getColor() may be called with different placeholder
        colors in the nPhClr parameter. Therefore, the resolved color will not
        be stored in this object, thus the state COLOR_FINAL will not be
        reached and the transformation container will not be cleared, but the
        original COLOR_PH state will be restored instead. */
    bool bIsPh = false;

    switch( meMode )
    {
        case COLOR_UNUSED:  mnC1 = API_RGB_TRANSPARENT; break;

        case COLOR_RGB:     break;  // nothing to do
        case COLOR_CRGB:    break;  // nothing to do
        case COLOR_HSL:     break;  // nothing to do

        case COLOR_SCHEME:  setResolvedRgb( rGraphicHelper.getSchemeColor( mnC1 ) );        break;
        case COLOR_PALETTE: setResolvedRgb( rGraphicHelper.getPaletteColor( mnC1 ) );       break;
        case COLOR_SYSTEM:  setResolvedRgb( rGraphicHelper.getSystemColor( mnC1, mnC2 ) );  break;
        case COLOR_PH:      setResolvedRgb( nPhClr ); bIsPh = true;                         break;

        case COLOR_FINAL:   return mnC1;
    }

    // if color is UNUSED or turns to UNUSED in setResolvedRgb, do not perform transformations
    if( meMode != COLOR_UNUSED )
    {
        for( TransformVec::const_iterator aIt = maTransforms.begin(), aEnd = maTransforms.end(); aIt != aEnd; ++aIt )
        {
            switch( aIt->mnToken )
            {
                case XML_red:       toCrgb(); lclSetValue( mnC1, aIt->mnValue );    break;
                case XML_redMod:    toCrgb(); lclModValue( mnC1, aIt->mnValue );    break;
                case XML_redOff:    toCrgb(); lclOffValue( mnC1, aIt->mnValue );    break;
                case XML_green:     toCrgb(); lclSetValue( mnC2, aIt->mnValue );    break;
                case XML_greenMod:  toCrgb(); lclModValue( mnC2, aIt->mnValue );    break;
                case XML_greenOff:  toCrgb(); lclOffValue( mnC2, aIt->mnValue );    break;
                case XML_blue:      toCrgb(); lclSetValue( mnC3, aIt->mnValue );    break;
                case XML_blueMod:   toCrgb(); lclModValue( mnC3, aIt->mnValue );    break;
                case XML_blueOff:   toCrgb(); lclOffValue( mnC3, aIt->mnValue );    break;

                case XML_hue:       toHsl(); lclSetValue( mnC1, aIt->mnValue, MAX_DEGREE ); break;
                case XML_hueMod:    toHsl(); lclModValue( mnC1, aIt->mnValue, MAX_DEGREE ); break;
                case XML_hueOff:    toHsl(); lclOffValue( mnC1, aIt->mnValue, MAX_DEGREE ); break;
                case XML_sat:       toHsl(); lclSetValue( mnC2, aIt->mnValue );             break;
                case XML_satMod:    toHsl(); lclModValue( mnC2, aIt->mnValue );             break;
                case XML_satOff:    toHsl(); lclOffValue( mnC2, aIt->mnValue );             break;

                case XML_lum:
                    toHsl();
                    lclSetValue( mnC3, aIt->mnValue );
                    // if color changes to black or white, it will stay gray if luminance changes again
                    if( (mnC3 == 0) || (mnC3 == MAX_PERCENT) ) mnC2 = 0;
                break;
                case XML_lumMod:
                    toHsl();
                    lclModValue( mnC3, aIt->mnValue );
                    // if color changes to black or white, it will stay gray if luminance changes again
                    if( (mnC3 == 0) || (mnC3 == MAX_PERCENT) ) mnC2 = 0;
                break;
                case XML_lumOff:
                    toHsl();
                    lclOffValue( mnC3, aIt->mnValue );
                    // if color changes to black or white, it will stay gray if luminance changes again
                    if( (mnC3 == 0) || (mnC3 == MAX_PERCENT) ) mnC2 = 0;
                break;

                case XML_shade:
                    // shade: 0% = black, 100% = original color
                    toCrgb();
                    OSL_ENSURE( (0 <= aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT), "Color::getColor - invalid shade value" );
                    if( (0 <= aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT) )
                    {
                        double fFactor = static_cast< double >( aIt->mnValue ) / MAX_PERCENT;
                        mnC1 = static_cast< sal_Int32 >( mnC1 * fFactor );
                        mnC2 = static_cast< sal_Int32 >( mnC2 * fFactor );
                        mnC3 = static_cast< sal_Int32 >( mnC3 * fFactor );
                    }
                break;
                case XML_tint:
                    // tint: 0% = white, 100% = original color
                    toCrgb();
                    OSL_ENSURE( (0 <= aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT), "Color::getColor - invalid tint value" );
                    if( (0 <= aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT) )
                    {
                        double fFactor = static_cast< double >( aIt->mnValue ) / MAX_PERCENT;
                        mnC1 = static_cast< sal_Int32 >( MAX_PERCENT - (MAX_PERCENT - mnC1) * fFactor );
                        mnC2 = static_cast< sal_Int32 >( MAX_PERCENT - (MAX_PERCENT - mnC2) * fFactor );
                        mnC3 = static_cast< sal_Int32 >( MAX_PERCENT - (MAX_PERCENT - mnC3) * fFactor );
                    }
                break;
                case XLS_TOKEN( tint ):
                    // Excel tint: move luminance relative to current value
                    toHsl();
                    OSL_ENSURE( (-MAX_PERCENT <= aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT), "Color::getColor - invalid tint value" );
                    if( (-MAX_PERCENT <= aIt->mnValue) && (aIt->mnValue < 0) )
                    {
                        // negative: luminance towards 0% (black)
                        lclModValue( mnC3, aIt->mnValue + MAX_PERCENT );
                    }
                    else if( (0 < aIt->mnValue) && (aIt->mnValue <= MAX_PERCENT) )
                    {
                        // positive: luminance towards 100% (white)
                        mnC3 = MAX_PERCENT - mnC3;
                        lclModValue( mnC3, MAX_PERCENT - aIt->mnValue );
                        mnC3 = MAX_PERCENT - mnC3;
                    }
                break;

                case XML_gray:
                    // change color to gray, weighted RGB: 22% red, 72% green, 6% blue
                    toRgb();
                    mnC1 = mnC2 = mnC3 = (mnC1 * 22 + mnC2 * 72 + mnC3 * 6) / 100;
                break;

                case XML_comp:
                    // comp: rotate hue by 180 degrees, do not change lum/sat
                    toHsl();
                    (mnC1 += 180 * PER_DEGREE) %= MAX_DEGREE;
                break;
                case XML_inv:
                    // invert percentual RGB values
                    toCrgb();
                    mnC1 = MAX_PERCENT - mnC1;
                    mnC2 = MAX_PERCENT - mnC2;
                    mnC3 = MAX_PERCENT - mnC3;
                break;

                case XML_gamma:
                    // increase gamma of color
                    toCrgb();
                    mnC1 = lclGamma( mnC1, INC_GAMMA );
                    mnC2 = lclGamma( mnC2, INC_GAMMA );
                    mnC3 = lclGamma( mnC3, INC_GAMMA );
                break;
                case XML_invGamma:
                    // decrease gamma of color
                    toCrgb();
                    mnC1 = lclGamma( mnC1, DEC_GAMMA );
                    mnC2 = lclGamma( mnC2, DEC_GAMMA );
                    mnC3 = lclGamma( mnC3, DEC_GAMMA );
                break;
            }
        }

        // store resulting RGB value in mnC1
        toRgb();
        mnC1 = lclRgbComponentsToRgb( mnC1, mnC2, mnC3 );
    }
    else // if( meMode != COLOR_UNUSED )
    {
        mnC1 = API_RGB_TRANSPARENT;
    }

    meMode = bIsPh ? COLOR_PH : COLOR_FINAL;
    if( meMode == COLOR_FINAL )
        maTransforms.clear();
    return mnC1;
}

bool Color::hasTransparency() const
{
    return mnAlpha < MAX_PERCENT;
}

sal_Int16 Color::getTransparency() const
{
    return static_cast< sal_Int16 >( (MAX_PERCENT - mnAlpha) / PER_PERCENT );
}

// private --------------------------------------------------------------------

void Color::setResolvedRgb( sal_Int32 nRgb ) const
{
    meMode = (nRgb < 0) ? COLOR_UNUSED : COLOR_RGB;
    lclRgbToRgbComponents( mnC1, mnC2, mnC3, nRgb );
}

void Color::toRgb() const
{
    switch( meMode )
    {
        case COLOR_RGB:
            // nothing to do
        break;
        case COLOR_CRGB:
            meMode = COLOR_RGB;
            mnC1 = lclCrgbCompToRgbComp( lclGamma( mnC1, INC_GAMMA ) );
            mnC2 = lclCrgbCompToRgbComp( lclGamma( mnC2, INC_GAMMA ) );
            mnC3 = lclCrgbCompToRgbComp( lclGamma( mnC3, INC_GAMMA ) );
        break;
        case COLOR_HSL:
        {
            meMode = COLOR_RGB;
            double fR = 0.0, fG = 0.0, fB = 0.0;
            if( (mnC2 == 0) || (mnC3 == MAX_PERCENT) )
            {
                fR = fG = fB = static_cast< double >( mnC3 ) / MAX_PERCENT;
            }
            else if( mnC3 > 0 )
            {
                // base color from hue
                double fHue = static_cast< double >( mnC1 ) / MAX_DEGREE * 6.0; // interval [0.0, 6.0)
                if( fHue <= 1.0 )       { fR = 1.0; fG = fHue; }        // red...yellow
                else if( fHue <= 2.0 )  { fR = 2.0 - fHue; fG = 1.0; }  // yellow...green
                else if( fHue <= 3.0 )  { fG = 1.0; fB = fHue - 2.0; }  // green...cyan
                else if( fHue <= 4.0 )  { fG = 4.0 - fHue; fB = 1.0; }  // cyan...blue
                else if( fHue <= 5.0 )  { fR = fHue - 4.0; fB = 1.0; }  // blue...magenta
                else                    { fR = 1.0; fB = 6.0 - fHue; }  // magenta...red

                // apply saturation
                double fSat = static_cast< double >( mnC2 ) / MAX_PERCENT;
                fR = (fR - 0.5) * fSat + 0.5;
                fG = (fG - 0.5) * fSat + 0.5;
                fB = (fB - 0.5) * fSat + 0.5;

                // apply luminance
                double fLum = 2.0 * static_cast< double >( mnC3 ) / MAX_PERCENT - 1.0;  // interval [-1.0, 1.0]
                if( fLum < 0.0 )
                {
                    double fShade = fLum + 1.0; // interval [0.0, 1.0] (black...full color)
                    fR *= fShade;
                    fG *= fShade;
                    fB *= fShade;
                }
                else if( fLum > 0.0 )
                {
                    double fTint = 1.0 - fLum;  // interval [0.0, 1.0] (white...full color)
                    fR = 1.0 - ((1.0 - fR) * fTint);
                    fG = 1.0 - ((1.0 - fG) * fTint);
                    fB = 1.0 - ((1.0 - fB) * fTint);
                }
            }
            mnC1 = static_cast< sal_Int32 >( fR * 255.0 + 0.5 );
            mnC2 = static_cast< sal_Int32 >( fG * 255.0 + 0.5 );
            mnC3 = static_cast< sal_Int32 >( fB * 255.0 + 0.5 );
        }
        break;
        default:
            OSL_ENSURE( false, "Color::toRgb - unexpected color mode" );
    }
}

void Color::toCrgb() const
{
    switch( meMode )
    {
        case COLOR_HSL:
            toRgb();
            // run through!
        case COLOR_RGB:
            meMode = COLOR_CRGB;
            mnC1 = lclGamma( lclRgbCompToCrgbComp( mnC1 ), DEC_GAMMA );
            mnC2 = lclGamma( lclRgbCompToCrgbComp( mnC2 ), DEC_GAMMA );
            mnC3 = lclGamma( lclRgbCompToCrgbComp( mnC3 ), DEC_GAMMA );
        break;
        case COLOR_CRGB:
            // nothing to do
        break;
        default:
            OSL_ENSURE( false, "Color::toCrgb - unexpected color mode" );
    }
}

void Color::toHsl() const
{
    switch( meMode )
    {
        case COLOR_CRGB:
            toRgb();
            // run through!
        case COLOR_RGB:
        {
            meMode = COLOR_HSL;
            double fR = static_cast< double >( mnC1 ) / 255.0;  // red [0.0, 1.0]
            double fG = static_cast< double >( mnC2 ) / 255.0;  // green [0.0, 1.0]
            double fB = static_cast< double >( mnC3 ) / 255.0;  // blue [0.0, 1.0]
            double fMin = ::std::min( ::std::min( fR, fG ), fB );
            double fMax = ::std::max( ::std::max( fR, fG ), fB );
            double fD = fMax - fMin;

            // hue: 0deg = red, 120deg = green, 240deg = blue
            if( fD == 0.0 )         // black/gray/white
                mnC1 = 0;
            else if( fMax == fR )   // magenta...red...yellow
                mnC1 = static_cast< sal_Int32 >( ((fG - fB) / fD * 60.0 + 360.0) * PER_DEGREE + 0.5 ) % MAX_DEGREE;
            else if( fMax == fG )   // yellow...green...cyan
                mnC1 = static_cast< sal_Int32 >( ((fB - fR) / fD * 60.0 + 120.0) * PER_DEGREE + 0.5 );
            else                    // cyan...blue...magenta
                mnC1 = static_cast< sal_Int32 >( ((fR - fG) / fD * 60.0 + 240.0) * PER_DEGREE + 0.5 );

            // luminance: 0% = black, 50% = full color, 100% = white
            mnC3 = static_cast< sal_Int32 >( (fMin + fMax) / 2.0 * MAX_PERCENT + 0.5 );

            // saturation: 0% = gray, 100% = full color
            if( (mnC3 == 0) || (mnC3 == MAX_PERCENT) )  // black/white
                mnC2 = 0;
            else if( mnC3 <= 50 * PER_PERCENT )         // dark...full color
                mnC2 = static_cast< sal_Int32 >( fD / (fMin + fMax) * MAX_PERCENT + 0.5 );
            else                                        // full color...light
                mnC2 = static_cast< sal_Int32 >( fD / (2.0 - fMax - fMin) * MAX_PERCENT + 0.5 );
        }
        break;
        case COLOR_HSL:
            // nothing to do
        break;
        default:
            OSL_ENSURE( false, "Color::toHsl - unexpected color mode" );
    }
}

// ============================================================================

} // namespace drawingml
} // namespace oox

