/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbCore module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbvghsleffect_p.h"
#include "hbvghsleffect_p_p.h"
#include <QPainter>
#include <qmath.h>

/*!
 * \class HbVgHslEffect
 *
 * \brief OpenVG-based hue-saturation-lightness filter effect.
 *
 * \internal
 */

HbVgHslEffectPrivate::HbVgHslEffectPrivate()
    : hue(0), saturation(1), lightness(0)
{
}

HbVgHslEffect::HbVgHslEffect(QObject *parent)
    : HbVgEffect(*new HbVgHslEffectPrivate, parent)
{
}

HbVgHslEffect::HbVgHslEffect(HbVgHslEffectPrivate &dd, QObject *parent)
    : HbVgEffect(dd, parent)
{
}

HbVgHslEffect::~HbVgHslEffect()
{
}

qreal HbVgHslEffect::hue() const
{
    Q_D(const HbVgHslEffect);
    return d->hue;
}

void HbVgHslEffect::setHue(qreal hue)
{
    Q_D(HbVgHslEffect);
    if (d->hue == hue) {
        return;
    }
    d->hue = hue;
    updateEffect();
    emit hueChanged(hue);
}

qreal HbVgHslEffect::saturation() const
{
    Q_D(const HbVgHslEffect);
    return d->saturation;
}

void HbVgHslEffect::setSaturation(qreal saturation)
{
    Q_D(HbVgHslEffect);
    if (d->saturation == saturation) {
        return;
    }
    d->saturation = saturation;
    updateEffect();
    emit saturationChanged(saturation);
}

qreal HbVgHslEffect::lightness() const
{
    Q_D(const HbVgHslEffect);
    return d->lightness;
}

void HbVgHslEffect::setLightness(qreal lightness)
{
    Q_D(HbVgHslEffect);
    if (d->lightness == lightness) {
        return;
    }
    d->lightness = lightness;
    updateEffect();
    emit lightnessChanged(lightness);
}

void HbVgHslEffect::desaturate()
{
    setHue(0);
    setSaturation(0);
    setLightness(0);
}

QRectF HbVgHslEffect::boundingRectFor(const QRectF &rect) const
{
    return rect;
}

#ifdef HB_EFFECTS_OPENVG

inline void getSaturationRotationMatrix(VGfloat *effectMatrix, VGfloat opacity,
                                        VGfloat saturation, VGfloat angle)
{
    const VGfloat sa = saturation;
    const VGfloat as = 1.0f - saturation;

    const VGfloat o = opacity;
    const VGfloat ao = 1.0f - o;

    const VGfloat c = qCos(angle);
    const VGfloat s = qSin(angle);

    effectMatrix[0] = o * ((-0.02473f * as + 0.66667f * sa) * c + (0.30450f * as * s + (0.33333f * as + 0.33333f * sa))) + ao;
    effectMatrix[1] = o * ((-0.02473f * as - 0.33333f * sa) * c + ((0.30450f * as + 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[2] = o * ((-0.02473f * as - 0.33333f * sa) * c + ((0.30450f * as - 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[3] = 0.0f;
    effectMatrix[4] = o * ((0.27607f * as - 0.33333f * sa) * c + ((-0.13083f * as - 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[5] = o * ((0.27607f * as + 0.66667f * sa) * c + (-0.13083f * as * s + (0.33333f * as + 0.33333f * sa))) + ao;
    effectMatrix[6] = o * ((0.27607f * as - 0.33333f * sa) * c + ((-0.13083f * as + 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[7] = 0.0f;
    effectMatrix[8] = o * ((-0.25134f * as - 0.33333f * sa) * c + ((-0.17367f * as + 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[9] = o * ((-0.25134f * as - 0.33333f * sa) * c + ((-0.17367f * as - 0.57736f * sa) * s + (0.33333f * as + 0.33333f * sa)));
    effectMatrix[10] = o * ((-0.25134f * as + 0.66667f * sa) * c + (-0.17367f * as * s + (0.33333f * as + 0.33333f * sa))) + ao;
    effectMatrix[11] = 0.0f;
    effectMatrix[12] = 0.0f;
    effectMatrix[13] = 0.0f;
    effectMatrix[14] = 0.0f;
    effectMatrix[15] = 1.0f;
}

const VGfloat Rw = 0.3086f;
const VGfloat Gw = 0.6094f;
const VGfloat Bw = 0.0820f;

inline void getSaturationMatrix(VGfloat *effectMatrix, VGfloat opacity, VGfloat saturation)
{
    const VGfloat sa = saturation;
    const VGfloat as = 1.0f - saturation;

    const VGfloat o = opacity;
    const VGfloat ao = 1.0f - o;

    const VGfloat asRw = o * as * Rw;
    const VGfloat asGw = o * as * Gw;
    const VGfloat asBw = o * as * Bw;

    effectMatrix[0] = asRw + sa + ao;
    effectMatrix[1] = asRw;
    effectMatrix[2] = asRw;
    effectMatrix[3] = 0.0f;
    effectMatrix[4] = asGw;
    effectMatrix[5] = asGw + sa + ao;
    effectMatrix[6] = asGw;
    effectMatrix[7] = 0.0f;
    effectMatrix[8] = asBw;
    effectMatrix[9] = asBw;
    effectMatrix[10] = asBw + sa + ao;
    effectMatrix[11] = 0.0f;
    effectMatrix[12] = 0.0f;
    effectMatrix[13] = 0.0f;
    effectMatrix[14] = 0.0f;
    effectMatrix[15] = 1.0f;
}

inline void getRotationMatrix(VGfloat *effectMatrix, VGfloat opacity, VGfloat angle)
{
    const VGfloat o = opacity;
    const VGfloat ao = 1.0f - o;

    const VGfloat c = qCos(angle);
    const VGfloat s = qSin(angle);

    effectMatrix[0] = o * (0.66667f * c + 0.33333f) + ao;
    effectMatrix[1] = o * (-0.33333f * c + (0.57736f * s + 0.33333f));
    effectMatrix[2] = o * (-0.33333f * c + (-0.57736f * s + 0.33333f));
    effectMatrix[3] =  0.0f;
    effectMatrix[4] = o * (-0.33333f * c + (-0.57736f * s + 0.33333f));
    effectMatrix[5] = o * (0.66667f * c + 0.33333f) + ao;
    effectMatrix[6] = o * (-0.33333f * c + (0.57736f * s + 0.33333f));
    effectMatrix[7] =  0.0f;
    effectMatrix[8] = o * (-0.33333f * c + (0.57736f * s + 0.33333f));
    effectMatrix[9] = o * (-0.33333f * c + (-0.57736f * s + 0.33333f));
    effectMatrix[10] = o * (0.66667f * c + 0.33333f) + ao;
    effectMatrix[11] = 0.0f;
    effectMatrix[12] = 0.0f;
    effectMatrix[13] = 0.0f;
    effectMatrix[14] = 0.0f;
    effectMatrix[15] = 1.0f;
}

inline void getIdentityMatrix(VGfloat *effectMatrix)
{
    effectMatrix[0] = 1.0f;
    effectMatrix[1] = 0.0f;
    effectMatrix[2] = 0.0f;
    effectMatrix[3] = 0.0f;
    effectMatrix[4] = 0.0f;
    effectMatrix[5] = 1.0f;
    effectMatrix[6] = 0.0f;
    effectMatrix[7] = 0.0f;
    effectMatrix[8] = 0.0f;
    effectMatrix[9] = 0.0f;
    effectMatrix[10] = 1.0f;
    effectMatrix[11] = 0.0f;
    effectMatrix[12] = 0.0f;
    effectMatrix[13] = 0.0f;
    effectMatrix[14] = 0.0f;
    effectMatrix[15] = 1.0f;
}

#endif // HB_EFFECTS_OPENVG

void HbVgHslEffect::performEffect(QPainter *painter,
                                  const QPointF &offset,
                                  const QVariant &vgImage,
                                  const QSize &vgImageSize)
{
#ifdef HB_EFFECTS_OPENVG
    QPixmap cachedPm = cached(vgImageSize);
    if (!cachedPm.isNull()) {
        painter->drawPixmap(offset, cachedPm);
        return;
    }

    Q_D(HbVgHslEffect);
    VGImage srcImage = vgImage.value<VGImage>();
    VGImage dstImage = d->ensurePixmap(&d->dstPixmap, vgImageSize);
    qreal opacity = clamp(d->opacity, 0.0f, 1.0f);
    if (opacity > HBVG_EPSILON) {
        if (d->paramsChanged) {
            // a helpful constant
            const qreal radsPerDeg = 2.0f * (qreal) M_PI / 360.0f;

            // make sure parameters are in range
            const VGfloat o = (VGfloat) opacity;
            const VGfloat angle = (VGfloat) clamp(d->hue * radsPerDeg, 0.0f, 2.0f * (qreal) M_PI); // angle [0, 2*pi]
            const VGfloat saturation = (VGfloat) clamp(d->saturation, 0.0f, 100.0f); // saturation [0, N]
            const VGfloat lightness = (VGfloat) clamp(d->lightness, -1.0f, 1.0f); // lightness [-1, 1]

            // check parameters which precalculated matrix we have to use.
            // Note: lightness affects offset and not matrix so we don't bother optimising that.
            const bool enableSaturation  = (saturation < 1.0f - HBVG_EPSILON || saturation > 1.0f + HBVG_EPSILON);
            const bool enableHueRotation = (HBVG_EPSILON < angle && angle < (2.0f * (qreal) M_PI - HBVG_EPSILON));

            if (enableSaturation && enableHueRotation) {
                // contains SaturateT*PrerotationT*HuerotationT*PostrotationT*I*opacity+I*(1-opacity) matrices
                // --- ugly, but saves lot of operations in FPU.
                // note: there are plenty of redundancy in these calculations
                // --- let compiler optimize them.
                getSaturationRotationMatrix(&d->colorMatrix[0], o, saturation, angle);
            } else if (enableSaturation && !enableHueRotation) {
                // saturationT*I*opacity+I*(1 - opacity) matrix without hue rotation
                getSaturationMatrix(&d->colorMatrix[0], o, saturation);
            } else if (!enableSaturation && enableHueRotation) {
                // PrerotationT*HuerotationT*PostrotationT*I*opacity+I*(1-opacity) matrices without saturation matrix
                getRotationMatrix(&d->colorMatrix[0], o, angle);
            } else {
                // identity matrix
                getIdentityMatrix(&d->colorMatrix[0]);
            }

            // colour component offsets
            d->colorMatrix[16] = lightness * o;
            d->colorMatrix[17] = lightness * o;
            d->colorMatrix[18] = lightness * o;
            d->colorMatrix[19] = 0.0f;
        }
        vgColorMatrix(dstImage, srcImage, d->colorMatrix);
        painter->drawPixmap(offset, d->dstPixmap);
        tryCache(d->dstPixmap);
    } else {
        painter->drawPixmap(offset, d->srcPixmap);
    }
#else
    Q_UNUSED(painter);
    Q_UNUSED(offset);
    Q_UNUSED(vgImage);
    Q_UNUSED(vgImageSize);
#endif
}
