/*
 *  Copyright (C) 2003, Ian Reinhart Geiser <geiseri@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */

#include <kdebug.h>
#include <tdeglobal.h>
#include <kjsembed/jsopaqueproxy.h>
#include <kjsembed/jsbinding.h>
#include <kjsembed/jsfactory.h>
#include <kjsembed/jsfactory_imp.h>
#include <kjsembed/kjsembedpart.h>
#include <kjsembed/customobject_imp.h>
#include <tqvariant.h>
#include <tqbrush.h>

#include "imagefx_plugin.h"

namespace KJSEmbed {
namespace Bindings {


ImageFXLoader::ImageFXLoader( TQObject *parent, const char *name, const TQStringList &args ) :
	JSBindingPlugin(parent, name, args)
{
}

KJS::Object ImageFXLoader::createBinding(KJSEmbedPart */*jspart*/, KJS::ExecState *exec, const KJS::List &/*args*/) const
{
	kdDebug() << "Loading a ImageFX object" << endl;
	JSOpaqueProxy *prx = new JSOpaqueProxy(  (int*)0, "ImageFX" );

	KJS::Object proxyObj(prx);
	ImageFX::addBindings( exec, proxyObj );
	return proxyObj;
}

ImageFX::ImageFX( KJS::ExecState *exec, int id )
    : JSProxyImp(exec), mid(id)
{
}

ImageFX::~ImageFX()
{
}

void ImageFX::addBindings( KJS::ExecState *exec, KJS::Object &object ) {

    kdDebug() << "ImageFX::addBindings()" << endl;
    JSOpaqueProxy *op = JSProxy::toOpaqueProxy( object.imp() );
    if ( !op ) {
        kdWarning() << "ImageFX::addBindings() failed, not a JSOpaqueProxy" << endl;
        return;
    }

    if ( op->typeName() != "ImageFX" ) {
	kdWarning() << "ImageFX::addBindings() failed, type is " << op->typeName() << endl;
	return;
    }

    JSProxy::MethodTable methods[] = {
	{ Methodgradient, "gradient" },
	{ MethodunbalancedGradient, "unbalancedGradient" },
	{ MethodblendColor, "blendColor" },
	{ MethodblendImage, "blendImage" },
	{ MethodcomputeDestinationRect, "computeDestinationRect" },
	{ MethodchannelIntensity, "channelIntensity" },
	{ Methodfade, "fade" },
	{ Methodflatten, "flatten" },
	{ Methodhash, "hash" },
	{ Methodintensity, "intensity" },
	{ Methodmodulate, "modulate" },
	{ MethodtoGray, "toGray" },
	{ Methoddesaturate, "desaturate" },
	{ Methoddither, "dither" },
	{ MethodselectedImage, "selectedImage" },
	{ MethodcontrastHSV, "contrastHSV" },
	{ Methodnormalize, "normalize" },
	{ Methodequalize, "equalize" },
	{ Methodthreshold, "threshold" },
	{ Methodsolarize, "solarize" },
	{ Methodemboss, "emboss" },
	{ Methoddespeckle, "despeckle" },
	{ Methodcharcoal, "charcoal" },
	{ Methodcharcoal2, "charcoal2" },
	{ Methodrotate, "rotate" },
	{ Methodsample, "sample" },
	{ MethodaddNoise, "addNoise" },
	{ Methodblur, "blur" },
	{ Methodedge, "edge" },
	{ Methodimplode, "implode" },
	{ MethodoilPaintConvolve, "oilPaintConvolve" },
	{ MethodoilPaint, "MethodoilPaint" },
	{ Methodsharpen, "sharpen" },
	{ Methodsharpen2, "sharpen2" },
	{ Methodspread, "spread" },
	{ Methodshade, "shade" },
	{ Methodswirl, "swirl" },
	{ Methodwave, "wave" },
	{ Methodcontrast, "contrast" },
	{ MethodbumpMap, "bumpmap" },
	{ 0, 0 }
    };

    int idx = 0;
    do {
        ImageFX *meth = new ImageFX( exec, methods[idx].id );
        object.put( exec , methods[idx].name, KJS::Object(meth) );
        ++idx;
    } while( methods[idx].id );

    //
    // Define the enum constants
    //
    struct EnumValue {
	const char *id;
	int val;
    };

    EnumValue enums[] = {
	// GradiantType
	{ "VerticalGradient", 0 },
	{ "HorizontalGradient", 1 },
	{ "DiagonalGradient", 2 },
	{ "CrossDiagonalGradient", 3 },
	{ "PyramidGradient", 4 },
	{ "RectangleGradient", 5 },
	{ "PipeCrossGradient", 6 },
	{ "EllipticGradient", 7 },
	// RGBComponent
	{ "Red", 0 },
	{ "Green", 1 },
	{ "Blue", 2 },
	{ "Gray", 3 },
	{ "All", 4 },
        // Lighting
	{ "NorthLite", 0 },
	{ "NWLite", 1 },
	{ "WestLite", 2 },
	{ "SWLite", 3 },
	{ "SouthLite", 4 },
	{ "SELite", 5 },
	{ "EastLite", 6 },
	{ "NELite", 7 },
        // ModulationType
	{ "Intensity", 0 },
	{ "Saturation", 1 },
	{ "HueShift", 2 },
	{ "Contrast", 3 },
        // NoiseType
	{ "UniformNoise", 0 },
	{ "GaussianNoise", 1 },
	{ "MultiplicativeGaussianNoise", 2 },
	{ "ImpulseNoise", 3 },
	{ "LaplacianNoise", 4 },
	{ "PoissonNoise", 5 },
        // RotateDirection
	{ "Rotate90", 0 },
	{ "Rotate180", 1 },
	{ "Rotate270", 2 },
	// BumpmapType
	{ "Linear", 0},
	{ "Spherical", 1},
	{ "Sinuosidal", 2},
	{ 0, 0 }
    };

    int enumidx = 0;
    do {
        object.put( exec, enums[enumidx].id, KJS::Number(enums[enumidx].val), KJS::ReadOnly );
        ++enumidx;
    } while( enums[enumidx].id );
}

KJS::Value ImageFX::call( KJS::ExecState *exec, KJS::Object &self, const KJS::List &args ) {

    kdDebug() << "ImageFX::call() " << mid << endl;
    JSOpaqueProxy *op = JSProxy::toOpaqueProxy( self.imp() );
    if ( !op ) {
        kdWarning() << "ImageFX::call() failed, not a JSOpaqueProxy" << endl;
        return KJS::Value();
    }

    if ( op->typeName() != "ImageFX" ) {
	kdWarning() << "ImageFX::call() failed, type is " << op->typeName() << endl;
	return KJS::Value();
    }


    KJS::Value retValue = KJS::Value();
    switch ( mid ) {
    case Methodgradient: {
            TQSize size = extractTQSize(exec, args, 0);
            TQColor ca = extractTQColor(exec, args, 1);
            TQColor cb = extractTQColor(exec, args, 2);
            int type = extractInt( exec, args, 3);
            int ncols = extractInt( exec, args, 4);
	    TQImage img = KImageEffect::gradient(size, ca, cb, (KImageEffect::GradientType)type, ncols);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodunbalancedGradient: {
            TQSize size = extractTQSize(exec, args, 0);
            TQColor ca = extractTQColor(exec, args, 1);
            TQColor cb = extractTQColor(exec, args, 2);
            int type = extractInt( exec, args, 3);
            int xfactor = extractInt( exec, args, 4);
            int yfactor = extractInt( exec, args, 5);
            int ncols = extractInt( exec, args, 6);
            TQImage img = KImageEffect::unbalancedGradient(size, ca, cb, (KImageEffect::GradientType)type, xfactor, yfactor, ncols);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodblendColor: {
            TQColor clr = extractTQColor(exec, args, 0);
	    TQImage dst = extractTQImage(exec, args, 1);
            float opacity = (float)extractDouble(exec, args, 2);
	    TQImage img = KImageEffect::blend(clr, dst, opacity);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodblendImage: {
            TQImage src = extractTQImage(exec, args, 0);
	    TQImage dst = extractTQImage(exec, args, 1);
            float opacity = (float)extractDouble(exec, args, 2);
	    TQImage img = KImageEffect::blend(src, dst, opacity);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodcomputeDestinationRect: {
            TQSize lowerSize = extractTQSize(exec, args, 0);
            int disposition = extractInt(exec, args, 1);
	    TQImage upper = extractTQImage(exec, args, 2);
	    TQRect rect = KImageEffect::computeDestinationRect(lowerSize,  (KImageEffect::Disposition) disposition, upper);
            retValue = convertToValue(exec, rect);
	    break;
        }
    case MethodchannelIntensity: {
    	    TQImage image = extractTQImage(exec, args, 0);
            float percent = (float)extractDouble(exec, args, 1);
            int channel = extractInt(exec, args, 2);
	    TQImage img = KImageEffect::channelIntensity(image, percent, (KImageEffect::RGBComponent)channel);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodfade: {
            TQImage image = extractTQImage(exec, args, 0);
            float val = (float)extractDouble(exec, args, 1);
            TQColor color = extractTQColor(exec, args, 2);
	    TQImage img = KImageEffect::fade(image, val, color);
            retValue = convertToValue(exec, img);
            break;
        }
    case Methodflatten: {
            TQImage image = extractTQImage(exec, args, 0);
	    TQColor ca = extractTQColor(exec, args, 1);
            TQColor cb = extractTQColor(exec, args, 2);
            int ncols = extractInt(exec, args, 3);
	    TQImage img = KImageEffect::flatten(image, ca, cb, ncols);
            retValue = convertToValue(exec, img);
            break;
        }
    case Methodhash: {

            TQImage image = extractTQImage(exec, args, 0);
	    int lite  = extractInt(exec, args, 1);
            int spacing = extractInt(exec, args, 2);
	    TQImage img = KImageEffect::hash(image, (KImageEffect::Lighting)lite, spacing);
            retValue = convertToValue(exec, img);
            break;
        }
    case Methodintensity: {

	    TQImage image = extractTQImage(exec, args, 0);
	    float percent = (float)extractDouble(exec, args, 1);
            TQImage img = KImageEffect::intensity(image, percent);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodmodulate: {
            TQImage image = extractTQImage(exec, args, 0);
	    TQImage modImage = extractTQImage(exec, args, 0);
            bool reverse = extractBool(exec, args, 1);
            int type = extractInt(exec, args, 2);
            int factor = extractInt(exec, args, 3);
            int channel = extractInt(exec, args, 4);
	    TQImage img = KImageEffect::modulate(image, modImage, reverse, (KImageEffect::ModulationType)type, factor, (KImageEffect::RGBComponent)channel);
            retValue = convertToValue(exec, img);
            break;
        }

    case MethodtoGray: {
            TQImage image = extractTQImage(exec, args, 0);
	    bool fast = extractBool(exec, args, 1);
            TQImage img = KImageEffect::toGray(image, fast);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methoddesaturate: {
            TQImage image = extractTQImage(exec, args, 0);
	    float desat = (float)extractDouble(exec, args, 1);
            TQImage img = KImageEffect::desaturate(image, desat);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methoddither: {

            //dither( palette, size)
            break;
        }
    case MethodselectedImage: {

            TQImage image = extractTQImage(exec, args, 0);
	    TQColor col = extractTQColor(exec, args, 1);
            TQImage img = KImageEffect::selectedImage(image, col);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodcontrastHSV: {
	    TQImage image = extractTQImage(exec, args, 0);
	    bool sharpen = extractBool(exec, args, 1);
            KImageEffect::contrastHSV(image, sharpen);
	    retValue = convertToValue(exec, image);
            break;
        }
    case Methodnormalize: {
            TQImage image = extractTQImage(exec, args, 0);
            KImageEffect::normalize(image);
	    retValue = convertToValue(exec, image);
            break;
        }
    case Methodequalize: {
            TQImage image = extractTQImage(exec, args, 0);
            KImageEffect::equalize(image);
	    retValue = convertToValue(exec, image);
            break;
        }
    case Methodthreshold: {
            TQImage image = extractTQImage(exec, args, 0);
	    uint value = extractUInt(exec, args, 1);
            KImageEffect::threshold(image, value);
	    retValue = convertToValue(exec, image);
            break;
        }
    case Methodsolarize: {
            TQImage image = extractTQImage(exec, args, 0);
            double factor = extractDouble(exec, args, 1);
            KImageEffect::solarize(image, factor);
	    retValue = convertToValue(exec, image);
            break;
        }
    case Methodemboss: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            double sigma = extractDouble(exec, args, 2);
            TQImage img = KImageEffect::emboss(image, radius, sigma);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methoddespeckle: {
            TQImage image = extractTQImage(exec, args, 0);
	    TQImage img = KImageEffect::despeckle(image);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodcharcoal: {
            TQImage image = extractTQImage(exec, args, 0);
	    double factor = extractDouble(exec, args, 1);
            TQImage img = KImageEffect::charcoal( image, factor);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodcharcoal2: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            double sigma = extractDouble(exec, args, 2);
            TQImage img = KImageEffect::charcoal(image, radius, sigma);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodrotate: {
            TQImage image = extractTQImage(exec, args, 0);
	    int r = extractInt(exec, args, 1);
            TQImage img = KImageEffect::rotate(image, (KImageEffect::RotateDirection) r);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodsample: {
            TQImage image = extractTQImage(exec, args, 0);
            int width = extractInt(exec, args, 1);
            int height = extractInt(exec, args, 2);
            TQImage img = KImageEffect::sample(image, width, height);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodaddNoise: {
            TQImage image = extractTQImage(exec, args, 0);
            int type = extractInt(exec, args, 1);
            TQImage img = KImageEffect::addNoise(image, (KImageEffect::NoiseType) type);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodblur: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            double sigma = extractDouble(exec, args, 2);
            TQImage img = KImageEffect::blur(image, radius, sigma);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodedge: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            TQImage img = KImageEffect::edge(image, radius);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodimplode: {
            TQImage image = extractTQImage(exec, args, 0);
	    double factor = extractDouble(exec, args, 1);
            uint background = extractUInt(exec, args, 2);
            TQImage img = KImageEffect::implode(image, factor, background);
            retValue = convertToValue(exec, img);
	    break;
        }
    case MethodoilPaintConvolve: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            TQImage img = KImageEffect::oilPaintConvolve(image, radius);
	    retValue = convertToValue(exec, img);
            break;
        }
    case MethodoilPaint: {
            TQImage image = extractTQImage(exec, args, 0);
            int radius = extractInt(exec, args, 1);
            TQImage img = KImageEffect::oilPaint(image, radius);
	    retValue = convertToValue(exec, img);
            break;
        }


    case Methodsharpen: {
            TQImage image = extractTQImage(exec, args, 0);
	    double factor = extractDouble(exec, args, 1);
            TQImage img = KImageEffect::sharpen(image, factor);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodsharpen2: {
            TQImage image = extractTQImage(exec, args, 0);
	    double radius = extractDouble(exec, args, 1);
            double sigma = extractDouble(exec, args, 2);
            TQImage img = KImageEffect::sharpen(image, radius, sigma);
	    retValue = convertToValue(exec, img);
            break;
        }
    case Methodspread: {
            TQImage image = extractTQImage(exec, args, 0);
	    uint amount = extractUInt(exec, args, 1);
            TQImage img = KImageEffect::spread(image, amount);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodshade: {
            TQImage image = extractTQImage(exec, args, 0);
	    bool color_shading = extractBool(exec, args, 1);
            double azimuth = extractDouble(exec, args, 2);
            double elevation = extractDouble(exec, args, 3);
            TQImage img = KImageEffect::shade(image, color_shading, azimuth, elevation);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodswirl: {
            TQImage image = extractTQImage(exec, args, 0);
	    double degrees  = extractDouble(exec, args, 1);
            uint background = extractUInt(exec, args, 2);
            TQImage img = KImageEffect::swirl(image, degrees, background);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodwave: {
            TQImage image = extractTQImage(exec, args, 0);
	    double amplitude  = extractDouble(exec, args, 1);
            double frequency  = extractDouble(exec, args, 2);
            uint background = extractUInt(exec, args, 3);
            TQImage img = KImageEffect::wave(image, amplitude, frequency, background);
            retValue = convertToValue(exec, img);
	    break;
        }
    case Methodcontrast: {
            TQImage image = extractTQImage(exec, args, 0);
	    int c = extractInt(exec, args, 1);
            TQImage img = KImageEffect::contrast(image, c);
            retValue = convertToValue(exec, img);
	    break;
        }
    case MethodbumpMap: {
        TQImage mask = extractTQImage(exec, args, 0);
        TQImage img = bumpmap(img,
	                                        mask,
						extractDouble(exec, args, 1),
						extractDouble(exec, args, 2),
						extractInt(exec, args, 3),
						extractInt(exec, args, 4),
						extractInt(exec, args, 5),
						extractInt(exec, args, 6),
						extractInt(exec, args, 7),
						extractBool(exec, args, 8),
						extractBool(exec, args, 9),
						(BumpmapType) extractInt(exec, args, 10),
						extractBool(exec, args, 11));

	retValue = convertToValue(exec, img);
	break;
    }
    default:
        kdWarning() << "ImageFX has no method " << mid << endl;
        break;
    }

    return retValue;
}

/***********************************************************************
 * Here's a pretty fast bumpmap implementation.
 * NOTE: remind me to move it to KImageEffects after 3.2.
 */
#define DegreesToRadians(x) ((x)*M_PI/180.0)
#define MOD(x, y) ((x) < 0 ? ((y) - 1 - ((y) - 1 - (x)) % (y)) : (x) % (y))

/**
 * NOTE: kclamp needs to be moved to kglobals.h along kmin and kmax
 */
#define KCLAMP(x,low,high) kClamp(x,low,high)
template<class T>
inline const T& kClamp( const T& x, const T& low, const T& high )
{
    if ( x < low )
        return low;
    else if ( x > high )
        return high;
    else
        return x;
}

static inline unsigned int intensityValue( unsigned int color ) {
    return (unsigned int)( (0.299*tqRed( color ) +
                            0.587*tqGreen( color ) +
                            0.1140000000000001*tqBlue( color ) ) );
}

struct BumpmapParams {
    BumpmapParams( double bm_azimuth, double bm_elevation,
                   int bm_depth, BumpmapType bm_type,
                   bool invert ) {
         /* Convert to radians */
        double azimuth = DegreesToRadians( bm_azimuth );
        double elevation = DegreesToRadians( bm_elevation );

        /* Calculate the light vector */
        lx = (int)( cos(azimuth) * cos(elevation) * 255.0 );
        ly = (int)( sin(azimuth) * cos(elevation) * 255.0 );
        int lz         = (int)( sin(elevation) * 255.0 );

        /* Calculate constant Z component of surface normal */
        int nz  = (6 * 255) / bm_depth;
        nz2     = nz * nz;
        nzlz    = nz * lz;

        /* Optimize for vertical normals */
        background = lz;

        /* Calculate darkness compensation factor */
        compensation = sin(elevation);

        /* Create look-up table for map type */
        for (int i = 0; i < 256; i++)
        {
            double n = 0;
            switch (bm_type)
            {
            case Spherical:
                n = i / 255.0 - 1.0;
                lut[i] = (int) (255.0 * sqrt(1.0 - n * n) + 0.5);
                break;

            case Sinuosidal:
                n = i / 255.0;
                lut[i] = (int) (255.0 * (sin((-M_PI / 2.0) + M_PI * n) + 1.0) /
                                        2.0 + 0.5);
                break;

            case Linear:
            default:
                lut[i] = i;
            }

            if (invert)
                lut[i] = 255 - lut[i];
        }
    }
    int lx,  ly;
    int nz2, nzlz;
    int background;
    double compensation;
    uchar lut[256];
};


static void bumpmap_convert_row( uint *row,
                                 int    width,
                                 int    bpp,
                                 int    has_alpha,
                                 uchar *lut,
                                 int waterlevel )
{
  uint *p;

  p = row;

  has_alpha = has_alpha ? 1 : 0;

  if (bpp >= 3)
      for (; width; width--)
      {
          if (has_alpha) {
              unsigned int idx = (unsigned int)(intensityValue( *row ) + 0.5);
              *p++ = lut[(unsigned int) ( waterlevel +
                                          ( ( idx -
                                              waterlevel) * tqBlue( *row )) / 255.0 )];
          } else {
              unsigned int idx = (unsigned int)(intensityValue( *row ) + 0.5);
              *p++ = lut[idx];
          }

          ++row;
      }
}

static void bumpmap_row( uint           *src,
                         uint           *dest,
                         int              width,
                         int              bpp,
                         int              has_alpha,
                         uint           *bm_row1,
                         uint           *bm_row2,
                         uint           *bm_row3,
                         int              bm_width,
                         int              bm_xofs,
                         bool          tiled,
                         bool          row_in_bumpmap,
                         int           ambient,
                         bool          compensate,
                         BumpmapParams *params )
{
    int xofs1, xofs2, xofs3;
    int shade;
    int ndotl;
    int nx, ny;
    int x;
    int pbpp;
    int tmp;

    if (has_alpha)
        pbpp = bpp - 1;
    else
        pbpp = bpp;

    tmp = bm_xofs;
    xofs2 = MOD(tmp, bm_width);

    for (x = 0; x < width; x++)
    {
        /* Calculate surface normal from bump map */

        if (tiled || (row_in_bumpmap &&
                      x >= - tmp && x < - tmp + bm_width)) {
            if (tiled) {
                xofs1 = MOD(xofs2 - 1, bm_width);
                xofs3 = MOD(xofs2 + 1, bm_width);
	    } else {
                xofs1 = KCLAMP(xofs2 - 1, 0, bm_width - 1);
                xofs3 = KCLAMP(xofs2 + 1, 0, bm_width - 1);
	    }
            nx = (bm_row1[xofs1] + bm_row2[xofs1] + bm_row3[xofs1] -
                  bm_row1[xofs3] - bm_row2[xofs3] - bm_row3[xofs3]);
            ny = (bm_row3[xofs1] + bm_row3[xofs2] + bm_row3[xofs3] -
                  bm_row1[xofs1] - bm_row1[xofs2] - bm_row1[xofs3]);
	} else {
            nx = ny = 0;
        }

      /* Shade */

        if ((nx == 0) && (ny == 0))
            shade = params->background;
        else {
            ndotl = nx * params->lx + ny * params->ly + params->nzlz;

            if (ndotl < 0)
                shade = (int)( params->compensation * ambient );
            else {
                shade = (int)( ndotl / sqrt(nx * nx + ny * ny + params->nz2) );

                shade = (int)( shade + KMAX(0.0, (255 * params->compensation - shade)) *
                               ambient / 255 );
	    }
	}

        /* Paint */

        /**
         * NOTE: if we want to work with non-32bit images the alpha handling would
         * also change
         */
        if (compensate) {
            int red = (int)((tqRed( *src ) * shade) / (params->compensation * 255));
            int green = (int)((tqGreen( *src ) * shade) / (params->compensation * 255));
            int blue = (int)((tqBlue( *src ) * shade) / (params->compensation * 255));
            int alpha = (int)((tqAlpha( *src ) * shade) / (params->compensation * 255));
            ++src;
            *dest++ = tqRgba( red, green, blue, alpha );
        } else {
            int red = tqRed( *src ) * shade / 255;
            int green = tqGreen( *src ) * shade / 255;
            int blue = tqBlue( *src ) * shade / 255;
            int alpha = tqAlpha( *src ) * shade / 255;
            ++src;
            *dest++ = tqRgba( red, green, blue, alpha );
        }

        /* Next pixel */

        if (++xofs2 == bm_width)
            xofs2 = 0;
    }
}

/**
 * A bumpmapping algorithm.
 *
 * @param img the image you want bumpmap
 * @param map the map used
 * @param azimuth azimuth
 * @param elevation elevation
 * @param depth depth (not the depth of the image, but of the map)
 * @param xofs X offset
 * @param yofs Y offset
 * @param waterlevel level that full transparency should represent
 * @param ambient ambient lighting factor
 * @param compensate compensate for darkening
 * @param invert invert bumpmap
 * @param type type of the bumpmap
 *
 * @return The destination image (dst) containing the result.
 * @author Zack Rusin <zack@kde.org>
 */
TQImage ImageFX::bumpmap(TQImage &img, TQImage &map, double azimuth, double elevation,
               int depth, int xofs, int yofs, int waterlevel,
               int ambient, bool compensate, bool invert,
               BumpmapType type, bool tiled)
{
    TQImage dst;

    if ( img.depth() != 32 || img.depth() != 32 ) {
        tqWarning( "Bump-mapping effect works only with 32 bit images");
        return dst;
    }

    dst.create( img.width(), img.height(), img.depth() );
    int bm_width  = map.width();
    int bm_height = map.height();
    int bm_bpp = map.depth();
    int bm_has_alpha = map.hasAlphaBuffer();

    int yofs1, yofs2, yofs3;

    if ( tiled ) {
        yofs2 = MOD( yofs, bm_height );
        yofs1 = MOD( yofs2 - 1, bm_height);
        yofs3 = MOD( yofs2 + 1, bm_height);
    } else {
        yofs1 = 0;
        yofs2 = 0;
        yofs3 = KCLAMP( yofs2+1, 0, bm_height - 1 );
    }

    BumpmapParams params( azimuth, elevation, depth, type, invert );

    uint* bm_row1 = (unsigned int*)map.scanLine( yofs1 );
    uint* bm_row2 = (unsigned int*)map.scanLine( yofs2 );
    uint* bm_row3 = (unsigned int*)map.scanLine( yofs3 );

    bumpmap_convert_row( bm_row1, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel );
    bumpmap_convert_row( bm_row2, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel );
    bumpmap_convert_row( bm_row3, bm_width, bm_bpp, bm_has_alpha, params.lut, waterlevel );

    for (int y = 0; y < img.height(); ++y)
    {
        int row_in_bumpmap = (y >= - yofs && y < - yofs + bm_height);

        uint* src_row = (unsigned int*)img.scanLine( y );
        uint* dest_row = (unsigned int*)dst.scanLine( y );

        bumpmap_row( src_row, dest_row, img.width(), img.depth(), img.hasAlphaBuffer(),
                     bm_row1, bm_row2, bm_row3, bm_width, xofs,
                     tiled,
                     row_in_bumpmap, ambient, compensate,
                     &params );

        /* Next line */

        if (tiled || row_in_bumpmap)
	{
            uint* bm_tmprow = bm_row1;
            bm_row1   = bm_row2;
            bm_row2   = bm_row3;
            bm_row3   = bm_tmprow;

            if (++yofs2 == bm_height)
                yofs2 = 0;

            if (tiled)
                yofs3 = MOD(yofs2 + 1, bm_height);
            else
                yofs3 = KCLAMP(yofs2 + 1, 0, bm_height - 1);

            bm_row3 = (unsigned int*)map.scanLine( yofs3 );
            bumpmap_convert_row( bm_row3, bm_width, bm_bpp, bm_has_alpha,
                                 params.lut, waterlevel );
	}
    }
    return dst;
}


} // namespace KJSEmbed::Bindings
} // namespace KJSEmbed

#include <kgenericfactory.h>
typedef KGenericFactory<KJSEmbed::Bindings::ImageFXLoader> ImageFXLoaderFactory;
K_EXPORT_COMPONENT_FACTORY( libimagefxplugin, ImageFXLoaderFactory( "ImageFXLoader" ) )
