/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2006-01-10
 * Description : a widget to display CIE tongue from
 *               an ICC profile.
 *
 * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * Any source code are inspired from lprof project and
 * Copyright (C) 1998-2001 Marti Maria
 *
 * 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++ includes.

#include <cmath>

// TQt includes.

#include <tqimage.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqfile.h>
#include <tqtimer.h>

// KDE includes.

#include <klocale.h>

// Local includes.

#include "ddebug.h"
#include "lcmsprf.h"
#include "cietonguewidget.h"
#include "cietonguewidget.moc"

namespace Digikam
{

    // The  following  table  gives  the  CIE  colour  matching  functions
    // \bar{x}(\lambda),  \bar{y}(\lambda),  and   \bar{z}(\lambda),   for
    // wavelengths  \lambda  at 5 nanometre increments from 380 nm through
    // 780 nm.  This table is used in conjunction with  Planck's  law  for
    // the  energy spectrum of a black body at a given temperature to plot
    // the black body curve on the CIE chart. 

    // The following table gives the  spectral  chromaticity  co-ordinates
    // x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments
    // from 380 nm through  780  nm.   These  co-ordinates  represent  the
    // position in the CIE x-y space of pure spectral colours of the given
    // wavelength, and  thus  define  the  outline  of  the  CIE  "tongue"
    // diagram.

    static const double spectral_chromaticity[81][3] =
    {
        { 0.1741, 0.0050 },               // 380 nm 
        { 0.1740, 0.0050 },
        { 0.1738, 0.0049 },
        { 0.1736, 0.0049 },
        { 0.1733, 0.0048 },
        { 0.1730, 0.0048 },
        { 0.1726, 0.0048 },
        { 0.1721, 0.0048 },
        { 0.1714, 0.0051 },
        { 0.1703, 0.0058 },
        { 0.1689, 0.0069 },
        { 0.1669, 0.0086 },
        { 0.1644, 0.0109 },
        { 0.1611, 0.0138 },
        { 0.1566, 0.0177 },
        { 0.1510, 0.0227 },
        { 0.1440, 0.0297 },
        { 0.1355, 0.0399 },
        { 0.1241, 0.0578 },
        { 0.1096, 0.0868 },
        { 0.0913, 0.1327 },
        { 0.0687, 0.2007 },
        { 0.0454, 0.2950 },
        { 0.0235, 0.4127 },
        { 0.0082, 0.5384 },
        { 0.0039, 0.6548 },
        { 0.0139, 0.7502 },
        { 0.0389, 0.8120 },
        { 0.0743, 0.8338 },
        { 0.1142, 0.8262 },
        { 0.1547, 0.8059 },
        { 0.1929, 0.7816 },
        { 0.2296, 0.7543 },
        { 0.2658, 0.7243 },
        { 0.3016, 0.6923 },
        { 0.3373, 0.6589 },
        { 0.3731, 0.6245 },
        { 0.4087, 0.5896 },
        { 0.4441, 0.5547 },
        { 0.4788, 0.5202 },
        { 0.5125, 0.4866 },
        { 0.5448, 0.4544 },
        { 0.5752, 0.4242 },
        { 0.6029, 0.3965 },
        { 0.6270, 0.3725 },
        { 0.6482, 0.3514 },
        { 0.6658, 0.3340 },
        { 0.6801, 0.3197 },
        { 0.6915, 0.3083 },
        { 0.7006, 0.2993 },
        { 0.7079, 0.2920 },
        { 0.7140, 0.2859 },
        { 0.7190, 0.2809 },
        { 0.7230, 0.2770 },
        { 0.7260, 0.2740 },
        { 0.7283, 0.2717 },
        { 0.7300, 0.2700 },
        { 0.7311, 0.2689 },
        { 0.7320, 0.2680 },
        { 0.7327, 0.2673 },
        { 0.7334, 0.2666 },
        { 0.7340, 0.2660 },
        { 0.7344, 0.2656 },
        { 0.7346, 0.2654 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 },
        { 0.7347, 0.2653 }  // 780 nm 
    };

class CIETongueWidgetPriv
{
public:

    CIETongueWidgetPriv()
    {
        hMonitorProfile      = 0;
        hXYZProfile          = 0;
        hXFORM               = 0;

        Measurement.Patches  = 0;
        Measurement.Allowed  = 0;
        blinkTimer           = 0;
        pos                  = 0;

        profileDataAvailable = true;
        loadingImageMode     = false;
        loadingImageSucess   = false;
    }

    bool             profileDataAvailable;
    bool             loadingImageMode;
    bool             loadingImageSucess;

    double           gridside;

    int              xBias;
    int              yBias;
    int              pxcols;
    int              pxrows;
    int              pos;           // Position of animation during loading/calculation.

    TQPainter         painter;
    TQPixmap          pixmap;
    TQTimer          *blinkTimer;

    cmsHPROFILE      hMonitorProfile;
    cmsHPROFILE      hXYZProfile;
    cmsHTRANSFORM    hXFORM;
    cmsCIExyYTRIPLE  Primaries;
    cmsCIEXYZ        MediaWhite;

    MEASUREMENT      Measurement;
};

CIETongueWidget::CIETongueWidget(int w, int h, TQWidget *parent, cmsHPROFILE hMonitor)
               : TQWidget(parent, 0, TQt::WDestructiveClose)
{
    d = new CIETongueWidgetPriv;
    d->blinkTimer = new TQTimer( this );
    setMinimumSize(w, h);
    cmsErrorAction(LCMS_ERROR_SHOW);

    if (hMonitor)
        d->hMonitorProfile = hMonitor;
    else
        d->hMonitorProfile = cmsCreate_sRGBProfile();

    d->hXYZProfile = cmsCreateXYZProfile();
    d->hXFORM      = cmsCreateTransform(d->hXYZProfile, TYPE_XYZ_16,
                                        d->hMonitorProfile, TYPE_RGB_8,
                                        INTENT_PERCEPTUAL, 0);

    connect(d->blinkTimer, TQT_SIGNAL(timeout()),
            this, TQT_SLOT(slotBlinkTimerDone()));
}

CIETongueWidget::~CIETongueWidget()
{
    if (d->Measurement.Patches)
        free(d->Measurement.Patches);

    if (d->Measurement.Allowed)
        free(d->Measurement.Allowed);

    cmsDeleteTransform(d->hXFORM);
    cmsCloseProfile(d->hXYZProfile);
    cmsCloseProfile(d->hMonitorProfile);
    delete d;
}

int CIETongueWidget::grids(double val) const
{
    return (int) floor(val * d->gridside + 0.5);
}

bool CIETongueWidget::setProfileData(const TQByteArray &profileData)
{
    if (!profileData.isEmpty())
    {
        cmsHPROFILE hProfile = cmsOpenProfileFromMem(const_cast<char*>(profileData.data()),
                                                    (DWORD)profileData.size());

        if (!hProfile)
        {
            d->profileDataAvailable = false;
            d->loadingImageSucess   = false;
        }
        else
        {
            setProfile(hProfile);
            cmsCloseProfile(hProfile);
            d->profileDataAvailable = true;
            d->loadingImageSucess   = true;
        }
    }
    else
    {
        d->profileDataAvailable = false;
        d->loadingImageSucess   = false;
    }

    d->loadingImageMode = false;

    d->blinkTimer->stop();
    repaint(false);
    return (d->profileDataAvailable);
}

bool CIETongueWidget::setProfileFromFile(const KURL& file)
{
    if (!file.isEmpty() && file.isValid())
    {
        cmsHPROFILE hProfile = cmsOpenProfileFromFile(TQFile::encodeName(file.path()), "r");

        if (!hProfile)
        {
            d->profileDataAvailable = false;
            d->loadingImageSucess   = false;
        }
        else
        {
            setProfile(hProfile);
            cmsCloseProfile(hProfile);
            d->profileDataAvailable = true;
            d->loadingImageSucess   = true;
        }
    }
    else
    {
        d->profileDataAvailable = false;
        d->loadingImageSucess   = false;
    }

    d->blinkTimer->stop();
    repaint(false);
    return (d->profileDataAvailable);
}

void CIETongueWidget::setProfile(cmsHPROFILE hProfile)
{
    // Get the white point.

    ZeroMemory(&(d->MediaWhite), sizeof(cmsCIEXYZ));
    cmsTakeMediaWhitePoint(&(d->MediaWhite), hProfile);
    cmsCIExyY White;
    cmsXYZ2xyY(&White, &(d->MediaWhite));

    // Get the colorant matrix.

    ZeroMemory(&(d->Primaries), sizeof(cmsCIExyYTRIPLE));

    if (cmsIsTag(hProfile, icSigRedColorantTag) &&
        cmsIsTag(hProfile, icSigGreenColorantTag) &&
        cmsIsTag(hProfile, icSigBlueColorantTag))
    {
        MAT3 Mat;

        if (cmsReadICCMatrixRGB2XYZ(&Mat, hProfile))
        {
            // Undo chromatic adaptation
            if (cmsAdaptMatrixFromD50(&Mat, &White))
            {
                cmsCIEXYZ tmp;

                tmp.X = Mat.v[0].n[0];
                tmp.Y = Mat.v[1].n[0];
                tmp.Z = Mat.v[2].n[0];

                // ScaleToWhite(&MediaWhite, &tmp);
                cmsXYZ2xyY(&(d->Primaries.Red), &tmp);

                tmp.X = Mat.v[0].n[1];
                tmp.Y = Mat.v[1].n[1];
                tmp.Z = Mat.v[2].n[1];
                // ScaleToWhite(&MediaWhite, &tmp);
                cmsXYZ2xyY(&(d->Primaries.Green), &tmp);

                tmp.X = Mat.v[0].n[2];
                tmp.Y = Mat.v[1].n[2];
                tmp.Z = Mat.v[2].n[2];
                // ScaleToWhite(&MediaWhite, &tmp);
                cmsXYZ2xyY(&(d->Primaries.Blue), &tmp);
            }
        }
    }

    // Get target data stored in profile

    ZeroMemory(&(d->Measurement), sizeof(MEASUREMENT));
    char*  CharTarget;
    size_t CharTargetSize;

    if (cmsTakeCharTargetData(hProfile, &CharTarget, &CharTargetSize))
    {
        LCMSHANDLE hSheet = cmsxIT8LoadFromMem(CharTarget, CharTargetSize);
        if (hSheet != NULL)
        {
            cmsxPCollLoadFromSheet(&(d->Measurement),  hSheet);
            cmsxIT8Free(hSheet);
            cmsxPCollValidatePatches(&(d->Measurement), PATCH_HAS_XYZ|PATCH_HAS_RGB);
        }
    }
}

void CIETongueWidget::mapPoint(int& icx, int& icy, LPcmsCIExyY xyY)
{
    icx = (int) floor((xyY->x * (d->pxcols - 1)) + .5);
    icy = (int) floor(((d->pxrows - 1) - xyY->y * (d->pxrows - 1)) + .5);
}

void CIETongueWidget::biasedLine(int x1, int y1, int x2, int y2)
{
    d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2);
}

void CIETongueWidget::biasedText(int x, int y, TQString Txt)
{
    d->painter.drawText(TQPoint(d->xBias + x, y), Txt);
}

TQRgb CIETongueWidget::colorByCoord(double x, double y)
{
    // Get xyz components scaled from coordinates

    double cx =       ((double) x) / (d->pxcols - 1);
    double cy = 1.0 - ((double) y) / (d->pxrows - 1);
    double cz = 1.0 - cx - cy;

    // Project xyz to XYZ space. Note that in this
    // particular case we are substituting XYZ with xyz

    cmsCIEXYZ XYZ = { cx , cy , cz };

    WORD XYZW[3];
    BYTE RGB[3];

    cmsFloat2XYZEncoded(XYZW, &XYZ);
    cmsDoTransform(d->hXFORM, XYZW, RGB, 1);

    return tqRgb(RGB[0], RGB[1], RGB[2]);
}

void CIETongueWidget::outlineTongue()
{
    int lx = 0, ly = 0;
    int fx=0, fy=0;

    for (int x = 380; x <= 700; x += 5)
    {
        int ix = (x - 380) / 5;

        cmsCIExyY p = {spectral_chromaticity[ix][0], 
                       spectral_chromaticity[ix][1], 1};

        int icx, icy;
        mapPoint(icx, icy, &p);

        if (x > 380)
        {
            biasedLine(lx, ly, icx, icy);
        }
        else
        {
            fx = icx;
            fy = icy;
        }

        lx = icx;
        ly = icy;
     }

    biasedLine(lx, ly, fx, fy);
}

void CIETongueWidget::fillTongue()
{

    TQImage Img = d->pixmap.convertToImage();

    int x;

    for (int y = 0; y < d->pxrows; y++)
    {
        int xe = 0;

        // Find horizontal extents of tongue on this line. 

        for (x = 0; x < d->pxcols; x++)
        {
            if ((TQColor) Img.pixel(x + d->xBias, y) != TQt::black)
            {
                for (xe = d->pxcols - 1; xe >= x; xe--)
                {
                    if ((TQColor) Img.pixel(xe + d->xBias, y) != TQt::black)
                    {
                        break;
                    }
                }

                break;
            }
        }

        if (x < d->pxcols)
        {
            for ( ; x <= xe; x++)
            {
                TQRgb Color = colorByCoord(x, y);
                Img.setPixel(x + d->xBias, y, Color);
            }
        }
    }

    d->pixmap.convertFromImage(Img, TQPixmap::AvoidDither );
}

void CIETongueWidget::drawTongueAxis()
{
    TQFont font;
    font.setPointSize(6);
    d->painter.setFont(font);

    d->painter.setPen(tqRgb(255, 255, 255));

    biasedLine(0, 0,          0,          d->pxrows - 1);
    biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1);

    for (int y = 1; y <= 9; y += 1)
    {
        TQString s;
        int xstart = (y * (d->pxcols - 1)) / 10;
        int ystart = (y * (d->pxrows - 1)) / 10;

        s.sprintf("0.%d", y);
        biasedLine(xstart, d->pxrows - grids(1), xstart,   d->pxrows - grids(4));
        biasedText(xstart - grids(11), d->pxrows + grids(15), s);

        s.sprintf("0.%d", 10 - y);
        biasedLine(0, ystart, grids(3), ystart);
        biasedText(grids(-25), ystart + grids(5), s);
    }
}

void CIETongueWidget::drawTongueGrid()
{
    d->painter.setPen(tqRgb(80, 80, 80));

    for (int y = 1; y <= 9; y += 1)
    {
        int xstart =  (y * (d->pxcols - 1)) / 10;
        int ystart =  (y * (d->pxrows - 1)) / 10;

        biasedLine(xstart, grids(4), xstart,   d->pxrows - grids(4) - 1);
        biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart);
    }
}

void CIETongueWidget::drawLabels()
{
    TQFont font;
    font.setPointSize(5);
    d->painter.setFont(font);

    for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10)
    {
        TQString wl;
        int bx = 0, by = 0, tx, ty;

        if (x < 520)
        {
            bx = grids(-22);
            by = grids(2);
        }
        else if (x < 535)
        {
            bx = grids(-8);
            by = grids(-6);
        }
        else
        {
            bx = grids(4);
        }

        int ix = (x - 380) / 5;

        cmsCIExyY p = {spectral_chromaticity[ix][0],
                       spectral_chromaticity[ix][1], 1};

        int icx, icy;
        mapPoint(icx, icy, &p);

        tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0));
        ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2))); 

        d->painter.setPen(tqRgb(255, 255, 255));
        biasedLine(icx, icy, tx, ty);

        TQRgb Color = colorByCoord(icx, icy);
        d->painter.setPen(Color);

        wl.sprintf("%d", x);
        biasedText(icx+bx, icy+by, wl);
    }
}

void CIETongueWidget::drawSmallElipse(LPcmsCIExyY xyY, BYTE r, BYTE g, BYTE b, int sz)
{
    int icx, icy;

    mapPoint(icx, icy, xyY);
    d->painter.setPen(tqRgb(r, g, b));
    d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz);
}

void CIETongueWidget::drawPatches()
{
    for (int i=0; i < d->Measurement.nPatches; i++)
    {
        LPPATCH p = d->Measurement.Patches + i;

        if (d->Measurement.Allowed[i])
        {
            LPcmsCIEXYZ XYZ = &p ->XYZ;
            cmsCIExyY xyY;
            cmsXYZ2xyY(&xyY, XYZ);

            drawSmallElipse(&xyY,  0, 0, 0, 4);

            if (p->dwFlags & PATCH_HAS_XYZ_PROOF)
            {
                if (p->XYZ.Y < 0.03)
                   continue;

                if (p->XYZProof.Y < 0.03)
                   continue;

                cmsCIExyY Pt;
                cmsXYZ2xyY(&Pt, &p->XYZProof);
                int icx1, icx2, icy1, icy2;

                mapPoint(icx1, icy1, &xyY);
                mapPoint(icx2, icy2, &Pt);

                if (icx2 < 5 || icy2 < 5 || icx1 < 5 || icy1 < 5)
                    continue;

                d->painter.setPen(tqRgb(255, 255, 0));
                biasedLine(icx1, icy1, icx2, icy2);
            }
        }
    }
}

void CIETongueWidget::drawColorantTriangle()
{
    drawSmallElipse(&(d->Primaries.Red),   255, 128, 128, 6);
    drawSmallElipse(&(d->Primaries.Green), 128, 255, 128, 6);
    drawSmallElipse(&(d->Primaries.Blue),  128, 128, 255, 6);

    int x1, y1, x2, y2, x3, y3;

    mapPoint(x1, y1, &(d->Primaries.Red));
    mapPoint(x2, y2, &(d->Primaries.Green));
    mapPoint(x3, y3, &(d->Primaries.Blue));

    d->painter.setPen(tqRgb(255, 255, 255));

    biasedLine(x1, y1, x2, y2);
    biasedLine(x2, y2, x3, y3);
    biasedLine(x3, y3, x1, y1);
}

void CIETongueWidget::sweep_sRGB()
{
    int r, g, b;
    cmsHPROFILE hXYZ, hsRGB;

    hXYZ = cmsCreateXYZProfile();
    hsRGB = cmsCreate_sRGBProfile();

    cmsHTRANSFORM xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hXYZ, TYPE_XYZ_16,
                                             INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_NOTPRECALC);
    WORD RGB[3], XYZ[3];
    cmsCIEXYZ xyz, MediaWhite;
    cmsCIExyY xyY, WhitePt;
    int x1, y1;

    cmsTakeMediaWhitePoint(&MediaWhite, hsRGB);
    cmsXYZ2xyY(&WhitePt, &MediaWhite);

    for (r=0; r < 65536; r += 1024)
    {
        for (g=0; g < 65536; g += 1024)
        {
            for (b=0; b < 65536; b += 1024)
            {
                RGB[0] = r; RGB[1] = g; RGB[2] = b;
                cmsDoTransform(xform, RGB, XYZ, 1);
                cmsXYZEncoded2Float(&xyz, XYZ);
                cmsXYZ2xyY(&xyY, &xyz);
                mapPoint(x1, y1, &xyY);
                d->painter.drawPoint(x1 + d->xBias, y1);
            }
        }
    }

    cmsDeleteTransform(xform);
    cmsCloseProfile(hXYZ);
    cmsCloseProfile(hsRGB);
}

void CIETongueWidget::drawWhitePoint()
{
    cmsCIExyY Whitem_pntxyY;
    cmsXYZ2xyY(&Whitem_pntxyY, &(d->MediaWhite));
    drawSmallElipse(&Whitem_pntxyY,  255, 255, 255, 8);
}

void CIETongueWidget::loadingStarted()
{
    d->pos                = 0;
    d->loadingImageMode   = true;
    d->loadingImageSucess = false;
    repaint(false);
    d->blinkTimer->start(200);
}

void CIETongueWidget::loadingFailed()
{
    d->blinkTimer->stop();
    d->pos                = 0;
    d->loadingImageMode   = false;
    d->loadingImageSucess = false;
    repaint(false);
}

void CIETongueWidget::paintEvent(TQPaintEvent*)
{
    d->pixmap = TQPixmap(size());
    d->pixmap.setOptimization(TQPixmap::BestOptim);

    // Widget is disable : drawing grayed frame.

    if ( !isEnabled() )
    {
        d->painter.begin(&d->pixmap);
        d->painter.fillRect(0, 0, size().width(), size().height(), palette().disabled().background());
        d->painter.setPen(TQPen(palette().disabled().foreground(), 1, TQt::SolidLine));
        d->painter.drawRect(0, 0, width(), height());
        d->painter.end();
        bitBlt(this, 0, 0, &d->pixmap);
        return;
    }

    // Loading image mode.

    if (d->loadingImageMode && !d->loadingImageSucess)
    {
        // In first, we draw an animation.

        int asize = 24;
        TQPixmap anim(asize, asize);
        TQPainter p2;
        p2.begin(&anim, TQT_TQOBJECT(this));
        p2.fillRect(0, 0, asize, asize, palette().active().background());
        p2.translate(asize/2, asize/2);

        d->pos = (d->pos + 10) % 360;
        p2.setPen(TQPen(palette().active().text()));
        p2.rotate(d->pos);
        for ( int i=0 ; i<12 ; i++ )
        {
            p2.drawLine(asize/2-5, 0, asize/2-2, 0);
            p2.rotate(30);
        }
        p2.end();

        // ... and we render busy text.

        d->painter.begin(&d->pixmap);
        d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background());
        d->painter.drawPixmap(width()/2 - asize /2, asize, anim);
        d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine));
        d->painter.drawRect(0, 0, width(), height());
        d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter,
                            i18n("Loading image..."));

        d->painter.end();
        bitBlt(this, 0, 0, &d->pixmap);
        return;
    }

    // No profile data to show.

    if (!d->profileDataAvailable || (!d->loadingImageMode && !d->loadingImageSucess))
    {
        d->painter.begin(&d->pixmap);
        d->painter.fillRect(0, 0, size().width(), size().height(), palette().active().background());
        d->painter.setPen(TQPen(palette().active().text(), 1, TQt::SolidLine));
        d->painter.drawRect(0, 0, width(), height());
        d->painter.drawText(0, 0, size().width(), size().height(), TQt::AlignCenter,
                            i18n("No profile available..."));

        d->painter.end();
        bitBlt(this, 0, 0, &d->pixmap);
        return;
    }

    // Draw the CIE tongue curve.

    d->pixmap.fill(TQt::black);
    d->painter.begin(&d->pixmap);

    int pixcols = d->pixmap.width();
    int pixrows = d->pixmap.height();

    d->gridside = (TQMIN(pixcols, pixrows)) / 512.0;
    d->xBias = grids(32);
    d->yBias = grids(20);
    d->pxcols = pixcols - d->xBias;
    d->pxrows = pixrows - d->yBias;

    d->painter.setBackgroundColor(tqRgb(0, 0, 0));
    d->painter.setPen(tqRgb(255, 255, 255));

    outlineTongue();
    fillTongue();

    drawTongueAxis();
    drawLabels();
    drawTongueGrid();

    if (d->MediaWhite.Y > 0.0)
        drawWhitePoint();

    if (d->Primaries.Red.Y != 0.0)
        drawColorantTriangle();

    if (d->Measurement.Patches && d->Measurement.Allowed)
        drawPatches();

    d->painter.end();

    bitBlt(this, 0, 0, &d->pixmap);
}

void CIETongueWidget::slotBlinkTimerDone()
{
    repaint(false);
    d->blinkTimer->start( 200 );
}

}  // namespace Digikam
