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 ¤tData = 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 }