diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp
index f847452d15..5198378459 100644
--- a/libs/pigment/KoColorSpace.cpp
+++ b/libs/pigment/KoColorSpace.cpp
@@ -1,833 +1,831 @@
/*
* Copyright (c) 2005 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoColorSpace.h"
#include "KoColorSpace_p.h"
#include "KoChannelInfo.h"
#include "DebugPigment.h"
#include "KoCompositeOp.h"
#include "KoColorTransformation.h"
#include "KoColorTransformationFactory.h"
#include "KoColorTransformationFactoryRegistry.h"
#include "KoColorConversionCache.h"
#include "KoColorConversionSystem.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorProfile.h"
#include "KoCopyColorConversionTransformation.h"
#include "KoFallBackColorTransformation.h"
#include "KoUniqueNumberForIdServer.h"
#include "KoMixColorsOp.h"
#include "KoConvolutionOp.h"
#include "KoCompositeOpRegistry.h"
#include "KoColorSpaceEngine.h"
#include
#include
#include
#include
#include
#include
#include
#include
KoColorSpace::KoColorSpace()
: d(new Private())
{
}
KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp)
: d(new Private())
{
d->id = id;
d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id);
d->name = name;
d->mixColorsOp = mixColorsOp;
d->convolutionOp = convolutionOp;
d->transfoToRGBA16 = 0;
d->transfoFromRGBA16 = 0;
d->transfoToLABA16 = 0;
d->transfoFromLABA16 = 0;
d->gamutXYY = QPolygonF();
d->TRCXYY = QPolygonF();
d->colorants = QVector (0);
d->lumaCoefficients = QVector (0);
d->iccEngine = 0;
d->deletability = NotOwnedByRegistry;
}
KoColorSpace::~KoColorSpace()
{
Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);
qDeleteAll(d->compositeOps);
Q_FOREACH (KoChannelInfo * channel, d->channels) {
delete channel;
}
if (d->deletability == NotOwnedByRegistry) {
KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache();
if (cache) {
cache->colorSpaceIsDestroyed(this);
}
}
delete d->mixColorsOp;
delete d->convolutionOp;
delete d->transfoToRGBA16;
delete d->transfoFromRGBA16;
delete d->transfoToLABA16;
delete d->transfoFromLABA16;
delete d;
}
bool KoColorSpace::operator==(const KoColorSpace& rhs) const
{
const KoColorProfile* p1 = rhs.profile();
const KoColorProfile* p2 = profile();
return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
}
QString KoColorSpace::id() const
{
return d->id;
}
QString KoColorSpace::name() const
{
return d->name;
}
//Color space info stuff.
QPolygonF KoColorSpace::gamutXYY() const
{
if (d->gamutXYY.empty()) {
//now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases.
//first make a list of colors.
qreal max = 1.0;
if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
//boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
max = this->channels()[0]->getUIMax();
}
int samples = 5;//amount of samples in our color space.
const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
quint8 *data = new quint8[pixelSize()];
quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel.
//QVector sampleCoordinates(pow(colorChannelCount(),samples));
//sampleCoordinates.fill(0.0);
// This is fixed to 5 since the maximum number of channels are 5 for CMYKA
QVector channelValuesF(5);//for getting the coordinates.
for(int x=0;xnormalisedChannelsValue(data2, channelValuesF);
qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->gamutXYY << QPointF(x,y);
} else {
for(int y=0;ynormalisedChannelsValue(data2, channelValuesF);
qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
d->gamutXYY<< QPointF(x,y);
}
} else {
channelValuesF[0]=(max/(samples-1))*(x);
channelValuesF[1]=(max/(samples-1))*(y);
channelValuesF[2]=(max/(samples-1))*(z);
channelValuesF[3]=max;
if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
}
qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->gamutXYY<< QPointF(x,y);
}
}
}
}
}
delete[] data;
//if we ever implement a boundary-checking thing I'd add it here.
return d->gamutXYY;
} else {
return d->gamutXYY;
}
}
QPolygonF KoColorSpace::estimatedTRCXYY() const
{
if (d->TRCXYY.empty()){
qreal max = 1.0;
if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
//boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
max = this->channels()[0]->getUIMax();
}
const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
quint8 *data = new quint8[pixelSize()];
quint8 *data2 = new quint8[xyzColorSpace->pixelSize()];
// This is fixed to 5 since the maximum number of channels are 5 for CMYKA
QVector channelValuesF(5);//for getting the coordinates.
+ d->colorants.resize(3*colorChannelCount());
+
+ const int segments = 10;
for (quint32 i=0; i=0; j--){
+ for (int j = 0; j <= segments; j++) {
channelValuesF.fill(0.0);
- channelValuesF[i] = ((max/4)*(4-j));
+ channelValuesF[channels()[i]->displayPosition()] = ((max/segments)*(segments-j));
if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
}
-
if (j==0) {
colorantY = channelValuesF[1];
- if (d->colorants.size()<2){
- d->colorants.resize(3*colorChannelCount());
- d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
- d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
- d->colorants[i+2]= channelValuesF[1];
- }
+ d->colorants[3*i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
+ d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
+ d->colorants[3*i+2] = channelValuesF[1];
}
- d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(4-j)));
+ d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(segments-j)));
}
} else {
- for (int j=0; j<5; j++){
+ for (int j = 0; j <= segments; j++) {
channelValuesF.fill(0.0);
- channelValuesF[i] = ((max/4)*(j));
+ channelValuesF[i] = ((max/segments)*(j));
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
if (j==0) {
colorantY = channelValuesF[1];
- if (d->colorants.size()<2){
- d->colorants.resize(3*colorChannelCount());
- d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
- d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
- d->colorants[i+2]= channelValuesF[1];
- }
+ d->colorants[3*i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
+ d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
+ d->colorants[3*i+2] = channelValuesF[1];
}
- d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j)));
+ d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(j)));
}
}
}
delete[] data;
delete[] data2;
return d->TRCXYY;
} else {
return d->TRCXYY;
}
}
QVector KoColorSpace::lumaCoefficients() const
{
if (d->lumaCoefficients.size()>1){
return d->lumaCoefficients;
} else {
d->lumaCoefficients.resize(3);
if (colorModelId().id()!="RGBA") {
d->lumaCoefficients.fill(0.33);
} else {
if (d->colorants.size() <= 0) {
if (profile() && profile()->hasColorants()) {
d->colorants.resize(3 * colorChannelCount());
d->colorants = profile()->getColorantsxyY();
}
else {
QPolygonF p = estimatedTRCXYY();
Q_UNUSED(p);
}
}
if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) {
d->lumaCoefficients[0]=0.2126;
d->lumaCoefficients[1]=0.7152;
d->lumaCoefficients[2]=0.0722;
} else {
- d->lumaCoefficients[0]=d->colorants[2];
- d->lumaCoefficients[1]=d->colorants[5];
- d->lumaCoefficients[2]=d->colorants[8];
+ // luma coefficients need to add up to 1.0
+ qreal sum = d->colorants[2] + d->colorants[5] + d->colorants[8];
+ d->lumaCoefficients[0] = d->colorants[2] / sum;
+ d->lumaCoefficients[1] = d->colorants[5] / sum;
+ d->lumaCoefficients[2] = d->colorants[8] / sum;
}
}
return d->lumaCoefficients;
}
}
QList KoColorSpace::channels() const
{
return d->channels;
}
QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
{
QBitArray ba(d->channels.size());
if (!color && !alpha) return ba;
for (int i = 0; i < d->channels.size(); ++i) {
KoChannelInfo * channel = d->channels.at(i);
if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
(alpha && channel->channelType() == KoChannelInfo::ALPHA))
ba.setBit(i, true);
}
return ba;
}
void KoColorSpace::addChannel(KoChannelInfo * ci)
{
d->channels.push_back(ci);
}
bool KoColorSpace::hasCompositeOp(const QString& id) const
{
return d->compositeOps.contains(id);
}
QList KoColorSpace::compositeOps() const
{
return d->compositeOps.values();
}
KoMixColorsOp* KoColorSpace::mixColorsOp() const
{
return d->mixColorsOp;
}
KoConvolutionOp* KoColorSpace::convolutionOp() const
{
return d->convolutionOp;
}
const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const
{
const QHash::ConstIterator it = d->compositeOps.constFind(id);
if (it != d->compositeOps.constEnd()) {
return it.value();
}
else {
warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER;
return d->compositeOps.value(COMPOSITE_OVER);
}
}
void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
{
if (op->colorSpace()->id() == id()) {
d->compositeOps.insert(op->id(), const_cast(op));
}
}
const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
{
if (!d->transfoToLABA16) {
d->transfoToLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoToLABA16;
}
const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
{
if (!d->transfoFromLABA16) {
d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoFromLABA16;
}
const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
{
if (!d->transfoToRGBA16) {
d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoToRGBA16;
}
const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
{
if (!d->transfoFromRGBA16) {
d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoFromRGBA16;
}
void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
toLabA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
fromLabA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
toRgbA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
fromRgbA16Converter()->transform(src, dst, nPixels);
}
KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (*this == *dstColorSpace) {
return new KoCopyColorConversionTransformation(this);
} else {
return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
}
}
bool KoColorSpace::convertPixelsTo(const quint8 * src,
quint8 * dst,
const KoColorSpace * dstColorSpace,
quint32 numPixels,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (*this == *dstColorSpace) {
if (src != dst) {
memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
}
} else {
KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
cct.transformation()->transform(src, dst, numPixels);
}
return true;
}
KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const
{
if (!d->iccEngine) {
d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
}
if (!d->iccEngine) return 0;
return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState);
}
bool KoColorSpace::proofPixelsTo(const quint8 *src,
quint8 *dst,
quint32 numPixels,
KoColorConversionTransformation *proofingTransform) const
{
proofingTransform->transform(src, dst, numPixels);
//the transform is deleted in the destructor.
return true;
}
void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1());
if(params.rows <= 0 || params.cols <= 0)
return;
if(!(*this == *srcSpace)) {
if (preferCompositionInSourceColorSpace() &&
srcSpace->hasCompositeOp(op->id())) {
quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize();
QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride);
quint8* conversionDstData = conversionDstCache->data();
for(qint32 row=0; rowcompositeOp(op->id());
KoCompositeOp::ParameterInfo paramInfo(params);
paramInfo.dstRowStart = conversionDstData;
paramInfo.dstRowStride = conversionDstBufferStride;
otherOp->composite(paramInfo);
for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride,
params.dstRowStart + row * params.dstRowStride, this, params.cols,
renderingIntent, conversionFlags);
}
} else {
quint32 conversionBufferStride = params.cols * pixelSize();
QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride);
quint8* conversionData = conversionCache->data();
for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride,
conversionData + row * conversionBufferStride, this, params.cols,
renderingIntent, conversionFlags);
}
KoCompositeOp::ParameterInfo paramInfo(params);
paramInfo.srcRowStart = conversionData;
paramInfo.srcRowStride = conversionBufferStride;
op->composite(paramInfo);
}
}
else {
op->composite(params);
}
}
QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const
{
QVector * ba = 0;
if (!d->conversionCache.hasLocalData()) {
ba = new QVector(size, '0');
d->conversionCache.setLocalData(ba);
} else {
ba = d->conversionCache.localData();
if ((quint8)ba->size() < size)
ba->resize(size);
}
return ba;
}
KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const
{
KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
if (!factory) return 0;
QPair model(colorModelId(), colorDepthId());
QList< QPair > models = factory->supportedModels();
if (models.isEmpty() || models.contains(model)) {
return factory->createTransformation(this, parameters);
} else {
// Find the best solution
// TODO use the color conversion cache
KoColorConversionTransformation* csToFallBack = 0;
KoColorConversionTransformation* fallBackToCs = 0;
KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs);
Q_ASSERT(csToFallBack);
Q_ASSERT(fallBackToCs);
KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
}
}
void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ihasTRC()){
//only linearise and crunch the luma if there's a TRC
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = pow(luma, 1/2.2);
luma = qMin(1.0, luma + step);
luma = pow(luma, 2.2);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
} else {
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = qMin(1.0, luma + step);
channelValues = fromHSY(&hue, &sat, &luma);
}
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ihasTRC()){
//only linearise and crunch the luma if there's a TRC
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = pow(luma, 1/2.2);
if (luma-step<0.0) {
luma=0.0;
} else {
luma -= step;
}
luma = pow(luma, 2.2);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
} else {
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (luma-step<0.0) {
luma=0.0;
} else {
luma -= step;
}
channelValues = fromHSY(&hue, &sat, &luma);
}
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
sat += step;
sat = qBound(0.0, sat, 1.0);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
sat -= step;
sat = qBound(0.0, sat, 1.0);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (hue+step>1.0){
hue=(hue+step)- 1.0;
} else {
hue += step;
}
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (hue-step<0.0){
hue=1.0-(step-hue);
} else {
hue -= step;
}
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
u += step;
u = qBound(0.0, u, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
u -= step;
u = qBound(0.0, u, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
v += step;
v = qBound(0.0, v, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i channelValues(channelnumber);
QVector channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;ilinearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
v -= step;
v = qBound(0.0, v, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;irgb8(dstProfile);
if (data)
this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
return img;
}
bool KoColorSpace::preferCompositionInSourceColorSpace() const
{
return false;
}
void KoColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const
{
/// Fallback implementation. All RGB color spaces have their own
/// implementation without any conversions.
const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
QScopedArrayPointer rgbBuffer(new quint8[(nPixels + 1) * rgbPixelSize]);
quint8 *rgbBrushColorBuffer = rgbBuffer.data() + nPixels * rgbPixelSize;
this->toRgbA16(dst, rgbBuffer.data(), nPixels);
this->toRgbA16(brushColor, rgbBrushColorBuffer, 1);
fillGrayBrushWithColorPreserveLightnessRGB(rgbBuffer.data(), brush, rgbBrushColorBuffer, nPixels);
this->fromRgbA16(rgbBuffer.data(), dst, nPixels);
}
diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc
index 3fbc2b1ada..cdb307bc07 100644
--- a/libs/ui/widgets/kis_advanced_color_space_selector.cc
+++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc
@@ -1,795 +1,801 @@
/*
* Copyright (C) 2007 Cyrille Berger
* Copyright (C) 2011 Boudewijn Rempt
* Copyright (C) 2011 Srikanth Tiyyagura
* Copyright (C) 2015 Wolthera van Hövell tot Westerflier
*
* 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.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_advanced_color_space_selector.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ui_wdgcolorspaceselectoradvanced.h"
#include
struct KisAdvancedColorSpaceSelector::Private {
Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector;
QString knsrcFile;
};
KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption)
: QDialog(parent)
, d(new Private)
{
setWindowTitle(caption);
d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced;
d->colorSpaceSelector->setupUi(this);
d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible));
fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem());
d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open"));
d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") );
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)),
this, SLOT(fillCmbDepths(KoID)));
connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)),
this, SLOT(fillLstProfiles()));
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)),
this, SLOT(fillLstProfiles()));
connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
this, SLOT(colorSpaceChanged()));
connect(this, SIGNAL(selectionChanged(bool)),
this, SLOT(fillDescription()));
connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint()));
connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint()));
connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile()));
connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept()));
connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject()));
fillLstProfiles();
}
KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector()
{
delete d->colorSpaceSelector;
delete d;
}
void KisAdvancedColorSpaceSelector::fillLstProfiles()
{
d->colorSpaceSelector->lstProfile->blockSignals(true);
const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem());
const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
d->colorSpaceSelector->lstProfile->clear();
QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
QStringList profileNames;
Q_FOREACH (const KoColorProfile *profile, profileList) {
profileNames.append(profile->name());
}
std::sort(profileNames.begin(), profileNames.end());
QListWidgetItem *defaultProfile = new QListWidgetItem;
defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"));
Q_FOREACH (QString stringName, profileNames) {
if (stringName == defaultProfileName) {
d->colorSpaceSelector->lstProfile->addItem(defaultProfile);
} else {
d->colorSpaceSelector->lstProfile->addItem(stringName);
}
}
d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile);
d->colorSpaceSelector->lstProfile->blockSignals(false);
colorSpaceChanged();
}
void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id)
{
KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem();
d->colorSpaceSelector->cmbColorDepth->clear();
QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible);
QList sortedDepths;
if (depths.contains(Integer8BitsColorDepthID)) {
sortedDepths << Integer8BitsColorDepthID;
}
if (depths.contains(Integer16BitsColorDepthID)) {
sortedDepths << Integer16BitsColorDepthID;
}
if (depths.contains(Float16BitsColorDepthID)) {
sortedDepths << Float16BitsColorDepthID;
}
if (depths.contains(Float32BitsColorDepthID)) {
sortedDepths << Float32BitsColorDepthID;
}
if (depths.contains(Float64BitsColorDepthID)) {
sortedDepths << Float64BitsColorDepthID;
}
d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths);
if (sortedDepths.contains(activeDepth)) {
d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth);
}
}
void KisAdvancedColorSpaceSelector::fillDescription()
{
QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A");
QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants.");
QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found");
QString whatIsColorant = i18n("Colorant in d50-adapted xyY.");
//set colorants
const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem());
QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
if (!profileList.isEmpty()) {
profileName = currentColorSpace()->profile()->name();
if (currentColorSpace()->profile()->hasColorants()){
QVector colorants = currentColorSpace()->profile()->getColorantsxyY();
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
//QString text = currentColorSpace()->profile()->info() + " =" +
d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint));
d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4));
d->colorSpaceSelector->TongueWidget->setToolTip(""+i18nc("@info:tooltip","This profile has the following xyY colorants:")+" |
---|
"+
i18n("Red:") +" | "+QString::number(colorants[0], 'f', 4) + " | " + QString::number(colorants[1], 'f', 4) + " | " + QString::number(colorants[2], 'f', 4)+" |
"+
i18n("Green:")+" | "+QString::number(colorants[3], 'f', 4) + " | " + QString::number(colorants[4], 'f', 4) + " | " + QString::number(colorants[5], 'f', 4)+" |
"+
i18n("Blue:") +" | "+QString::number(colorants[6], 'f', 4) + " | " + QString::number(colorants[7], 'f', 4) + " | " + QString::number(colorants[8], 'f', 4)+" |
");
} else {
QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2));
d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4));
d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip);
}
} else {
d->colorSpaceSelector->lblXYZ_W->setText(notApplicable);
d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip);
d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip);
}
//set TRC
QVector estimatedTRC(3);
QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: ");
QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC");
QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC.");
QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id();
if (profileList.isEmpty()) {
d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false);
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
else if (currentModelStr == "RGBA") {
QVector colorants = currentColorSpace()->profile()->getColorantsxyY();
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
if (currentColorSpace()->profile()->hasColorants()){
d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants);
} else {
colorants.fill(0.0);
d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants);
}
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF redcurve;
QPolygonF greencurve;
QPolygonF bluecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve);
} else {
QPolygonF curve = currentColorSpace()->estimatedTRCXYY();
- redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4);
- greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9);
- bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14);
+ int numPoints = curve.size() / 3;
+ for (int i = 0; i < numPoints; i++) {
+ redcurve << curve.at(i);
+ greencurve << curve.at(i + numPoints);
+ bluecurve << curve.at(i + 2*numPoints);
+ }
d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve);
}
if (estimatedTRC[0] == -1) {
d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+"");
} else {
d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+"");
}
}
else if (currentModelStr == "GRAYA") {
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
if (estimatedTRC[0] == -1) {
d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+"");
} else {
d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+"");
}
}
else if (currentModelStr == "CMYKA") {
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
QPolygonF cyancurve;
QPolygonF magentacurve;
QPolygonF yellowcurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
QPolygonF curve = currentColorSpace()->estimatedTRCXYY();
- cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4);
- magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9);
- yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14);
- tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19);
+ int numPoints = curve.size() / 4;
+ for (int i = 0; i < numPoints; i++) {
+ cyancurve << curve.at(i);
+ magentacurve << curve.at(i + numPoints);
+ yellowcurve << curve.at(i + 2*numPoints);
+ tonecurve << curve.at(i + 3*numPoints);
+ }
d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve);
}
d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK."));
}
else if (currentModelStr == "XYZA") {
QString estimatedCurve = " Estimated curve: ";
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+"");
}
else if (currentModelStr == "LABA") {
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setLABData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+"");
}
else if (currentModelStr == "YCbCrA") {
QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint);
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb."));
}
d->colorSpaceSelector->textProfileDescription->clear();
if (profileList.isEmpty()==false) {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("About ","About ") + currentColorSpace()->name() + "/" + profileName + "
");
d->colorSpaceSelector->textProfileDescription->append(""+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "
");
//d->colorSpaceSelector->textProfileDescription->append(""+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "
"); //This would work if people actually wrote the manufacturer into the manufacturer fiedl...
d->colorSpaceSelector->textProfileDescription->append(""+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "
");
} else {
d->colorSpaceSelector->textProfileDescription->append("" + profileName + "
");
}
if (currentModelStr == "RGBA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is RGB",
"RGB (Red, Green, Blue), is the color model used by screens and other light-based media.
"
"RGB is an additive color model: adding colors together makes them brighter. This color "
"model is the most extensive of all color models, and is recommended as a model for painting,"
"that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"
");
} else if (currentModelStr == "CMYKA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is CMYK",
"CMYK (Cyan, Magenta, Yellow, Key), "
"is the model used by printers and other ink-based media.
"
"CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK "
"profiles being very specific per printer, it is recommended to work in RGB space, and then later convert "
"to a CMYK profile, preferably one delivered by your printer.
"
"CMYK is not recommended for painting."
"Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"
");
} else if (currentModelStr == "XYZA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is XYZ",
"CIE XYZ"
"is the space determined by the CIE as the space that encompasses all other colors, and used to "
"convert colors between profiles. XYZ is an additive color model, meaning that adding colors together "
"makes them brighter. XYZ is not recommended for painting, but can be useful to encode in. The Tone Response "
"Curve is assumed to be linear.")+"
");
} else if (currentModelStr == "GRAYA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is Grayscale",
"Grayscale only allows for "
"gray values and transparent values. Grayscale images use half "
"the memory and disk space compared to an RGB image of the same bit-depth.
"
"Grayscale is useful for inking and grayscale images. In "
"Krita, you can mix Grayscale and RGB layers in the same image.")+"
");
} else if (currentModelStr == "LABA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is LAB",
"L*a*b. L stands for Lightness, "
"the a and b components represent color channels.
"
"L*a*b is a special model for color correction. It is based on human perception, meaning that it "
"tries to encode the difference in lightness, red-green balance and yellow-blue balance. "
"This makes it useful for color correction, but the vast majority of color maths in the blending "
"modes do not work as expected here.
"
"Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color "
"information. This colorspace is not recommended for painting, nor for export, "
"but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"
");
} else if (currentModelStr == "YCbCrA") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("If the selected model is YCbCr",
"YCbCr (Luma, Blue Chroma, Red Chroma), is a "
"model designed for video encoding. It is based on human perception, meaning that it tries to "
"encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in "
"this case is then a word indicating a special type of saturation, in these cases the saturation "
"of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It "
"is available to open up certain images correctly, but Krita does not currently ship a profile for "
"this due to lack of open source ICC profiles for YCrCb.")+"
");
}
QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id();
if (currentDepthStr == "U8") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("When the selected Bitdepth is 8",
"8 bit integer: The default number of colors per channel. Each channel will have 256 values available, "
"leading to a total amount of colors of 256 to the power of the number of channels. Recommended to use for images intended for the web, "
"or otherwise simple images.")+"
");
}
else if (currentDepthStr == "U16") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("When the selected Bitdepth is 16",
"16 bit integer: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large "
"color space, or just when you need more precise color blending. This does take twice as much space on "
"the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it "
"takes much more processing power. We recommend watching the RAM usage of the file carefully, or "
"otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization "
"when converting from 16 bit/channel to 8 bit/channel.")+"
");
}
else if (currentDepthStr == "F16") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("When the selected Bitdepth is 16 bit float",
"16 bit floating point: Also known as 'Half Floating Point', and the standard in VFX industry images. "
"16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need "
"more precise color blending. It being floating point is an absolute requirement for Scene Referred "
"(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image "
"of the same properties, and for some devices it takes much more processing power. We recommend watching "
"the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"
");
}
else if (currentDepthStr == "F32") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("When the selected Bitdepth is 32bit float",
"32 bit float point: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images "
"with a linear TRC, large color space, or just when you need more precise color blending. It being "
"floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times "
"as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for "
"some devices it takes much more processing power. We recommend watching the RAM usage of the file "
"carefully, or otherwise use 8 bit if your computer slows down.")+"
");
}
else if (currentDepthStr == "F64") {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\
"64 bit float point: 64 bit float is as precise as it gets in current technology, and this depth is used "
"most of the time for images that are generated or used as an input for software. It being floating point "
"is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on "
"the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes "
"much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use "
"8 bit if your computer slows down.")+"
");
}
if (profileList.isEmpty()==false) {
QString possibleConversionIntents = ""+i18n("The following conversion intents are possible: ")+"
";
if (currentColorSpace()->profile()->supportsPerceptual()){
possibleConversionIntents += "- "+i18n("Perceptual")+"
";
}
if (currentColorSpace()->profile()->supportsRelative()){
possibleConversionIntents += "- "+i18n("Relative Colorimetric")+"
";
}
if (currentColorSpace()->profile()->supportsAbsolute()){
possibleConversionIntents += "- "+i18n("Absolute Colorimetric")+"
";
}
if (currentColorSpace()->profile()->supportsSaturation()){
possibleConversionIntents += "- "+i18n("Saturation")+"
";
}
possibleConversionIntents += "
";
d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents);
}
if (profileName.contains("-elle-")) {
d->colorSpaceSelector->textProfileDescription->append(""+i18nc("These are Elle Stone's notes on her profiles that we ship.",
"
Extra notes on profiles by Elle Stone:
"
"Krita comes with a number of high quality profiles created by "
"Elle Stone. This is a summary. Please check "
"the full documentation as well.
"));
if (profileName.contains("ACES-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image "
"encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for "
"a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion "
"picture images regardless of source'.
"));
}
if (profileName.contains("ACEScg-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut "
"and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors "
"that fall just barely outside the area of real colors humans can see
"));
}
if (profileName.contains("ClayRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name "
"for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble "
"to the AdobeRGB 1998 color space specifications.
"
"The Adobe RGB 1998 color gamut covers a higher "
"percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable "
"cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. "
"BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's "
"wide gamut printers.
"
"The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's "
"high-end wide gamut monitors.
"));
}
if (profileName.contains("AllColorsRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"This profile's color gamut is roughly the same size and shape as the ACES color space gamut, "
"and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB "
"actually has a slightly larger color gamut (to capture some fringe colors that barely qualify "
"as real when viewed by the standard observer) and uses the D50 white point.
"
"Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely "
""
"Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs for more information about imaginary "
"colors.
"
"There is no particular reason why anyone would want to use this profile "
"for editing, unless one needs to make sure your color space really does hold all "
"possible real colors.
"));
}
if (profileName.contains("CIERGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"This profile is included mostly for its historical significance. "
"It's the color space that was used in the original color matching experiments "
"that led to the creation of the XYZ reference color space.
"
"The ASTM E white point "
"is probably the right E white point to use when making the CIERGB color space profile. "
"It's not clear to me what the correct CIERGB primaries really are. "
"Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. "
"Experts in the field contend that the real primaries "
"should be calculated from the spectral wavelengths, so I did.
"));
}
if (profileName.contains("IdentityRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"The IdentityRGB working space is included in the profile pack because it's a mathematically "
"obvious way to include all possible visible colors, though it has a higher percentage of "
"imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason "
"why you'd ever want to actually edit images in the IdentityRGB working space.
"));
}
if (profileName.contains("LargeRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) "
"as the base name for these profiles.
"
"Kodak designed the RIMM/ROMM (ProPhotoRGB) color "
"gamut to include all printable and most real world colors. It includes some imaginary colors "
"and excludes some of the real world blues and violet blues that can be captured by digital "
"cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by "
"some (and probably many) camera matrix input profiles.
"
"The ProPhotoRGB primaries are "
"hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, "
"other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit "
"as an RGB working space. Personally I recommend the Rec.2020 or ACEScg profiles over "
"ProPhotoRGB. But if you have an already well-established workflow using ProPhotoRGB, you "
"might find a shift to another RGB working space a little odd, at least at first, and so you "
"have to weight the pros and cons of changing your workflow.
"));
}
if (profileName.contains("Rec2020-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of "
"June 2015, very few (if any) display devices (and certainly no affordable display devices) can "
"display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are "
"already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the "
"digital darkroom Rec.2020 is much more suitable as a general RGB working space than the "
"exceedingly small sRGB color space.
"));
}
if (profileName.contains("sRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs "
"from the 1990s. sRGB is the standard color space for the world wide web and is still the best "
"choice for exporting images to the internet.
"
"The sRGB color gamut was a good match to "
"calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, "
"which often cannot display the more saturated sRGB blues and magentas (the good news: as technology "
"progresses, wider gamuts are trickling down to consumer grade monitors).
"
"Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated "
"camera raw files also often exceed the sRGB color gamut.
"
"As a very relevant aside, using perceptual "
"intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the "
"sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided "
"in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.
"));
}
if (profileName.contains("WideRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.
"
"WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors "
"as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles "
"from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe "
"switched their emphasis to the ProPhotoRGB color space.
"));
}
if (profileName.contains("Gray-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"These profiles are for use with RGB images that have been converted to monotone gray (black and white). "
"The main reason to convert from RGB to Gray is to save the file space needed to encode the image. "
"Google places a premium on fast-loading web pages, and images are one of the slower-loading elements "
"of a web page. So converting black and white images to Grayscale images does save some kilobytes. "
" For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.
"));
}
if (profileName.contains("-g10")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and "
"should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit "
"floating point, 32-bit integer). Many editing operations produce better results in linear gamma color "
"spaces.
"));
}
if (profileName.contains("-labl")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really "
"should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth "
"images.
"));
}
if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-rec709")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-rec709.icc' have approximately but not exactly "
"perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.
"));
}
if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"When editing 8-bit images, you should use a profile with a small color gamut and an approximately or "
"exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 "
"(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to "
"be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles "
"for 8-bit editing.
"));
}
if (profileName.contains("-V4-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. "
"This includes Krita, digiKam/showFoto, and GIMP 2.9.
"));
}
if (profileName.contains("-V2-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that "
"cannot read V4 profiles.
"));
}
}
d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start);
}
QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) {
QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4));
//A (0.451170, 0.40594) (2856K)(tungsten)
if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) &&
(whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){
name="A";
return name;
}
//B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete)
//C (0.31039, 0.31905) (6774K) (average/north sky daylight)(obsolete)
//D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant)
if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) &&
(whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){
name="D50";
return name;
}
//D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight)
if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) &&
(whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){
name="D55";
return name;
}
//D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default)
if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) &&
(whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){
name="D60";
return name;
}
//D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default)
//Elle's are old school with 0.3127 and 0.3289
if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) &&
(whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){
name="D65";
return name;
}
//D75 (0.29968, 0.31740) (7504K) (North sky Daylight)
if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) &&
(whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){
name="D75";
return name;
}
//E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default)
if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) &&
(whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){
name="E";
return name;
}
//The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.//
//F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent)
//F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent)
//F3 (0.41761, 0.38324) (3450K) (White Fluorescent)
//F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent)
//F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent)
//F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent)
//F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator)
//F8 (0.34902, 0.35939) (5000K) (D50 simulator)
//F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent)
//F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50)
//F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40)
//F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30)
return name;
}
const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace()
{
QString check = "";
if (d->colorSpaceSelector->lstProfile->currentItem()) {
check = d->colorSpaceSelector->lstProfile->currentItem()->text();
} else if (d->colorSpaceSelector->lstProfile->item(0)) {
check = d->colorSpaceSelector->lstProfile->item(0)->text();
}
return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(),
d->colorSpaceSelector->cmbColorDepth->currentItem().id(),
check);
}
void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id)
{
d->colorSpaceSelector->cmbColorModels->setCurrent(id);
fillLstProfiles();
fillCmbDepths(id);
}
void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id)
{
d->colorSpaceSelector->cmbColorDepth->setCurrent(id);
fillLstProfiles();
}
void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name)
{
QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith);
d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0));
}
void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace)
{
if (!colorSpace) {
return;
}
setCurrentColorModel(colorSpace->colorModelId());
setCurrentColorDepth(colorSpace->colorDepthId());
setCurrentProfile(colorSpace->profile()->name());
}
void KisAdvancedColorSpaceSelector::colorSpaceChanged()
{
bool valid = d->colorSpaceSelector->lstProfile->count() != 0;
emit(selectionChanged(valid));
if (valid) {
emit colorSpaceChanged(currentColorSpace());
}
}
void KisAdvancedColorSpaceSelector::installProfile()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC");
dialog.setCaption(i18n("Install Color Profiles"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile");
QStringList profileNames = dialog.filenames();
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
Q_ASSERT(iccEngine);
QString saveLocation = KoResourcePaths::saveLocation("icc_profiles");
Q_FOREACH (const QString &profileName, profileNames) {
QUrl file(profileName);
if (!QFile::copy(profileName, saveLocation + file.fileName())) {
dbgKrita << "Could not install profile!";
return;
}
iccEngine->addProfile(saveLocation + file.fileName());
}
fillLstProfiles();
}