1
/* This file is part of the KDE project
2
* Copyright (c) 2009 Jan Hambrecht <jaham@gmx.net>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Library General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public License
15
* along with this library; see the file COPYING.LIB. If not, write to
16
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
* Boston, MA 02110-1301, USA.
20
#include "ComponentTransferEffect.h"
21
#include "ColorChannelConversion.h"
22
#include <KoFilterEffectRenderContext.h>
23
#include <KoXmlWriter.h>
24
#include <KoXmlReader.h>
26
#include <QtCore/QRect>
29
ComponentTransferEffect::ComponentTransferEffect()
30
: KoFilterEffect(ComponentTransferEffectId, i18n("Component transfer"))
34
ComponentTransferEffect::Function ComponentTransferEffect::function(Channel channel) const
36
return m_data[channel].function;
39
void ComponentTransferEffect::setFunction(Channel channel, Function function)
41
m_data[channel].function = function;
44
QList<qreal> ComponentTransferEffect::tableValues(Channel channel) const
46
return m_data[channel].tableValues;
49
void ComponentTransferEffect::setTableValues(Channel channel, QList<qreal> tableValues)
51
m_data[channel].tableValues = tableValues;
54
void ComponentTransferEffect::setSlope(Channel channel, qreal slope)
56
m_data[channel].slope = slope;
59
qreal ComponentTransferEffect::slope(Channel channel) const
61
return m_data[channel].slope;
64
void ComponentTransferEffect::setIntercept(Channel channel, qreal intercept)
66
m_data[channel].intercept = intercept;
69
qreal ComponentTransferEffect::intercept(Channel channel) const
71
return m_data[channel].intercept;
74
void ComponentTransferEffect::setAmplitude(Channel channel, qreal amplitude)
76
m_data[channel].amplitude = amplitude;
79
qreal ComponentTransferEffect::amplitude(Channel channel) const
81
return m_data[channel].amplitude;
84
void ComponentTransferEffect::setExponent(Channel channel, qreal exponent)
86
m_data[channel].exponent = exponent;
89
qreal ComponentTransferEffect::exponent(Channel channel) const
91
return m_data[channel].exponent;
94
void ComponentTransferEffect::setOffset(Channel channel, qreal offset)
96
m_data[channel].offset = offset;
99
qreal ComponentTransferEffect::offset(Channel channel) const
101
return m_data[channel].offset;
104
QImage ComponentTransferEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
106
QImage result = image;
108
QRgb *src = (QRgb*)image.bits();
109
QRgb *dst = (QRgb*)result.bits();
110
int w = result.width();
112
qreal sa, sr, sg, sb;
113
qreal da, dr, dg, db;
116
const QRect roi = context.filterRegion().toRect();
117
const int minRow = roi.top();
118
const int maxRow = roi.bottom();
119
const int minCol = roi.left();
120
const int maxCol = roi.right();
122
for (int row = minRow; row <= maxRow; ++row) {
123
for (int col = minCol; col <= maxCol; ++col) {
124
pixel = row * w + col;
125
const QRgb &s = src[pixel];
127
sa = fromIntColor[qAlpha(s)];
128
sr = fromIntColor[qRed(s)];
129
sg = fromIntColor[qGreen(s)];
130
sb = fromIntColor[qBlue(s)];
131
// the matrix is applied to non-premultiplied color values
132
// so we have to convert colors by dividing by alpha value
133
if (sa > 0.0 && sa < 1.0) {
139
dr = transferChannel(ChannelR, sr);
140
dg = transferChannel(ChannelG, sg);
141
db = transferChannel(ChannelB, sb);
142
da = transferChannel(ChannelA, sa);
146
// set pre-multiplied color values on destination image
147
dst[pixel] = qRgba(static_cast<quint8>(qBound((qreal) 0.0, dr * da, (qreal) 255.0)),
148
static_cast<quint8>(qBound((qreal) 0.0, dg * da, (qreal) 255.0)),
149
static_cast<quint8>(qBound((qreal) 0.0, db * da, (qreal) 255.0)),
150
static_cast<quint8>(qBound((qreal) 0.0, da, (qreal) 255.0)));
157
qreal ComponentTransferEffect::transferChannel(Channel channel, qreal value) const
159
const Data &d = m_data[channel];
161
switch (d.function) {
165
qreal valueCount = d.tableValues.count() - 1;
166
if (valueCount < 0.0)
168
qreal k1 = static_cast<int>(value * valueCount);
169
qreal k2 = qMin(k1 + 1, valueCount);
170
qreal vk1 = d.tableValues[k1];
171
qreal vk2 = d.tableValues[k2];
172
return vk1 + (value - static_cast<qreal>(k1) / valueCount)*valueCount *(vk2 - vk1);
175
qreal valueCount = d.tableValues.count() - 1;
176
if (valueCount < 0.0)
178
return d.tableValues[static_cast<int>(value*valueCount)];
181
return d.slope * value + d.intercept;
183
return d.amplitude * pow(value, d.exponent) + d.offset;
189
bool ComponentTransferEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &)
191
if (element.tagName() != id())
195
m_data[ChannelR] = Data();
196
m_data[ChannelG] = Data();
197
m_data[ChannelB] = Data();
198
m_data[ChannelA] = Data();
200
for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
201
KoXmlElement node = n.toElement();
202
if (node.tagName() == "feFuncR") {
203
loadChannel(ChannelR, node);
204
} else if (node.tagName() == "feFuncG") {
205
loadChannel(ChannelG, node);
206
} else if (node.tagName() == "feFuncB") {
207
loadChannel(ChannelB, node);
208
} else if (node.tagName() == "feFuncA") {
209
loadChannel(ChannelA, node);
216
void ComponentTransferEffect::loadChannel(Channel channel, const KoXmlElement &element)
218
QString typeStr = element.attribute("type");
219
if (typeStr.isEmpty())
222
Data &d = m_data[channel];
224
if (typeStr == "table" || typeStr == "discrete") {
225
d.function = typeStr == "table" ? Table : Discrete;
226
QString valueStr = element.attribute("tableValues");
227
QStringList values = valueStr.split(QRegExp("(\\s+|,)"), QString::SkipEmptyParts);
228
foreach(const QString &v, values) {
229
d.tableValues.append(v.toDouble());
231
} else if (typeStr == "linear") {
233
if (element.hasAttribute("slope")) {
234
d.slope = element.attribute("slope").toDouble();
236
if (element.hasAttribute("intercept")) {
237
d.intercept = element.attribute("intercept").toDouble();
239
} else if (typeStr == "gamma") {
241
if (element.hasAttribute("amplitude")) {
242
d.amplitude = element.attribute("amplitude").toDouble();
244
if (element.hasAttribute("exponent")) {
245
d.exponent = element.attribute("exponent").toDouble();
247
if (element.hasAttribute("offset")) {
248
d.offset = element.attribute("offset").toDouble();
253
void ComponentTransferEffect::save(KoXmlWriter &writer)
255
writer.startElement(ComponentTransferEffectId);
257
saveCommonAttributes(writer);
259
saveChannel(ChannelR, writer);
260
saveChannel(ChannelG, writer);
261
saveChannel(ChannelB, writer);
262
saveChannel(ChannelA, writer);
267
void ComponentTransferEffect::saveChannel(Channel channel, KoXmlWriter &writer)
269
Function function = m_data[channel].function;
270
// we can omit writing the transfer function when
271
if (function == Identity)
276
writer.startElement("feFuncR");
279
writer.startElement("feFuncG");
282
writer.startElement("feFuncB");
285
writer.startElement("feFuncA");
290
const Data ¤tData = m_data[channel];
292
if (function == Linear) {
293
writer.addAttribute("type", "linear");
294
// only write non default data
295
if (defaultData.slope != currentData.slope)
296
writer.addAttribute("slope", QString("%1").arg(currentData.slope));
297
if (defaultData.intercept != currentData.intercept)
298
writer.addAttribute("intercept", QString("%1").arg(currentData.intercept));
299
} else if (function == Gamma) {
300
writer.addAttribute("type", "gamma");
301
// only write non default data
302
if (defaultData.amplitude != currentData.amplitude)
303
writer.addAttribute("amplitude", QString("%1").arg(currentData.amplitude));
304
if (defaultData.exponent != currentData.exponent)
305
writer.addAttribute("exponent", QString("%1").arg(currentData.exponent));
306
if (defaultData.offset != currentData.offset)
307
writer.addAttribute("offset", QString("%1").arg(currentData.offset));
309
writer.addAttribute("type", function == Table ? "table" : "discrete");
310
if (currentData.tableValues.count()) {
312
foreach(qreal v, currentData.tableValues) {
313
tableStr += QString("%1 ").arg(v);
315
writer.addAttribute("tableValues", tableStr.trimmed());