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/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp
index 59dc873f00..2706cd7e85 100644
--- a/libs/ui/kis_popup_palette.cpp
+++ b/libs/ui/kis_popup_palette.cpp
@@ -1,988 +1,995 @@
/* This file is part of the KDE project
Copyright 2009 Vera Lukman
Copyright 2011 Sven Langkamp
Copyright 2016 Scott Petrovic
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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
Library General Public License for more details.
You should have received a copy of the GNU Library 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 "kis_canvas2.h"
#include "kis_config.h"
#include "kis_popup_palette.h"
#include "kis_favorite_resource_manager.h"
#include "kis_icon_utils.h"
#include "KisResourceServerProvider.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_signal_compressor.h"
#include "brushhud/kis_brush_hud.h"
#include "brushhud/kis_round_hud_button.h"
#include "kis_signals_blocker.h"
#include "kis_canvas_controller.h"
#include "kis_acyclic_signal_connector.h"
#include "KisMouseClickEater.h"
class PopupColorTriangle : public KoTriangleColorSelector
{
public:
PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent)
: KoTriangleColorSelector(displayRenderer, parent)
, m_dragging(false)
{
}
~PopupColorTriangle() override {}
void tabletEvent(QTabletEvent* event) override {
event->accept();
QMouseEvent* mouseEvent = 0;
// this will tell the pop-up palette widget to close
if(event->button() == Qt::RightButton) {
emit requestCloseContainer();
}
// ignore any tablet events that are done with the right click
// Tablet move events don't return a "button", so catch that too
if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove)
{
switch (event->type()) {
case QEvent::TabletPress:
mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(),
Qt::LeftButton, Qt::LeftButton, event->modifiers());
m_dragging = true;
mousePressEvent(mouseEvent);
break;
case QEvent::TabletMove:
mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(),
(m_dragging) ? Qt::LeftButton : Qt::NoButton,
(m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers());
mouseMoveEvent(mouseEvent);
break;
case QEvent::TabletRelease:
mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
m_dragging = false;
mouseReleaseEvent(mouseEvent);
break;
default: break;
}
}
delete mouseEvent;
}
private:
bool m_dragging;
};
KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager,
const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent)
: QWidget(parent, Qt::FramelessWindowHint)
, m_coordinatesConverter(coordinatesConverter)
, m_viewManager(viewManager)
, m_actionManager(viewManager->actionManager())
, m_resourceManager(manager)
, m_displayRenderer(displayRenderer)
, m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE))
, m_actionCollection(viewManager->actionCollection())
, m_acyclicConnector(new KisAcyclicSignalConnector(this))
, m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this))
{
// some UI controls are defined and created based off these variables
const int borderWidth = 3;
if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) {
KisVisualColorSelector *selector = new KisVisualColorSelector(this);
selector->setAcceptTabletEvents(true);
m_triangleColorSelector = selector;
}
else {
m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this);
connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide()));
}
m_triangleColorSelector->setDisplayRenderer(displayRenderer);
m_triangleColorSelector->setConfig(true,false);
m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth);
m_triangleColorSelector->resize(m_popupPaletteSize - 2*m_triangleColorSelector->x(), m_popupPaletteSize - 2*m_triangleColorSelector->y());
m_triangleColorSelector->setVisible(true);
KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8());
if (m_resourceManager) {
fgcolor = provider->fgColor();
}
m_triangleColorSelector->slotSetColor(fgcolor);
/**
* Tablet support code generates a spurious right-click right after opening
* the window, so we should ignore it. Next right-click will be used for
* closing the popup palette
*/
this->installEventFilter(m_clicksEater);
m_triangleColorSelector->installEventFilter(m_clicksEater);
QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse );
m_triangleColorSelector->setMask(maskedRegion);
//setAttribute(Qt::WA_TranslucentBackground, true);
connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)),
m_colorChangeCompressor.data(), SLOT(start()));
connect(m_colorChangeCompressor.data(), SIGNAL(timeout()),
SLOT(slotEmitColorChanged()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged()));
+ connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged()));
m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)),
this, SLOT(slotExternalFgColorChanged(KoColor)));
m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)),
m_resourceManager, SIGNAL(sigSetFGColor(KoColor)));
connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int)));
connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int)));
connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int)));
connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate()));
connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide()));
// This is used to handle a bug:
// If pop up palette is visible and a new colour is selected, the new colour
// will be added when the user clicks on the canvas to hide the palette
// In general, we want to be able to store recent color if the pop up palette
// is not visible
m_timer.setSingleShot(true);
connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer()));
connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor()));
connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool)));
setCursor(Qt::ArrowCursor);
setMouseTracking(true);
setHoveredPreset(-1);
setHoveredColor(-1);
setSelectedColor(-1);
m_brushHud = new KisBrushHud(provider, parent);
m_brushHud->setFixedHeight(int(m_popupPaletteSize));
m_brushHud->setVisible(false);
const int auxButtonSize = 35;
m_settingsButton = new KisRoundHudButton(this);
m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
auxButtonSize, auxButtonSize);
connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup()));
KisConfig cfg(true);
m_brushHudButton = new KisRoundHudButton(this);
m_brushHudButton->setCheckable(true);
m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
auxButtonSize, auxButtonSize);
connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool)));
m_brushHudButton->setChecked(cfg.showBrushHud());
// add some stuff below the pop-up palette that will make it easier to use for tablet people
QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout
QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom
QHBoxLayout* hLayout = new QHBoxLayout();
vLayout->addLayout(hLayout);
mirrorMode = new KisHighlightedToolButton(this);
mirrorMode->setFixedSize(35, 35);
mirrorMode->setToolTip(i18n("Mirror Canvas"));
mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas"));
canvasOnlyButton = new KisHighlightedToolButton(this);
canvasOnlyButton->setFixedSize(35, 35);
canvasOnlyButton->setToolTip(i18n("Canvas Only"));
canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only"));
zoomToOneHundredPercentButton = new QPushButton(this);
zoomToOneHundredPercentButton->setText(i18n("100%"));
zoomToOneHundredPercentButton->setFixedHeight(35);
zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%"));
connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked()));
zoomCanvasSlider = new QSlider(Qt::Horizontal, this);
zoomSliderMinValue = 10; // set in %
zoomSliderMaxValue = 200; // set in %
zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue);
zoomCanvasSlider->setFixedHeight(35);
zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent());
zoomCanvasSlider->setSingleStep(1);
zoomCanvasSlider->setPageStep(1);
connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int)));
connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed()));
connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased()));
slotUpdateIcons();
hLayout->addWidget(mirrorMode);
hLayout->addWidget(canvasOnlyButton);
hLayout->addWidget(zoomToOneHundredPercentButton);
hLayout->addWidget(zoomCanvasSlider);
setVisible(true);
setVisible(false);
opacityChange = new QGraphicsOpacityEffect(this);
setGraphicsEffect(opacityChange);
// Prevent tablet events from being captured by the canvas
setAttribute(Qt::WA_NoMousePropagation, true);
}
-void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color)
+void KisPopupPalette::slotDisplayConfigurationChanged()
{
+ // Visual Color Selector picks up color space from input
+ KoColor col = m_viewManager->canvasResourceProvider()->fgColor();
+ const KoColorSpace *paintingCS = m_displayRenderer->getPaintingColorSpace();
//hack to get around cmyk for now.
- if (color.colorSpace()->colorChannelCount()>3) {
- KoColor c(KoColorSpaceRegistry::instance()->rgb8());
- c.fromKoColor(color);
- m_triangleColorSelector->slotSetColor(c);
- } else {
- m_triangleColorSelector->slotSetColor(color);
+ if (paintingCS->colorChannelCount()>3) {
+ paintingCS = KoColorSpaceRegistry::instance()->rgb8();
}
+ m_triangleColorSelector->slotSetColorSpace(paintingCS);
+ m_triangleColorSelector->slotSetColor(col);
+}
+
+void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color)
+{
+ m_triangleColorSelector->slotSetColor(color);
}
void KisPopupPalette::slotEmitColorChanged()
{
if (isVisible()) {
update();
emit sigChangefGColor(m_triangleColorSelector->getCurrentColor());
}
}
//setting KisPopupPalette properties
int KisPopupPalette::hoveredPreset() const
{
return m_hoveredPreset;
}
void KisPopupPalette::setHoveredPreset(int x)
{
m_hoveredPreset = x;
}
int KisPopupPalette::hoveredColor() const
{
return m_hoveredColor;
}
void KisPopupPalette::setHoveredColor(int x)
{
m_hoveredColor = x;
}
int KisPopupPalette::selectedColor() const
{
return m_selectedColor;
}
void KisPopupPalette::setSelectedColor(int x)
{
m_selectedColor = x;
}
void KisPopupPalette::slotTriggerTimer()
{
m_timer.start(750);
}
void KisPopupPalette::slotEnableChangeFGColor()
{
emit sigEnableChangeFGColor(true);
}
void KisPopupPalette::slotZoomSliderChanged(int zoom) {
emit zoomLevelChanged(zoom);
}
void KisPopupPalette::slotZoomSliderPressed()
{
m_isZoomingCanvas = true;
}
void KisPopupPalette::slotZoomSliderReleased()
{
m_isZoomingCanvas = false;
}
void KisPopupPalette::adjustLayout(const QPoint &p)
{
KIS_ASSERT_RECOVER_RETURN(m_brushHud);
if (isVisible() && parentWidget()) {
float hudMargin = 30.0;
const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin
const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
QRect paletteRect = rect();
paletteRect.moveTo(p - paletteCenterOffset);
if (m_brushHudButton->isChecked()) {
m_brushHud->updateGeometry();
paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0);
}
paletteRect = kisEnsureInRect(paletteRect, fitRect);
move(paletteRect.topLeft());
m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0));
m_lastCenterPoint = p;
}
}
void KisPopupPalette::slotUpdateIcons()
{
this->setPalette(qApp->palette());
for(int i=0; ichildren().size(); i++) {
QWidget *w = qobject_cast(this->children().at(i));
if (w) {
w->setPalette(qApp->palette());
}
}
zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon());
m_brushHud->updateIcons();
m_settingsButton->setIcon(KisIconUtils::loadIcon("tag"));
m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right"));
}
void KisPopupPalette::showHudWidget(bool visible)
{
KIS_ASSERT_RECOVER_RETURN(m_brushHud);
const bool reallyVisible = visible && m_brushHudButton->isChecked();
if (reallyVisible) {
m_brushHud->updateProperties();
}
m_brushHud->setVisible(reallyVisible);
adjustLayout(m_lastCenterPoint);
KisConfig cfg(false);
cfg.setShowBrushHud(visible);
}
void KisPopupPalette::showPopupPalette(const QPoint &p)
{
showPopupPalette(!isVisible());
adjustLayout(p);
}
void KisPopupPalette::showPopupPalette(bool show)
{
if (show) {
// don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within
// the bounds and cause the canvas to jump between the slider's min and max
if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue &&
m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){
KisSignalsBlocker b(zoomCanvasSlider);
zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider
}
emit sigEnableChangeFGColor(!show);
} else {
emit sigTriggerTimer();
}
setVisible(show);
m_brushHud->setVisible(show && m_brushHudButton->isChecked());
}
//redefinition of setVariable function to change the scope to private
void KisPopupPalette::setVisible(bool b)
{
QWidget::setVisible(b);
}
void KisPopupPalette::setParent(QWidget *parent) {
m_brushHud->setParent(parent);
QWidget::setParent(parent);
}
QSize KisPopupPalette::sizeHint() const
{
return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below
}
void KisPopupPalette::resizeEvent(QResizeEvent*)
{
}
void KisPopupPalette::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
QPainter painter(this);
QPen pen(palette().color(QPalette::Text));
pen.setWidth(3);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// painting background color indicator
QPainterPath bgColor;
bgColor.addEllipse(QPoint( 50, 80), 30, 30);
painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor()));
painter.drawPath(bgColor);
// painting foreground color indicator
QPainterPath fgColor;
fgColor.addEllipse(QPoint( 60, 50), 30, 30);
painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor()));
painter.drawPath(fgColor);
// create a circle background that everything else will go into
QPainterPath backgroundContainer;
float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it
QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount,
m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
backgroundContainer.addEllipse( circleRect );
painter.fillPath(backgroundContainer,palette().brush(QPalette::Background));
painter.drawPath(backgroundContainer);
// create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas
// with the indicator
QPainterPath rotationTrackPath;
shrinkCircleAmount = 18;
QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount,
m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
rotationTrackPath.addEllipse( circleRect2 );
pen.setWidth(1);
painter.setPen(pen);
painter.drawPath(rotationTrackPath);
// this thing will help indicate where the starting brush preset is at.
// also what direction they go to give sor order to the presets populated
/*
pen.setWidth(6);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees
QPainterPath brushDir;
brushDir.arcMoveTo(circleRect, 60);
brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14);
painter.drawPath(brushDir);
brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6);
painter.drawPath(brushDir);
*/
// the following things needs to be based off the center, so let's translate the painter
painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
// create the canvas rotation handle
QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true);
painter.fillPath(rotationIndicator,palette().brush(QPalette::Text));
// hover indicator for the canvas rotation
if (m_isOverCanvasRotationIndicator == true) {
painter.save();
QPen pen(palette().color(QPalette::Highlight));
pen.setWidth(2);
painter.setPen(pen);
painter.drawPath(rotationIndicator);
painter.restore();
}
// create a reset canvas rotation indicator to bring the canvas back to 0 degrees
QPainterPath resetRotationIndicator = drawRotationIndicator(0, false);
QPen resetPen(palette().color(QPalette::Text));
resetPen.setWidth(1);
painter.save();
painter.setPen(resetPen);
painter.drawPath(resetRotationIndicator);
painter.restore();
// painting favorite brushes
QList images(m_resourceManager->favoritePresetImages());
// painting favorite brushes pixmap/icon
QPainterPath presetPath;
for (int pos = 0; pos < numSlots(); pos++) {
painter.save();
presetPath = createPathFromPresetIndex(pos);
if (pos < images.size()) {
painter.setClipPath(presetPath);
QRect bounds = presetPath.boundingRect().toAlignedRect();
painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
}
else {
painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it
}
QPen pen = painter.pen();
pen.setWidth(1);
painter.setPen(pen);
painter.drawPath(presetPath);
painter.restore();
}
if (hoveredPreset() > -1) {
presetPath = createPathFromPresetIndex(hoveredPreset());
QPen pen(palette().color(QPalette::Highlight));
pen.setWidth(3);
painter.setPen(pen);
painter.drawPath(presetPath);
}
// paint recent colors area.
painter.setPen(Qt::NoPen);
float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal();
// there might be no recent colors at the start, so paint a placeholder
if (m_resourceManager->recentColorsTotal() == 0) {
painter.setBrush(Qt::transparent);
QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
painter.drawPath(emptyRecentColorsPath);
} else {
for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) {
QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
//accessing recent color of index pos
painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) ));
painter.drawPath(recentColorsPath);
painter.rotate(rotationAngle);
}
}
// painting hovered color
if (hoveredColor() > -1) {
painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
if (m_resourceManager->recentColorsTotal() == 1) {
QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.drawPath(path_ColorDonut);
} else {
painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle);
QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
painter.drawPath(path);
painter.rotate(hoveredColor() * -1 * rotationAngle);
}
}
// painting selected color
if (selectedColor() > -1) {
painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
if (m_resourceManager->recentColorsTotal() == 1) {
QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.drawPath(path_ColorDonut);
} else {
painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle);
QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
painter.drawPath(path);
painter.rotate(selectedColor() * -1 * rotationAngle);
}
}
// if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better
if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) {
opacityChange->setOpacity(0.4);
} else {
opacityChange->setOpacity(1.0);
}
}
QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius)
{
QPainterPath path;
path.addEllipse(QPointF(x, y), outer_radius, outer_radius);
path.addEllipse(QPointF(x, y), inner_radius, inner_radius);
path.setFillRule(Qt::OddEvenFill);
return path;
}
QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit)
{
QPainterPath path;
path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit));
path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit,
360.0 / limit);
path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit,
- 360.0 / limit);
path.closeSubpath();
return path;
}
QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag)
{
// used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator,
// and another time by the reset canvas position
float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top
float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius
float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10);
QPainterPath canvasRotationIndicator;
int canvasIndicatorSize = 15;
int canvasIndicatorMiddle = canvasIndicatorSize / 2;
QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle,
canvasIndicatorSize, canvasIndicatorSize );
if (canDrag) {
m_canvasRotationIndicatorRect = indicatorRectangle;
} else {
m_resetCanvasRotationIndicatorRect = indicatorRectangle;
}
canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(),
indicatorRectangle.width(), indicatorRectangle.height() );
return canvasRotationIndicator;
}
void KisPopupPalette::mouseMoveEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
setToolTip(QString());
setHoveredPreset(-1);
setHoveredColor(-1);
// calculate if we are over the canvas rotation knob
// before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to
// correct them first before looking for a click event intersection
float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height());
if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) {
m_isOverCanvasRotationIndicator = true;
} else {
m_isOverCanvasRotationIndicator = false;
}
if (m_isRotatingCanvasIndicator) {
// we are rotating the canvas, so calculate the rotation angle based off the center
// calculate the angle we are at first
QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2);
float dX = point.x() - widgetCenterPoint.x();
float dY = point.y() - widgetCenterPoint.y();
float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle
finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up
float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out
KisCanvasController *canvasController =
dynamic_cast(m_viewManager->canvasBase()->canvasController());
canvasController->rotateCanvas(angleDifference);
emit sigUpdateCanvas();
}
// don't highlight the presets if we are in the middle of rotating the canvas
if (m_isRotatingCanvasIndicator == false) {
QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
{
int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets());
if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) {
setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name());
setHoveredPreset(pos);
}
}
if (pathColor.contains(point)) {
int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
setHoveredColor(pos);
}
}
}
update();
}
void KisPopupPalette::mousePressEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
if (event->button() == Qt::LeftButton) {
//in favorite brushes area
int pos = calculateIndex(point, m_resourceManager->numFavoritePresets());
if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()
&& isPointInPixmap(point, pos)) {
//setSelectedBrush(pos);
update();
}
if (m_isOverCanvasRotationIndicator) {
m_isRotatingCanvasIndicator = true;
}
// reset the canvas if we are over the reset canvas rotation indicator
float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height());
if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) {
float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs
KisCanvasController *canvasController =
dynamic_cast(m_viewManager->canvasBase()->canvasController());
canvasController->rotateCanvas(angleDifference);
emit sigUpdateCanvas();
}
}
}
void KisPopupPalette::slotShowTagsPopup()
{
KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
QStringList tags = rServer->tagNamesList();
std::sort(tags.begin(), tags.end());
if (!tags.isEmpty()) {
QMenu menu;
Q_FOREACH (const QString& tag, tags) {
menu.addAction(tag);
}
QAction *action = menu.exec(QCursor::pos());
if (action) {
m_resourceManager->setCurrentTag(action->text());
}
} else {
QWhatsThis::showText(QCursor::pos(),
i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here."));
}
}
void KisPopupPalette::slotZoomToOneHundredPercentClicked() {
QAction *action = m_actionCollection->action("zoom_to_100pct");
if (action) {
action->trigger();
}
// also move the zoom slider to 100% position so they are in sync
zoomCanvasSlider->setValue(100);
}
void KisPopupPalette::tabletEvent(QTabletEvent *event) {
event->ignore();
}
void KisPopupPalette::showEvent(QShowEvent *event)
{
m_clicksEater->reset();
QWidget::showEvent(event);
}
void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
if (event->buttons() == Qt::NoButton &&
event->button() == Qt::RightButton) {
showPopupPalette(false);
return;
}
m_isOverCanvasRotationIndicator = false;
m_isRotatingCanvasIndicator = false;
if (event->button() == Qt::LeftButton) {
QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
//in favorite brushes area
if (hoveredPreset() > -1) {
//setSelectedBrush(hoveredBrush());
emit sigChangeActivePaintop(hoveredPreset());
}
if (pathColor.contains(point)) {
int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
emit sigUpdateRecentColor(pos);
}
}
}
}
int KisPopupPalette::calculateIndex(QPointF point, int n)
{
calculatePresetIndex(point, n);
//translate to (0,0)
point.setX(point.x() - m_popupPaletteSize / 2);
point.setY(point.y() - m_popupPaletteSize / 2);
//rotate
float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x());
float radius = sqrt((float)point.x() * point.x() + point.y() * point.y());
point.setX(radius * cos(smallerAngle));
point.setY(radius * sin(smallerAngle));
//calculate brush index
int pos = floor(acos(point.x() / radius) * n / (2 * M_PI));
if (point.y() < 0) pos = n - pos - 1;
return pos;
}
bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos)
{
if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) {
return true;
}
return false;
}
KisPopupPalette::~KisPopupPalette()
{
}
QPainterPath KisPopupPalette::createPathFromPresetIndex(int index)
{
qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get
// the starting angle of the slice we need to draw. the negative sign makes us go clockwise.
// adding 90 degrees makes us start at the top. otherwise we would start at the right
qreal startingAngle = -(index * angleSlice) + 90;
// the radius will get smaller as the amount of presets shown increases. 10 slots == 41
qreal radians = qDegreesToRadians((360.0/10)/2);
qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2;
radians = qDegreesToRadians(angleSlice/2);
qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians));
//If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking.
qreal length = m_colorHistoryOuterRadius + presetRadius;
// can we can fit in a second row? We don't want the preset icons to get too tiny.
if (maxRadius > presetRadius) {
//redo all calculations assuming a second row.
if (numSlots() % 2) {
angleSlice = 360.0/(numSlots()+1);
startingAngle = -(index * angleSlice) + 90;
}
if (numSlots() != m_cachedNumSlots){
qreal tempRadius = presetRadius;
qreal distance = 0;
do{
tempRadius+=0.1;
// Calculate the XY of two adjectant circles using this tempRadius.
qreal length1 = m_colorHistoryOuterRadius + tempRadius;
qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius);
qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius;
qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius;
qreal startingAngle2 = -(index+1 * angleSlice) + 90;
qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius;
qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius;
// Use Pythagorean Theorem to calculate the distance between these two values.
qreal m1 = pathX2-pathX1;
qreal m2 = pathY2-pathY1;
distance = sqrt((m1*m1)+(m2*m2));
}
//As long at there's more distance than the radius of the two presets, continue increasing the radius.
while((tempRadius+1)*2 < distance);
m_cachedRadius = tempRadius;
}
m_cachedNumSlots = numSlots();
presetRadius = m_cachedRadius;
length = m_colorHistoryOuterRadius + presetRadius;
if (index % 2) {
length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius);
}
}
QPainterPath path;
qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius;
qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius;
qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size
path.addEllipse(pathX, pathY, pathDiameter, pathDiameter);
return path;
}
int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/)
{
for(int i = 0; i < numSlots(); i++)
{
QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2);
if(createPathFromPresetIndex(i).contains(adujustedPoint))
{
return i;
}
}
return -1;
}
int KisPopupPalette::numSlots()
{
KisConfig config(true);
return qMax(config.favoritePresets(), 10);
}
diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h
index d2df8e7232..51732f0e29 100644
--- a/libs/ui/kis_popup_palette.h
+++ b/libs/ui/kis_popup_palette.h
@@ -1,190 +1,191 @@
/* This file is part of the KDE project
Copyright 2009 Vera Lukman
Copyright 2016 Scott Petrovic
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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
Library General Public License for more details.
You should have received a copy of the GNU Library 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.
*/
#ifndef KIS_POPUP_PALETTE_H
#define KIS_POPUP_PALETTE_H
#include
#include
#include
#include
#include "KisViewManager.h"
#include "kactioncollection.h"
#include "kis_tool_button.h"
#include "KisHighlightedToolButton.h"
#include
class KisFavoriteResourceManager;
class QWidget;
class KoColor;
class KoTriangleColorSelector;
class KisSignalCompressor;
class KisBrushHud;
class KisRoundHudButton;
class KisCanvasResourceProvider;
class KisVisualColorSelector;
class KisAcyclicSignalConnector;
class KisMouseClickEater;
class KisPopupPalette : public QWidget
{
Q_OBJECT
Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset)
Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor)
Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor)
public:
KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer,
KisCanvasResourceProvider *provider, QWidget *parent = 0);
~KisPopupPalette() override;
QSize sizeHint() const override;
void showPopupPalette(const QPoint&);
void showPopupPalette(bool b);
//functions to set up selectedBrush
void setSelectedBrush(int x);
int selectedBrush() const;
//functions to set up selectedColor
void setSelectedColor(int x);
int selectedColor() const;
void setParent(QWidget *parent);
void tabletEvent(QTabletEvent *event) override;
protected:
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent*) override;
void resizeEvent(QResizeEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void mousePressEvent(QMouseEvent*) override;
//functions to calculate index of favorite brush or recent color in array
//n is the total number of favorite brushes or recent colors
int calculateIndex(QPointF, int n);
int calculatePresetIndex(QPointF, int n);
//functions to set up hoveredBrush
void setHoveredPreset(int x);
int hoveredPreset() const;
//functions to set up hoveredColor
void setHoveredColor(int x);
int hoveredColor() const;
private:
void setVisible(bool b) override;
QPainterPath drawDonutPathFull(int, int, int, int);
QPainterPath drawDonutPathAngle(int, int, int);
QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag);
bool isPointInPixmap(QPointF&, int pos);
QPainterPath createPathFromPresetIndex(int index);
int numSlots();
void adjustLayout(const QPoint &p);
private:
int m_hoveredPreset {0};
int m_hoveredColor {0};
int m_selectedColor {0};
KisCoordinatesConverter *m_coordinatesConverter;
KisViewManager *m_viewManager;
KisActionManager *m_actionManager;
KisFavoriteResourceManager *m_resourceManager;
KisColorSelectorInterface *m_triangleColorSelector {0};
const KoColorDisplayRendererInterface *m_displayRenderer;
QScopedPointer m_colorChangeCompressor;
KActionCollection *m_actionCollection;
QTimer m_timer;
KisBrushHud *m_brushHud {0};
float m_popupPaletteSize {385.0};
float m_colorHistoryInnerRadius {72.0};
qreal m_colorHistoryOuterRadius {92.0};
KisRoundHudButton *m_settingsButton {0};
KisRoundHudButton *m_brushHudButton {0};
QPoint m_lastCenterPoint;
QRect m_canvasRotationIndicatorRect;
QRect m_resetCanvasRotationIndicatorRect;
bool m_isOverCanvasRotationIndicator {false};
bool m_isRotatingCanvasIndicator {false};
bool m_isZoomingCanvas {false};
KisHighlightedToolButton *mirrorMode {0};
KisHighlightedToolButton *canvasOnlyButton {0};
QPushButton *zoomToOneHundredPercentButton {0};
QSlider *zoomCanvasSlider {0};
int zoomSliderMinValue {10};
int zoomSliderMaxValue {200};
KisAcyclicSignalConnector *m_acyclicConnector = 0;
int m_cachedNumSlots {0};
qreal m_cachedRadius {0.0};
// updates the transparency and effects of the whole widget
QGraphicsOpacityEffect *opacityChange {0};
KisMouseClickEater *m_clicksEater;
Q_SIGNALS:
void sigChangeActivePaintop(int);
void sigUpdateRecentColor(int);
void sigChangefGColor(const KoColor&);
void sigUpdateCanvas();
void zoomLevelChanged(int);
// These are used to handle a bug:
// If pop up palette is visible and a new colour is selected, the new colour
// will be added when the user clicks on the canvas to hide the palette
// In general, we want to be able to store recent color if the pop up palette
// is not visible
void sigEnableChangeFGColor(bool);
void sigTriggerTimer();
public Q_SLOTS:
void slotUpdateIcons();
private Q_SLOTS:
+ void slotDisplayConfigurationChanged();
void slotExternalFgColorChanged(const KoColor &color);
void slotEmitColorChanged();
void slotSetSelectedColor(int x) { setSelectedColor(x); update(); }
void slotTriggerTimer();
void slotEnableChangeFGColor();
void slotUpdate() { update(); }
void slotHide() { showPopupPalette(false); }
void slotShowTagsPopup();
void showHudWidget(bool visible);
void slotZoomToOneHundredPercentClicked();
void slotZoomSliderChanged(int zoom);
void slotZoomSliderPressed();
void slotZoomSliderReleased();
};
#endif // KIS_POPUP_PALETTE_H
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();
}
diff --git a/libs/widgets/KisColorSelectorInterface.cpp b/libs/widgets/KisColorSelectorInterface.cpp
index 840b621023..93d9b0d960 100644
--- a/libs/widgets/KisColorSelectorInterface.cpp
+++ b/libs/widgets/KisColorSelectorInterface.cpp
@@ -1,20 +1,25 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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
+
+void KisColorSelectorInterface::slotSetColorSpace(const KoColorSpace *cs)
+{
+ Q_UNUSED(cs)
+}
diff --git a/libs/widgets/KisColorSelectorInterface.h b/libs/widgets/KisColorSelectorInterface.h
index 993196c15b..e98f04db06 100644
--- a/libs/widgets/KisColorSelectorInterface.h
+++ b/libs/widgets/KisColorSelectorInterface.h
@@ -1,56 +1,67 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#ifndef KISCOLORSELECTORINTERFACE_H
#define KISCOLORSELECTORINTERFACE_H
#include "kritawidgets_export.h"
#include
#include
class KoColorDisplayRendererInterface;
+class KoColorSpace;
class KRITAWIDGETS_EXPORT KisColorSelectorInterface : public QWidget {
Q_OBJECT
public:
KisColorSelectorInterface(QWidget *parent = 0)
: QWidget(parent)
{}
~KisColorSelectorInterface() override {}
virtual void setConfig(bool forceCircular, bool forceSelfUpdate)
{
Q_UNUSED(forceCircular);
Q_UNUSED(forceSelfUpdate);
}
virtual void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer)
{
Q_UNUSED(displayRenderer);
}
virtual KoColor getCurrentColor() const = 0;
Q_SIGNALS:
void sigNewColor(const KoColor &c);
public Q_SLOTS:
virtual void slotSetColor(const KoColor &c) = 0;
+ /**
+ * @brief slotSetColorSpace
+ * Set the color space the selector should cover
+ *
+ * This is mostly a hint to decide visual presentation.
+ * Internal processing may be in a different color space and
+ * input conversion shall be handled by the selector itself.
+ * Calling this voids the currently selected color.
+ */
+ virtual void slotSetColorSpace(const KoColorSpace *cs);
};
#endif // KISCOLORSELECTORINTERFACE_H
diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp
index e55b563a04..ae1d13303c 100644
--- a/libs/widgets/KisDlgInternalColorSelector.cpp
+++ b/libs/widgets/KisDlgInternalColorSelector.cpp
@@ -1,385 +1,386 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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
#include
#include
#include
#include
#include
#include
#include
#include "KoColorSpaceRegistry.h"
#include
#include
#include
#include
#include
#include
#include "kis_signal_compressor.h"
#include "KoColorDisplayRendererInterface.h"
#include "kis_spinbox_color_selector.h"
#include "KisDlgInternalColorSelector.h"
#include "ui_WdgDlgInternalColorSelector.h"
#include "kis_config_notifier.h"
#include "kis_color_input.h"
#include "kis_icon_utils.h"
#include "KisSqueezedComboBox.h"
std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = 0;
struct KisDlgInternalColorSelector::Private
{
bool allowUpdates = true;
KoColor currentColor;
KoColor previousColor;
KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8());
const KoColorSpace *currentColorSpace;
bool lockUsedCS = false;
bool chooseAlpha = false;
KisSignalCompressor *compressColorChanges;
const KoColorDisplayRendererInterface *displayRenderer;
KisHexColorInput *hexColorInput = 0;
KisPaletteModel *paletteModel = 0;
KisPaletteListWidget *paletteChooser = 0;
KisScreenColorPickerBase *screenColorPicker = 0;
};
KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer)
: QDialog(parent)
, m_d(new Private)
{
setModal(config.modal);
setFocusPolicy(Qt::ClickFocus);
m_ui = new Ui_WdgDlgInternalColorSelector();
m_ui->setupUi(this);
setWindowTitle(caption);
m_d->currentColor = color;
m_d->currentColorSpace = m_d->currentColor.colorSpace();
m_d->displayRenderer = displayRenderer;
m_ui->spinboxselector->slotSetColor(color);
connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
m_ui->spinboxHSXSelector->attachToSelector(m_ui->visualSelector);
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->visualSelector->setConfig(false, config.modal);
if (config.visualColorSelector) {
connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
connect(m_ui->visualSelector, SIGNAL(sigColorModelChanged()), this, SLOT(slotSelectorModelChanged()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged()));
} else {
m_ui->visualSelector->hide();
}
m_ui->visualSelector->slotSetColor(color);
m_d->paletteChooser = new KisPaletteListWidget(this);
m_d->paletteModel = new KisPaletteModel(this);
m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library"));
m_ui->paletteBox->setPaletteModel(m_d->paletteModel);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
m_ui->cmbNameList->setCompanionView(m_ui->paletteBox);
connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*)));
connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor)));
// For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not.
if (config.paletteBox) {
//TODO: Add disable signal as well. Might be not necessary...?
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString());
KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer();
KoColorSet *savedPal = rServer->resourceByName(paletteName);
if (savedPal) {
this->slotChangePalette(savedPal);
} else {
if (rServer->resources().count()) {
savedPal = rServer->resources().first();
if (savedPal) {
this->slotChangePalette(savedPal);
}
}
}
connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this,
SLOT(slotColorUpdated(KoColor)));
m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser);
} else {
m_ui->paletteBox->setEnabled(false);
m_ui->cmbNameList->setEnabled(false);
m_ui->bnPaletteChooser->setEnabled(false);
}
if (config.prevNextButtons) {
m_ui->currentColor->setColor(m_d->currentColor);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setColor(m_d->currentColor);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*)));
} else {
m_ui->currentColor->hide();
m_ui->previousColor->hide();
}
if (config.hexInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB);
m_d->hexColorInput->update();
connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex()));
m_ui->rightPane->addWidget(m_d->hexColorInput);
m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space."));
}
// KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it.
m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget));
if (s_screenColorPickerFactory) {
m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget);
m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker);
if (config.screenColorPicker) {
connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor)));
} else {
m_d->screenColorPicker->hide();
}
}
m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this);
connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor()));
connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection);
connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection);
connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp()));
}
KisDlgInternalColorSelector::~KisDlgInternalColorSelector()
{
delete m_ui;
}
void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor)
{
// not-so-nice solution: if someone calls this slot directly and that code was
// triggered by our compressor signal, our compressor is technically the sender()!
if (sender() == m_d->compressColorChanges) {
return;
}
// Do not accept external updates while a color update emit is pending;
// Note: Assumes external updates only come from parent(), a separate slot might be better
if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) {
// Enforce palette colors
KConfigGroup group(KSharedConfig::openConfig(), "");
if (group.readEntry("colorsettings/forcepalettecolors", false)) {
newColor = m_ui->paletteBox->closestColor(newColor);
}
if (m_d->lockUsedCS){
newColor.convertTo(m_d->currentColorSpace);
- m_d->currentColor = newColor;
} else {
- m_d->currentColor = newColor;
+ colorSpaceChanged(newColor.colorSpace());
}
+ m_d->currentColor = newColor;
updateAllElements(QObject::sender());
}
}
void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch)
{
slotColorUpdated(patch->color());
}
void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs)
{
if (cs == m_d->currentColorSpace) {
return;
}
m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile());
m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace);
- m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace);
+ m_ui->visualSelector->slotSetColorSpace(m_d->currentColorSpace);
}
void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs)
{
colorSpaceChanged(cs);
if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) {
m_d->currentColor.convertTo(m_d->currentColorSpace);
m_ui->spinboxselector->slotSetColor(m_d->currentColor);
+ m_ui->visualSelector->slotSetColor(m_d->currentColor);
}
m_d->lockUsedCS = true;
}
void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
m_d->displayRenderer = displayRenderer;
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
} else {
m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption)
{
Config config = Config();
KisDlgInternalColorSelector dialog(parent, color, config, caption);
dialog.setPreviousColor(color);
dialog.exec();
return dialog.getCurrentColor();
}
KoColor KisDlgInternalColorSelector::getCurrentColor()
{
return m_d->currentColor;
}
void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha)
{
m_d->chooseAlpha = chooseAlpha;
}
void KisDlgInternalColorSelector::setPreviousColor(KoColor c)
{
m_d->previousColor = c;
}
void KisDlgInternalColorSelector::reject()
{
slotColorUpdated(m_d->previousColor);
QDialog::reject();
}
void KisDlgInternalColorSelector::updateAllElements(QObject *source)
{
//update everything!!!
if (source != m_ui->spinboxselector) {
m_ui->spinboxselector->slotSetColor(m_d->currentColor);
}
if (source != m_ui->visualSelector) {
m_ui->visualSelector->slotSetColor(m_d->currentColor);
}
if (source != m_d->hexColorInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput->update();
}
if (source != m_ui->paletteBox) {
m_ui->paletteBox->selectClosestColor(m_d->currentColor);
}
m_ui->previousColor->setColor(m_d->previousColor);
m_ui->currentColor->setColor(m_d->currentColor);
if (source && source != this->parent()) {
m_d->allowUpdates = false;
m_d->compressColorChanges->start();
}
if (m_d->screenColorPicker) {
m_d->screenColorPicker->updateIcons();
}
}
void KisDlgInternalColorSelector::slotSelectorModelChanged()
{
if (m_ui->visualSelector->isHSXModel()) {
QString label;
switch (m_ui->visualSelector->getColorModel()) {
case KisVisualColorSelector::HSV:
label = i18n("HSV");
break;
case KisVisualColorSelector::HSL:
label = i18n("HSL");
break;
case KisVisualColorSelector::HSI:
label = i18n("HSI");
break;
case KisVisualColorSelector::HSY:
label = i18n("HSY'");
break;
default:
label = i18n("Unknown");
}
if (m_ui->tabWidget->count() == 1) {
m_ui->tabWidget->addTab(m_ui->tab_hsx, label);
}
else {
m_ui->tabWidget->setTabText(1, label);
}
}
else {
if (m_ui->tabWidget->count() == 2) {
m_ui->tabWidget->removeTab(1);
}
}
}
void KisDlgInternalColorSelector::endUpdateWithNewColor()
{
emit signalForegroundColorChosen(m_d->currentColor);
m_d->allowUpdates = true;
}
void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *)
{
//setPreviousColor();
}
void KisDlgInternalColorSelector::slotFinishUp()
{
setPreviousColor(m_d->currentColor);
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
if (m_d->paletteModel) {
if (m_d->paletteModel->colorSet()) {
cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name());
}
}
}
void KisDlgInternalColorSelector::slotSetColorFromHex()
{
slotColorUpdated(m_d->sRGB);
}
void KisDlgInternalColorSelector::slotChangePalette(KoColorSet *set)
{
if (!set) {
return;
}
m_d->paletteModel->setPalette(set);
}
void KisDlgInternalColorSelector::showEvent(QShowEvent *event)
{
updateAllElements(0);
QDialog::showEvent(event);
}
diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp
index 9fafd6bb61..ee3b4720c1 100644
--- a/libs/widgets/KisVisualColorSelector.cpp
+++ b/libs/widgets/KisVisualColorSelector.cpp
@@ -1,616 +1,657 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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 "KisVisualColorSelector.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorConversions.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoColorProfile.h"
#include "KoChannelInfo.h"
#include
#include
#include "kis_signal_compressor.h"
#include "kis_debug.h"
#include "KisVisualColorSelectorShape.h"
#include "KisVisualRectangleSelectorShape.h"
#include "KisVisualTriangleSelectorShape.h"
#include "KisVisualEllipticalSelectorShape.h"
struct KisVisualColorSelector::Private
{
KoColor currentcolor;
const KoColorSpace *currentCS {0};
QList widgetlist;
bool acceptTabletEvents {false};
bool circular {false};
bool exposureSupported {false};
bool isRGBA {false};
bool isLinear {false};
+ bool applyGamma {false};
int displayPosition[4]; // map channel index to storage index for display
int colorChannelCount {0};
+ qreal gamma {2.2};
+ qreal lumaRGB[3] {0.2126, 0.7152, 0.0722};
QVector4D channelValues;
QVector4D channelMaxValues;
ColorModel model {ColorModel::None};
const KoColorDisplayRendererInterface *displayRenderer {0};
KisColorSelectorConfiguration acs_config;
KisSignalCompressor *updateTimer {0};
};
KisVisualColorSelector::KisVisualColorSelector(QWidget *parent)
: KisColorSelectorInterface(parent)
, m_d(new Private)
{
this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE);
connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection);
}
KisVisualColorSelector::~KisVisualColorSelector()
{
delete m_d->updateTimer;
}
void KisVisualColorSelector::slotSetColor(const KoColor &c)
{
- m_d->currentcolor = c;
- if (m_d->currentCS != c.colorSpace()) {
- slotsetColorSpace(c.colorSpace());
+ if (!m_d->currentCS) {
+ m_d->currentcolor = c;
+ slotSetColorSpace(c.colorSpace());
}
else {
+ m_d->currentcolor = c.convertedTo(m_d->currentCS);
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
}
}
if (isHSXModel()) {
emit sigHSXChanged(QVector3D(m_d->channelValues));
}
}
-void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs)
+void KisVisualColorSelector::slotSetColorSpace(const KoColorSpace *cs)
{
- if (m_d->currentCS != cs) {
- m_d->currentCS = cs;
+ if (!m_d->currentCS || *m_d->currentCS != *cs) {
+ const KoColorSpace *csNew = cs;
+
+ // PQ color space is not very suitable for color picking, substitute with linear one
+ if (cs->colorModelId() == RGBAColorModelID &&
+ cs->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()) {
+
+ csNew = KoColorSpaceRegistry::instance()->
+ colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(),
+ KoColorSpaceRegistry::instance()->p2020G10Profile());
+ }
+ m_d->currentCS = csNew;
+ m_d->currentcolor = KoColor(csNew);
slotRebuildSelectors();
}
}
void KisVisualColorSelector::slotSetHSX(const QVector3D &hsx)
{
if (isHSXModel()) {
m_d->channelValues = QVector4D(hsx, 0.f);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
}
KoColor newColor = convertShapeCoordsToKoColor(QVector4D(hsx));
if (newColor != m_d->currentcolor) {
m_d->currentcolor = newColor;
emit sigNewColor(m_d->currentcolor);
}
}
}
void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate)
{
Q_UNUSED(forceSelfUpdate)
m_d->circular = forceCircular;
}
void KisVisualColorSelector::setAcceptTabletEvents(bool on)
{
m_d->acceptTabletEvents = on;
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setAcceptTabletEvents(on);
}
}
KoColor KisVisualColorSelector::getCurrentColor() const
{
return m_d->currentcolor;
}
QVector4D KisVisualColorSelector::getChannelValues() const
{
return m_d->channelValues;
}
KisVisualColorSelector::ColorModel KisVisualColorSelector::getColorModel() const
{
return m_d->model;
}
bool KisVisualColorSelector::isHSXModel() const
{
return (m_d->model >= ColorModel::HSV && m_d->model <= ColorModel::HSY);
}
KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const
{
KoColor c(m_d->currentCS);
QVector4D baseValues(coordinates);
QVector channelValues(c.colorSpace()->channelCount());
channelValues.fill(1.0);
if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) {
if (m_d->model == ColorModel::HSV) {
HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
}
else if (m_d->model == ColorModel::HSL) {
HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
}
else if (m_d->model == ColorModel::HSI) {
// why suddenly qreal?
qreal temp[3];
HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]);
baseValues.setX(temp[0]);
baseValues.setY(temp[1]);
baseValues.setZ(temp[2]);
}
else /*if (m_d->model == ColorModel::HSY)*/ {
- QVector luma= m_d->currentCS->lumaCoefficients();
qreal temp[3];
- HSYToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2],
- luma[0], luma[1], luma[2]);
+ qreal Y = pow(coordinates.z(), m_d->gamma);
+ HSYToRGB(coordinates.x(), coordinates.y(), Y, &temp[0], &temp[1], &temp[2],
+ m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
baseValues.setX(temp[0]);
baseValues.setY(temp[1]);
baseValues.setZ(temp[2]);
+ if (!m_d->isLinear) {
+ // Note: not all profiles define a TRC necessary for (de-)linearization,
+ // substituting with a linear profiles would be better
+ QVector temp({baseValues[0], baseValues[1], baseValues[2]});
+ if (m_d->exposureSupported) {
+ m_d->currentCS->profile()->delinearizeFloatValue(temp);
+ }
+ else {
+ m_d->currentCS->profile()->delinearizeFloatValueFast(temp);
+ }
+ baseValues = QVector4D(temp[0], temp[1], temp[2], 0);
+ }
}
- if (m_d->isLinear) {
+ if (m_d->applyGamma) {
for (int i=0; i<3; i++) {
baseValues[i] = pow(baseValues[i], 2.2);
}
}
}
if (m_d->exposureSupported) {
baseValues *= m_d->channelMaxValues;
}
for (int i=0; icolorChannelCount; i++) {
channelValues[m_d->displayPosition[i]] = baseValues[i];
}
c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues);
return c;
}
QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const
{
if (c.colorSpace() != m_d->currentCS) {
c.convertTo(m_d->currentCS);
}
QVector channelValues (c.colorSpace()->channelCount());
channelValues.fill(1.0);
m_d->currentCS->normalisedChannelsValue(c.data(), channelValues);
QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0);
for (int i =0; icolorChannelCount; i++) {
channelValuesDisplay[i] = channelValues[m_d->displayPosition[i]];
}
if (m_d->exposureSupported) {
channelValuesDisplay /= m_d->channelMaxValues;
}
if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) {
if (m_d->isRGBA == true) {
- if (m_d->isLinear) {
+ if (m_d->applyGamma) {
for (int i=0; i<3; i++) {
channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2);
}
}
if (m_d->model == ColorModel::HSV) {
QVector3D hsv;
RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]);
hsv[0] /= 360;
coordinates = QVector4D(hsv, 0.f);
} else if (m_d->model == ColorModel::HSL) {
QVector3D hsl;
RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]);
hsl[0] /= 360;
coordinates = QVector4D(hsl, 0.f);
} else if (m_d->model == ColorModel::HSI) {
qreal hsi[3];
RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]);
coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f);
} else if (m_d->model == ColorModel::HSY) {
- QVector luma = m_d->currentCS->lumaCoefficients();
+ if (!m_d->isLinear) {
+ // Note: not all profiles define a TRC necessary for (de-)linearization,
+ // substituting with a linear profiles would be better
+ QVector temp({channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2]});
+ m_d->currentCS->profile()->linearizeFloatValue(temp);
+ channelValuesDisplay = QVector4D(temp[0], temp[1], temp[2], 0);
+ }
qreal hsy[3];
- RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], luma[0], luma[1], luma[2]);
+ RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2],
+ m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
+ hsy[2] = pow(hsy[2], 1/m_d->gamma);
coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f);
}
// if we couldn't determine a hue, keep last value
if (coordinates[0] < 0) {
coordinates[0] = m_d->channelValues[0];
}
for (int i=0; i<3; i++) {
coordinates[i] = qBound(0.f, coordinates[i], 1.f);
}
}
} else {
for (int i=0; i<4; i++) {
coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f);
}
}
return coordinates;
}
void KisVisualColorSelector::configurationChanged()
{
if (m_d->updateTimer) {
m_d->updateTimer->start();
}
}
void KisVisualColorSelector::slotDisplayConfigurationChanged()
{
Q_ASSERT(m_d->displayRenderer);
if (m_d->currentCS)
{
m_d->channelMaxValues = QVector4D(1, 1, 1, 1);
QList channels = m_d->currentCS->channels();
for (int i=0; icolorChannelCount; ++i)
{
m_d->channelMaxValues[i] = m_d->displayRenderer->maxVisibleFloatValue(channels[m_d->displayPosition[i]]);
}
// need to re-scale our normalized channel values on exposure changes:
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
}
if (isHSXModel()) {
emit sigHSXChanged(QVector3D(m_d->channelValues));
}
}
}
void KisVisualColorSelector::slotRebuildSelectors()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
ColorModel oldModel = m_d->model;
QList channelList = m_d->currentCS->channels();
int cCount = 0;
Q_FOREACH(const KoChannelInfo *channel, channelList)
{
if (channel->channelType() != KoChannelInfo::ALPHA)
{
m_d->displayPosition[cCount] = channel->displayPosition();
++cCount;
}
}
Q_ASSERT_X(cCount < 5, "", "unsupported channel count!");
m_d->colorChannelCount = cCount;
// TODO: The following is done because the IDs are actually strings. Ideally, in the future, we
// refactor everything so that the IDs are actually proper enums or something faster.
if (m_d->displayRenderer
&& (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID
|| m_d->currentCS->colorDepthId() == Float32BitsColorDepthID
|| m_d->currentCS->colorDepthId() == Float64BitsColorDepthID)
&& m_d->currentCS->colorModelId() != LABAColorModelID
&& m_d->currentCS->colorModelId() != CMYKAColorModelID) {
m_d->exposureSupported = true;
} else {
m_d->exposureSupported = false;
}
m_d->isRGBA = (m_d->currentCS->colorModelId() == RGBAColorModelID);
const KoColorProfile *profile = m_d->currentCS->profile();
m_d->isLinear = (profile && profile->isLinear());
qDeleteAll(children());
m_d->widgetlist.clear();
// TODO: Layout only used for monochrome selector currently, but always present
QLayout *layout = new QHBoxLayout;
//recreate all the widgets.
m_d->model = KisVisualColorSelector::Channel;
if (m_d->currentCS->colorChannelCount() == 1) {
KisVisualColorSelectorShape *bar;
if (m_d->circular==false) {
bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20);
bar->setMaximumWidth(width()*0.1);
bar->setMaximumHeight(height());
}
else {
bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored);
layout->setMargin(0);
}
connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
layout->addWidget(bar);
m_d->widgetlist.append(bar);
}
else if (m_d->currentCS->colorChannelCount() == 3) {
KisVisualColorSelector::ColorModel modelS = KisVisualColorSelector::HSV;
int channel1 = 0;
int channel2 = 1;
int channel3 = 2;
switch(m_d->acs_config.subTypeParameter)
{
case KisColorSelectorConfiguration::H:
channel1 = 0;
break;
case KisColorSelectorConfiguration::hsyS:
case KisColorSelectorConfiguration::hsiS:
case KisColorSelectorConfiguration::hslS:
case KisColorSelectorConfiguration::hsvS:
channel1 = 1;
break;
case KisColorSelectorConfiguration::V:
case KisColorSelectorConfiguration::L:
case KisColorSelectorConfiguration::I:
case KisColorSelectorConfiguration::Y:
channel1 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter");
}
switch(m_d->acs_config.mainTypeParameter)
{
case KisColorSelectorConfiguration::hsySH:
modelS = KisVisualColorSelector::HSY;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsiSH:
modelS = KisVisualColorSelector::HSI;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hslSH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsvSH:
modelS = KisVisualColorSelector::HSV;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::YH:
modelS = KisVisualColorSelector::HSY;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::LH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::IH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::VH:
modelS = KisVisualColorSelector::HSV;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SY:
modelS = KisVisualColorSelector::HSY;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SI:
modelS = KisVisualColorSelector::HSI;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SL:
modelS = KisVisualColorSelector::HSL;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SV:
case KisColorSelectorConfiguration::SV2:
modelS = KisVisualColorSelector::HSV;
channel2 = 1;
channel3 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter");
}
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
modelS = KisVisualColorSelector::HSV;
//Triangle only really works in HSV mode.
}
- // L*a*b* mimics the HSX selector types, but model is still Channel
+ // L*a*b* mimics the HSX selector types, but model is still Channel (until someone implements LCH)
if (m_d->isRGBA) {
m_d->model = modelS;
+ m_d->gamma = cfg.readEntry("gamma", 2.2);
+ m_d->applyGamma = (m_d->isLinear && modelS != ColorModel::HSY);
+ // Note: only profiles that define colorants will give precise luma coefficients.
+ // Maybe using the explicitly set values of the Advanced Color Selector is better?
+ QVector luma = m_d->currentCS->lumaCoefficients();
+ memcpy(m_d->lumaRGB, luma.constData(), 3*sizeof(qreal));
}
KisVisualColorSelectorShape *bar;
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) {
bar = new KisVisualRectangleSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored);
} else {
// Accessing bar below would crash since it's not initialized.
// Hopefully this can never happen.
warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape";
Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape");
return;
}
m_d->widgetlist.append(bar);
KisVisualColorSelectorShape *block;
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
else {
block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
m_d->widgetlist.append(block);
}
else if (m_d->currentCS->colorChannelCount() == 4) {
KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 0, 1);
KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 2, 3);
connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
m_d->widgetlist.append(block);
m_d->widgetlist.append(block2);
}
this->setLayout(layout);
// make sure we call "our" resize function
KisVisualColorSelector::resizeEvent(0);
// finally recalculate channel values and update widgets
if (m_d->displayRenderer) {
slotDisplayConfigurationChanged();
}
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
shape->setAcceptTabletEvents(m_d->acceptTabletEvents);
// if this widget is currently visible, new children are hidden by default
shape->show();
}
if (m_d->model != oldModel) {
emit sigColorModelChanged();
}
}
void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) {
m_d->displayRenderer = displayRenderer;
if (m_d->widgetlist.size()>0) {
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setDisplayRenderer(displayRenderer);
}
}
connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection);
slotDisplayConfigurationChanged();
}
void KisVisualColorSelector::slotCursorMoved(QPointF pos)
{
const KisVisualColorSelectorShape *shape = qobject_cast(sender());
Q_ASSERT(shape);
QVector channels = shape->getChannels();
m_d->channelValues[channels.at(0)] = pos.x();
if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) {
m_d->channelValues[channels.at(1)] = pos.y();
}
KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues);
if (newColor != m_d->currentcolor) {
m_d->currentcolor = newColor;
emit sigNewColor(m_d->currentcolor);
}
if (isHSXModel()) {
emit sigHSXChanged(QVector3D(m_d->channelValues));
}
Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) {
if (widget != shape){
widget->setChannelValues(m_d->channelValues, false);
}
}
}
void KisVisualColorSelector::resizeEvent(QResizeEvent *) {
int sizeValue = qMin(width(), height());
int borderWidth = qMax(sizeValue*0.1, 20.0);
QRect newrect(0,0, this->geometry().width(), this->geometry().height());
if (!m_d->currentCS) {
- slotsetColorSpace(m_d->currentcolor.colorSpace());
+ slotSetColorSpace(m_d->currentcolor.colorSpace());
}
if (m_d->currentCS->colorChannelCount()==3) {
// set border width first, else the resized painting may have happened already, and we'd have to re-render
m_d->widgetlist.at(0)->setBorderWidth(borderWidth);
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) {
m_d->widgetlist.at(0)->resize(borderWidth, sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect));
}
}
else if (m_d->currentCS->colorChannelCount() == 4) {
int sizeBlock = qMin(width()/2 - 8, height());
m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock);
m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock);
}
}
diff --git a/libs/widgets/KisVisualColorSelector.h b/libs/widgets/KisVisualColorSelector.h
index 1dde10ab7b..ceae1aa13f 100644
--- a/libs/widgets/KisVisualColorSelector.h
+++ b/libs/widgets/KisVisualColorSelector.h
@@ -1,103 +1,103 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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.
*/
#ifndef KIS_VISUAL_COLOR_SELECTOR_H
#define KIS_VISUAL_COLOR_SELECTOR_H
#include
#include
#include
#include
#include
#include
#include
#include "KoColorDisplayRendererInterface.h"
#include "KisColorSelectorConfiguration.h"
#include "KisColorSelectorInterface.h"
#include "kritawidgets_export.h"
/**
* @brief The KisVisualColorSelector class
*
* This gives a color selector box that draws gradients and everything.
*
* Unlike other color selectors, this one draws the full gamut of the given
* colorspace.
*/
class KRITAWIDGETS_EXPORT KisVisualColorSelector : public KisColorSelectorInterface
{
Q_OBJECT
public:
enum ColorModel { None, Channel, HSV, HSL, HSI, HSY, YUV };
explicit KisVisualColorSelector(QWidget *parent = 0);
~KisVisualColorSelector() override;
/**
* @brief setConfig
* @param forceCircular
* Force circular is for space where you only have room for a circular selector.
* @param forceSelfUpdate
* ignored, can possibly be removed from parent class now
*/
void setConfig(bool forceCircular, bool forceSelfUpdate) override;
void setAcceptTabletEvents(bool on);
KoColor getCurrentColor() const override;
QVector4D getChannelValues() const;
ColorModel getColorModel() const;
bool isHSXModel() const;
KoColor convertShapeCoordsToKoColor(const QVector4D &coordinates) const;
QVector4D convertKoColorToShapeCoordinates(KoColor c) const;
public Q_SLOTS:
void slotSetColor(const KoColor &c) override;
- void slotsetColorSpace(const KoColorSpace *cs);
+ void slotSetColorSpace(const KoColorSpace *cs) override;
void slotSetHSX(const QVector3D &hsx);
void configurationChanged();
void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) override;
private Q_SLOTS:
void slotCursorMoved(QPointF pos);
void slotDisplayConfigurationChanged();
void slotRebuildSelectors();
Q_SIGNALS:
/**
* @brief sigColorModelChanged is emitted whenever the selector's color model changes.
*
* This is mostly relevant for configuration changes where the same RGB model
* gets represented in a different way like HSV, HSL etc. so the values of
* sigHSXChanged() change meaning.
*
* @see getColorModel()
*/
void sigColorModelChanged();
void sigHSXChanged(const QVector3D &hsx);
protected:
void resizeEvent(QResizeEvent *) override;
private:
struct Private;
const QScopedPointer m_d;
};
#endif
diff --git a/libs/widgets/KisVisualColorSelectorShape.cpp b/libs/widgets/KisVisualColorSelectorShape.cpp
index 1c2ce3de86..6417fb1fc7 100644
--- a/libs/widgets/KisVisualColorSelectorShape.cpp
+++ b/libs/widgets/KisVisualColorSelectorShape.cpp
@@ -1,380 +1,388 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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 "KisVisualColorSelectorShape.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorConversions.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoChannelInfo.h"
#include
#include
#include "kis_signal_compressor.h"
#include "kis_debug.h"
struct KisVisualColorSelectorShape::Private
{
QImage gradient;
QImage alphaMask;
QImage fullSelector;
bool imagesNeedUpdate { true };
bool alphaNeedsUpdate { true };
bool acceptTabletEvents { false };
QPointF currentCoordinates; // somewhat redundant?
+ QPointF dragStart;
QVector4D currentChannelValues;
Dimensions dimension;
const KoColorSpace *colorSpace;
int channel1;
int channel2;
const KoColorDisplayRendererInterface *displayRenderer = 0;
};
KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent,
KisVisualColorSelectorShape::Dimensions dimension,
const KoColorSpace *cs,
int channel1,
int channel2,
const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private)
{
m_d->dimension = dimension;
m_d->colorSpace = cs;
int maxchannel = m_d->colorSpace->colorChannelCount()-1;
m_d->channel1 = qBound(0, channel1, maxchannel);
m_d->channel2 = qBound(0, channel2, maxchannel);
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setDisplayRenderer(displayRenderer);
}
KisVisualColorSelectorShape::~KisVisualColorSelectorShape()
{
}
QPointF KisVisualColorSelectorShape::getCursorPosition() {
return m_d->currentCoordinates;
}
void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
{
QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
if (newPos != m_d->currentCoordinates)
{
m_d->currentCoordinates = newPos;
// for internal consistency, because we have a bit of redundancy here
m_d->currentChannelValues[m_d->channel1] = newPos.x();
if (m_d->dimension == Dimensions::twodimensional){
m_d->currentChannelValues[m_d->channel2] = newPos.y();
}
update();
if (signal){
emit sigCursorMoved(newPos);
}
}
}
void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor)
{
//qDebug() << this << "setChannelValues";
m_d->currentChannelValues = channelValues;
if (setCursor) {
m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
qBound(0.f, channelValues[m_d->channel2], 1.f));
}
else {
// for internal consistency, because we have a bit of redundancy here
m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
if (m_d->dimension == Dimensions::twodimensional){
m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
}
}
m_d->imagesNeedUpdate = true;
update();
}
void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on)
{
m_d->acceptTabletEvents = on;
}
void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
if (m_d->displayRenderer) {
m_d->displayRenderer->disconnect(this);
}
m_d->displayRenderer = displayRenderer;
} else {
m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KisVisualColorSelectorShape::forceImageUpdate()
{
//qDebug() << this << "forceImageUpdate";
m_d->alphaNeedsUpdate = true;
m_d->imagesNeedUpdate = true;
}
QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){
QColor col;
KoColor color = c;
if (m_d->displayRenderer) {
color.convertTo(m_d->displayRenderer->getPaintingColorSpace());
col = m_d->displayRenderer->toQColor(c);
} else {
col = c.toQColor();
}
return col;
}
// currently unused?
void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2)
{
//qDebug() << this << "slotSetActiveChannels";
int maxchannel = m_d->colorSpace->colorChannelCount()-1;
m_d->channel1 = qBound(0, channel1, maxchannel);
m_d->channel2 = qBound(0, channel2, maxchannel);
m_d->imagesNeedUpdate = true;
update();
}
bool KisVisualColorSelectorShape::imagesNeedUpdate() const {
return m_d->imagesNeedUpdate;
}
QImage KisVisualColorSelectorShape::getImageMap()
{
//qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
if (m_d->imagesNeedUpdate) {
// Fill a buffer with the right kocolors
m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize());
m_d->imagesNeedUpdate = false;
}
return m_d->gradient;
}
const QImage KisVisualColorSelectorShape::getAlphaMask() const
{
if (m_d->alphaNeedsUpdate) {
m_d->alphaMask = renderAlphaMask();
m_d->alphaNeedsUpdate = false;
}
return m_d->alphaMask;
}
QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
{
Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize());
QImage image;
// Convert the buffer to a qimage
if (m_d->displayRenderer) {
image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height());
}
else {
image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
// safeguard:
if (image.isNull())
{
image = QImage(width(), height(), QImage::Format_ARGB32);
image.fill(Qt::black);
}
return image;
}
QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const
{
const KisVisualColorSelector *selector = qobject_cast(parent());
Q_ASSERT(selector);
// Hi-DPI aware rendering requires that we determine the device pixel dimension;
// actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
const qreal deviceDivider = 1.0 / devicePixelRatioF();
const int deviceWidth = qCeil(width() * devicePixelRatioF());
const int deviceHeight = qCeil(height() * devicePixelRatioF());
quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize();
QScopedArrayPointer raw(new quint8[imageSize] {});
quint8 *dataPtr = raw.data();
QVector4D coordinates = channelValues;
QImage alpha = getAlphaMask();
bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
checkAlpha = false;
}
KoColor filler(Qt::white, m_d->colorSpace);
for (int y = 0; y < deviceHeight; y++) {
const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
for (int x=0; x < deviceWidth; x++) {
if (!checkAlpha || alphaLine[x]) {
QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
coordinates[m_d->channel1] = newcoordinate.x();
if (m_d->dimension == Dimensions::twodimensional) {
coordinates[m_d->channel2] = newcoordinate.y();
}
KoColor c = selector->convertShapeCoordsToKoColor(coordinates);
memcpy(dataPtr, c.data(), pixelSize);
}
else {
// need to write a color with non-zero alpha, otherwise the display converter
// will for some arcane reason crop the final QImage and screw rendering
memcpy(dataPtr, filler.data(), pixelSize);
}
dataPtr += pixelSize;
}
}
QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
image.setDevicePixelRatio(devicePixelRatioF());
if (!alpha.isNull()) {
QPainter painter(&image);
// transfer alphaMask to Alpha channel
painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
painter.drawImage(0, 0, alpha);
}
return image;
}
QImage KisVisualColorSelectorShape::renderAlphaMask() const
{
return QImage();
}
+QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
+{
+ Q_UNUSED(dragStart)
+ return convertWidgetCoordinateToShapeCoordinate(pos);
+}
+
void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
- QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->localPos());
+ m_d->dragStart = e->localPos();
+ QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
setCursorPosition(coordinates, true);
}
else {
e->ignore();
}
}
void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton) {
- QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->localPos());
+ QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
setCursorPosition(coordinates, true);
} else {
e->ignore();
}
}
void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() != Qt::LeftButton) {
e->ignore();
}
}
void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event)
{
// only accept tablet events that are associated to "left" button
// NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
if (m_d->acceptTabletEvents &&
(event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
{
event->accept();
switch (event->type()) {
case QEvent::TabletPress: {
QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
event->globalPosF(), event->button(), event->buttons(),
event->modifiers(), Qt::MouseEventSynthesizedByApplication);
mousePressEvent(&mouseEvent);
break;
}
case QEvent::TabletMove: {
QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
event->globalPosF(), event->button(), event->buttons(),
event->modifiers(), Qt::MouseEventSynthesizedByApplication);
mouseMoveEvent(&mouseEvent);
break;
}
case QEvent::TabletRelease: {
QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
event->globalPosF(), event->button(), event->buttons(),
event->modifiers(), Qt::MouseEventSynthesizedByApplication);
mouseReleaseEvent(&mouseEvent);
break;
}
default:
event->ignore();
}
}
}
void KisVisualColorSelectorShape::paintEvent(QPaintEvent*)
{
QPainter painter(this);
drawCursor();
painter.drawImage(0,0,m_d->fullSelector);
}
void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *)
{
forceImageUpdate();
setMask(getMaskMap());
}
KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const
{
return m_d->dimension;
}
void KisVisualColorSelectorShape::setFullImage(QImage full)
{
m_d->fullSelector = full;
}
KoColor KisVisualColorSelectorShape::getCurrentColor()
{
const KisVisualColorSelector *selector = qobject_cast(parent());
if (selector)
{
return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues);
}
return KoColor(m_d->colorSpace);
}
QVector KisVisualColorSelectorShape::getChannels() const
{
QVector channels(2);
channels[0] = m_d->channel1;
channels[1] = m_d->channel2;
return channels;
}
diff --git a/libs/widgets/KisVisualColorSelectorShape.h b/libs/widgets/KisVisualColorSelectorShape.h
index 5b42d2a18b..8121ff8713 100644
--- a/libs/widgets/KisVisualColorSelectorShape.h
+++ b/libs/widgets/KisVisualColorSelectorShape.h
@@ -1,228 +1,232 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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.
*/
#ifndef KIS_VISUAL_COLOR_SELECTOR_SHAPE_H
#define KIS_VISUAL_COLOR_SELECTOR_SHAPE_H
#include
#include
#include
#include
#include
#include
#include
#include "KoColorDisplayRendererInterface.h"
#include "KisVisualColorSelector.h"
#include "KisColorSelectorConfiguration.h"
/**
* @brief The KisVisualColorSelectorShape class
* A 2d widget can represent at maximum 2 coordinates.
* So first decide howmany coordinates you need. (onedimensional, or twodimensional)
* Then the model, (Channel, HSV, HSL, HSI, YUV). Channel is the raw color channels.
* When it finds a non-implemented feature it'll return to Channel.
* Then, select the channels you wish to be affected. This uses the model, so for cmyk
* the channel is c=0, m=1, y=2, k=3, but for hsv, hue=0, sat=1, and val=2
* These can also be set with 'slotsetactive channels'.
* Then finally, connect the displayrenderer, you can also do this with 'setdisplayrenderer'
*
* Either way, this class is made to be subclassed, with a few virtuals so that the geometry
* can be calculated properly.
*/
class KisVisualColorSelectorShape : public QWidget
{
Q_OBJECT
public:
/**
* @brief The Dimensions enum
* Whether or not the shape is single or two dimensional.
**/
enum Dimensions{onedimensional, twodimensional};
enum ColorModel{Channel, HSV, HSL, HSI, HSY, YUV};
explicit KisVisualColorSelectorShape(QWidget *parent,
KisVisualColorSelectorShape::Dimensions dimension,
const KoColorSpace *cs,
int channel1, int channel2,
const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance());
~KisVisualColorSelectorShape() override;
/**
* @brief getCursorPosition
* @return current cursor position in shape-coordinates.
*/
QPointF getCursorPosition();
/**
* @brief getDimensions
* @return whether this is a single or twodimensional widget.
*/
Dimensions getDimensions() const;
/**
* @brief getPixmap
* @return the pixmap of the gradient, for drawing on with a subclass.
* the pixmap will not change unless 'm_d->setPixmap=true' which is toggled by
* refresh and update functions.
*/
bool imagesNeedUpdate() const;
QImage getImageMap();
const QImage getAlphaMask() const;
/**
* @brief setFullImage
* Set the full widget image to be painted.
* @param full this should be the full image.
*/
void setFullImage(QImage full);
/**
* @brief getCurrentColor
* @return the current kocolor
*/
KoColor getCurrentColor();
/**
* @brief setDisplayRenderer
* disconnect the old display renderer if needed and connect the new one.
* @param displayRenderer
*/
void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer);
/**
* @brief getColorFromConverter
* @param c a koColor.
* @return get the qcolor from the given kocolorusing this widget's display renderer.
*/
QColor getColorFromConverter(KoColor c);
/**
* @brief getSpaceForSquare
* @param geom the full widget rectangle
* @return rectangle with enough space for second widget
*/
virtual QRect getSpaceForSquare(QRect geom) = 0;
virtual QRect getSpaceForCircle(QRect geom) = 0;
virtual QRect getSpaceForTriangle(QRect geom) = 0;
/**
* @brief forceImageUpdate
* force the image to recache.
*/
void forceImageUpdate();
/**
* @brief setBorderWidth
* set the border of the single dimensional selector.
* @param width
*/
virtual void setBorderWidth(int width) = 0;
/**
* @brief getChannels
* get used channels
* @return
*/
QVector getChannels() const;
/**
* @brief setCursorPosition
* Set the cursor to normalized shape coordinates. This will only repaint the cursor.
* @param position normalized shape coordinates ([0,1] range, not yet transformed to actual channel values!)
* @param signal if true, emit a sigCursorMoved signal
*/
void setCursorPosition(QPointF position, bool signal = false);
/**
* @brief setChannelValues
* Set the current channel values;
* Note that channel values controlled by the shape itself have no effect unless setCursor is true.
* This will trigger a full widget repaint.
* @param position normalized shape coordinates ([0,1] range)
* these are not yet transformed to color space specific ranges!
* @param setCursor if true, sets the cursor too, otherwise the shape-controlled channels are not set
*/
void setChannelValues(QVector4D channelValues, bool setCursor);
void setAcceptTabletEvents(bool on);
Q_SIGNALS:
void sigCursorMoved(QPointF pos);
public Q_SLOTS:
/**
* @brief slotSetActiveChannels
* Change the active channels if necessary.
* @param channel1 used by single and twodimensional widgets.
* @param channel2 only used by twodimensional widgets.
*/
void slotSetActiveChannels(int channel1, int channel2);
protected:
/**
* @brief convertImageMap
* convert image data containing raw KoColor data into a QImage
* @param data must point to memory of size width()*height()*pixelSize
* @param size the number of bytes to read from data, must match aforementioned cirteria
* @return the converted QImage guaranteed to match the widget size (black content on failure)
*/
QImage convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const;
/**
* @brief renderBackground
* Render the widget background visible inside the widget's mask in current color space
* Rendering shall be done with the conversion functions of KisVisualColorSelector
* @param data points to zero-initialized memory of size width()*height()*pixelSize
* @param pixelSize the data size to transfer from KoColor::data() to data per pixel
* in the current color space
* @param channelValues the normalized channel values of the currently picked color
*/
virtual QImage renderBackground(const QVector4D &channelValues, quint32 pixelSize) const;
/**
* @brief render the alpha mask for the widget background
* the returned image is expected to be QImage::Format_Alpha8
*/
virtual QImage renderAlphaMask() const;
+ /**
+ * @brief default implementation just calls convertWidgetCoordinateToShapeCoordinate(pos)
+ */
+ virtual QPointF mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void tabletEvent(QTabletEvent* event) override;
void paintEvent(QPaintEvent*) override;
void resizeEvent(QResizeEvent *) override;
private:
struct Private;
const QScopedPointer m_d;
/**
* @brief convertShapeCoordinateToWidgetCoordinate
* @return take the position in the shape and convert it to screen coordinates.
*/
virtual QPointF convertShapeCoordinateToWidgetCoordinate(QPointF) const = 0;
/**
* @brief convertWidgetCoordinateToShapeCoordinate
* Convert a coordinate in the widget's height/width to a shape coordinate.
* @param coordinate the position your wish to have the shape coordinates of.
*/
virtual QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const = 0;
/**
* @brief getPixmap
* @return the pixmap of this shape.
*/
virtual QRegion getMaskMap() = 0;
virtual void drawCursor() = 0;
};
#endif
diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.cpp b/libs/widgets/KisVisualEllipticalSelectorShape.cpp
index c25a1bdacd..abbfa8275b 100644
--- a/libs/widgets/KisVisualEllipticalSelectorShape.cpp
+++ b/libs/widgets/KisVisualEllipticalSelectorShape.cpp
@@ -1,242 +1,257 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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 "KisVisualEllipticalSelectorShape.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorConversions.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoChannelInfo.h"
#include
#include
#include "kis_signal_compressor.h"
#include "kis_debug.h"
#include "kis_global.h"
KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(QWidget *parent,
Dimensions dimension,
const KoColorSpace *cs,
int channel1, int channel2,
const KoColorDisplayRendererInterface *displayRenderer,
int barWidth,
singelDTypes d)
: KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer)
{
//qDebug() << "creating KisVisualEllipticalSelectorShape" << this;
m_type = d;
m_barWidth = barWidth;
}
KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape()
{
//qDebug() << "deleting KisVisualEllipticalSelectorShape" << this;
}
QSize KisVisualEllipticalSelectorShape::sizeHint() const
{
return QSize(180,180);
}
+
void KisVisualEllipticalSelectorShape::setBorderWidth(int width)
{
m_barWidth = width;
forceImageUpdate();
update();
}
QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom)
{
int sizeValue = qMin(width(),height());
QRect b(geom.left(), geom.top(), sizeValue, sizeValue);
QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) );
radius.setAngle(135);
QPointF tl = radius.p2();
radius.setAngle(315);
QPointF br = radius.p2();
QRect r(tl.toPoint(), br.toPoint());
return r;
}
QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom)
{
int sizeValue = qMin(width(),height());
QRect b(geom.left(), geom.top(), sizeValue, sizeValue);
QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth);
QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth);
QRect r(tl.toPoint(), br.toPoint());
return r;
}
QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom)
{
int sizeValue = qMin(width(),height());
QPointF center(0.5 * width(), 0.5 * height());
qreal radius = 0.5 * sizeValue - (m_barWidth + 4);
QLineF rLine(center, QPointF(center.x() + radius, center.y()));
rLine.setAngle(330);
QPoint br(rLine.p2().toPoint());
//QPoint br(qCeil(rLine.p2().x()), qCeil(rLine.p2().y()));
QPoint tl(width() - br.x(), m_barWidth + 4);
QRect bound(tl, br);
// adjust with triangle default margin for cursor rendering
// it's not +5 because above calculation is for pixel center and ignores
// the fact that dimensions are then effectively 1px smaller...
bound.adjust(-5, -5, 4, 4);
return bound.intersected(geom);
}
QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const
{
qreal offset = 7.0;
qreal a = (qreal)width()*0.5;
QPointF center(a, a);
QLineF line(center, QPoint((m_barWidth*0.5),a));
qreal angle = coordinate.x()*360.0;
angle = 360.0 - fmod(angle+180.0, 360.0);
if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) {
angle = (coordinate.x()/2)*360.0;
angle = fmod((angle+270.0), 360.0);
}
line.setAngle(angle);
if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) {
line.setLength(qMin(coordinate.y()*(a-offset), a-offset));
}
return line.p2();
}
QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const
{
//default implementation:
qreal x = 0.5;
qreal y = 1.0;
qreal offset = 7.0;
QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center();
qreal a = (qreal(this->width()) / qreal(2));
qreal xRel = center.x()-coordinate.x();
qreal yRel = center.y()-coordinate.y();
qreal radius = sqrt(xRel*xRel+yRel*yRel);
if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){
qreal angle = atan2(yRel, xRel);
angle = kisRadiansToDegrees(angle);
angle = fmod(angle+360, 360.0);
x = angle/360.0;
if (getDimensions()==KisVisualColorSelectorShape::twodimensional) {
y = qBound(0.0,radius/(a-offset), 1.0);
}
} else {
qreal angle = atan2(xRel, yRel);
angle = kisRadiansToDegrees(angle);
angle = fmod(angle+180, 360.0);
if (angle>180.0) {
angle = 360.0-angle;
}
x = (angle/360.0)*2;
if (getDimensions()==KisVisualColorSelectorShape::twodimensional) {
y = qBound(0.0,(radius+offset)/a, 1.0);
}
}
return QPointF(x, y);
}
+QPointF KisVisualEllipticalSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
+{
+ QPointF pos2(pos);
+ if (m_type == KisVisualEllipticalSelectorShape::borderMirrored) {
+ qreal h_center = width()/2.0;
+ bool start_left = dragStart.x() < h_center;
+ bool cursor_left = pos.x() < h_center;
+ if (start_left != cursor_left) {
+ pos2.setX(h_center);
+ }
+ }
+ return convertWidgetCoordinateToShapeCoordinate(pos2);
+}
+
QRegion KisVisualEllipticalSelectorShape::getMaskMap()
{
QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse);
if (getDimensions()==KisVisualColorSelectorShape::onedimensional) {
mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse));
}
return mask;
}
QImage KisVisualEllipticalSelectorShape::renderAlphaMask() const
{
// Hi-DPI aware rendering requires that we determine the device pixel dimension;
// actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
const int deviceWidth = qCeil(width() * devicePixelRatioF());
const int deviceHeight = qCeil(height() * devicePixelRatioF());
QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8);
alphaMask.fill(0);
alphaMask.setDevicePixelRatio(devicePixelRatioF());
QPainter painter(&alphaMask);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::white);
painter.setPen(Qt::NoPen);
painter.drawEllipse(2, 2, width() - 4, height() - 4);
//painter.setBrush(Qt::black);
if (getDimensions() == KisVisualColorSelectorShape::onedimensional) {
painter.setCompositionMode(QPainter::CompositionMode_Clear);
painter.drawEllipse(m_barWidth - 2, m_barWidth - 2, width() - 2*(m_barWidth-2), height() - 2*(m_barWidth-2));
}
return alphaMask;
}
void KisVisualEllipticalSelectorShape::drawCursor()
{
//qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate();
QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition());
QImage fullSelector = getImageMap();
QColor col = getColorFromConverter(getCurrentColor());
QPainter painter;
painter.begin(&fullSelector);
painter.setRenderHint(QPainter::Antialiasing);
QBrush fill;
fill.setStyle(Qt::SolidPattern);
int cursorwidth = 5;
if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) {
painter.setPen(Qt::white);
fill.setColor(Qt::white);
painter.setBrush(fill);
painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth);
QPointF mirror(width() - cursorPoint.x(), cursorPoint.y());
painter.drawEllipse(mirror, cursorwidth, cursorwidth);
fill.setColor(col);
painter.setPen(Qt::black);
painter.setBrush(fill);
painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1);
painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1);
} else {
painter.setPen(Qt::white);
fill.setColor(Qt::white);
painter.setBrush(fill);
painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth);
fill.setColor(col);
painter.setPen(Qt::black);
painter.setBrush(fill);
painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0);
}
painter.end();
setFullImage(fullSelector);
}
diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.h b/libs/widgets/KisVisualEllipticalSelectorShape.h
index ed2028f1cd..a484468096 100644
--- a/libs/widgets/KisVisualEllipticalSelectorShape.h
+++ b/libs/widgets/KisVisualEllipticalSelectorShape.h
@@ -1,73 +1,73 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* 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.
*/
#ifndef KISVISUALCOLORSELECTOR_H
#define KISVISUALCOLORSELECTOR_H
#include
#include
#include
#include
#include
#include
#include
#include "KoColorDisplayRendererInterface.h"
#include "KisColorSelectorConfiguration.h"
#include "KisVisualColorSelectorShape.h"
class KisVisualEllipticalSelectorShape : public KisVisualColorSelectorShape
{
Q_OBJECT
public:
enum singelDTypes{border, borderMirrored};
explicit KisVisualEllipticalSelectorShape(QWidget *parent,
Dimensions dimension,
const KoColorSpace *cs,
int channel1, int channel2,
const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int barWidth=20,
KisVisualEllipticalSelectorShape::singelDTypes d = KisVisualEllipticalSelectorShape::border
);
~KisVisualEllipticalSelectorShape() override;
void setBorderWidth(int width) override;
/**
* @brief getSpaceForSquare
* @param geom the full widget rectangle
* @return rectangle with enough space for second widget
*/
QRect getSpaceForSquare(QRect geom) override;
QRect getSpaceForCircle(QRect geom) override;
QRect getSpaceForTriangle(QRect geom) override;
protected:
QImage renderAlphaMask() const override;
+ QPointF mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const override;
private:
QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override;
QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override;
-
singelDTypes m_type;
int m_barWidth;
QRegion getMaskMap() override;
void drawCursor() override;
QSize sizeHint() const override;
};
#endif // KISVISUALCOLORSELECTOR_H
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
index 691b601b09..0707f67e55 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
@@ -1,570 +1,562 @@
/*
* This file is part of the KDE project
* Copyright (c) 2000 Matthias Elter
* 2001 John Califf
* 2004 Boudewijn Rempt
* Copyright (c) 2007 Thomas Zander
* Copyright (c) 2007 Adrian Page
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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 "LcmsColorProfileContainer.h"
#include
#include
#include
#include
#include
#include "kis_debug.h"
class LcmsColorProfileContainer::Private
{
public:
cmsHPROFILE profile;
cmsColorSpaceSignature colorSpaceSignature;
cmsProfileClassSignature deviceClass;
QString productDescription;
QString manufacturer;
QString copyright;
QString name;
float version;
IccColorProfile::Data *data {0};
bool valid {false};
bool suitableForOutput {false};
bool hasColorants;
bool hasTRC;
bool isLinear {false};
bool adaptedFromD50;
cmsCIEXYZ mediaWhitePoint;
cmsCIExyY whitePoint;
cmsCIEXYZTRIPLE colorants;
cmsToneCurve *redTRC {0};
cmsToneCurve *greenTRC {0};
cmsToneCurve *blueTRC {0};
cmsToneCurve *grayTRC {0};
cmsToneCurve *redTRCReverse {0};
cmsToneCurve *greenTRCReverse {0};
cmsToneCurve *blueTRCReverse {0};
cmsToneCurve *grayTRCReverse {0};
cmsUInt32Number defaultIntent;
bool isPerceptualCLUT;
bool isRelativeCLUT;
bool isAbsoluteCLUT;
bool isSaturationCLUT;
bool isMatrixShaper;
QByteArray uniqueId;
};
LcmsColorProfileContainer::LcmsColorProfileContainer()
: d(new Private())
{
d->profile = 0;
}
LcmsColorProfileContainer::LcmsColorProfileContainer(IccColorProfile::Data *data)
: d(new Private())
{
d->data = data;
d->profile = 0;
init();
}
QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile)
{
cmsUInt32Number bytesNeeded = 0;
// Make a raw data image ready for saving
cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size
QByteArray rawData;
rawData.resize(bytesNeeded);
if (rawData.size() >= (int)bytesNeeded) {
cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer
} else {
qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory.";
rawData.resize(0);
}
return rawData;
}
IccColorProfile *LcmsColorProfileContainer::createFromLcmsProfile(const cmsHPROFILE profile)
{
IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile));
cmsCloseProfile(profile);
return iccprofile;
}
LcmsColorProfileContainer::~LcmsColorProfileContainer()
{
cmsCloseProfile(d->profile);
delete d;
}
#define _BUFFER_SIZE_ 1000
bool LcmsColorProfileContainer::init()
{
if (d->profile) {
cmsCloseProfile(d->profile);
}
d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size());
#ifndef NDEBUG
if (d->data->rawData().size() == 4096) {
qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code.";
}
#endif
if (d->profile) {
wchar_t buffer[_BUFFER_SIZE_];
d->colorSpaceSignature = cmsGetColorSpace(d->profile);
d->deviceClass = cmsGetDeviceClass(d->profile);
cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->name = QString::fromWCharArray(buffer);
//apparently this should give us a localised string??? Not sure about this.
cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->productDescription = QString::fromWCharArray(buffer);
cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->manufacturer = QString::fromWCharArray(buffer);
cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->copyright = QString::fromWCharArray(buffer);
cmsProfileClassSignature profile_class;
profile_class = cmsGetDeviceClass(d->profile);
d->valid = ( profile_class != cmsSigNamedColorClass
&& profile_class != cmsSigLinkClass);
//This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is
//present. This is necessary for profiles following the v4 spec.
cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption.
if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)) {
d->mediaWhitePoint = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag));
baseMediaWhitePoint = d->mediaWhitePoint;
cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)) {
//the chromatic adaption tag represent a matrix from the actual white point of the profile to D50.
cmsCIEXYZ *CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag);
//We first put all our data into structures we can manipulate.
double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z};
QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy);
QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z);
//we then abuse QTransform's invert function because it probably does matrix inversion 20 times better than I can program.
//if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeble
//effect when we start multiplying.
QTransform invertedDummy = invertDummy.inverted();
//we then put the QTransform into a generic 3x3 matrix.
double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(),
invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(),
invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33()
};
QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy);
//multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint.
QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix;
//and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it
//uses column/row for initialising.
d->mediaWhitePoint.X = result(0, 0);
d->mediaWhitePoint.Y = result(1, 0);
d->mediaWhitePoint.Z = result(2, 0);
cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
}
}
//This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles.
if (cmsIsTag(d->profile, cmsSigRedColorantTag)) {
cmsCIEXYZTRIPLE tempColorants;
tempColorants.Red = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag));
tempColorants.Green = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag));
tempColorants.Blue = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag));
//convert to d65, this is useless.
cmsAdaptToIlluminant(&d->colorants.Red, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Red);
cmsAdaptToIlluminant(&d->colorants.Green, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Green);
cmsAdaptToIlluminant(&d->colorants.Blue, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Blue);
//d->colorants = tempColorants;
d->hasColorants = true;
} else {
//qDebug()<name<<": has no colorants";
d->hasColorants = false;
}
//retrieve TRC.
if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) {
d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag));
d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag));
d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag));
if (d->redTRC) d->redTRCReverse = cmsReverseToneCurve(d->redTRC);
if (d->greenTRC) d->greenTRCReverse = cmsReverseToneCurve(d->greenTRC);
if (d->blueTRC) d->blueTRCReverse = cmsReverseToneCurve(d->blueTRC);
d->hasTRC = (d->redTRC && d->greenTRC && d->blueTRC && d->redTRCReverse && d->greenTRCReverse && d->blueTRCReverse);
if (d->hasTRC) d->isLinear = cmsIsToneCurveLinear(d->redTRC)
&& cmsIsToneCurveLinear(d->greenTRC)
&& cmsIsToneCurveLinear(d->blueTRC);
} else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag));
if (d->grayTRC) d->grayTRCReverse = cmsReverseToneCurve(d->grayTRC);
d->hasTRC = (d->grayTRC && d->grayTRCReverse);
if (d->hasTRC) d->isLinear = cmsIsToneCurveLinear(d->grayTRC);
} else {
d->hasTRC = false;
}
// Check if the profile can convert (something->this)
d->suitableForOutput = cmsIsMatrixShaper(d->profile)
|| (cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) &&
cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT));
d->version = cmsGetProfileVersion(d->profile);
d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile);
d->isMatrixShaper = cmsIsMatrixShaper(d->profile);
d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT);
return true;
}
return false;
}
cmsHPROFILE LcmsColorProfileContainer::lcmsProfile() const
{
return d->profile;
}
cmsColorSpaceSignature LcmsColorProfileContainer::colorSpaceSignature() const
{
return d->colorSpaceSignature;
}
cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const
{
return d->deviceClass;
}
QString LcmsColorProfileContainer::manufacturer() const
{
return d->manufacturer;
}
QString LcmsColorProfileContainer::copyright() const
{
return d->copyright;
}
bool LcmsColorProfileContainer::valid() const
{
return d->valid;
}
float LcmsColorProfileContainer::version() const
{
return d->version;
}
bool LcmsColorProfileContainer::isSuitableForOutput() const
{
return d->suitableForOutput;
}
bool LcmsColorProfileContainer::isSuitableForPrinting() const
{
return deviceClass() == cmsSigOutputClass;
}
bool LcmsColorProfileContainer::isSuitableForDisplay() const
{
return deviceClass() == cmsSigDisplayClass;
}
bool LcmsColorProfileContainer::supportsPerceptual() const
{
return d->isPerceptualCLUT;
}
bool LcmsColorProfileContainer::supportsSaturation() const
{
return d->isSaturationCLUT;
}
bool LcmsColorProfileContainer::supportsAbsolute() const
{
return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow.
}
bool LcmsColorProfileContainer::supportsRelative() const
{
if (d->isRelativeCLUT || d->isMatrixShaper){
return true;
}
return false;
}
bool LcmsColorProfileContainer::hasColorants() const
{
return d->hasColorants;
}
bool LcmsColorProfileContainer::hasTRC() const
{
return d->hasTRC;
}
bool LcmsColorProfileContainer::isLinear() const
{
return d->isLinear;
}
QVector LcmsColorProfileContainer::getColorantsXYZ() const
{
QVector colorants(9);
colorants[0] = d->colorants.Red.X;
colorants[1] = d->colorants.Red.Y;
colorants[2] = d->colorants.Red.Z;
colorants[3] = d->colorants.Green.X;
colorants[4] = d->colorants.Green.Y;
colorants[5] = d->colorants.Green.Z;
colorants[6] = d->colorants.Blue.X;
colorants[7] = d->colorants.Blue.Y;
colorants[8] = d->colorants.Blue.Z;
return colorants;
}
QVector LcmsColorProfileContainer::getColorantsxyY() const
{
cmsCIEXYZ temp1;
cmsCIExyY temp2;
QVector colorants(9);
temp1.X = d->colorants.Red.X;
temp1.Y = d->colorants.Red.Y;
temp1.Z = d->colorants.Red.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[0] = temp2.x;
colorants[1] = temp2.y;
colorants[2] = temp2.Y;
temp1.X = d->colorants.Green.X;
temp1.Y = d->colorants.Green.Y;
temp1.Z = d->colorants.Green.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[3] = temp2.x;
colorants[4] = temp2.y;
colorants[5] = temp2.Y;
temp1.X = d->colorants.Blue.X;
temp1.Y = d->colorants.Blue.Y;
temp1.Z = d->colorants.Blue.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[6] = temp2.x;
colorants[7] = temp2.y;
colorants[8] = temp2.Y;
return colorants;
}
QVector LcmsColorProfileContainer::getWhitePointXYZ() const
{
QVector tempWhitePoint(3);
tempWhitePoint[0] = d->mediaWhitePoint.X;
tempWhitePoint[1] = d->mediaWhitePoint.Y;
tempWhitePoint[2] = d->mediaWhitePoint.Z;
return tempWhitePoint;
}
QVector LcmsColorProfileContainer::getWhitePointxyY() const
{
QVector tempWhitePoint(3);
tempWhitePoint[0] = d->whitePoint.x;
tempWhitePoint[1] = d->whitePoint.y;
tempWhitePoint[2] = d->whitePoint.Y;
return tempWhitePoint;
}
QVector LcmsColorProfileContainer::getEstimatedTRC() const
{
QVector TRCtriplet(3);
if (d->hasColorants) {
if (cmsIsToneCurveLinear(d->redTRC)) {
TRCtriplet[0] = 1.0;
} else {
TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01);
}
if (cmsIsToneCurveLinear(d->greenTRC)) {
TRCtriplet[1] = 1.0;
} else {
TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01);
}
if (cmsIsToneCurveLinear(d->blueTRC)) {
TRCtriplet[2] = 1.0;
} else {
TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
if (cmsIsToneCurveLinear(d->grayTRC)) {
TRCtriplet.fill(1.0);
} else {
TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01));
}
} else {
TRCtriplet.fill(1.0);
}
}
return TRCtriplet;
}
void LcmsColorProfileContainer::LinearizeFloatValue(QVector & Value) const
{
if (d->hasColorants) {
if (!cmsIsToneCurveLinear(d->redTRC)) {
Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]);
}
if (!cmsIsToneCurveLinear(d->greenTRC)) {
Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]);
}
if (!cmsIsToneCurveLinear(d->blueTRC)) {
Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]);
}
}
}
void LcmsColorProfileContainer::DelinearizeFloatValue(QVector & Value) const
{
if (d->hasColorants) {
if (!cmsIsToneCurveLinear(d->redTRC)) {
Value[0] = cmsEvalToneCurveFloat(d->redTRCReverse, Value[0]);
}
if (!cmsIsToneCurveLinear(d->greenTRC)) {
Value[1] = cmsEvalToneCurveFloat(d->greenTRCReverse, Value[1]);
}
if (!cmsIsToneCurveLinear(d->blueTRC)) {
Value[2] = cmsEvalToneCurveFloat(d->blueTRCReverse, Value[2]);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
Value[0] = cmsEvalToneCurveFloat(d->grayTRCReverse, Value[0]);
}
}
}
void LcmsColorProfileContainer::LinearizeFloatValueFast(QVector & Value) const
{
const qreal scale = 65535.0;
const qreal invScale = 1.0 / scale;
if (d->hasColorants) {
//we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
- QVector TRCtriplet(3);
- TRCtriplet[0] = Value[0] * scale;
- TRCtriplet[1] = Value[1] * scale;
- TRCtriplet[2] = Value[2] * scale;
if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
- TRCtriplet[0] = cmsEvalToneCurve16(d->redTRC, TRCtriplet[0]);
- Value[0] = TRCtriplet[0] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->redTRC, Value[0] * scale);
+ Value[0] = newValue * invScale;
}
if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
- TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRC, TRCtriplet[1]);
- Value[1] = TRCtriplet[1] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->greenTRC, Value[1] * scale);
+ Value[1] = newValue * invScale;
}
if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
- TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRC, TRCtriplet[2]);
- Value[2] = TRCtriplet[2] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->blueTRC, Value[2] * scale);
+ Value[2] = newValue * invScale;
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale);
Value[0] = newValue * invScale;
}
}
}
void LcmsColorProfileContainer::DelinearizeFloatValueFast(QVector & Value) const
{
const qreal scale = 65535.0;
const qreal invScale = 1.0 / scale;
if (d->hasColorants) {
//we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
- QVector TRCtriplet(3);
- TRCtriplet[0] = Value[0] * scale;
- TRCtriplet[1] = Value[1] * scale;
- TRCtriplet[2] = Value[2] * scale;
if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
- TRCtriplet[0] = cmsEvalToneCurve16(d->redTRCReverse, TRCtriplet[0]);
- Value[0] = TRCtriplet[0] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->redTRCReverse, Value[0] * scale);
+ Value[0] = newValue * invScale;
}
if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
- TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRCReverse, TRCtriplet[1]);
- Value[1] = TRCtriplet[1] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->greenTRCReverse, Value[1] * scale);
+ Value[1] = newValue * invScale;
}
if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
- TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRCReverse, TRCtriplet[2]);
- Value[2] = TRCtriplet[2] * invScale;
+ quint16 newValue = cmsEvalToneCurve16(d->blueTRCReverse, Value[2] * scale);
+ Value[2] = newValue * invScale;
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
quint16 newValue = cmsEvalToneCurve16(d->grayTRCReverse, Value[0] * scale);
Value[0] = newValue * invScale;
}
}
}
QString LcmsColorProfileContainer::name() const
{
return d->name;
}
QString LcmsColorProfileContainer::info() const
{
return d->productDescription;
}
QByteArray LcmsColorProfileContainer::getProfileUniqueId() const
{
if (d->uniqueId.isEmpty() && d->profile) {
QByteArray id(sizeof(cmsProfileID), 0);
cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
bool isNull = std::all_of(id.constBegin(),
id.constEnd(),
[](char c) {return c == 0;});
if (isNull) {
if (cmsMD5computeID(d->profile)) {
cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
isNull = false;
}
}
if (!isNull) {
d->uniqueId = id;
}
}
return d->uniqueId;
}
diff --git a/plugins/filters/asccdl/kis_wdg_asccdl.cpp b/plugins/filters/asccdl/kis_wdg_asccdl.cpp
index 5ad76ce753..ccd9f14d69 100644
--- a/plugins/filters/asccdl/kis_wdg_asccdl.cpp
+++ b/plugins/filters/asccdl/kis_wdg_asccdl.cpp
@@ -1,116 +1,120 @@
/*
* Copyright (c) 2017 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_wdg_asccdl.h"
#include
#include
#include
#include
#include
#include
KisASCCDLConfigWidget::KisASCCDLConfigWidget(QWidget *parent, const KoColorSpace *cs)
:KisConfigWidget(parent),
m_page(new Ui_WdgASCCDL),
m_cs(cs)
{
KoColor black(Qt::black, cs);
m_page->setupUi(this);
m_page->btnSlope->setColor(black);
m_page->btnOffset->setColor(black);
m_page->btnPower->setColor(black);
+ m_page->slopeSelector->slotSetColorSpace(m_cs);
+ m_page->offsetSelector->slotSetColorSpace(m_cs);
+ m_page->powerSelector->slotSetColorSpace(m_cs);
+
connect(m_page->btnSlope , SIGNAL(changed(KoColor)), this, SLOT(slopeColorChanged(KoColor)));
connect(m_page->btnOffset, SIGNAL(changed(KoColor)), this, SLOT(offsetColorChanged(KoColor)));
connect(m_page->btnPower , SIGNAL(changed(KoColor)), this, SLOT(powerColorChanged(KoColor)));
connect(m_page->slopeSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slopeColorChanged(KoColor)));
connect(m_page->offsetSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(offsetColorChanged(KoColor)));
connect(m_page->powerSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(powerColorChanged(KoColor)));
}
KisASCCDLConfigWidget::~KisASCCDLConfigWidget()
{
delete m_page;
}
KisPropertiesConfigurationSP KisASCCDLConfigWidget::configuration() const
{
KisFilterConfigurationSP config = new KisFilterConfiguration("asc-cdl", 0);
QVariant colorVariant("KoColor");
colorVariant.setValue(m_page->btnSlope->color());
config->setProperty("slope", colorVariant);
colorVariant.setValue(m_page->btnOffset->color());
config->setProperty("offset", colorVariant);
colorVariant.setValue(m_page->btnPower->color());
config->setProperty("power", colorVariant);
return config;
}
void KisASCCDLConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
KoColor white(m_cs);
QVector channels(m_cs->channelCount());
m_cs->normalisedChannelsValue(white.data(), channels);
channels.fill(1.0);
m_cs->fromNormalisedChannelsValue(white.data(), channels);
KoColor black(Qt::black, m_cs);
KoColor slope = config->getColor("slope", white);
slope.convertTo(m_cs);
KoColor offset = config->getColor("offset", black);
offset.convertTo(m_cs);
KoColor power = config->getColor("power", white);
power.convertTo(m_cs);
m_page->btnSlope->setColor(slope);
m_page->slopeSelector->slotSetColor(slope);
m_page->btnOffset->setColor(offset);
m_page->offsetSelector->slotSetColor(offset);
m_page->btnPower->setColor (power);
m_page->powerSelector->slotSetColor(power);
}
void KisASCCDLConfigWidget::slopeColorChanged(const KoColor &c)
{
if (QObject::sender() == m_page->btnSlope) {
m_page->slopeSelector->slotSetColor(c);
} else {
m_page->btnSlope->setColor(c);
}
emit sigConfigurationItemChanged();
}
void KisASCCDLConfigWidget::offsetColorChanged(const KoColor &c)
{
if (QObject::sender() == m_page->btnOffset) {
m_page->offsetSelector->slotSetColor(c);
} else {
m_page->btnOffset->setColor(c);
}
emit sigConfigurationItemChanged();
}
void KisASCCDLConfigWidget::powerColorChanged(const KoColor &c)
{
if (QObject::sender() == m_page->btnPower) {
m_page->powerSelector->slotSetColor(c);
} else {
m_page->btnPower->setColor(c);
}
emit sigConfigurationItemChanged();
}