#include "kuickimage.h"

KuickImage::KuickImage( const KuickFile * file, ImlibImage *im, ImlibData *id)
    : TQObject( 0L, 0L )
{
    myFile       = file;
    myId         = id;
    myPixmap     = 0L;
    myWidth      = im->rgb_width;
    myHeight     = im->rgb_height;

    myOrigWidth  = myWidth;
    myOrigHeight = myHeight;
    myIsDirty    = true;
    myIm.im      = im;
    mySavedIm.im = nullptr;
}

KuickImage::~KuickImage()
{
    if ( myPixmap )
    {
	Imlib_free_pixmap( myId, myPixmap );
	myPixmap = 0L;
    }

    if ( mySavedIm.im )
    {
	// In this case, the original image loaded from file is in 'mySavedIm.im' while
	// 'myIm.im' contains a copy made out of it. There is no point in keeping
	// 'myIm.im' in the imlib cache, so use 'Imlib_kill_image' for it
	Imlib_destroy_image( myId, mySavedIm.im );
	Imlib_kill_image( myId, myIm.im );
    }
    else
    {
	// In this case, the original image loaded from file is in 'myIm.im'.
	// Keep in the internal 'imlib' cache in case we need to reload it.
	Imlib_destroy_image( myId, myIm.im );
    }
}

Pixmap& KuickImage::pixmap()
{
    if ( myIsDirty )
	renderPixmap();

    return myPixmap;
}

void KuickImage::renderPixmap()
{
    if ( !myIsDirty )
	return;

//     tqDebug("### rendering: %s", myFilename.latin1());

    if ( myPixmap )
    {
	Imlib_free_pixmap( myId, myPixmap );
	myPixmap = 0L;
    }

    emit startRendering();

// #ifndef NDEBUG
//     struct timeval tms1, tms2;
//     gettimeofday( &tms1, NULL );
// #endif

    Imlib_render( myId, myIm.im, myWidth, myHeight );
    myPixmap = Imlib_move_image( myId, myIm.im );

// #ifndef NDEBUG
//     gettimeofday( &tms2, NULL );
//     tqDebug("*** rendering image: %s, took %ld ms", myFilename.latin1(),
//            (tms2.tv_usec - tms1.tv_usec)/1000);
// #endif

    emit stoppedRendering();

    myIsDirty = false;
}

void KuickImage::flipRotateImage(ImOrientedImage *image, FlipRotation fr) {
    // Note: 'Imlib_rotate_image' does not rotate an image but actually
    //       transpose it against the top-left/bottom-right diagonal.
    if (!fr.flip) {
	switch (fr.rot) {
	case ROT_0:
	    break;
	case ROT_90:
	    Imlib_rotate_image(myId, image->im, 0);
	    Imlib_flip_image_horizontal(myId, image->im);
	    break;
	case ROT_180:
	    Imlib_flip_image_horizontal(myId, image->im);
	    Imlib_flip_image_vertical(myId, image->im);
	    break;
	case ROT_270:
	    Imlib_rotate_image(myId, image->im, 0);
	    Imlib_flip_image_vertical(myId, image->im);
	    break;
	}
    } else {
	switch (fr.rot) {
	case ROT_0:
	    Imlib_flip_image_horizontal(myId, image->im);
	    break;
	case ROT_90:
	    Imlib_rotate_image(myId, image->im, 0);
	    Imlib_flip_image_horizontal(myId, image->im);
	    Imlib_flip_image_vertical(myId, image->im);
	    break;
	case ROT_180:
	    Imlib_flip_image_vertical(myId, image->im);
	    break;
	case ROT_270:
	    Imlib_rotate_image(myId, image->im, 0);
	    break;
	}
    }

    image->orient *= fr;
}

void KuickImage::flipRotate( FlipRotation fr ) {
    flipRotateImage(&myIm, fr);
    if (fr.rot == ROT_90 || fr.rot == ROT_270)
    {
	tqSwap(myWidth, myHeight);
    }
    myIsDirty = true;
}

bool KuickImage::flipRotateAbs(FlipRotation fr)
{
    FlipRotation diff = myIm.orient.transitionTo(fr);
    if(diff.isNoop()) {
	return false;
    }

    flipRotate(diff);
    return true;
}

void KuickImage::restoreOriginalSize()
{
    restoreSavedImage();
    myWidth = myOrigWidth;
    myHeight = myOrigHeight;
    if (myIm.orient.rot == ROT_90 || myIm.orient.rot == ROT_270)
    {
	tqSwap(myWidth, myHeight);
    }
    myIsDirty = true;
}

void KuickImage::resize( int width, int height, KuickImage::ResizeMode mode )
{
    if ( myWidth == width && myHeight == height )
    {
	return;
    }
    if ( mode == KuickImage::SMOOTH && smoothResize( width, height ) )
    {
	return;
    }
    fastResize( width, height );
}


void KuickImage::fastResize( int width, int height )
{
    // tqDebug("-- fastResize: %i x %i", width, height );

    // lazy resizing (only done when rendering pixmap)
    myWidth   = width;
    myHeight  = height;
    myIsDirty = true;
}

bool KuickImage::smoothResize( int newWidth, int newHeight )
{
    // Save original image to enhance quality of multiple smooth resizing
    saveOrigImage();
    // Orient original image to match current image state
    orientSavedImage();

    TQImage *image = getNewTQImage();
    // Note: TQImage::ScaleMin seems to have a bug (off-by-one, sometimes results in width being 1 pixel too small)
    TQImage scaledImage = image->smoothScale(newWidth, newHeight, TQImage::ScaleFree);
    delete image;

    ImlibImage *newIm = toImage(myId, scaledImage);
    if (newIm)
    {
	myIm.im = newIm;
	myWidth = newWidth;
	myHeight = newHeight;
	myIsDirty = true;
	return true;
    }

    // If the resize operation failes, restore the original image
    // to make sure we still show something on the screen
    restoreSavedImage();
    return false;
}

// Store a reference to 'myIm.im' in 'mySavedIm.im' before modifying it, so we
// can keep using the original-resolution pixels in smoothResize().
void KuickImage::saveOrigImage()
{
    if (mySavedIm.im)
    {
	return;
    }
    mySavedIm = myIm;
    myIm.im = nullptr;
}

void KuickImage::restoreSavedImage()
{
    if (!mySavedIm.im)
    {
	return;
    }
    orientSavedImage();
    // 'myIm.im' contains a copy made out of 'mySavedIm.im', there is no point in keeping
    // 'myIm.im' in the imlib cache, so use 'Imlib_kill_image' for it
    Imlib_kill_image(myId, myIm.im);
    myIm = mySavedIm;
    mySavedIm.im = nullptr;
}

// Rotate/flip 'mySavedIm' to match the current orientation of 'myIm'
void KuickImage::orientSavedImage()
{
    if (!mySavedIm.im)
    {
	return;
    }
    flipRotateImage(&mySavedIm, mySavedIm.orient.transitionTo(myIm.orient));
}

TQImage* KuickImage::getNewTQImage() const
{
    ImlibImage *im = mySavedIm.im;
    int w = im->rgb_width;
    int h = im->rgb_height;

    TQImage *image = new TQImage(w, h, 32);
    uchar *rgb = im->rgb_data;
    TQRgb **destImageData = reinterpret_cast<TQRgb**>(image->jumpTable());

    int byteIndex = 0;
    int destLineIndex = 0;
    int destByteIndex = 0;
    for (int pixel = 0; pixel < (w * h); pixel++)
    {
	if (pixel != 0 && (pixel % w) == 0)
	{
	    destLineIndex++;
	    destByteIndex = 0;
	}

	uchar r = rgb[byteIndex++];
	uchar g = rgb[byteIndex++];
	uchar b = rgb[byteIndex++];
	destImageData[destLineIndex][destByteIndex++] = tqRgb(r, g, b);
    }

    return image;
}

ImlibImage * KuickImage::toImage( ImlibData *id, TQImage& image )
{
    if ( image.isNull() )
	return 0L;

    if ( image.depth() != 32 )
    {
	image.setAlphaBuffer(false);
	image = image.convertDepth(32);

	if ( image.isNull() )
	    return 0L;
    }

    // convert to 24 bpp (discard alpha)
    int numPixels = image.width() * image.height();
    const int NUM_BYTES_NEW  = 3; // 24 bpp
    uchar *newImageData = new uchar[numPixels * NUM_BYTES_NEW];
    uchar *newData = newImageData;

    int w = image.width();
    int h = image.height();

    for (int y = 0; y < h; y++)
    {
	TQRgb *scanLine = reinterpret_cast<TQRgb *>( image.scanLine(y) );
	for (int x = 0; x < w; x++)
	{
	    const TQRgb& pixel = scanLine[x];
	    *(newData++) = tqRed(pixel);
	    *(newData++) = tqGreen(pixel);
	    *(newData++) = tqBlue(pixel);
	}
    }

    ImlibImage *im = Imlib_create_image_from_data( id, newImageData, NULL,
                                                   image.width(), image.height() );

    delete [] newImageData;

    return im;
}

#include "kuickimage.moc"
