// $Id: qtcietonge.cpp,v 1.3 2008/03/06 21:18:58 hvengel Exp $
//  Little cms - profiler construction set
//  Copyright (C) 1998-2001 Marti Maria
// Copyright (C) 2005 Hal Engel
//
// THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
// INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
// LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//
// 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 3 of the License, 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.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


// Based on a previous work of John Walker
// Version 1.99

#include "qtlcmswidgets.h"


#include <qpixmap.h>
#include <qcolor.h>
#include <qimage.h>
#include <QRect>


// 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 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
};

static bool firstTime = true;


const int pixcols = 2000;
const int pixrows = 2000;
        
QImage backGround(pixcols, pixrows, QImage::Format_RGB32);    

void cmsxCIETonge::MapPoint(int& icx, int& icy, cmsCIExyY xyY)
{
	icx = (int) floor((xyY.x * (pxcols - 1)) + .5);
	icy = (int) floor(((pxrows - 1) - xyY.y * (pxrows - 1)) + .5);
}



void cmsxCIETonge::BiasedLine(QPainter &painter, int x1, int y1, int x2, int y2)
{
    painter.drawLine(x1 + xBias, y1, x2 + xBias, y2);
}


void cmsxCIETonge::BiasedText(QPainter &painter, int x, int y, const char* Txt)
{
    painter.drawText(QPoint(xBias + x, y), QString::fromLocal8Bit(Txt));
}


inline QRgb cmsxCIETonge::ColorByCoord(double x, double y)
{
    // Get xyz components scaled from coordinates

    double cx = ((double) x) / (pxcols - 1);
    double cy = 1.0 - ((double) y) / (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(hXFORM, XYZW, RGB, 1);

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


void cmsxCIETonge::OutlineTonge(QPainter &painter)
{
    bool first = true;
    int x1, y1;
    for (int x = 380; x <= 700; x += 1)
    {
        int ix = (x - 380) / 5;
        cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1};
        int icx, icy;
        MapPoint(icx, icy, p);
        if (first)
        {
            first = false;
            x1 = icx;
            y1 = icy;
        }
        tongePoly << QPoint(icx + xBias, icy);
    }
    tongePoly << QPoint(x1 + xBias, y1);
    QPen outline;
    outline.setWidth(5);
    outline.setColor(Qt::white);
    path.addPolygon(tongePoly);
    painter.setBackgroundColor(Qt::black);
    painter.setPen(outline);
    painter.drawPath(path);
}


void cmsxCIETonge::FillTonge(QPainter &painter)
{
    QRect rect;
    bool started, done;
    
    rect = path.controlPointRect().toRect();
    for (int y = rect.top() + 1; y < rect.bottom() - 1; y++)
    {
        started = false;
        done = false;
        for (int x = rect.left() + 1; x < rect.right() - 1; x++)
        {
            if (path.contains(QPoint(x, y)))
            {
                started = true;
                QRgb Color = ColorByCoord(x, y);
                painter.setPen(QColor(Color));
                painter.drawPoint(x,     y);
            }
            else if (started)
                break;
        }
    }
}

void cmsxCIETonge::DrawTongeAxis(QPainter &painter)
{
    QFont font;
    font.setPointSize(16 * scale);
    painter.setFont(font);
    QPen ticks;
    ticks.setWidth(3);
    ticks.setColor(Qt::white);
    painter.setPen(ticks);
    painter.drawLine(xBias, 0, xBias, pxrows - 1);
    painter.drawLine(xBias, pxrows-1, pxcols-1 + xBias, pxrows - 1);
    int y;
    for (y = 1; y <= 9; y += 1)
    {
        QString s;
        int xstart =  (y * (pxcols - 1)) / 10;
        int ystart =  (y * (pxrows - 1)) / 10;
        
        s.sprintf("0.%d", y);
        painter.drawLine(xstart + xBias, pxrows - Grids(1), xstart + xBias, pxrows - Grids(4));
        painter.drawText(QPoint(xstart - Grids(11) + xBias, pxrows + Grids(15)), s);
    
        s.sprintf("0.%d", 10 - y);
        painter.drawLine(xBias, ystart, Grids(3) + xBias, ystart);
        painter.drawText(QPoint(Grids(-25) + xBias, ystart + Grids(5)), s);
    } 
}


void cmsxCIETonge::DrawTongeGrid(QPainter &painter)
{
    QPen gridPen;
    gridPen.setWidth(2);
    gridPen.setColor(QColor(80, 80, 80));
    painter.setPen(gridPen);
    for (int y = 1; y <= 9; y += 1)
    {
        int xstart =  (y * (pxcols - 1)) / 10;
        int ystart =  (y * (pxrows - 1)) / 10;
        painter.drawLine(xstart + xBias, Grids(4), xstart + xBias, pxrows - Grids(4) - 1);
        painter.drawLine(Grids(7) + xBias, ystart, pxcols-1-Grids(7) + xBias, ystart);
    }
}

void cmsxCIETonge::DrawLabels(QPainter &painter)
{
    QFont font;
    font.setPointSize(14 * scale);
    painter.setFont(font);
    int x = 4200;
    while (x <=6600)
    {
        char wl[20];
        int bx = 0, by = 0, tx, ty;

        if (x < 4800)
        {
            bx = Grids(-16);
            by = Grids(2);
        }
        else if (x < 5200)
        {
            bx = Grids(-15);
            by = Grids(2);
        }
        else if (x < 5350)
        {
            bx = Grids(-5);
            by = Grids(-6);
        }
        else
        {
            bx = Grids(4);
        }
        int ix = (int (float(x)/10.0 + 0.5) - 380) / 5;
        cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1};
        int icx, icy;
        MapPoint(icx, icy, p);
        tx = icx + ((x < 5200) ? Grids(-2) : ((x >= 5350) ? Grids(2) : 0));
        ty = icy + ((x < 5200) ? 0 : ((x >= 5350) ? Grids(-1) : Grids(-2)));
        QPen tickPen;
        tickPen.setWidth(2);
        tickPen.setColor(Qt::white);
        painter.setPen(tickPen);
        painter.drawLine(icx + xBias, icy, tx + xBias, ty);
        QRgb Color = ColorByCoord(icx, icy);
        painter.setPen(Color);
        sprintf(wl, "%d", x/10);
        painter.drawText(QPoint(icx+bx + xBias, icy+by), QString::fromLocal8Bit(wl));
        if (x == 4200)
            x = 4600;
        else if (x == 4600)
            x = 4700;
        else if (x < 4800)
            x = x + 50;
        else if (x < 5100)
            x = x + 25;
        else if (x < 6100)
            x = x + 50;
        else if (x == 6100)
            x = 6200;
        else if (x == 6200)
            x = 6350;
        else if (x == 6350)
            x = 6600;
        else
            x = x + 100;
    }
}

void cmsxCIETonge::DrawSmallElipse(QPainter &painter, cmsCIExyY xyY, int r, int g, int b, int sz)
{
    int icx, icy;

    MapPoint(icx, icy, xyY);
    painter.setPen(qRgb(r, g, b));
    painter.drawEllipse(icx + xBias- sz/2, icy-sz/2, sz * scale, sz * scale);
}

void cmsxCIETonge::DrawPatches(QPainter &painter)
{
    if (m != NULL)
    {
        if (m -> Patches && m -> Allowed)
        {
            for (int i=0; i < m ->nPatches; i++)
            {
                LPPATCH p = m -> Patches + i;
                if (m->Allowed[i])
                {
                    LPcmsCIEXYZ XYZ = &p ->XYZ;
                    cmsCIExyY xyY;
                    cmsXYZ2xyY(&xyY, XYZ);
                    DrawSmallElipse(painter, 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;
                        }
                        QPen proofPen;
                        proofPen.setWidth(3);
                        proofPen.setColor(QColor(255, 255, 0));
                        painter.setPen(proofPen);
                        BiasedLine(painter, icx1, icy1, icx2, icy2);
                    }
                }
            }
        }
    }
}


void cmsxCIETonge::DrawTonge(LPcmsCIExyYTRIPLE inPrimaries, LPcmsCIEXYZ inWhitePoint, LPMEASUREMENT inM)
{
    drawCalled = true;
    Primaries = inPrimaries;
    WhitePoint = inWhitePoint;
    m = inM;
    update(); 
}


void cmsxCIETonge::DrawColorantTriangle(QPainter &painter)
{
    if (Primaries != NULL)
    {
        if (Primaries -> Red.Y != 0.0)
        {   
            QPen prim1Pen;
            prim1Pen.setWidth(4); 
            painter.setPen(prim1Pen);
            
            DrawSmallElipse(painter, Primaries -> Red, 255, 128, 128, 7);
            DrawSmallElipse(painter, Primaries -> Green, 128, 255, 128, 7);
            DrawSmallElipse(painter, Primaries -> Blue, 128, 128, 255, 7);
            
            int x1, y1, x2, y2, x3, y3;
        
            MapPoint(x1, y1, Primaries -> Red);
            MapPoint(x2, y2, Primaries -> Green);
            MapPoint(x3, y3, Primaries -> Blue);
            
            QPen primPen;
            primPen.setWidth(2);
            primPen.setColor(QColor(255, 255, 255));
        
            painter.setPen(primPen);
                
            BiasedLine(painter, x1, y1, x2, y2);
            BiasedLine(painter, x2, y2, x3, y3);
            BiasedLine(painter, x3, y3, x1, y1);
        }
    }
}


void cmsxCIETonge::Sweep_sRGB(QPainter &painter)
{
    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);
                if (path.contains(QPoint(x1 + xBias, y1)))
                    painter.drawPoint(x1 + xBias, y1);
            }
    cmsDeleteTransform(xform);
    cmsCloseProfile(hXYZ);
    cmsCloseProfile(hsRGB);
}

void cmsxCIETonge::DrawWhitePoint(QPainter &painter)
{
    int icx, icy;
    if (WhitePoint != NULL)
    {
        if (WhitePoint -> Y > 0.0)
        {
            cmsCIExyY WhitePntxyY;
            cmsXYZ2xyY(&WhitePntxyY, WhitePoint);
            DrawSmallElipse(painter, WhitePntxyY,  255, 255, 255, 8);
        }
    }
}


static
int xmin(int a, int b)
{
    return (a > b? b : a);
}

void cmsxCIETonge::paintBackground()
{    
    QPainter paint(&backGround);
        
    paint.setWindow( 0, 0, pixcols - 1, pixrows - 1);
        
    paint.setRenderHints(QPainter::SmoothPixmapTransform | 
            QPainter::Antialiasing | 
            QPainter::TextAntialiasing, true);
        
        // Paint the black background
    QPolygon a;
    a << QPoint(0, 0);
    a << QPoint(pixcols, 0);
    a << QPoint(pixcols, pixrows);
    a << QPoint(0, pixrows);
    QBrush brush(Qt::black);
    paint.setBrush(brush); 	
    paint.drawPolygon(a);
    
    gridside = (xmin(pixcols, pixrows)) / 512.0;
    
    xBias = Grids(32);
    yBias = Grids(20);
    
    pxcols = pixcols - xBias;
    pxrows = pixrows - yBias;
    
    OutlineTonge(paint);
        
    FillTonge(paint);
    
    DrawTongeAxis(paint);
    
    DrawTongeGrid(paint);
    
    DrawLabels(paint);
        
    if (backGround.isNull())
        qDebug("background image is null");
    else if (firstTime)
    {
        QString file = QString::fromLocal8Bit("pcheckerbackgr.png");
        if(backGround.save(file, "PNG", 100))
            qDebug("it worked");
        else
            qDebug("it didn't work");
    }
}

void cmsxCIETonge::paintEvent(QPaintEvent *)
{
    // qDebug("cmsxCIETonge::paintEvent");
    scale = pixcols / 1000;
    
    gridside = (xmin(pixcols, pixrows)) / 512.0;

    xBias = Grids(32);
    yBias = Grids(20);

    pxcols = pixcols - xBias;
    pxrows = pixrows - yBias;
    
    QPainter painter(this);
    
     if (firstTime)
     {
         
         // To create and test a new version of the backgound
         // for the CIE diagram uncomment the following two lines
         // and comment the line after that.  This will create the
         // pcheckerbackgr,png file in the data directory.
         // to make this new background file the default move
         // it to src/images and change the commented code back
         
         // paintBackground();
         // backGround.load(QString("pcheckerbackgr.png"));
         
         backGround.load(QString::fromLocal8Bit(":/pcheckerbackgr.png"));
         firstTime=false;
     }
    
 
    painter.setWindow( 0, 0, pixcols - 1, pixrows - 1);
    
    painter.setRenderHints(QPainter::SmoothPixmapTransform | 
            QPainter::Antialiasing, true);
    
    QRect rect(0, 0, pixcols - 1, pixrows - 1);
     
    painter.drawImage(rect, backGround, rect);

    if (drawCalled)
    {
        DrawWhitePoint(painter);

        DrawColorantTriangle(painter);

        DrawPatches(painter);
    } 
}

cmsxCIETonge::cmsxCIETonge(QWidget *parent)
    : QLabel(parent)
{
    // if (hMonitor) hMonitorProfile = hMonitor;
    // else 
    firstTime = true;
    drawCalled = false;
    hMonitorProfile = cmsCreate_sRGBProfile();
   
    setFocusPolicy(Qt::StrongFocus);

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

}


cmsxCIETonge::~cmsxCIETonge()
{
    cmsDeleteTransform(hXFORM);
    cmsCloseProfile(hXYZProfile);
    cmsCloseProfile(hMonitorProfile);
}

