diff --git a/krita/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/krita/plugins/filters/colorsfilters/kis_perchannel_filter.cpp index 6bdc8286b5..e9aa307be8 100644 --- a/krita/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/krita/plugins/filters/colorsfilters/kis_perchannel_filter.cpp @@ -1,540 +1,572 @@ /* * This file is part of Krita * * Copyright (c) 2005 C. Boemann * * 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_perchannel_filter.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoBasicHistogramProducers.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoCompositeColorTransformation.h" #include "KoCompositeOp.h" #include "KoID.h" #include "kis_signals_blocker.h" #include "kis_bookmarked_configuration_manager.h" #include "kis_config_widget.h" #include #include #include #include #include "kis_histogram.h" #include "kis_painter.h" #include "widgets/kis_curve_widget.h" QVector getVirtualChannels(const KoColorSpace *cs) { QVector vchannels; QList sortedChannels = KoChannelInfo::displayOrderSorted(cs->channels()); - vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0); + vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs); foreach(KoChannelInfo *channel, sortedChannels) { int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels); - vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel); + vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs); } + vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); + return vchannels; } KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f) : KisConfigWidget(parent, f), m_histogram(0) { Q_ASSERT(dev); m_page = new WdgPerChannel(this); QHBoxLayout * layout = new QHBoxLayout(this); Q_CHECK_PTR(layout); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_page); m_dev = dev; m_activeVChannel = 0; // fill in the channel chooser, in the display order, but store the pixel index as well. m_virtualChannels = getVirtualChannels(dev->colorSpace()); const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_page->cmbChannel->addItem(info.name(), info.pixelIndex()); m_curves[i].setName(info.name()); } connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int))); // create the horizontal and vertical gradient labels m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); // init histogram calculator QList keys = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(m_dev->colorSpace()); if(keys.size() > 0) { KoHistogramProducerFactory *hpf; hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); } connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100); { KisSignalsBlocker b(m_page->curveWidget); m_page->curveWidget->setCurve(m_curves[0]); setActiveChannel(0); } } KisPerChannelConfigWidget::~KisPerChannelConfigWidget() { delete m_histogram; } inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) { int width; int height; int *i, inc, col; int x = 0, y = 0; if (orient == Qt::Horizontal) { i = &x; inc = 1; col = 0; width = 256; height = 1; } else { i = &y; inc = -1; col = 255; width = 1; height = 256; } QPixmap gradientpix(width, height); QPainter p(&gradientpix); p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); for (; *i < 256; (*i)++, col += inc) { p.setPen(QColor(col, col, col)); p.drawPoint(x, y); } return gradientpix; } inline QPixmap KisPerChannelConfigWidget::getHistogram() { int i; int height = 256; QPixmap pix(256, height); pix.fill(); QPainter p(&pix); p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; if (m_histogram && info.type() == VirtualChannelInfo::REAL) { m_histogram->setChannel(info.pixelIndex()); double highest = (double)m_histogram->calculations().getHighest(); qint32 bins = m_histogram->producer()->numberOfBins(); if (m_histogram->getHistogramType() == LINEAR) { double factor = (double)height / highest; for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); } } else { double factor = (double)height / (double)log(highest); for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); } } } return pix; } #define BITS_PER_BYTE 8 #define pwr2(p) (1<curveWidget->curve(); m_activeVChannel = ch; m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); m_page->curveWidget->setPixmap(getHistogram()); m_page->cmbChannel->setCurrentIndex(m_activeVChannel); // Getting range accepted by channel VirtualChannelInfo ¤tVChannel = m_virtualChannels[m_activeVChannel]; KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType(); int order = BITS_PER_BYTE * currentVChannel.channelSize(); int maxValue = pwr2(order); int min; int max; m_page->curveWidget->dropInOutControls(); switch (valueType) { case KoChannelInfo::UINT8: case KoChannelInfo::UINT16: case KoChannelInfo::UINT32: m_shift = 0; m_scale = double(maxValue); min = 0; max = maxValue - 1; break; case KoChannelInfo::INT8: case KoChannelInfo::INT16: m_shift = 0.5; m_scale = double(maxValue); min = -maxValue / 2; max = maxValue / 2 - 1; break; case KoChannelInfo::FLOAT16: case KoChannelInfo::FLOAT32: case KoChannelInfo::FLOAT64: default: m_shift = 0; m_scale = 100.0; //Hack Alert: should be changed to float min = 0; max = 100; break; } m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max); } KisPropertiesConfiguration * KisPerChannelConfigWidget::configuration() const { int numChannels = m_virtualChannels.size(); KisPerChannelFilterConfiguration * cfg = new KisPerChannelFilterConfiguration(numChannels); KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; } m_curves[m_activeVChannel] = m_page->curveWidget->curve(); cfg->setCurves(m_curves); return cfg; } void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfiguration * config) { const KisPerChannelFilterConfiguration * cfg = dynamic_cast(config); if (!cfg) return; if (cfg->curves().size() == 0) { /** * HACK ALERT: our configuration factory generates * default configuration with nTransfers==0. * Catching it here. Just reset all the transfers. */ const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_curves[i].setName(info.name()); } } else if (cfg->curves().size() != int(m_virtualChannels.size())) { qWarning() << "WARNING: trying to load a curve with incorrect number of channels!"; qWarning() << "WARNING: expected:" << m_virtualChannels.size(); qWarning() << "WARNING: got:" << cfg->curves().size(); return; } else { for (int ch = 0; ch < cfg->curves().size(); ch++) m_curves[ch] = cfg->curves()[ch]; } // HACK: we save the previous curve in setActiveChannel, so just copy it m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); setActiveChannel(0); } KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh) : KisFilterConfiguration("perchannel", 1) { initDefaultCurves(m_curves, nCh); updateTransfers(); } KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration() { } bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const { return (int)dev->colorSpace()->channelCount() == m_curves.size(); } void KisPerChannelFilterConfiguration::setCurves(QList &curves) { m_curves.clear(); m_curves = curves; updateTransfers(); } void KisPerChannelFilterConfiguration::initDefaultCurves(QList &curves, int nCh) { curves.clear(); for (int i = 0; i < nCh; i++) { curves.append(KisCubicCurve()); } } void KisPerChannelFilterConfiguration::updateTransfers() { m_transfers.resize(m_curves.size()); for (int i = 0; i < m_curves.size(); i++) { m_transfers[i] = m_curves[i].uint16Transfer(); } } const QVector >& KisPerChannelFilterConfiguration::transfers() const { return m_transfers; } const QList& KisPerChannelFilterConfiguration::curves() const { return m_curves; } void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) { fromXML(root); } void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root) { QList curves; quint16 numTransfers = 0; int version; version = root.attribute("version").toInt(); QDomElement e = root.firstChild().toElement(); QString attributeName; KisCubicCurve curve; quint16 index; while (!e.isNull()) { if ((attributeName = e.attribute("name")) == "nTransfers") { numTransfers = e.text().toUShort(); } else { QRegExp rx("curve(\\d+)"); if (rx.indexIn(attributeName, 0) != -1) { index = rx.cap(1).toUShort(); index = qMin(index, quint16(curves.count())); if (!e.text().isEmpty()) { curve.fromString(e.text()); } curves.insert(index, curve); } } e = e.nextSiblingElement(); } if (!numTransfers) return; setVersion(version); setCurves(curves); } /** * Inherited from KisPropertiesConfiguration */ //void KisPerChannelFilterConfiguration::fromXML(const QString& s) void addParamNode(QDomDocument& doc, QDomElement& root, const QString &name, const QString &value) { QDomText text = doc.createTextNode(value); QDomElement t = doc.createElement("param"); t.setAttribute("name", name); t.appendChild(text); root.appendChild(t); } void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { /** * * 3 * 0,0;0.5,0.5;1,1; * 0,0;1,1; * 0,0;1,1; * */ root.setAttribute("version", version()); QDomText text; QDomElement t; addParamNode(doc, root, "nTransfers", QString::number(m_curves.size())); KisCubicCurve curve; QString paramName; for (int i = 0; i < m_curves.size(); ++i) { QString name = QLatin1String("curve") + QString::number(i); QString value = m_curves[i].toString(); addParamNode(doc, root, name, value); } } /** * Inherited from KisPropertiesConfiguration */ //QString KisPerChannelFilterConfiguration::toXML() KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves...")) { setSupportsPainting(true); setColorSpaceIndependence(TO_LAB16); } KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { return new KisPerChannelConfigWidget(parent, dev); } KisFilterConfiguration * KisPerChannelFilter::factoryConfiguration(const KisPaintDeviceSP) const { return new KisPerChannelFilterConfiguration(0); } KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfiguration* config) const { const KisPerChannelFilterConfiguration* configBC = dynamic_cast(config); // Somehow, this shouldn't happen Q_ASSERT(configBC); const QVector > &originalTransfers = configBC->transfers(); const QList &originalCurves = configBC->curves(); /** * TODO: What about the order of channels? (DK) * * Virtual channels are sorted in display order, does Lcms accepts * transforms in display order? Why on Earth it works?! Is it * documented anywhere? */ const QVector virtualChannels = getVirtualChannels(cs); if (originalTransfers.size() != int(virtualChannels.size())) { // We got an illegal number of colorchannels :( return 0; } bool colorsNull = true; bool lightnessNull = true; + bool allColorsNull = true; + int alphaIndexInReal = -1; QVector > realTransfers; - QVector lightnessTansfer; + QVector lightnessTransfer; + QVector allColorsTransfer; + for (int i = 0; i < virtualChannels.size(); i++) { if (virtualChannels[i].type() == VirtualChannelInfo::REAL) { realTransfers << originalTransfers[i]; + + if (virtualChannels[i].isAlpha()) { + alphaIndexInReal = realTransfers.size() - 1; + } + if (colorsNull && !originalCurves[i].isNull()) { colorsNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { - KIS_ASSERT_RECOVER_NOOP(lightnessTansfer.isEmpty()); - lightnessTansfer = originalTransfers[i]; + KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); + lightnessTransfer = originalTransfers[i]; if (lightnessNull && !originalCurves[i].isNull()) { lightnessNull = false; } - } - } + } else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) { + KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty()); + allColorsTransfer = originalTransfers[i]; - const quint16** transfers = new const quint16*[configBC->curves().size()]; - for(int i = 0; i < realTransfers.size(); ++i) { - transfers[i] = realTransfers[i].constData(); + if (allColorsNull && !originalCurves[i].isNull()) { + allColorsNull = false; + } + } } KoColorTransformation *lightnessTransform = 0; + KoColorTransformation *allColorsTransform = 0; KoColorTransformation *colorTransform = 0; if (!colorsNull) { + const quint16** transfers = new const quint16*[realTransfers.size()]; + for(int i = 0; i < realTransfers.size(); ++i) { + transfers[i] = realTransfers[i].constData(); + + /** + * createPerChannelAdjustment() expects alpha channel to + * be the last channel in the list, so just it here + */ + KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || + alphaIndexInReal == (realTransfers.size() - 1)); + } + colorTransform = cs->createPerChannelAdjustment(transfers); + delete [] transfers; } if (!lightnessNull) { - lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTansfer.constData()); + lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData()); } - KoColorTransformation *finalTransform = 0; - - if (colorTransform && lightnessTransform) { - KoCompositeColorTransformation *compositeTransform = - new KoCompositeColorTransformation( - KoCompositeColorTransformation::INPLACE); - - compositeTransform->appendTransform(colorTransform); - compositeTransform->appendTransform(lightnessTransform); + if (!allColorsNull) { + const quint16** allColorsTransfers = new const quint16*[realTransfers.size()]; + for(int i = 0; i < realTransfers.size(); ++i) { + allColorsTransfers[i] = (i != alphaIndexInReal) ? + allColorsTransfer.constData() : 0; + + /** + * createPerChannelAdjustment() expects alpha channel to + * be the last channel in the list, so just it here + */ + KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || + alphaIndexInReal == (realTransfers.size() - 1)); + } - finalTransform = compositeTransform; - } else if (lightnessTransform) { - finalTransform = lightnessTransform; - } else if (colorTransform) { - finalTransform = colorTransform; + allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers); + delete[] allColorsTransfers; } - delete [] transfers; - return finalTransform; + QVector allTransforms; + allTransforms << lightnessTransform; + allTransforms << allColorsTransform; + allTransforms << colorTransform; + + return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms); } #include "kis_perchannel_filter.moc" diff --git a/krita/plugins/filters/colorsfilters/virtual_channel_info.cpp b/krita/plugins/filters/colorsfilters/virtual_channel_info.cpp index aae58061ba..3b042add9f 100644 --- a/krita/plugins/filters/colorsfilters/virtual_channel_info.cpp +++ b/krita/plugins/filters/colorsfilters/virtual_channel_info.cpp @@ -1,62 +1,79 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 "virtual_channel_info.h" #include +#include VirtualChannelInfo::VirtualChannelInfo() : m_type(LIGHTNESS), m_pixelIndex(-1), m_realChannelInfo(0) { } VirtualChannelInfo::VirtualChannelInfo(Type type, int pixelIndex, - KoChannelInfo *realChannelInfo) + KoChannelInfo *realChannelInfo, + const KoColorSpace *cs) : m_type(type), m_pixelIndex(pixelIndex), m_realChannelInfo(realChannelInfo) { + if (m_type == LIGHTNESS) { + m_nameOverride = i18n("Lightness"); + m_valueTypeOverride = KoChannelInfo::FLOAT32; + m_channelSizeOverride = 4; + } else if (m_type == ALL_COLORS) { + m_nameOverride = cs->colorModelId().id(); + m_valueTypeOverride = cs->channels().first()->channelValueType(); + m_channelSizeOverride = cs->channels().first()->size(); + } } VirtualChannelInfo::Type VirtualChannelInfo::type() const { return m_type; } KoChannelInfo* VirtualChannelInfo::channelInfo() const { return m_realChannelInfo; } QString VirtualChannelInfo::name() const { - return m_type == REAL ? m_realChannelInfo->name() : i18n("Lightness"); + return m_type == REAL ? m_realChannelInfo->name() : m_nameOverride; } int VirtualChannelInfo::pixelIndex() const { return m_pixelIndex; } KoChannelInfo::enumChannelValueType VirtualChannelInfo::valueType() const { - return m_type == REAL ? m_realChannelInfo->channelValueType() : KoChannelInfo::FLOAT32; + return m_type == REAL ? m_realChannelInfo->channelValueType() : m_valueTypeOverride; } int VirtualChannelInfo::channelSize() const { - return m_type == REAL ? m_realChannelInfo->size() : 4; + return m_type == REAL ? m_realChannelInfo->size() : m_channelSizeOverride; +} + +bool VirtualChannelInfo::isAlpha() const +{ + return m_type == REAL && + m_realChannelInfo->channelType() == KoChannelInfo::ALPHA; } diff --git a/krita/plugins/filters/colorsfilters/virtual_channel_info.h b/krita/plugins/filters/colorsfilters/virtual_channel_info.h index 9af3ab54c7..204fa8fc66 100644 --- a/krita/plugins/filters/colorsfilters/virtual_channel_info.h +++ b/krita/plugins/filters/colorsfilters/virtual_channel_info.h @@ -1,49 +1,58 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 __VIRTUAL_CHANNEL_INFO_H #define __VIRTUAL_CHANNEL_INFO_H #include +class KoColorSpace; + class VirtualChannelInfo { public: enum Type { REAL, - LIGHTNESS + LIGHTNESS, + ALL_COLORS }; VirtualChannelInfo(); - VirtualChannelInfo(Type type, int pixelIndex, KoChannelInfo *realChannelInfo); + VirtualChannelInfo(Type type, int pixelIndex, KoChannelInfo *realChannelInfo, const KoColorSpace *cs); Type type() const; KoChannelInfo* channelInfo() const; QString name() const; int pixelIndex() const; KoChannelInfo::enumChannelValueType valueType() const; int channelSize() const; + bool isAlpha() const; + private: Type m_type; int m_pixelIndex; KoChannelInfo *m_realChannelInfo; + + QString m_nameOverride; + KoChannelInfo::enumChannelValueType m_valueTypeOverride; + int m_channelSizeOverride; }; #endif /* __VIRTUAL_CHANNEL_INFO_H */ diff --git a/libs/pigment/KoCompositeColorTransformation.cpp b/libs/pigment/KoCompositeColorTransformation.cpp index 2de2ecff96..d9bf2f115d 100644 --- a/libs/pigment/KoCompositeColorTransformation.cpp +++ b/libs/pigment/KoCompositeColorTransformation.cpp @@ -1,62 +1,98 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 "KoCompositeColorTransformation.h" #include struct KoCompositeColorTransformation::Private { ~Private() { qDeleteAll(transformations); } QVector transformations; }; KoCompositeColorTransformation::KoCompositeColorTransformation(Mode mode) : m_d(new Private) { Q_ASSERT_X(mode == INPLACE, "KoCompositeColorTransformation", "BUFFERED mode is not implemented yet!"); } KoCompositeColorTransformation::~KoCompositeColorTransformation() { } void KoCompositeColorTransformation::appendTransform(KoColorTransformation *transform) { - m_d->transformations.append(transform); + if (transform) { + m_d->transformations.append(transform); + } } void KoCompositeColorTransformation::transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { QVector::const_iterator begin = m_d->transformations.begin(); QVector::const_iterator it = begin; QVector::const_iterator end = m_d->transformations.end(); for (; it != end; ++it) { if (it == begin) { (*it)->transform(src, dst, nPixels); } else { (*it)->transform(dst, dst, nPixels); } } } + +KoColorTransformation* KoCompositeColorTransformation::createOptimizedCompositeTransform(const QVector transforms) +{ + KoColorTransformation *finalTransform = 0; + + int numValidTransforms = 0; + foreach (KoColorTransformation *t, transforms) { + numValidTransforms += bool(t); + } + + if (numValidTransforms > 1) { + KoCompositeColorTransformation *compositeTransform = + new KoCompositeColorTransformation( + KoCompositeColorTransformation::INPLACE); + + foreach (KoColorTransformation *t, transforms) { + if (t) { + compositeTransform->appendTransform(t); + } + } + + finalTransform = compositeTransform; + + } else if (numValidTransforms == 1) { + foreach (KoColorTransformation *t, transforms) { + if (t) { + finalTransform = t; + break; + } + } + } + + return finalTransform; +} diff --git a/libs/pigment/KoCompositeColorTransformation.h b/libs/pigment/KoCompositeColorTransformation.h index 1c4bf28480..020363c83b 100644 --- a/libs/pigment/KoCompositeColorTransformation.h +++ b/libs/pigment/KoCompositeColorTransformation.h @@ -1,48 +1,50 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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 __KO_COMPOSITE_COLOR_TRANSFORMATION_H #define __KO_COMPOSITE_COLOR_TRANSFORMATION_H #include "KoColorTransformation.h" #include class PIGMENTCMS_EXPORT KoCompositeColorTransformation : public KoColorTransformation { public: enum Mode { INPLACE = 0, /// transform pixels in place (in 'dst' buffer) BUFFERED /// transform using a temporary buffer (not implemented yet) }; public: KoCompositeColorTransformation(Mode mode); ~KoCompositeColorTransformation(); void appendTransform(KoColorTransformation *transform); void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const; + static KoColorTransformation* createOptimizedCompositeTransform(const QVector transforms); + private: struct Private; const QScopedPointer m_d; }; #endif /* __KO_COMPOSITE_COLOR_TRANSFORMATION_H */ diff --git a/plugins/colorengines/lcms2/LcmsColorSpace.h b/plugins/colorengines/lcms2/LcmsColorSpace.h index 576fb52270..bf9a262c2e 100644 --- a/plugins/colorengines/lcms2/LcmsColorSpace.h +++ b/plugins/colorengines/lcms2/LcmsColorSpace.h @@ -1,442 +1,446 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005-2006 C. Boemann * Copyright (c) 2004,2006-2007 Cyrille Berger * * 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. */ #ifndef KOLCMSCOLORSPACE_H_ #define KOLCMSCOLORSPACE_H_ #include #include #include class KoLcmsInfo { struct Private { cmsUInt32Number cmType; // The colorspace type as defined by littlecms cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files }; public: KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : d(new Private) { d->cmType = cmType; d->colorSpaceSignature = colorSpaceSignature; } virtual ~KoLcmsInfo() { delete d; } virtual quint32 colorSpaceType() const { return d->cmType; } virtual cmsColorSpaceSignature colorSpaceSignature() const { return d->colorSpaceSignature; } private: Private* const d; }; struct KoLcmsDefaultTransformations { cmsHTRANSFORM toRGB; cmsHTRANSFORM fromRGB; static cmsHPROFILE s_RGBProfile; static QMap< QString, QMap< LcmsColorProfileContainer*, KoLcmsDefaultTransformations* > > s_transformations; }; /** * This is the base class for all colorspaces that are based on the lcms library, for instance * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... */ template class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo { struct KoLcmsColorTransformation : public KoColorTransformation { KoLcmsColorTransformation(const KoColorSpace* colorSpace) : KoColorTransformation() , m_colorSpace(colorSpace) { csProfile = 0; cmstransform = 0; cmsAlphaTransform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; } ~KoLcmsColorTransformation() { if (cmstransform) cmsDeleteTransform(cmstransform); if (profiles[0] && profiles[0] != csProfile) cmsCloseProfile(profiles[0]); if (profiles[1] && profiles[1] != csProfile) cmsCloseProfile(profiles[1]); if (profiles[2] && profiles[2] != csProfile) cmsCloseProfile(profiles[2]); } virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const { cmsDoTransform(cmstransform, const_cast(src), dst, nPixels); qint32 numPixels = nPixels; qint32 pixelSize = m_colorSpace->pixelSize(); int index = 0; if (cmsAlphaTransform) { qreal *alpha = new qreal[nPixels]; qreal *dstalpha = new qreal[nPixels]; while (index < nPixels) { alpha[index] = m_colorSpace->opacityF(src); src += pixelSize; index++; } cmsDoTransform(cmsAlphaTransform, const_cast(alpha), static_cast(dstalpha), nPixels); for(int i = 0 ; i < numPixels ; i++) { m_colorSpace->setOpacity(dst, dstalpha[i], 1); dst += pixelSize; } delete [] alpha; delete [] dstalpha; } else { while (numPixels > 0) { qreal alpha = m_colorSpace->opacityF(src); m_colorSpace->setOpacity(dst, alpha, 1); src += pixelSize; dst += pixelSize; numPixels--; } } } const KoColorSpace* m_colorSpace; cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM cmstransform; cmsHTRANSFORM cmsAlphaTransform; }; struct Private { mutable quint8 *qcolordata; // A small buffer for conversion from and to qcolor. KoLcmsDefaultTransformations* defaultTransformations; mutable cmsHPROFILE lastRGBProfile; // Last used profile to transform to/from RGB mutable cmsHTRANSFORM lastToRGB; // Last used transform to transform to RGB mutable cmsHTRANSFORM lastFromRGB; // Last used transform to transform from RGB LcmsColorProfileContainer *profile; KoColorProfile* colorProfile; }; protected: LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p) : KoColorSpaceAbstract<_CSTraits>(id, name) , KoLcmsInfo(cmType, colorSpaceSignature) , d(new Private()) { Q_ASSERT(p); // No profile means the lcms color space can't work Q_ASSERT(profileIsCompatible(p)); d->profile = asLcmsProfile(p); Q_ASSERT(d->profile); d->colorProfile = p; d->qcolordata = 0; d->lastRGBProfile = 0; d->lastToRGB = 0; d->lastFromRGB = 0; d->defaultTransformations = 0; } virtual ~LcmsColorSpace() { delete d->colorProfile; delete[] d->qcolordata; delete d; } void init() { // Default pixel buffer for QColor conversion d->qcolordata = new quint8[3]; Q_CHECK_PTR(d->qcolordata); Q_ASSERT(d->profile); if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); } d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; if (!d->defaultTransformations) { d->defaultTransformations = new KoLcmsDefaultTransformations; d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); Q_ASSERT(d->defaultTransformations->fromRGB); d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); Q_ASSERT(d->defaultTransformations->toRGB); KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; } } public: virtual bool hasHighDynamicRange() const { return false; } virtual const KoColorProfile * profile() const { return d->colorProfile; } virtual bool profileIsCompatible(const KoColorProfile* profile) const { const IccColorProfile* p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * koprofile = 0) const { d->qcolordata[2] = color.red(); d->qcolordata[1] = color.green(); d->qcolordata[0] = color.blue(); LcmsColorProfileContainer* profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB Q_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); cmsDoTransform(d->defaultTransformations->fromRGB, d->qcolordata, dst, 1); } else { if (d->lastFromRGB == 0 || (d->lastFromRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastFromRGB = cmsCreateTransform(profile->lcmsProfile(), TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastFromRGB, d->qcolordata, dst, 1); } this->setOpacity(dst, (quint8)(color.alpha()) , 1); } virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * koprofile = 0) const { LcmsColorProfileContainer* profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB transform Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); cmsDoTransform(d->defaultTransformations->toRGB, const_cast (src), d->qcolordata, 1); } else { if (d->lastToRGB == 0 || (d->lastToRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastToRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), profile->lcmsProfile(), TYPE_BGR_8, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastToRGB, const_cast (src), d->qcolordata, 1); } c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]); c->setAlpha(this->opacityU8(src)); } virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const { if (!d->profile) return 0; cmsToneCurve* transferFunctions[3]; transferFunctions[0] = cmsBuildTabulatedToneCurve16( 0, 256, transferValues); transferFunctions[1] = cmsBuildGamma(0, 1.0); transferFunctions[2] = cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); adj->profiles[0] = d->profile->lcmsProfile(); adj->profiles[2] = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), KoColorConversionTransformation::AdjustmentRenderingIntent, KoColorConversionTransformation::AdjustmentConversionFlags); adj->csProfile = d->profile->lcmsProfile(); return adj; } virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const*transferValues) const { if (!d->profile) return 0; cmsToneCurve ** transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; for (uint ch = 0; ch < this->colorChannelCount(); ch++) { - transferFunctions[ch] = cmsBuildTabulatedToneCurve16( 0, 256, transferValues[ch]); + transferFunctions[ch] = transferValues[ch] ? + cmsBuildTabulatedToneCurve16( 0, 256, transferValues[ch]) : + cmsBuildGamma(0, 1.0); } cmsToneCurve ** alphaTransferFunctions = new cmsToneCurve*[1]; - alphaTransferFunctions[0] = cmsBuildTabulatedToneCurve16( 0, 256, transferValues[this->colorChannelCount()]); + alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? + cmsBuildTabulatedToneCurve16( 0, 256, transferValues[this->colorChannelCount()]) : + cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); adj->profiles[2] = NULL; adj->csProfile = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), NULL, this->colorSpaceType(), KoColorConversionTransformation::AdjustmentRenderingIntent, KoColorConversionTransformation::AdjustmentConversionFlags); adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_DBL, NULL, TYPE_GRAY_DBL, KoColorConversionTransformation::AdjustmentRenderingIntent, KoColorConversionTransformation::AdjustmentConversionFlags); delete [] transferFunctions; delete [] alphaTransferFunctions; return adj; } virtual quint8 difference(const quint8* src1, const quint8* src2) const { quint8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); qreal diff = cmsDeltaE(&labF1, &labF2); if (diff > 255.0) return 255; else return quint8(diff); } virtual quint8 differenceA(const quint8* src1, const quint8* src2) const { quint8 lab1[8]; quint8 lab2[8]; cmsCIELab labF1; cmsCIELab labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); cmsFloat64Number dL; cmsFloat64Number da; cmsFloat64Number db; cmsFloat64Number dAlpha; dL = fabs((qreal)(labF1.L - labF2.L)); da = fabs((qreal)(labF1.a - labF2.a)); db = fabs((qreal)(labF1.b - labF2.b)); static const int LabAAlphaPos = 3; static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits::max; quint16 alpha1 = reinterpret_cast(lab1)[LabAAlphaPos]; quint16 alpha2 = reinterpret_cast(lab2)[LabAAlphaPos]; dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); if (diff > 255.0) return 255; else return quint8(diff); } private: inline LcmsColorProfileContainer* lcmsProfile() const { return d->profile; } inline static LcmsColorProfileContainer* asLcmsProfile(const KoColorProfile* p) { if (!p) return 0; const IccColorProfile* iccp = dynamic_cast(p); if (!iccp) { return 0; } Q_ASSERT(iccp->asLcms()); return iccp->asLcms(); } Private * const d; }; /** * Base class for all LCMS based ColorSpace factories. */ class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo { public: LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : KoLcmsInfo(cmType, colorSpaceSignature) { } virtual bool profileIsCompatible(const KoColorProfile* profile) const { const IccColorProfile* p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } virtual QString colorSpaceEngine() const { return "icc"; } virtual bool isHdr() const { return false; } virtual QList colorConversionLinks() const; virtual KoColorProfile* createColorProfile(const QByteArray& rawData) const; }; #endif