File indexing completed on 2024-05-12 16:34:27

0001 /* This file is part of the KDE project
0002  * Copyright (c) 2009 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) any later version.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Library General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Lesser General Public License
0015  * along with this library; see the file COPYING.LIB.  If not, write to
0016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018  */
0019 
0020 #include "ComponentTransferEffect.h"
0021 #include "ColorChannelConversion.h"
0022 #include <KoFilterEffectRenderContext.h>
0023 #include <KoXmlWriter.h>
0024 #include <KoXmlReader.h>
0025 #include <klocalizedstring.h>
0026 #include <QRect>
0027 #include <QImage>
0028 #include <math.h>
0029 
0030 ComponentTransferEffect::ComponentTransferEffect()
0031         : KoFilterEffect(ComponentTransferEffectId, i18n("Component transfer"))
0032 {
0033 }
0034 
0035 ComponentTransferEffect::Function ComponentTransferEffect::function(Channel channel) const
0036 {
0037     return m_data[channel].function;
0038 }
0039 
0040 void ComponentTransferEffect::setFunction(Channel channel, Function function)
0041 {
0042     m_data[channel].function = function;
0043 }
0044 
0045 QList<qreal> ComponentTransferEffect::tableValues(Channel channel) const
0046 {
0047     return m_data[channel].tableValues;
0048 }
0049 
0050 void ComponentTransferEffect::setTableValues(Channel channel, QList<qreal> tableValues)
0051 {
0052     m_data[channel].tableValues = tableValues;
0053 }
0054 
0055 void ComponentTransferEffect::setSlope(Channel channel, qreal slope)
0056 {
0057     m_data[channel].slope = slope;
0058 }
0059 
0060 qreal ComponentTransferEffect::slope(Channel channel) const
0061 {
0062     return m_data[channel].slope;
0063 }
0064 
0065 void ComponentTransferEffect::setIntercept(Channel channel, qreal intercept)
0066 {
0067     m_data[channel].intercept = intercept;
0068 }
0069 
0070 qreal ComponentTransferEffect::intercept(Channel channel) const
0071 {
0072     return m_data[channel].intercept;
0073 }
0074 
0075 void ComponentTransferEffect::setAmplitude(Channel channel, qreal amplitude)
0076 {
0077     m_data[channel].amplitude = amplitude;
0078 }
0079 
0080 qreal ComponentTransferEffect::amplitude(Channel channel) const
0081 {
0082     return m_data[channel].amplitude;
0083 }
0084 
0085 void ComponentTransferEffect::setExponent(Channel channel, qreal exponent)
0086 {
0087     m_data[channel].exponent = exponent;
0088 }
0089 
0090 qreal ComponentTransferEffect::exponent(Channel channel) const
0091 {
0092     return m_data[channel].exponent;
0093 }
0094 
0095 void ComponentTransferEffect::setOffset(Channel channel, qreal offset)
0096 {
0097     m_data[channel].offset = offset;
0098 }
0099 
0100 qreal ComponentTransferEffect::offset(Channel channel) const
0101 {
0102     return m_data[channel].offset;
0103 }
0104 
0105 QImage ComponentTransferEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
0106 {
0107     QImage result = image;
0108 
0109     const QRgb *src = (const QRgb*)image.constBits();
0110     QRgb *dst = (QRgb*)result.bits();
0111     int w = result.width();
0112 
0113     qreal sa, sr, sg, sb;
0114     qreal da, dr, dg, db;
0115     int pixel;
0116 
0117     const QRect roi = context.filterRegion().toRect();
0118     const int minRow = roi.top();
0119     const int maxRow = roi.bottom();
0120     const int minCol = roi.left();
0121     const int maxCol = roi.right();
0122 
0123     for (int row = minRow; row <= maxRow; ++row) {
0124         for (int col = minCol; col <= maxCol; ++col) {
0125             pixel = row * w + col;
0126             const QRgb &s = src[pixel];
0127 
0128             sa = fromIntColor[qAlpha(s)];
0129             sr = fromIntColor[qRed(s)];
0130             sg = fromIntColor[qGreen(s)];
0131             sb = fromIntColor[qBlue(s)];
0132             // the matrix is applied to non-premultiplied color values
0133             // so we have to convert colors by dividing by alpha value
0134             if (sa > 0.0 && sa < 1.0) {
0135                 sr /= sa;
0136                 sb /= sa;
0137                 sg /= sa;
0138             }
0139 
0140             dr = transferChannel(ChannelR, sr);
0141             dg = transferChannel(ChannelG, sg);
0142             db = transferChannel(ChannelB, sb);
0143             da = transferChannel(ChannelA, sa);
0144 
0145             da *= 255.0;
0146 
0147             // set pre-multiplied color values on destination image
0148             dst[pixel] = qRgba(static_cast<quint8>(qBound(qreal(0.0), dr * da, qreal(255.0))),
0149                                static_cast<quint8>(qBound(qreal(0.0), dg * da, qreal(255.0))),
0150                                static_cast<quint8>(qBound(qreal(0.0), db * da, qreal(255.0))),
0151                                static_cast<quint8>(qBound(qreal(0.0), da, qreal(255.0))));
0152         }
0153     }
0154 
0155     return result;
0156 }
0157 
0158 qreal ComponentTransferEffect::transferChannel(Channel channel, qreal value) const
0159 {
0160     const Data &d = m_data[channel];
0161 
0162     switch (d.function) {
0163     case Identity:
0164         return value;
0165     case Table: {
0166         qreal valueCount = d.tableValues.count() - 1;
0167         if (valueCount < 0.0)
0168             return value;
0169         qreal k1 = static_cast<int>(value * valueCount);
0170         qreal k2 = qMin(k1 + 1, valueCount);
0171         qreal vk1 = d.tableValues[k1];
0172         qreal vk2 = d.tableValues[k2];
0173         return vk1 + (value - static_cast<qreal>(k1) / valueCount)*valueCount *(vk2 - vk1);
0174     }
0175     case Discrete: {
0176         qreal valueCount = d.tableValues.count() - 1;
0177         if (valueCount < 0.0)
0178             return value;
0179         return d.tableValues[static_cast<int>(value*valueCount)];
0180     }
0181     case Linear:
0182         return d.slope * value + d.intercept;
0183     case Gamma:
0184         return d.amplitude * pow(value, d.exponent) + d.offset;
0185     }
0186 
0187     return value;
0188 }
0189 
0190 bool ComponentTransferEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &)
0191 {
0192     if (element.tagName() != id())
0193         return false;
0194 
0195     // reset data
0196     m_data[ChannelR] = Data();
0197     m_data[ChannelG] = Data();
0198     m_data[ChannelB] = Data();
0199     m_data[ChannelA] = Data();
0200 
0201     for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0202         KoXmlElement node = n.toElement();
0203         if (node.tagName() == "feFuncR") {
0204             loadChannel(ChannelR, node);
0205         } else if (node.tagName() == "feFuncG") {
0206             loadChannel(ChannelG, node);
0207         } else if (node.tagName() == "feFuncB") {
0208             loadChannel(ChannelB, node);
0209         } else if (node.tagName() == "feFuncA") {
0210             loadChannel(ChannelA, node);
0211         }
0212     }
0213 
0214     return true;
0215 }
0216 
0217 void ComponentTransferEffect::loadChannel(Channel channel, const KoXmlElement &element)
0218 {
0219     QString typeStr = element.attribute("type");
0220     if (typeStr.isEmpty())
0221         return;
0222 
0223     Data &d = m_data[channel];
0224 
0225     if (typeStr == "table" || typeStr == "discrete") {
0226         d.function = typeStr == "table" ? Table : Discrete;
0227         QString valueStr = element.attribute("tableValues");
0228         QStringList values = valueStr.split(QRegExp("(\\s+|,)"), QString::SkipEmptyParts);
0229         foreach(const QString &v, values) {
0230             d.tableValues.append(v.toDouble());
0231         }
0232     } else if (typeStr == "linear") {
0233         d.function = Linear;
0234         if (element.hasAttribute("slope")) {
0235             d.slope = element.attribute("slope").toDouble();
0236         }
0237         if (element.hasAttribute("intercept")) {
0238             d.intercept = element.attribute("intercept").toDouble();
0239         }
0240     } else if (typeStr == "gamma") {
0241         d.function = Gamma;
0242         if (element.hasAttribute("amplitude")) {
0243             d.amplitude = element.attribute("amplitude").toDouble();
0244         }
0245         if (element.hasAttribute("exponent")) {
0246             d.exponent = element.attribute("exponent").toDouble();
0247         }
0248         if (element.hasAttribute("offset")) {
0249             d.offset = element.attribute("offset").toDouble();
0250         }
0251     }
0252 }
0253 
0254 void ComponentTransferEffect::save(KoXmlWriter &writer)
0255 {
0256     writer.startElement(ComponentTransferEffectId);
0257 
0258     saveCommonAttributes(writer);
0259 
0260     saveChannel(ChannelR, writer);
0261     saveChannel(ChannelG, writer);
0262     saveChannel(ChannelB, writer);
0263     saveChannel(ChannelA, writer);
0264 
0265     writer.endElement();
0266 }
0267 
0268 void ComponentTransferEffect::saveChannel(Channel channel, KoXmlWriter &writer)
0269 {
0270     Function function = m_data[channel].function;
0271     // we can omit writing the transfer function when
0272     if (function == Identity)
0273         return;
0274 
0275     switch (channel) {
0276     case ChannelR:
0277         writer.startElement("feFuncR");
0278         break;
0279     case ChannelG:
0280         writer.startElement("feFuncG");
0281         break;
0282     case ChannelB:
0283         writer.startElement("feFuncB");
0284         break;
0285     case ChannelA:
0286         writer.startElement("feFuncA");
0287         break;
0288     }
0289 
0290     Data defaultData;
0291     const Data &currentData = m_data[channel];
0292 
0293     if (function == Linear) {
0294         writer.addAttribute("type", "linear");
0295         // only write non default data
0296         if (defaultData.slope != currentData.slope)
0297             writer.addAttribute("slope", QString("%1").arg(currentData.slope));
0298         if (defaultData.intercept != currentData.intercept)
0299             writer.addAttribute("intercept", QString("%1").arg(currentData.intercept));
0300     } else if (function == Gamma) {
0301         writer.addAttribute("type", "gamma");
0302         // only write non default data
0303         if (defaultData.amplitude != currentData.amplitude)
0304             writer.addAttribute("amplitude", QString("%1").arg(currentData.amplitude));
0305         if (defaultData.exponent != currentData.exponent)
0306             writer.addAttribute("exponent", QString("%1").arg(currentData.exponent));
0307         if (defaultData.offset != currentData.offset)
0308             writer.addAttribute("offset", QString("%1").arg(currentData.offset));
0309     } else {
0310         writer.addAttribute("type", function == Table ? "table" : "discrete");
0311         if (currentData.tableValues.count()) {
0312             QString tableStr;
0313             foreach(qreal v, currentData.tableValues) {
0314                 tableStr += QString("%1 ").arg(v);
0315             }
0316             writer.addAttribute("tableValues", tableStr.trimmed());
0317         }
0318     }
0319 
0320     writer.endElement();
0321 }