/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2005-06-14
 * Description : digiKam 8/16 bits image management API
 *
 * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> 
 * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
 *
 * 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, 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.
 * 
 * ============================================================ */

// C ANSI includes.

extern "C"
{
#if !defined(__STDC_LIMIT_MACROS)
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
}

// C++ includes.

#include <cstdio>

// TQt includes.

#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqmap.h>

// LibKDcraw includes.

#include <libkdcraw/version.h>
#include <libkdcraw/kdcraw.h>

#if KDCRAW_VERSION < 0x000106
#include <libkdcraw/dcrawbinary.h>
#endif

// Local includes.

#include "pngloader.h"
#include "jpegloader.h"
#include "tiffloader.h"
#include "ppmloader.h"
#include "rawloader.h"
#include "jp2kloader.h"
#include "qimageloader.h"
#include "icctransform.h"
#include "exposurecontainer.h"
#include "ddebug.h"
#include "dimgprivate.h"
#include "dimgloaderobserver.h"
#include "dimg.h"

typedef uint64_t ullong;
typedef int64_t  llong;

namespace Digikam
{

DImg::DImg()
    : m_priv(new DImgPrivate)
{
}

DImg::DImg(const TQCString& filePath, DImgLoaderObserver *observer,
           DRawDecoding rawDecodingSettings)
    : m_priv(new DImgPrivate)
{
    load(filePath, observer, rawDecodingSettings);
}

DImg::DImg(const TQString& filePath, DImgLoaderObserver *observer,
           DRawDecoding rawDecodingSettings)
    : m_priv(new DImgPrivate)
{
    load(filePath, observer, rawDecodingSettings);
}

DImg::DImg(const DImg& image)
{
    m_priv = image.m_priv;
    m_priv->ref();
}

DImg::DImg(uint width, uint height, bool sixteenBit, bool alpha, uchar* data, bool copyData)
    : m_priv(new DImgPrivate)
{
    putImageData(width, height, sixteenBit, alpha, data, copyData);
}

DImg::DImg(const DImg &image, int w, int h)
    : m_priv(new DImgPrivate)
{
    // This private constructor creates a copy of everything except the data.
    // The image size is set to the given values and a buffer corresponding to these values is allocated.
    // This is used by copy and scale.
    copyImageData(image.m_priv);
    copyMetaData(image.m_priv);
    setImageDimension(w, h);
    allocateData();
}

DImg::DImg(const TQImage& image)
    : m_priv(new DImgPrivate)
{
    if (!image.isNull())
    {
        TQImage target = image.convertDepth(32);
        
        uint w      = target.width();
        uint h      = target.height();
        uchar* data = new uchar[w*h*4];
        uint*  sptr = (uint*)target.bits();
        uchar* dptr = data;
        
        for (uint i = 0 ; i < w*h ; i++)
        {
            dptr[0] = tqBlue(*sptr);
            dptr[1] = tqGreen(*sptr);
            dptr[2] = tqRed(*sptr);
            dptr[3] = tqAlpha(*sptr);
            
            dptr += 4;
            sptr++;
        }
    
        putImageData(w, h, false, image.hasAlphaBuffer(), data, false);
    }
}

DImg::~DImg()
{
    if (m_priv->deref())
        delete m_priv;
}


//---------------------------------------------------------------------------------------------------
// data management


DImg& DImg::operator=(const DImg& image)
{
    if (m_priv == image.m_priv)
        return *this;

    if (m_priv->deref())
    {
        delete m_priv;
        m_priv = 0;
    }

    m_priv = image.m_priv;
    m_priv->ref();

    return *this;
}

bool DImg::operator==(const DImg& image) const
{
    return m_priv == image.m_priv;
}

void DImg::reset(void)
{
    if (m_priv->deref())
        delete m_priv;

    m_priv = new DImgPrivate;
}

void DImg::detach()
{
    // are we being shared?
    if (m_priv->count <= 1)
    {
        return;
    }

    DImgPrivate* old = m_priv;

    m_priv = new DImgPrivate;
    copyImageData(old);
    copyMetaData(old);

    if (old->data)
    {
        int size = allocateData();
        memcpy(m_priv->data, old->data, size);
    }

    old->deref();
}

void DImg::putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar *data, bool copyData)
{
    // set image data, metadata is untouched

    bool null = (width == 0) || (height == 0);
    // allocateData, or code below will set null to false
    setImageData(true, width, height, sixteenBit, alpha);

    // replace data
    delete [] m_priv->data;
    if (null)
    {
        // image is null - no data
        m_priv->data = 0;
    }
    else if (copyData)
    {
        int size = allocateData();
        if (data)
            memcpy(m_priv->data, data, size);
    }
    else
    {
        if (data)
        {
            m_priv->data = data;
            m_priv->null = false;
        }
        else
            allocateData();
    }
}

void DImg::putImageData(uchar *data, bool copyData)
{
    if (!data)
    {
        delete [] m_priv->data;
        m_priv->data = 0;
        m_priv->null = true;
    }
    else if (copyData)
    {
        memcpy(m_priv->data, data, numBytes());
    }
    else
    {
        m_priv->data = data;
    }
}

void DImg::resetMetaData()
{
    m_priv->attributes.clear();
    m_priv->embeddedText.clear();
    m_priv->metaData.clear();
}

uchar *DImg::stripImageData()
{
    uchar *data  = m_priv->data;
    m_priv->data = 0;
    m_priv->null = true;
    return data;
}

void DImg::copyMetaData(const DImgPrivate *src)
{
    m_priv->isReadOnly   = src->isReadOnly;
    m_priv->attributes   = src->attributes;
    m_priv->embeddedText = src->embeddedText;

    // since qbytearrays are explicitly shared, we need to make sure that they are
    // detached from any shared references

    for (TQMap<int, TQByteArray>::const_iterator it = src->metaData.begin();
         it != src->metaData.end(); ++it)
    {
        m_priv->metaData.insert(it.key(), it.data().copy());
    }
}

void DImg::copyImageData(const DImgPrivate *src)
{
    setImageData(src->null, src->width, src->height, src->sixteenBit, src->alpha);
}

int DImg::allocateData()
{
    int size = m_priv->width * m_priv->height * (m_priv->sixteenBit ? 8 : 4);
    m_priv->data = new uchar[size];
    m_priv->null = false;
    return size;
}

void DImg::setImageDimension(uint width, uint height)
{
    m_priv->width  = width;
    m_priv->height = height;
}

void DImg::setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha)
{
    m_priv->null       = null;
    m_priv->width      = width;
    m_priv->height     = height;
    m_priv->alpha      = alpha;
    m_priv->sixteenBit = sixteenBit;
}


//---------------------------------------------------------------------------------------------------
// load and save


bool DImg::load(const TQString& filePath, DImgLoaderObserver *observer,
                DRawDecoding rawDecodingSettings)
{
    FORMAT format = fileFormat(filePath);

    switch (format)
    {
        case(NONE):
        {
            DDebug() << filePath << " : Unknown image format !!!" << endl;
            return false;
            break;
        }
        case(JPEG):
        {
            DDebug() << filePath << " : JPEG file identified" << endl;
            JPEGLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
        case(TIFF):
        {
            DDebug() << filePath << " : TIFF file identified" << endl;
            TIFFLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
        case(PNG):
        {
            DDebug() << filePath << " : PNG file identified" << endl;
            PNGLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
        case(PPM):
        {
            DDebug() << filePath << " : PPM file identified" << endl;
            PPMLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
        case(RAW):
        {
            DDebug() << filePath << " : RAW file identified" << endl;
            RAWLoader loader(this, rawDecodingSettings);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
        case(JP2K):
        {
            DDebug() << filePath << " : JPEG2000 file identified" << endl;
            JP2KLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }       
        default:
        {
            DDebug() << filePath << " : TQIMAGE file identified" << endl;
            TQImageLoader loader(this);
            if (loader.load(filePath, observer))
            {
                m_priv->null       = false;
                m_priv->alpha      = loader.hasAlpha();
                m_priv->sixteenBit = loader.sixteenBit();
                m_priv->isReadOnly = loader.isReadOnly();
                return true;
            }
            break;
        }
    }

    return false;
}

bool DImg::save(const TQString& filePath, const TQString& format, DImgLoaderObserver *observer)
{
    if (isNull())
        return false;

    if (format.isEmpty())
        return false;

    TQString frm = format.upper();

    if (frm == "JPEG" || frm == "JPG" || frm == "JPE")
    {
        JPEGLoader loader(this);
        return loader.save(filePath, observer);
    }
    else if (frm == "PNG")
    {
        PNGLoader loader(this);
        return loader.save(filePath, observer);
    }
    else if (frm == "TIFF" || frm == "TIF")
    {
        TIFFLoader loader(this);
        return loader.save(filePath, observer);
    }
    else if (frm == "PPM")
    {
        PPMLoader loader(this);
        return loader.save(filePath, observer);
    }
    if (frm == "JP2" || frm == "JPX" || frm == "JPC" || frm == "PGX")
    {
        JP2KLoader loader(this);
        return loader.save(filePath, observer);
    }
    else
    {
        setAttribute("format", format);
        TQImageLoader loader(this);
        return loader.save(filePath, observer);
    }

    return false;
}

DImg::FORMAT DImg::fileFormat(const TQString& filePath)
{
    if ( filePath.isNull() )
        return NONE;

    // In first we trying to check the file extension. This is mandatory because
    // some tiff files are detected like RAW files by dcraw::identify method.

    TQFileInfo fileInfo(filePath);
    if (!fileInfo.exists())
    {
        DDebug() << k_funcinfo << "File \"" << filePath << "\" does not exist" << endl;
        return NONE;
    }

#if KDCRAW_VERSION < 0x000106
    TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
#else
    TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
#endif
    TQString ext = fileInfo.extension(false).upper();

    if (!ext.isEmpty())
    {
        if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
            return JPEG;
        else if (ext == TQString("PNG"))
            return PNG;
        else if (ext == TQString("TIFF") || ext == TQString("TIF"))
            return TIFF;
        else if (rawFilesExt.upper().contains(ext))
            return RAW;
        if (ext == TQString("JP2") || ext == TQString("JPX") || // JPEG2000 file format
            ext == TQString("JPC") ||                          // JPEG2000 code stream
            ext == TQString("PGX"))                            // JPEG2000 WM format
            return JP2K;
    }

    // In second, we trying to parse file header.

    FILE* f = fopen(TQFile::encodeName(filePath), "rb");

    if (!f)
    {
        DDebug() << k_funcinfo << "Failed to open file \"" << filePath << "\"" << endl;
        return NONE;
    }

    const int headerLen = 9;
    unsigned char header[headerLen];

    if (fread(&header, headerLen, 1, f) != 1)
    {
        DDebug() << k_funcinfo << "Failed to read header of file \"" << filePath << "\"" << endl;
        fclose(f);
        return NONE;
    }

    fclose(f);

    KDcrawIface::DcrawInfoContainer dcrawIdentify;
    KDcrawIface::KDcraw::rawFileIdentify(dcrawIdentify, filePath);
    uchar jpegID[2]    = { 0xFF, 0xD8 };
    uchar tiffBigID[2] = { 0x4D, 0x4D };
    uchar tiffLilID[2] = { 0x49, 0x49 };
    uchar pngID[8]     = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
    uchar jp2ID[5]     = { 0x6A, 0x50, 0x20, 0x20, 0x0D, };
    uchar jpcID[2]     = { 0xFF, 0x4F };

    if (memcmp(&header, &jpegID, 2) == 0)            // JPEG file ?
    {
        return JPEG;
    }
    else if (memcmp(&header, &pngID, 8) == 0)        // PNG file ?
    {
        return PNG;
    }
    else if (memcmp(&header[0], "P", 1)  == 0 &&
             memcmp(&header[2], "\n", 1) == 0)       // PPM 16 bits file ?
    {
        int width, height, rgbmax;
        char nl;
        FILE *file = fopen(TQFile::encodeName(filePath), "rb");

        if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) == 4) 
        {
            if (rgbmax > 255)
            {
                pclose (file);
                return PPM;
            }
        }

        pclose (file);
    }
    else if (dcrawIdentify.isDecodable)
    {
        // RAW File test using dcraw::identify method.  
        // Need to test it before TIFF because any RAW file 
        // formats using TIFF header.
        return RAW;
    }
    else if (memcmp(&header, &tiffBigID, 2) == 0 ||  // TIFF file ?
             memcmp(&header, &tiffLilID, 2) == 0)
    {
        return TIFF;
    }
    else if (memcmp(&header[4], &jp2ID, 5) == 0 ||         // JPEG2000 file ?
             memcmp(&header,    &jpcID, 2) == 0)
    {
        return JP2K;
    }

    // In others cases, TQImage will be used to try to open file.
    return TQIMAGE;
}


//---------------------------------------------------------------------------------------------------
// accessing properties


bool DImg::isNull() const
{
    return m_priv->null;
}

uint DImg::width() const
{
    return m_priv->width;
}

uint DImg::height() const
{
    return m_priv->height;
}

TQSize DImg::size() const
{
    return TQSize(m_priv->width, m_priv->height);
}

uchar* DImg::bits() const
{
    return m_priv->data;
}

uchar* DImg::scanLine(uint i) const
{
    if ( i >= height() )
        return 0;

    uchar *data = bits() + (width() * bytesDepth() * i);
    return data;
}
    
bool DImg::hasAlpha() const
{
    return m_priv->alpha;
}

bool DImg::sixteenBit() const
{
    return m_priv->sixteenBit;
}

bool DImg::isReadOnly() const
{
    return m_priv->isReadOnly;
}

bool DImg::getICCProfilFromFile(const TQString& filePath)
{
    TQFile file(filePath);
    if ( !file.open(IO_ReadOnly) ) 
        return false;
    
    TQByteArray data(file.size());
    TQDataStream stream( &file );
    stream.readRawBytes(data.data(), data.size());
    setICCProfil(data);
    file.close();
    return true;
}

bool DImg::setICCProfilToFile(const TQString& filePath)
{
    TQFile file(filePath);
    if ( !file.open(IO_WriteOnly) ) 
        return false;
    
    TQByteArray data(getICCProfil());
    TQDataStream stream( &file );
    stream.writeRawBytes(data.data(), data.size());
    file.close();
    return true;
}

TQByteArray DImg::getComments() const
{
    return metadata(COM);
}

TQByteArray DImg::getExif() const
{
    return metadata(EXIF);
}

TQByteArray DImg::getIptc() const
{
    return metadata(IPTC);
}

TQByteArray DImg::getICCProfil() const
{
    return metadata(ICC);
}

void DImg::setComments(const TQByteArray& commentsData)
{
    m_priv->metaData.replace(COM, commentsData);
}

void DImg::setExif(const TQByteArray& exifData)
{
    m_priv->metaData.replace(EXIF, exifData);
}

void DImg::setIptc(const TQByteArray& iptcData)
{
    m_priv->metaData.replace(IPTC, iptcData);
}

void DImg::setICCProfil(const TQByteArray& profile)
{
    m_priv->metaData.replace(ICC, profile);
}

TQByteArray DImg::metadata(DImg::METADATA key) const
{
    typedef TQMap<int, TQByteArray> MetaDataMap;
    
    for (MetaDataMap::iterator it = m_priv->metaData.begin(); it != m_priv->metaData.end(); ++it)
    {
        if (it.key() == key)
            return it.data();
    }

    return TQByteArray();
}

uint DImg::numBytes() const
{
    return (width() * height() * bytesDepth());
}

uint DImg::numPixels() const
{
    return (width() * height());
}

int DImg::bytesDepth() const
{
    if (sixteenBit())
       return 8;

    return 4;
}

int DImg::bitsDepth() const
{
    if (sixteenBit())
       return 16;

    return 8;
}

void DImg::setAttribute(const TQString& key, const TQVariant& value)
{
    m_priv->attributes.insert(key, value);
}

TQVariant DImg::attribute(const TQString& key) const
{
    if (m_priv->attributes.contains(key))
        return m_priv->attributes[key];

    return TQVariant();
}

void DImg::setEmbeddedText(const TQString& key, const TQString& text)
{
    m_priv->embeddedText.insert(key, text);
}

TQString DImg::embeddedText(const TQString& key) const
{
    if (m_priv->embeddedText.contains(key))
        return m_priv->embeddedText[key];

    return TQString();
}

DColor DImg::getPixelColor(uint x, uint y) const
{
    if (isNull() || x > width() || y > height())
    {
        DDebug() << k_funcinfo << " : wrong pixel position!" << endl;
        return DColor();
    }

    uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth());

    return( DColor(data, sixteenBit()) );
}

void DImg::setPixelColor(uint x, uint y, DColor color)
{
    if (isNull() || x > width() || y > height())
    {
        DDebug() << k_funcinfo << " : wrong pixel position!" << endl;
        return;
    }

    if (color.sixteenBit() != sixteenBit())
    {
        DDebug() << k_funcinfo << " : wrong color depth!" << endl;
        return;
    }

    uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth());
    color.setPixel(data);
}


//---------------------------------------------------------------------------------------------------
// copying operations


DImg DImg::copy()
{
    DImg img(*this);
    img.detach();
    return img;
}

DImg DImg::copyImageData()
{
    DImg img(width(), height(), sixteenBit(), hasAlpha(), bits(), true);
    return img;
}

DImg DImg::copyMetaData()
{
    DImg img;
    // copy width, height, alpha, sixteenBit, null
    img.copyImageData(m_priv);
    // deeply copy metadata
    img.copyMetaData(m_priv);
    // set image to null
    img.m_priv->null = true;
    return img;
}

DImg DImg::copy(TQRect rect)
{
    return copy(rect.x(), rect.y(), rect.width(), rect.height());
}

DImg DImg::copy(int x, int y, int w, int h)
{
    if ( isNull() || w <= 0 || h <= 0)
    {
        DDebug() << k_funcinfo << " : return null image!" << endl;
        return DImg();
    }

    DImg image(*this, w, h);
    image.bitBltImage(this, x, y, w, h, 0, 0);

    return image;
}


//---------------------------------------------------------------------------------------------------
// bitwise operations


void DImg::bitBltImage(const DImg* src, int dx, int dy)
{
    bitBltImage(src, 0, 0, src->width(), src->height(), dx, dy);
}

void DImg::bitBltImage(const DImg* src, int sx, int sy, int dx, int dy)
{
    bitBltImage(src, sx, sy, src->width() - sx, src->height() - sy, dx, dy);
}

void DImg::bitBltImage(const DImg* src, int sx, int sy, int w, int h, int dx, int dy)
{
    if (isNull())
       return;

    if (src->sixteenBit() != sixteenBit())
    {
        DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl;
        return;
    }

    if (w == -1 && h == -1)
    {
        w = src->width();
        h = src->height();
    }

    bitBlt(src->bits(), bits(), sx, sy, w, h, dx, dy,
           src->width(), src->height(), width(), height(), sixteenBit(), src->bytesDepth(), bytesDepth());
}

void DImg::bitBltImage(const uchar* src, int sx, int sy, int w, int h, int dx, int dy,
                       uint swidth, uint sheight, int sdepth)
{
    if (isNull())
        return;

    if (bytesDepth() != sdepth)
    {
        DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl;
        return;
    }

    if (w == -1 && h == -1)
    {
        w = swidth;
        h = sheight;
    }

    bitBlt(src, bits(), sx, sy, w, h, dx, dy, swidth, sheight, width(), height(), sixteenBit(), sdepth, bytesDepth());
}

bool DImg::normalizeRegionArguments(int &sx, int &sy, int &w, int &h, int &dx, int &dy,
                                    uint swidth, uint sheight, uint dwidth, uint dheight)
{
    if (sx < 0)
    {
        // sx is negative, so + is - and - is +
        dx -= sx;
        w  += sx;
        sx = 0;
    }

    if (sy < 0)
    {
        dy -= sy;
        h  += sy;
        sy = 0;
    }

    if (dx < 0)
    {
        sx -= dx;
        w  += dx;
        dx = 0;
    }

    if (dy < 0)
    {
        sy -= dy;
        h  += dy;
        dy = 0;
    }

    if (sx + w > (int)swidth)
    {
        w = swidth - sx;
    }

    if (sy + h > (int)sheight)
    {
        h = sheight - sy;
    }

    if (dx + w > (int)dwidth)
    {
        w = dwidth - dx;
    }

    if (dy + h > (int)dheight)
    {
        h = dheight - dy;
    }

    // Nothing left to copy
    if (w <= 0 || h <= 0)
        return false;

    return true;
}

void DImg::bitBlt (const uchar *src, uchar *dest,
                         int sx, int sy, int w, int h, int dx, int dy,
                         uint swidth, uint sheight, uint dwidth, uint dheight,
                         bool /*sixteenBit*/, int sdepth, int ddepth)
{
    // Normalize
    if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight))
        return;

    // Same pixels
    if (src == dest && dx==sx && dy==sy)
        return;

    const uchar *sptr;
    uchar *dptr;
    uint   slinelength = swidth * sdepth;
    uint   dlinelength = dwidth * ddepth;

    int scurY = sy;
    int dcurY = dy;
    for (int j = 0 ; j < h ; j++, scurY++, dcurY++) 
    {
        sptr  = &src [ scurY * slinelength ] + sx * sdepth;
        dptr  = &dest[ dcurY * dlinelength ] + dx * ddepth;

            // plain and simple bitBlt
        for (int i = 0; i < w * sdepth ; i++, sptr++, dptr++)
        {
            *dptr = *sptr;
        }
    }
}

void DImg::bitBlendImage(DColorComposer *composer, const DImg* src,
                         int sx, int sy, int w, int h, int dx, int dy,
                         DColorComposer::MultiplicationFlags multiplicationFlags)
{
    if (isNull())
        return;

    if (src->sixteenBit() != sixteenBit())
    {
        DWarning() << "Blending from 8-bit to 16-bit or vice versa is not supported" << endl;
        return;
    }

    bitBlend(composer, src->bits(), bits(), sx, sy, w, h, dx, dy,
             src->width(), src->height(), width(), height(), sixteenBit(),
             src->bytesDepth(), bytesDepth(), multiplicationFlags);
}

void DImg::bitBlend (DColorComposer *composer, const uchar *src, uchar *dest,
                     int sx, int sy, int w, int h, int dx, int dy,
                     uint swidth, uint sheight, uint dwidth, uint dheight,
                     bool sixteenBit, int sdepth, int ddepth,
                     DColorComposer::MultiplicationFlags multiplicationFlags)
{
    // Normalize
    if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight))
        return;

    const uchar *sptr;
    uchar *dptr;
    uint   slinelength = swidth * sdepth;
    uint   dlinelength = dwidth * ddepth;

    int scurY = sy;
    int dcurY = dy;
    for (int j = 0 ; j < h ; j++, scurY++, dcurY++) 
    {
        sptr  = &src [ scurY * slinelength ] + sx * sdepth;
        dptr  = &dest[ dcurY * dlinelength ] + dx * ddepth;

        // blend src and destination
        for (int i = 0 ; i < w ; i++, sptr+=sdepth, dptr+=ddepth)
        {
            DColor src(sptr, sixteenBit);
            DColor dst(dptr, sixteenBit);

            // blend colors
            composer->compose(dst, src, multiplicationFlags);

            dst.setPixel(dptr);
        }
    }
}


//---------------------------------------------------------------------------------------------------
// TQImage / TQPixmap access


TQImage DImg::copyTQImage()
{
    if (isNull())
        return TQImage();

    if (sixteenBit())
    {
        DImg img(*this);
        img.detach();
        img.convertDepth(32);
        return img.copyTQImage();
    }

    TQImage img(width(), height(), 32);

    uchar* sptr = bits();
    uint*  dptr = (uint*)img.bits();

    for (uint i=0; i < width()*height(); i++)
    {
        *dptr++ = tqRgba(sptr[2], sptr[1], sptr[0], sptr[3]);
        sptr += 4;
    }

    if (hasAlpha())
    {
        img.setAlphaBuffer(true);
    }

    return img;
}

TQImage DImg::copyTQImage(TQRect rect)
{
    return (copyTQImage(rect.x(), rect.y(), rect.width(), rect.height()));
}

TQImage DImg::copyTQImage(int x, int y, int w, int h)
{
    if (isNull())
        return TQImage();

    DImg img = copy(x, y, w, h);

    if (img.sixteenBit())
        img.convertDepth(32);

    return img.copyTQImage();
}

TQPixmap DImg::convertToPixmap()
{
    if (isNull())
        return TQPixmap();

    if (sixteenBit())
    {
        // make fastaaaa..
        return TQPixmap(copyTQImage(0, 0, width(), height()));
    }

    if (TQImage::systemByteOrder() == TQImage::BigEndian)
    {
        TQImage img(width(), height(), 32);

        uchar* sptr = bits();
        uint*  dptr = (uint*)img.bits();

        for (uint i=0; i<width()*height(); i++)
        {
            *dptr++ = tqRgba(sptr[2], sptr[1], sptr[0], sptr[3]);
            sptr += 4;
        }

        if (hasAlpha())
        {
            img.setAlphaBuffer(true);
        }

        return TQPixmap(img);
    }
    else
    {
        TQImage img(bits(), width(), height(), 32, 0, 0, TQImage::IgnoreEndian);

        if (hasAlpha())
        {
            img.setAlphaBuffer(true);
        }

        return TQPixmap(img);
    }
}

TQPixmap DImg::convertToPixmap(IccTransform *monitorICCtrans)
{
    if (isNull())
        return TQPixmap();

    if (!monitorICCtrans->hasOutputProfile())
    {
        DDebug() << k_funcinfo << " : no monitor ICC profile available!" << endl;
        return convertToPixmap();
    }
    
    DImg img = copy();

    // Without embedded profile
    if (img.getICCProfil().isNull())
    {
        TQByteArray fakeProfile; 
        monitorICCtrans->apply(img, fakeProfile, monitorICCtrans->getRenderingIntent(),
                               monitorICCtrans->getUseBPC(), false, 
                               monitorICCtrans->inputProfile().isNull()); 
    }
    // With embedded profile.
    else
    {
        monitorICCtrans->getEmbeddedProfile( img );
        monitorICCtrans->apply( img );
    }

    return (img.convertToPixmap());
}

TQImage DImg::pureColorMask(ExposureSettingsContainer *expoSettings)
{
    if (isNull() || (!expoSettings->underExposureIndicator && !expoSettings->overExposureIndicator))
        return TQImage();

    TQImage img(size(), 32);
    img.fill(0x00000000);      // Full transparent.
    img.setAlphaBuffer(true);

    uchar *bits = img.bits();
    int    max  = sixteenBit() ? 65535 : 255;
    int    index;
    DColor pix;

    for (uint x=0 ; x < width() ; x++)
    {
        for (uint y=0 ; y<height() ; y++)
        {
            pix   = getPixelColor(x, y);
            index = y*img.bytesPerLine() + x*4;
    
            if (expoSettings->underExposureIndicator && 
                pix.red() == 0 && pix.green() == 0 && pix.blue() == 0)
            {
                bits[index    ] = expoSettings->underExposureColor.blue();
                bits[index + 1] = expoSettings->underExposureColor.green();
                bits[index + 2] = expoSettings->underExposureColor.red();
                bits[index + 3] = 0xFF;
            }

            if (expoSettings->overExposureIndicator && 
                pix.red() == max && pix.green() == max && pix.blue() == max)
            {
                bits[index    ] = expoSettings->overExposureColor.blue();
                bits[index + 1] = expoSettings->overExposureColor.green();
                bits[index + 2] = expoSettings->overExposureColor.red();
                bits[index + 3] = 0xFF;
            }
        }
    }

    return img;
}


//---------------------------------------------------------------------------------------------------
// basic imaging operations


void DImg::crop(TQRect rect)
{
    crop(rect.x(), rect.y(), rect.width(), rect.height());
}

void DImg::crop(int x, int y, int w, int h)
{
    if ( isNull() || w <= 0 || h <= 0)
        return;

    uint  oldw = width();
    uint  oldh = height();
    uchar *old = stripImageData();

    // set new image data, bits(), width(), height() change
    setImageDimension(w, h);
    allocateData();

    // copy image region (x|y), wxh, from old data to point (0|0) of new data
    bitBlt(old, bits(), x, y, w, h, 0, 0, oldw, oldh, width(), height(), sixteenBit(), bytesDepth(), bytesDepth());
    delete [] old;
}

void DImg::resize(int w, int h)
{
    if ( w <= 0 || h <= 0)
        return;

    DImg image = smoothScale(w, h);

    delete [] m_priv->data;
    m_priv->data = image.stripImageData();
    setImageDimension(w, h);
}

void DImg::rotate(ANGLE angle)
{
    if (isNull())
        return;
    
    switch (angle)
    {
    case(ROT90):
    {
        uint w  = height();
        uint h  = width();

        if (sixteenBit())
        {
            ullong* newData = new ullong[w*h];
        
            ullong *from = (ullong*) m_priv->data;
            ullong *to;
        
            for (int y = w-1; y >=0; y--)
            {
                to = newData + y;
                
                for (uint x=0; x < h; x++)
                {
                    *to = *from++;
                    to += w;
                }
            }

            setImageDimension(w, h);

            delete [] m_priv->data;
            m_priv->data = (uchar*)newData;
        }
        else
        {
            uint* newData = new uint[w*h];
        
            uint *from = (uint*) m_priv->data;
            uint *to;
        
            for (int y = w-1; y >=0; y--)
            {
                to = newData + y;
                
                for (uint x=0; x < h; x++)
                {
                    *to = *from++;
                    to += w;
                }
            }

            setImageDimension(w, h);

            delete [] m_priv->data;
            m_priv->data = (uchar*)newData;
        }
        
        break;
    }
    case(ROT180):
    {
        uint w  = width();
        uint h  = height();

        int middle_line = -1;
        if (h % 2)
           middle_line = h / 2;

        if (sixteenBit())
        {
            ullong *line1;
            ullong *line2;

            ullong* data = (ullong*) bits();
            ullong  tmp;
        
            // can be done inplace
            for (uint y = 0; y < (h+1)/2; y++)
            {
                line1 = data + y * w;
                line2 = data + (h-y) * w;
                for (uint x=0; x < w; x++)
                {
                    tmp    = *line1;
                    *line1 = *line2;
                    *line2 = tmp;

                    line1++;
                    line2--;
                    if ((int)y == middle_line && x * 2 >= w)
                        break;
                }
            }
        }
        else
        {
            uint *line1;
            uint *line2;

            uint* data = (uint*) bits();
            uint  tmp;
        
            // can be done inplace
            for (uint y = 0; y < (h+1)/2; y++)
            {
                line1 = data + y * w;
                line2 = data + (h-y) * w;
                
                for (uint x=0; x < w; x++)
                {
                    tmp    = *line1;
                    *line1 = *line2;
                    *line2 = tmp;

                    line1++;
                    line2--;
                    if ((int)y == middle_line && x * 2 >= w)
                        break;
                }
            }
        }
        
        break;
    }
    case(ROT270):
    {
        uint w  = height();
        uint h  = width();

        if (sixteenBit())
        {
            ullong* newData = new ullong[w*h];
        
            ullong *from = (ullong*) m_priv->data;
            ullong *to;
        
            for (uint y = 0; y < w; y++)
            {
                to = newData + y + w*(h-1);
                
                for (uint x=0; x < h; x++)
                {
                    *to = *from++;
                    to -= w;
                }
            }

            setImageDimension(w, h);

            delete [] m_priv->data;
            m_priv->data = (uchar*)newData;
        }
        else
        {
            uint* newData = new uint[w*h];
        
            uint *from = (uint*) m_priv->data;
            uint *to;
        
            for (uint y = 0; y < w; y++)
            {
                to = newData + y + w*(h-1);
                
                for (uint x=0; x < h; x++)
                {
                    *to = *from++;
                    to -= w;
                }
            }

            setImageDimension(w, h);

            delete [] m_priv->data;
            m_priv->data = (uchar*)newData;
        }
        
        break;
    }
    default:
        break;
    }
}

// 15-11-2005: This method have been tested indeep with valgrind by Gilles.

void DImg::flip(FLIP direction)
{
    if (isNull())
        return;
    
    switch (direction)
    {
        case(HORIZONTAL):
        {
            uint w  = width();
            uint h  = height();
    
            if (sixteenBit())
            {
                unsigned short  tmp[4];
                unsigned short *beg;
                unsigned short *end;
    
                unsigned short * data = (unsigned short *)bits();
    
                // can be done inplace
                for (uint y = 0 ; y < h ; y++)
                {
                    beg = data + y * w * 4;
                    end = beg  + (w-1) * 4;
    
                    for (uint x=0 ; x < (w/2) ; x++)
                    {
                        memcpy(&tmp, beg, 8);
                        memcpy(beg, end, 8);
                        memcpy(end, &tmp, 8);
    
                        beg+=4;
                        end-=4;
                    }
                }
            }
            else
            {
                uchar  tmp[4];
                uchar *beg;
                uchar *end;
    
                uchar* data = bits();
    
                // can be done inplace
                for (uint y = 0 ; y < h ; y++)
                {
                    beg = data + y * w * 4;
                    end = beg  + (w-1) * 4;
    
                    for (uint x=0 ; x < (w/2) ; x++)
                    {
                        memcpy(&tmp, beg, 4);
                        memcpy(beg, end, 4);
                        memcpy(end, &tmp, 4);

                        beg+=4;
                        end-=4;
                    }
                }
            }
    
            break;
        }
        case(VERTICAL):
        {
            uint w  = width();
            uint h  = height();
    
            if (sixteenBit())
            {
                unsigned short  tmp[4];
                unsigned short *line1;
                unsigned short *line2;
    
                unsigned short* data = (unsigned short*) bits();
            
                // can be done inplace
                for (uint y = 0 ; y < (h/2) ; y++)
                {
                    line1 = data + y * w * 4;
                    line2 = data + (h-y-1) * w * 4;
                    
                    for (uint x=0 ; x < w ; x++)
                    {
                        memcpy(&tmp, line1, 8);
                        memcpy(line1, line2, 8);
                        memcpy(line2, &tmp, 8);

                        line1+=4;
                        line2+=4;
                    }
                }
            }
            else
            {
                uchar  tmp[4];
                uchar *line1;
                uchar *line2;
    
                uchar* data = bits();
            
                // can be done inplace
                for (uint y = 0 ; y < (h/2) ; y++)
                {
                    line1 = data + y * w * 4;
                    line2 = data + (h-y-1) * w * 4;
                    
                    for (uint x=0 ; x < w ; x++)
                    {
                        memcpy(&tmp, line1, 4);
                        memcpy(line1, line2, 4);
                        memcpy(line2, &tmp, 4);

                        line1+=4;
                        line2+=4;
                    }
                }
            }
            
            break;
        }
        default:
            break;
    }
}

void DImg::convertToSixteenBit()
{
    convertDepth(64);
}

void DImg::convertToEightBit()
{
    convertDepth(32);
}

void DImg::convertToDepthOfImage(const DImg *otherImage)
{
    if (otherImage->sixteenBit())
        convertToSixteenBit();
    else
        convertToEightBit();
}

void DImg::convertDepth(int depth)
{
    if (isNull())
        return;

    if (depth != 32 && depth != 64)
    {
        DDebug() << k_funcinfo << " : wrong color depth!" << endl;
        return;
    }

    if (((depth == 32) && !sixteenBit()) ||
        ((depth == 64) && sixteenBit()))
        return;

    if (depth == 32)
    {
        // downgrading from 16 bit to 8 bit

        uchar*  data = new uchar[width()*height()*4];
        uchar*  dptr = data;
        ushort* sptr = (ushort*)bits();

        for (uint i=0; i<width()*height()*4; i++)
        {
            *dptr++ = (*sptr++ * 255UL)/65535UL;
        }

        delete [] m_priv->data;
        m_priv->data = data;
        m_priv->sixteenBit = false;
    }
    else if (depth == 64)
    {
        // upgrading from 8 bit to 16 bit

        uchar*  data = new uchar[width()*height()*8];
        ushort* dptr = (ushort*)data;
        uchar*  sptr = bits();

        for (uint i=0; i<width()*height()*4; i++)
        {
            *dptr++ = (*sptr++ * 65535ULL)/255ULL;
        }

        delete [] m_priv->data;
        m_priv->data = data;
        m_priv->sixteenBit = true;
    }
}

void DImg::fill(DColor color)
{
    if (sixteenBit())
    {
        unsigned short *imgData16 = (unsigned short *)m_priv->data;

        for (uint i = 0 ; i < width()*height()*4 ; i+=4)
        {
            imgData16[ i ] = (unsigned short)color.blue();
            imgData16[i+1] = (unsigned short)color.green();
            imgData16[i+2] = (unsigned short)color.red();
            imgData16[i+3] = (unsigned short)color.alpha();
        }
    }
    else
    {
        uchar *imgData = m_priv->data;
        
        for (uint i = 0 ; i < width()*height()*4 ; i+=4)
        {
            imgData[ i ] = (uchar)color.blue();
            imgData[i+1] = (uchar)color.green();
            imgData[i+2] = (uchar)color.red();
            imgData[i+3] = (uchar)color.alpha();
        }
    }
}

}  // NameSpace Digikam
