/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/
/***************************************************************************
                          sccolor.cpp  -  description
                             -------------------
    begin                : Sun Sep 9 2001
    copyright            : (C) 2001 by Franz Schmid
    email                : Franz.Schmid@altmuehlnet.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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "sccolorengine.h"
#include "scribuscore.h"
#include "colormgmt/sccolormgmtengine.h"

QColor ScColorEngine::getRGBColor(const ScColor& color, const ScribusDoc* doc)
{
	RGBColor rgb;
	getRGBValues(color, doc, rgb);
	return QColor(rgb.r, rgb.g, rgb.b);
}

ScColor ScColorEngine::convertToModel(const ScColor& color, const ScribusDoc* doc, colorModel model)
{
	colorModel oldModel = color.getColorModel();
	if( oldModel == model )
		return ScColor(color);
	ScColor newCol;
	if( model == colorModelRGB )
	{
		RGBColor rgb;
		getRGBValues(color, doc, rgb);
		newCol.setColorRGB(rgb.r, rgb.g, rgb.b);
	}
	else
	{
		CMYKColor cmyk;
		getCMYKValues(color, doc, cmyk);
		newCol.setColor(cmyk.c, cmyk.m, cmyk.y, cmyk.k);
	}
	return newCol;
}

void ScColorEngine::getRGBValues(const ScColor& color, const ScribusDoc* doc, RGBColor& rgb)
{
	colorModel    model = color.getColorModel();
	ScColorTransform transRGB = doc ? doc->stdTransRGB : ScCore->defaultCMYKToRGBTrans;
	if (ScCore->haveCMS() && transRGB)
	{
		if (model == colorModelRGB)
		{
			rgb.r = color.CR;
			rgb.g = color.MG;
			rgb.b = color.YB;
		}
		else
		{
			unsigned short inC[4];
			unsigned short outC[4];
			inC[0] = color.CR * 257;
			inC[1] = color.MG * 257;
			inC[2] = color.YB * 257;
			inC[3] = color.K * 257;
			transRGB.apply(inC, outC, 1);
			rgb.r = outC[0] / 257;
			rgb.g = outC[1] / 257;
			rgb.b = outC[2] / 257;
		}
	}
	else if (model == colorModelCMYK)
	{
		rgb.r = 255 - qMin(255, color.CR + color.K);
		rgb.g = 255 - qMin(255, color.MG + color.K);
		rgb.b = 255 - qMin(255, color.YB + color.K);
	}
	else
	{
		rgb.r = color.CR;
		rgb.g = color.MG;
		rgb.b = color.YB;
	}
}

void ScColorEngine::getCMYKValues(const ScColor& color, const ScribusDoc* doc, CMYKColor& cmyk)
{
	unsigned short inC[4];
	unsigned short outC[4];
	colorModel model = color.getColorModel();
	ScColorTransform transCMYK = doc ? doc->stdTransCMYK : ScCore->defaultRGBToCMYKTrans;
	if (ScCore->haveCMS() && transCMYK)
	{
		if (model == colorModelRGB)
		{
			// allow RGB greys to go got to CMYK greys without transform
			if (color.CR == color.MG && color.MG == color.YB)
			{
				cmyk.c = cmyk.m = cmyk.y = 0;
				cmyk.k = 255 - color.CR;
			}
			else
			{
				inC[0] = color.CR * 257;
				inC[1] = color.MG * 257;
				inC[2] = color.YB * 257;
				transCMYK.apply(inC, outC, 1);
				cmyk.c = outC[0] / 257;
				cmyk.m = outC[1] / 257;
				cmyk.y = outC[2] / 257;
				cmyk.k = outC[3] / 257;
			}
		}
		else
		{
			cmyk.c = color.CR;
			cmyk.m = color.MG;
			cmyk.y = color.YB;
			cmyk.k = color.K;
		}
	}
	else if (model == colorModelRGB)
	{
		cmyk.k = qMin(qMin(255 - color.CR, 255 - color.MG), 255 - color.YB);
		cmyk.c = 255 - color.CR - cmyk.k;
		cmyk.m = 255 - color.MG - cmyk.k;
		cmyk.y = 255 - color.YB - cmyk.k;
	}
	else
	{
		cmyk.c = color.CR;
		cmyk.m = color.MG;
		cmyk.y = color.YB;
		cmyk.k = color.K;
	}
}

void ScColorEngine::getShadeColorCMYK(const ScColor& color, const ScribusDoc* doc, 
										  CMYKColor& cmyk, double level)
{
	if (color.getColorModel() == colorModelRGB)
	{
		RGBColor rgb;
		getShadeColorRGB(color, doc, rgb, level);
		ScColor tmpR(rgb.r, rgb.g, rgb.b);
		getCMYKValues(tmpR, doc, cmyk);
	}
	else
	{
		cmyk.c = qRound(color.CR * level / 100.0);
		cmyk.m = qRound(color.MG * level / 100.0);
		cmyk.y = qRound(color.YB * level / 100.0);
		cmyk.k = qRound(color.K * level / 100.0);
	}
}

void ScColorEngine::getShadeColorRGB(const ScColor& color, const ScribusDoc* doc, RGBColor& rgb, double level)
{
	if (color.getColorModel() == colorModelCMYK)
	{
		CMYKColor cmyk;
		getShadeColorCMYK(color, doc, cmyk, level);
		ScColor tmpC(cmyk.c, cmyk.m, cmyk.y, cmyk.k);
		getRGBValues(tmpC, doc, rgb);
	}
	else
	{
		int h, s, v, snew, vnew;
		QColor tmpR(color.CR, color.MG, color.YB);
		tmpR.getHsv(&h, &s, &v);
		snew = qRound(s * level / 100.0);
		vnew = 255 - qRound(((255 - v) * level / 100.0));
		tmpR.setHsv(h, snew, vnew);
		tmpR.getRgb(&rgb.r, &rgb.g, &rgb.b);
		//We could also compute rgb shade using rgb directly
		/*rgb.CR = 255 - ((255 - color.CR) * level / 100);
		rgb.MG = 255 - ((255 - color.MG) * level / 100);
		rgb.YB = 255 - ((255 - color.YB) * level / 100);*/
	}
}

QColor ScColorEngine::getDisplayColor(const ScColor& color, const ScribusDoc* doc)
{
	QColor tmp;
	if (color.getColorModel() == colorModelRGB)
	{
		RGBColor rgb;
		rgb.r = color.CR;
		rgb.g = color.MG;
		rgb.b = color.YB;
		tmp = getDisplayColor(rgb, doc, color.isSpotColor());
	}
	else
	{
		CMYKColor cmyk;
		cmyk.c = color.CR;
		cmyk.m = color.MG;
		cmyk.y = color.YB;
		cmyk.k = color.K;
		tmp = getDisplayColor(cmyk, doc, color.isSpotColor());
	}
	return tmp;
}

QColor ScColorEngine::getDisplayColor(const ScColor& color, const ScribusDoc* doc, double level)
{
	QColor tmp;
	if (color.getColorModel() == colorModelRGB)
	{
		RGBColor rgb;
		rgb.r = color.CR;
		rgb.g = color.MG;
		rgb.b = color.YB;
		getShadeColorRGB(color, doc, rgb, level);
		tmp = getDisplayColor(rgb, doc, color.isSpotColor());
	}
	else
	{
		CMYKColor cmyk;
		cmyk.c = color.CR;
		cmyk.m = color.MG;
		cmyk.y = color.YB;
		cmyk.k = color.K;
		getShadeColorCMYK(color, doc, cmyk, level);
		tmp = getDisplayColor(cmyk, doc, color.isSpotColor());
	}
	return tmp;
}

QColor ScColorEngine::getDisplayColorGC(const ScColor& color, const ScribusDoc* doc)
{
	QColor tmp;
	bool doSoftProofing = doc ? doc->SoftProofing : false;
	bool doGamutCheck   = doc ? doc->Gamut : false;
	if ( doSoftProofing && doGamutCheck )
	{
		bool outOfGamutFlag = isOutOfGamut(color, doc);
		tmp = outOfGamutFlag ? QColor(0, 255, 0) : getDisplayColor(color, doc);
	}
	else
		tmp = getDisplayColor(color, doc);
	return tmp;
}

QColor ScColorEngine::getColorProof(const ScColor& color, const ScribusDoc* doc, bool gamutCheck)
{
	QColor tmp;
	bool gamutChkEnabled = doc ? doc->Gamut : false;
	bool spot = color.isSpotColor();
	if (color.getColorModel() == colorModelRGB)
	{
		// Match 133x behavior (RGB greys map to cmyk greys) until we are able to make rgb profiled output
		if ( color.CR == color.MG && color.MG == color.YB )
			gamutChkEnabled = false;
		RGBColor rgb;
		rgb.r = color.CR;
		rgb.g = color.MG;
		rgb.b = color.YB;
		tmp = getColorProof(rgb, doc, spot, gamutCheck & gamutChkEnabled);
	}
	else
	{
		CMYKColor cmyk;
		cmyk.c = color.CR;
		cmyk.m = color.MG;
		cmyk.y = color.YB;
		cmyk.k = color.K;
		tmp = getColorProof(cmyk, doc, spot, gamutCheck & gamutChkEnabled);
	}
	return tmp;
}

QColor ScColorEngine::getShadeColor(const ScColor& color, const ScribusDoc* doc, double level)
{
	RGBColor rgb;
	rgb.r = color.CR;
	rgb.g = color.MG;
	rgb.b = color.YB;
	getShadeColorRGB(color, doc, rgb, level);
	return QColor(rgb.r, rgb.g, rgb.b);
}

QColor ScColorEngine::getShadeColorProof(const ScColor& color, const ScribusDoc* doc, double level)
{
	QColor tmp;
	bool doGC = doc ? doc->Gamut : false;
	bool cmsUse = doc ? doc->HasCMS : false;
	bool softProof = doc ? doc->SoftProofing : false;

	if (color.getColorModel() == colorModelRGB)
	{
		RGBColor rgb;
		rgb.r = color.CR;
		rgb.g = color.MG;
		rgb.b = color.YB;
		getShadeColorRGB(color, doc, rgb, level);
		// Match 133x behavior for rgb grey until we are able to make rgb profiled output
		// (RGB greys map to cmyk greys)
		if ((cmsUse && softProof) && (rgb.r == rgb.g && rgb.g == rgb.b))
		{
			doGC = false;
			CMYKColor cmyk;
			cmyk.c = cmyk.m = cmyk.y = 0;
			cmyk.k = 255 - rgb.g;
			tmp = getColorProof(cmyk, doc, color.isSpotColor(), doGC);
		}
		else
			tmp = getColorProof(rgb, doc, color.isSpotColor(), doGC);
	}
	else
	{
		CMYKColor cmyk;
		cmyk.c = color.CR;
		cmyk.m = color.MG;
		cmyk.y = color.YB;
		cmyk.k = color.K;
		getShadeColorCMYK(color, doc, cmyk, level);
		tmp = getColorProof(cmyk, doc, color.isSpotColor(), doGC);
	}
	
	return tmp;
}

QColor ScColorEngine::getColorProof(RGBColor& rgb, const ScribusDoc* doc, bool spot, bool gamutCkeck)
{
	unsigned short inC[4];
	unsigned short outC[4];
	int  r = rgb.r, g = rgb.g, b = rgb.b;
	ScColorTransform transRGBMon  = doc ? doc->stdTransRGBMon : ScCore->defaultRGBToScreenSolidTrans;
	ScColorTransform transProof   = doc ? doc->stdProof   : ScCore->defaultRGBToScreenSolidTrans;
	ScColorTransform transProofGC = doc ? doc->stdProofGC : ScCore->defaultRGBToScreenSolidTrans;
	bool cmsUse   = doc ? doc->HasCMS : false;
	bool cmsTrans = (transRGBMon && transProof && transProofGC); 
	if (ScCore->haveCMS() && cmsTrans)
	{
		inC[0] = rgb.r * 257;
		inC[1] = rgb.g * 257;
		inC[2] = rgb.b * 257;
		if (cmsUse && !spot && doc->SoftProofing)
		{
			ScColorTransform xform = gamutCkeck ? transProofGC : transProof;
			xform.apply(inC, outC, 1);
			r = outC[0] / 257;
			g = outC[1] / 257;
			b = outC[2] / 257;
		}
		else
		{
			transRGBMon.apply(inC, outC, 1);
			r = outC[0] / 257;
			g = outC[1] / 257;
			b = outC[2] / 257;
		}
	}
	return QColor(r, g, b);
}

QColor ScColorEngine::getColorProof(CMYKColor& cmyk, const ScribusDoc* doc, bool spot, bool gamutCkeck)
{
	int  r = 0, g = 0, b = 0;
	unsigned short inC[4];
	unsigned short outC[4];
	ScColorTransform transCMYKMon     = doc ? doc->stdTransCMYKMon : ScCore->defaultCMYKToRGBTrans;
	ScColorTransform transProofCMYK   = doc ? doc->stdProofCMYK   : ScCore->defaultCMYKToRGBTrans;
	ScColorTransform transProofCMYKGC = doc ? doc->stdProofCMYKGC : ScCore->defaultCMYKToRGBTrans;
	bool cmsUse   = doc ? doc->HasCMS : false;
	bool cmsTrans = (transCMYKMon && transProofCMYK && transProofCMYKGC); 
	if (ScCore->haveCMS() && cmsTrans)
	{
		inC[0] = cmyk.c * 257;
		inC[1] = cmyk.m * 257;
		inC[2] = cmyk.y * 257;
		inC[3] = cmyk.k * 257;
		if (cmsUse && !spot && doc->SoftProofing)
		{
			ScColorTransform xform = gamutCkeck ? transProofCMYKGC : transProofCMYK;
			xform.apply(inC, outC, 1);
			r = outC[0] / 257;
			g = outC[1] / 257;
			b = outC[2] / 257;
		}
		else
		{
			transCMYKMon.apply(inC, outC, 1);
			r = outC[0] / 257;
			g = outC[1] / 257;
			b = outC[2] / 257;
		}
	}
	else
	{
		r = 255 - qMin(255, cmyk.c + cmyk.k);
		g = 255 - qMin(255, cmyk.m + cmyk.k);
		b = 255 - qMin(255, cmyk.y + cmyk.k);
	}
	return QColor(r, g, b);
}

QColor ScColorEngine::getDisplayColor(RGBColor& rgb, const ScribusDoc* doc, bool spot)
{
	unsigned short inC[4];
	unsigned short outC[4];
	int r = rgb.r;
	int g = rgb.g;
	int b = rgb.b; 
	ScColorTransform transRGBMon = doc ? doc->stdTransRGBMon : ScCore->defaultRGBToScreenSolidTrans;
	if (ScCore->haveCMS() && transRGBMon)
	{
		inC[0] = r * 257;
		inC[1] = g * 257;
		inC[2] = b * 257;
		transRGBMon.apply(inC, outC, 1);
		r = outC[0] / 257;
		g = outC[1] / 257;
		b = outC[2] / 257;
	}
	return QColor(r, g, b);
}

QColor ScColorEngine::getDisplayColor(CMYKColor& cmyk, const ScribusDoc* doc, bool spot)
{
	int  r = 0, g = 0, b = 0;
	unsigned short inC[4];
	unsigned short outC[4];
	ScColorTransform transCMYKMon = doc ? doc->stdTransCMYKMon : ScCore->defaultCMYKToRGBTrans;
	if (ScCore->haveCMS() && transCMYKMon)
	{
		inC[0] = cmyk.c * 257;
		inC[1] = cmyk.m * 257;
		inC[2] = cmyk.y * 257;
		inC[3] = cmyk.k * 257;
		transCMYKMon.apply(inC, outC, 1);
		r = outC[0] / 257;
		g = outC[1] / 257;
		b = outC[2] / 257;
	}
	else
	{
		r = 255 - qMin(255, cmyk.c + cmyk.k);
		g = 255 - qMin(255, cmyk.m + cmyk.k);
		b = 255 - qMin(255, cmyk.y + cmyk.k);
	}
	return QColor(r, g, b);
}

bool ScColorEngine::isOutOfGamut(const ScColor& color, const ScribusDoc* doc)
{
	bool outOfGamutFlag = false;
	if (color.isSpotColor())
		return false;
	unsigned short inC[4];
	unsigned short outC[4];
	bool cmsUse = doc ? doc->HasCMS : false;
	if (ScCore->haveCMS() && cmsUse)
	{
		bool alert = true;
		ScColorTransform xformProof;
		if (color.getColorModel() == colorModelRGB)
		{
			inC[0] = color.CR * 257;
			inC[1] = color.MG * 257;
			inC[2] = color.YB * 257;
			xformProof = doc->stdProofGC;
			if ((color.CR == 0) && (color.YB == 0) && (color.MG == 255))
				alert = false;
			if ((color.CR == color.MG && color.MG == color.YB))
				alert = false;
		}
		else
		{
			inC[0] = color.CR * 257;
			inC[1] = color.MG * 257;
			inC[2] = color.YB * 257;
			inC[3] = color.K * 257;
			xformProof = doc->stdProofCMYKGC;
			if ((color.MG == 0) && (color.K == 0) && (color.CR == 255) && (color.YB == 255))
				alert = false;
			if ((color.MG == 0) && (color.CR == 0) && (color.YB == 0))
				alert = false;
			if ((color.MG == color.CR) && (color.CR == color.YB) && (color.YB == color.K))
				alert = false;
		}
		if (alert)
		{
			xformProof.apply(inC, outC, 1);
			if ((outC[0]/257 == 0) && (outC[1]/257 == 255) && (outC[2]/257 == 0))
				outOfGamutFlag = true;
		}
	}
	return outOfGamutFlag;
}

void ScColorEngine::applyGCR(ScColor& color, const ScribusDoc* doc)
{
	bool cmsUse = doc ? doc->HasCMS : false;
	if (!(ScCore->haveCMS() && cmsUse))
	{
		CMYKColor cmyk;
		getCMYKValues(color, doc, cmyk);
		int k = qMin(qMin(cmyk.c, cmyk.m), cmyk.y);
		color.CR = cmyk.c - k;
		color.MG = cmyk.m - k;
		color.YB = cmyk.y - k;
		color.K = qMin((cmyk.k + k), 255);
	}
}
