/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2007 Lubos Lunak <l.lunak@kde.org>
Copyright (C) 2008 Lucas Murray <lmurray@undefinedfire.com>
Copyright (C) 2008 Martin Gräßlin <ubuntu@martin-graesslin.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

#include "shadow.h"
#include "shadow_helper.h"

#include <kwinglutils.h>

#include <kconfiggroup.h>
#include <kdebug.h>
#include <KStandardDirs>
#include <kcolorscheme.h>
#include <KGlobalSettings>

#include <cmath>

namespace KWin
{

KWIN_EFFECT( shadow, ShadowEffect )

enum {
    ShadowOverrideRole = 0x22A982D1
};

ShadowEffect::ShadowEffect()
    : shadowSize( 0 )
    {
    cachedBlendPicture = X::None;
    reconfigure( ReconfigureAll );
    connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
             this, SLOT(updateShadowColor()));
    shadowOverride = XInternAtom( display(), "_KDE_SHADOW_OVERRIDE", False );
    effects->registerPropertyType( shadowOverride, true );
    }

ShadowEffect::~ShadowEffect()
    {
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    for( int i = 0; i < mDefaultShadowTextures.size(); i++ )
        delete mDefaultShadowTextures.at( i );
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    for( int i = 0; i < mDefaultShadowPics.size(); i++ )
        delete mDefaultShadowPics.at( i );
#endif
    effects->registerPropertyType( shadowOverride, false );
    }

void ShadowEffect::reconfigure( ReconfigureFlags )
    {
    KConfigGroup conf = effects->effectConfig("Shadow");
    shadowXOffset = conf.readEntry( "XOffset", 0 );
    shadowYOffset = conf.readEntry( "YOffset", 3 );
    shadowOpacity = conf.readEntry( "Opacity", 0.25 );
    shadowFuzzyness = conf.readEntry( "Fuzzyness", 10 );
    shadowSize = conf.readEntry( "Size", 5 );
    intensifyActiveShadow = conf.readEntry( "IntensifyActiveShadow", true );
    updateShadowColor();

    // Load decoration shadow related things
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    if( effects->compositingType() == OpenGLCompositing )
        {
        // Delete any other textures in memory
        for( int i = 0; i < mDefaultShadowTextures.size(); i++ )
                delete mDefaultShadowTextures.at( i );
        mDefaultShadowTextures.clear();

        //-------------------------
        // Create default textures

        mDefaultShadowQuadType = effects->newWindowQuadType(); // TODO: Unregister?
        QImage shadowImageSrc( KGlobal::dirs()->findResource( "data", "kwin/shadow-texture.png" ));

        // Might as well process the shadow color here
        QImage shadowImage( shadowImageSrc.width(), shadowImageSrc.height(), QImage::Format_ARGB32 );
        QPainter painter( &shadowImage );
        //painter.setCompositionMode( QPainter::CompositionMode_Multiply );
        painter.setPen( Qt::NoPen );
        painter.setBrush( shadowColor );
        painter.drawRect( 0, 0, shadowImage.width(), shadowImage.height() );
        painter.end();
        shadowImage.setAlphaChannel( shadowImageSrc.alphaChannel() ); // Cheat, just use the alpha mask

        int hw = shadowImage.width() / 2;
        int hh = shadowImage.height() / 2;
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0,  0,  hw, hh )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, 0,  1,  hh )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, 0,  hw, hh )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0,  hh, hw, 1 )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, 1,  1 )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, hw, 1 )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0,  hh, hw, hh )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, 1,  hh )));
        mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, hw, hh )));
        }
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if( effects->compositingType() == XRenderCompositing )
        {
        // Delete any other pictures in memory
        for( int i = 0; i < mDefaultShadowPics.size(); i++ )
            delete mDefaultShadowPics.at( i );
        mDefaultShadowPics.clear();

        // Create default pictures
        mDefaultShadowQuadType = effects->newWindowQuadType(); // TODO: Unregister?
        QPixmap shadowPixmap( KGlobal::dirs()->findResource( "data", "kwin/shadow-texture.png" ));
        shadowPixmap = shadowPixmap.scaled( QSize( shadowFuzzyness * 4, shadowFuzzyness * 4 ),
            Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
        int hw = shadowPixmap.width() / 2;
        int hh = shadowPixmap.height() / 2;
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0,  0,  hw, hh )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, 0,  1,  hh )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, 0,  hw, hh )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0,  hh, hw, 1 )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, 1,  1 )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, hw, 1 )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0,  hh, hw, hh )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, 1,  hh )));
        mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, hw, hh )));

        // Apply repeat attribute to all pictures
        XRenderPictureAttributes pa;
        pa.repeat = true;
        for( int i = 0; i < mDefaultShadowPics.size(); i++ )
            XRenderChangePicture( display(), *mDefaultShadowPics.at( i ), CPRepeat, &pa );
        }
#endif

    // Force rebuild of all quads to clear their caches
    foreach( EffectWindow *w, effects->stackingOrder() )
        if( w ) // TODO/HACK: stackingOrder() should NOT return NULL
            w->buildQuads( true );
    }

void ShadowEffect::updateShadowColor()
    {
    KConfigGroup conf = effects->effectConfig("Shadow");
    shadowColor = conf.readEntry( "Color",  schemeShadowColor() );
    }

QRect ShadowEffect::shadowRectangle( EffectWindow* w, const QRect& windowRectangle ) const
    {
    Q_UNUSED( w );
    int shadowGrow = shadowFuzzyness + shadowSize;
    return windowRectangle.adjusted( shadowXOffset - shadowGrow, shadowYOffset - shadowGrow,
            shadowXOffset + shadowGrow, shadowYOffset + shadowGrow);
    }

#ifdef KWIN_HAVE_XRENDER_COMPOSITING
static ScreenPaintData gScreenData;
#endif

void ShadowEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
    {
    shadowDatas.clear();
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if ((mask & PAINT_SCREEN_TRANSFORMED) &&
        (effects->compositingType() == XRenderCompositing)) // TODO: copy constructor?
        {
        gScreenData.xTranslate = data.xTranslate;
        gScreenData.yTranslate = data.yTranslate;
        gScreenData.xScale = data.xScale;
        gScreenData.yScale = data.yScale;
        }
#endif
    // Draw windows
    effects->paintScreen( mask, region, data );

    // Draw shadows
    drawQueuedShadows( 0 );
    }

void ShadowEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time )
    {
    if( useShadow( w ))
        data.paint |= shadowRectangle( w, data.paint.boundingRect() );
    effects->prePaintWindow( w, data, time );
    }

void ShadowEffect::drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
    {
    // Whether the shadow drawing can be delayed or not.
    bool optimize = !(mask & ( PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED |
            PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS | PAINT_WINDOW_TRANSLUCENT | PAINT_DECORATION_ONLY ));

    QRegion rgn = region;
    if ( w->keepAbove() )
        {
            QRegion shape = w->shape().translated( w->x(), w->y());
            if (optimize)
                {
                    rgn -= shape;
                    QList<ShadowData>::iterator i;
                    for( i = shadowDatas.begin(); i != shadowDatas.end(); ++i )
                        (*i).clip |= shape;
                }
            optimize = false;
        }
    if( !optimize )
        {
        // Transformed or translucent windows are drawn bottom-to-top, so
        //  first we need to draw all queued shadows.
        drawQueuedShadows( w );
        }
    if( useShadow( w ) && (!(mask & PAINT_DECORATION_ONLY) || w->keepAbove()) )
        {
        if( !optimize )
            {
            // For translucent windows, shadow needs to be drawn before the
            //  window itself.
            drawShadow( w, mask, rgn, data );
            }
        else
            {
            // For opaque windows, just schedule the shadow to be drawn later
            ShadowData d(w, data);
            d.clip = w->shape().translated( w->x(), w->y());
            if( !shadowDatas.isEmpty())
                d.clip |= shadowDatas.last().clip;
            d.mask = mask;
            foreach( const QRect &r, region.rects() )
                d.region |= shadowRectangle( w, r );
            d.region &= region;
            shadowDatas.append(d);
            }
        }

    effects->drawWindow( w, mask, region, data );
    }

void ShadowEffect::buildQuads( EffectWindow* w, WindowQuadList& quadList )
    {
    // TODO: add config option to not have shadows for menus, etc.
    // Make our own shadow as the decoration doesn't support it
    int fuzzy = shadowFuzzyness;
    // Shadow's size must be a least 2*fuzzy in both directions (or the corners will be broken)
    int width = qMax( fuzzy * 2, w->width() + 2 * shadowSize );
    int height = qMax( fuzzy * 2, w->height() + 2 * shadowSize );
    double x1, y1, x2, y2;
    int id = 0;
    // top-left
    x1 = shadowXOffset - shadowSize + 0 - fuzzy;
    y1 = shadowYOffset - shadowSize + 0 - fuzzy;
    x2 = shadowXOffset - shadowSize + 0 + fuzzy;
    y2 = shadowYOffset - shadowSize + 0 + fuzzy;
    WindowQuad topLeftQuad( mDefaultShadowQuadType, id++ );
    topLeftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    topLeftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    topLeftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    topLeftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( topLeftQuad );
    // top
    x1 = shadowXOffset - shadowSize + 0 + fuzzy;
    y1 = shadowYOffset - shadowSize + 0 - fuzzy;
    x2 = shadowXOffset - shadowSize + width - fuzzy;
    y2 = shadowYOffset - shadowSize + 0 + fuzzy;
    WindowQuad topQuad( mDefaultShadowQuadType, id++ );
    topQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    topQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    topQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    topQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( topQuad );
    // top-right
    x1 = shadowXOffset - shadowSize + width - fuzzy;
    y1 = shadowYOffset - shadowSize + 0 - fuzzy;
    x2 = shadowXOffset - shadowSize + width + fuzzy;
    y2 = shadowYOffset - shadowSize + 0 + fuzzy;
    WindowQuad topRightQuad( mDefaultShadowQuadType, id++ );
    topRightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    topRightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    topRightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    topRightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( topRightQuad );
    // left
    x1 = shadowXOffset - shadowSize + 0 - fuzzy;
    y1 = shadowYOffset - shadowSize + 0 + fuzzy;
    x2 = shadowXOffset - shadowSize + 0 + fuzzy;
    y2 = shadowYOffset - shadowSize + height - fuzzy;
    WindowQuad leftQuad( mDefaultShadowQuadType, id++ );
    leftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    leftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    leftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    leftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( leftQuad );
    // center
    x1 = shadowXOffset - shadowSize + 0 + fuzzy;
    y1 = shadowYOffset - shadowSize + 0 + fuzzy;
    x2 = shadowXOffset - shadowSize + width - fuzzy;
    y2 = shadowYOffset - shadowSize + height - fuzzy;
    WindowQuad contentsQuad( mDefaultShadowQuadType, id++ );
    contentsQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    contentsQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    contentsQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    contentsQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( contentsQuad );
    // right
    x1 = shadowXOffset - shadowSize + width - fuzzy;
    y1 = shadowYOffset - shadowSize + 0 + fuzzy;
    x2 = shadowXOffset - shadowSize + width + fuzzy;
    y2 = shadowYOffset - shadowSize + height - fuzzy;
    WindowQuad rightQuad( mDefaultShadowQuadType, id++ );
    rightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    rightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    rightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    rightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( rightQuad );
    // bottom-left
    x1 = shadowXOffset - shadowSize + 0 - fuzzy;
    y1 = shadowYOffset - shadowSize + height - fuzzy;
    x2 = shadowXOffset - shadowSize + 0 + fuzzy;
    y2 = shadowYOffset - shadowSize + height + fuzzy;
    WindowQuad bottomLeftQuad( mDefaultShadowQuadType, id++ );
    bottomLeftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    bottomLeftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    bottomLeftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    bottomLeftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( bottomLeftQuad );
    // bottom
    x1 = shadowXOffset - shadowSize + 0 + fuzzy;
    y1 = shadowYOffset - shadowSize + height - fuzzy;
    x2 = shadowXOffset - shadowSize + width - fuzzy;
    y2 = shadowYOffset - shadowSize + height + fuzzy;
    WindowQuad bottomQuad( mDefaultShadowQuadType, id++ );
    bottomQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    bottomQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    bottomQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    bottomQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( bottomQuad );
    // bottom-right
    x1 = shadowXOffset - shadowSize + width - fuzzy;
    y1 = shadowYOffset - shadowSize + height - fuzzy;
    x2 = shadowXOffset - shadowSize + width + fuzzy;
    y2 = shadowYOffset - shadowSize + height + fuzzy;
    WindowQuad bottomRightQuad( mDefaultShadowQuadType, id++ );
    bottomRightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 );
    bottomRightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 );
    bottomRightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 );
    bottomRightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 );
    quadList.append( bottomRightQuad );

    effects->buildQuads( w, quadList );
    }

QRect ShadowEffect::transformWindowDamage( EffectWindow* w, const QRect& r )
    {
    if( !useShadow( w ))
        return effects->transformWindowDamage( w, r );
    QRect r2 = r | shadowRectangle( w, r );
    return effects->transformWindowDamage( w, r2 );
    }

void ShadowEffect::propertyNotify( EffectWindow* w, long atom )
    {
    if( !w || atom != shadowOverride )
        return;
    const QByteArray value = w->readProperty( atom, atom, 32 );
    w->setData( ShadowOverrideRole, !value.isNull() );
    effects->addRepaint( shadowRectangle( w, w->geometry() ));
    }

void ShadowEffect::windowAdded( EffectWindow* c )
    {
    const QByteArray value = c->readProperty( shadowOverride, shadowOverride, 32 );
    c->setData( ShadowOverrideRole, !value.isNull() );
    }

void ShadowEffect::windowClosed( EffectWindow* c )
    {
    effects->addRepaint( shadowRectangle( c, c->geometry() ));
    }

bool ShadowEffect::useShadow( EffectWindow* w ) const
    {
    return !w->isDeleted() && !w->isDesktop() && !w->isDock()
        // popups may have shadow even if shaped, their shape is almost rectangular
        && ( !w->hasOwnShape() || w->isDropdownMenu() || w->isPopupMenu() || w->isComboBox())
        // Ignore windows that draw their own shadows
        && !w->data( ShadowOverrideRole ).toBool()
        // If decoration has it's own shadow leave it alone
        && !( w->hasDecoration() && effects->hasDecorationShadows() )
        && !( w->windowClass() == "ksmserver ksmserver" &&
                ( w->windowRole() == "logoutdialog" || w->windowRole() == "logouteffect" ));
    }

void ShadowEffect::addQuadVertices(QVector<float>& verts, float x1, float y1, float x2, float y2) const
    {
    verts << x1 << y1;
    verts << x1 << y2;
    verts << x2 << y2;
    verts << x2 << y1;
    }

void ShadowEffect::drawQueuedShadows( EffectWindow* behindWindow )
    {
    QList<ShadowData> newShadowDatas;
    QList<ShadowData> thisTime;
    EffectWindowList stack = effects->stackingOrder();
    foreach( const ShadowData &d, shadowDatas )
        {
        // If behindWindow is given then only render shadows of the windows
        //  that are behind that window.
        if( !behindWindow || stack.indexOf(d.w) < stack.indexOf(behindWindow))
            thisTime.append(d);
        else
            newShadowDatas.append(d);
        }
    if( thisTime.count() )
        { // Render them in stacking order
        foreach( EffectWindow *w, stack )
            for( int i = 0; i < thisTime.size(); i++ )
                { // Cannot use foreach() due to thisTime.removeOne()
                const ShadowData d = thisTime.at(i);
                if( d.w == w )
                    {
                    drawShadow( d.w, d.mask,
                        d.region.subtracted( d.clip ), d.data );
                    thisTime.removeAt( i );
                    break;
                    }
                }
        }
    // Render the rest on the top (For menus, etc.)
    foreach( const ShadowData &d, thisTime )
        drawShadow( d.w, d.mask, d.region.subtracted( d.clip ), d.data );
    shadowDatas = newShadowDatas;
    }

// Modified version of SceneOpenGL::Window::prepareRenderStates() from scene_opengl.cpp
void ShadowEffect::prepareRenderStates( GLTexture *texture, double opacity, double brightness, double saturation )
    {
    Q_UNUSED( texture );
    Q_UNUSED( saturation );
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    // setup blending of transparent windows
    glPushAttrib( GL_ENABLE_BIT );
    /*if( saturation != 1.0 && texture->saturationSupported() )
        {
        // First we need to get the color from [0; 1] range to [0.5; 1] range
        glActiveTexture( GL_TEXTURE0 );
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
        glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA );
        const float scale_constant[] = { 1.0, 1.0, 1.0, 0.5 };
        glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, scale_constant );
        texture->bind();

        // Then we take dot product of the result of previous pass and
        //  saturation_constant. This gives us completely unsaturated
        //  (greyscale) image
        // Note that both operands have to be in range [0.5; 1] since opengl
        //  automatically substracts 0.5 from them
        glActiveTexture( GL_TEXTURE1 );
        float saturation_constant[] = { 0.5 + 0.5*0.30, 0.5 + 0.5*0.59, 0.5 + 0.5*0.11, saturation };
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
        glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
        glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant );
        texture->bind();

        // Finally we need to interpolate between the original image and the
        //  greyscale image to get wanted level of saturation
        glActiveTexture( GL_TEXTURE2 );
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
        glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0 );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA );
        glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant );
        // Also replace alpha by primary color's alpha here
        glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE );
        glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PRIMARY_COLOR );
        glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA );
        // And make primary color contain the wanted opacity
        glColor4f( opacity, opacity, opacity, opacity );
        texture->bind();

        if( brightness != 1.0 )
            {
            glActiveTexture( GL_TEXTURE3 );
            glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
            glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE );
            glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS );
            glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
            glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR );
            glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
            // The color has to be multiplied by both opacity and brightness
            float opacityByBrightness = opacity * brightness;
            glColor4f( opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity );
            glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE );
            glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS );
            glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA );
            texture->bind();
            }

        glActiveTexture(GL_TEXTURE0 );
        }
    else*/ if( opacity != 1.0 || brightness != 1.0 )
        { // The window is additionally configured to have its opacity adjusted
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
        glColor4f( brightness, brightness, brightness, opacity );
        }
#endif
    }

// Modified version of SceneOpenGL::Window::restoreRenderStates() from scene_opengl.cpp
void ShadowEffect::restoreRenderStates( GLTexture *texture, double opacity, double brightness, double saturation )
    {
    Q_UNUSED( texture );
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    if( opacity != 1.0 || saturation != 1.0 || brightness != 1.0 )
        {
        /*if( saturation != 1.0 && texture->saturationSupported())
            {
            glActiveTexture(GL_TEXTURE3);
            glDisable( texture->target() );
            glActiveTexture(GL_TEXTURE2);
            glDisable( texture->target() );
            glActiveTexture(GL_TEXTURE1);
            glDisable( texture->target() );
            glActiveTexture(GL_TEXTURE0);
            }*/
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
        glColor4f( 0.0, 0.0, 0.0, 0.0 );
        }

    glPopAttrib();  // ENABLE_BIT
#endif
    }

void ShadowEffect::drawShadowQuadOpenGL( GLTexture *texture, QVector<float> verts, QVector<float> texCoords,
    QRegion region, float opacity, float brightness, float saturation,  GLShader* shader )
    {
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    glColor4f( 1.0, 1.0, 1.0, 1.0 );
    glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
    prepareRenderStates( texture, opacity, brightness, saturation ); // Usually overrides the above
    if( shader )
        {
        shader->setUniform("opacity", opacity);
        shader->setUniform("saturation", saturation);
        shader->setUniform("brightness", brightness);
        shader->setUniform("textureWidth", 1.0f);
        shader->setUniform("textureHeight", 1.0f);
        }
    texture->bind();
    texture->enableNormalizedTexCoords();
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    renderGLGeometry( region, 4, verts.data(), texCoords.data() );
    texture->disableNormalizedTexCoords();
    texture->unbind();
    restoreRenderStates( texture, opacity, brightness, saturation );
#endif
    }

void ShadowEffect::drawShadowQuadXRender( XRenderPicture *picture, QRect rect, float xScale, float yScale,
    QColor color, float opacity, float brightness, float saturation )
    {
    Q_UNUSED( saturation );
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    XRenderPicture fill;
    if( color.isValid() )
        {
        if ( color == cachedColor && cachedBlendPicture != X::None )
            fill = cachedBlendPicture;
        else
            {
            XRenderColor xc = preMultiply( color, opacity );
            cachedBlendPicture = fill = xRenderFill( &xc );
            cachedColor = color;
            }
        }
    else
        fill = xRenderBlendPicture(opacity);

    // Scale if required
    if( xScale != 1.0 || yScale != 1.0 )
        {
        XTransform xform = {{
            { XDoubleToFixed( 1.0 / xScale ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ) },
            { XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 / yScale ), XDoubleToFixed( 0.0 ) },
            { XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ) }
        }};
        XRenderSetPictureTransform( display(), *picture, &xform );
        }

    // Render it
    // TODO: This always uses the fast filter, detect when to use smooth instead
    if( color.isValid() )
        XRenderComposite( display(), PictOpOver, fill, *picture, effects->xrenderBufferPicture(), 0, 0, 0, 0,
            rect.x(), rect.y(), rect.width(), rect.height() );
    else
        XRenderComposite( display(), PictOpOver, *picture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0,
            rect.x(), rect.y(), rect.width(), rect.height() );

    // Fake brightness by overlaying black
    // Cannot use XRenderFillRectangle() due to ARGB
    fill = xRenderBlendPicture(( 1 - brightness ) * opacity);
    XRenderComposite( display(), PictOpOver, fill, *picture, effects->xrenderBufferPicture(), 0, 0, 0, 0,
        rect.x(), rect.y(), rect.width(), rect.height() );

    // Return to scale to 1.0
    if( xScale != 1.0 || yScale != 1.0 )
        {
        XTransform xform = {{
            { XDoubleToFixed( 1.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ) },
            { XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ), XDoubleToFixed( 0.0 ) },
            { XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ) }
        }};
        XRenderSetPictureTransform( display(), *picture, &xform );
        }
#endif
    }

void ShadowEffect::drawShadow( EffectWindow* window, int mask, QRegion region, const WindowPaintData& data )
    {
    // Don't allow windows to cast shadows on other displays
    QRegion clipperGeom;
    for( int screen = 0; screen < effects->numScreens(); screen++ )
        {
        QRect screenGeom = effects->clientArea( ScreenArea, screen, 0 );
        if( !( window->geometry() & screenGeom ).isNull() )
            clipperGeom |= screenGeom;
        }
    PaintClipper pc( clipperGeom );

#ifdef KWIN_HAVE_OPENGL_COMPOSITING
    if( effects->compositingType() == OpenGLCompositing )
        {
        glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT );
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

        foreach( const WindowQuad &quad, data.quads )
            {
            if( quad.type() != mDefaultShadowQuadType )
                continue; // Not a shadow quad

            glPushMatrix();

            // Use the window's top-left as the origin
            glTranslatef( window->x(), window->y(), 0 );
            if( mask & PAINT_WINDOW_TRANSFORMED )
                glTranslatef( data.xTranslate, data.yTranslate, data.zTranslate );
            if(( mask & PAINT_WINDOW_TRANSFORMED ) && ( data.xScale != 1 || data.yScale != 1 ))
                glScalef( data.xScale, data.yScale, data.zScale );
            if(( mask & PAINT_WINDOW_TRANSFORMED ) && data.rotation )
                {
                glTranslatef( data.rotation->xRotationPoint,
                    data.rotation->yRotationPoint,
                    data.rotation->zRotationPoint );
                float xAxis = 0.0;
                float yAxis = 0.0;
                float zAxis = 0.0;
                switch( data.rotation->axis )
                    {
                    case RotationData::XAxis:
                        xAxis = 1.0;
                        break;
                    case RotationData::YAxis:
                        yAxis = 1.0;
                        break;
                    case RotationData::ZAxis:
                        zAxis = 1.0;
                        break;
                    }
                glRotatef( data.rotation->angle, xAxis, yAxis, zAxis );
                glTranslatef( -data.rotation->xRotationPoint,
                    -data.rotation->yRotationPoint,
                    -data.rotation->zRotationPoint );
                }

            // Create our polygon
            QVector<float> verts, texcoords;
            verts.reserve(8);
            texcoords.reserve(8);
            verts << quad[0].x() << quad[0].y();
            verts << quad[1].x() << quad[1].y();
            verts << quad[2].x() << quad[2].y();
            verts << quad[3].x() << quad[3].y();
            texcoords << quad[0].textureX() << quad[0].textureY();
            texcoords << quad[1].textureX() << quad[1].textureY();
            texcoords << quad[2].textureX() << quad[2].textureY();
            texcoords << quad[3].textureX() << quad[3].textureY();

            // Work out which texture to use
            float opacity = shadowOpacity;
            if( intensifyActiveShadow && window == effects->activeWindow() )
                opacity = 1 - ( 1 - shadowOpacity ) * ( 1 - shadowOpacity );

            drawShadowQuadOpenGL( mDefaultShadowTextures.at( quad.id() ),
                verts, texcoords, region,
                data.opacity * opacity,
                data.brightness,
                data.saturation,
                data.shader);

            glPopMatrix();
            }

        glPopAttrib();
        }
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
    if( effects->compositingType() == XRenderCompositing )
        {
        XRenderSetPictureClipRegion( display(), effects->xrenderBufferPicture(), region.handle() );

        foreach( const WindowQuad &quad, data.quads )
            {
            if( quad.type() != mDefaultShadowQuadType )
                continue; // Not a shadow quad

            // Determine transformed quad position
            QRect windowRect = window->geometry();
            float xScale = 1.0;
            float yScale = 1.0;
            float xTranslate = 0.0;
            float yTranslate = 0.0;
            if( mask & PAINT_SCREEN_TRANSFORMED)
                {
                xScale = gScreenData.xScale;
                yScale = gScreenData.yScale;
                xTranslate += ( xScale - 1.0 ) * windowRect.x() + gScreenData.xTranslate;
                yTranslate += ( yScale - 1.0 ) * windowRect.y() + gScreenData.yTranslate;
                }
            if( mask & PAINT_WINDOW_TRANSFORMED)
                {
                xTranslate += xScale * data.xTranslate;
                yTranslate += yScale * data.yTranslate;
                xScale *= data.xScale;
                yScale *= data.yScale;
                }
            QRect quadRect(
                window->x() + quad[0].x() * xScale + xTranslate,
                window->y() + quad[0].y() * yScale + yTranslate,
                ( quad[2].x() - quad[0].x() ) * xScale,
                ( quad[2].y() - quad[0].y() ) * yScale );

            // Work out which texture to use
            float opacity = shadowOpacity;
            if( intensifyActiveShadow && window == effects->activeWindow() )
                opacity = 1 - ( 1 - shadowOpacity ) * ( 1 - shadowOpacity );

            drawShadowQuadXRender( mDefaultShadowPics.at( quad.id() ), quadRect, xScale, yScale,
                shadowColor, opacity * data.opacity, data.brightness, data.saturation );
            }
        }
#endif
    }

} // namespace

