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

0001 /* This file is part of the KDE project
0002  * Copyright (c) 2010 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 "ConvolveMatrixEffect.h"
0021 #include "KoFilterEffectRenderContext.h"
0022 #include "KoFilterEffectLoadingContext.h"
0023 #include "KoViewConverter.h"
0024 #include "KoXmlWriter.h"
0025 #include "KoXmlReader.h"
0026 #include <klocalizedstring.h>
0027 #include <QRect>
0028 #include <QVector>
0029 #include <QImage>
0030 #include <QColor>
0031 
0032 #include <cmath>
0033 
0034 ConvolveMatrixEffect::ConvolveMatrixEffect()
0035         : KoFilterEffect(ConvolveMatrixEffectId, i18n("Convolve Matrix"))
0036 {
0037     setDefaults();
0038 }
0039 
0040 void ConvolveMatrixEffect::setDefaults()
0041 {
0042     m_order = QPoint(3,3);
0043     m_divisor = 0.0;
0044     m_bias = 0.0;
0045     m_target = QPoint(-1,-1);
0046     m_edgeMode = Duplicate;
0047     m_preserveAlpha = false;
0048     m_kernel.resize(m_order.x()*m_order.y());
0049     for (int i = 0; i < m_kernel.size(); ++i) {
0050         m_kernel[i] = 0.0;
0051     }
0052     m_kernelUnitLength = QPointF(1,1);
0053 }
0054 
0055 QPoint ConvolveMatrixEffect::order() const
0056 {
0057     return m_order;
0058 }
0059 
0060 void ConvolveMatrixEffect::setOrder(const QPoint &order)
0061 {
0062     m_order = QPoint(qMax(1, order.x()), qMax(1, order.y()));
0063 }
0064 
0065 QVector<qreal> ConvolveMatrixEffect::kernel() const
0066 {
0067     return m_kernel;
0068 }
0069 
0070 void ConvolveMatrixEffect::setKernel(const QVector<qreal> &kernel)
0071 {
0072     if (m_order.x()*m_order.y() != kernel.count())
0073         return;
0074     m_kernel = kernel;
0075 }
0076 
0077 qreal ConvolveMatrixEffect::divisor() const
0078 {
0079     return m_divisor;
0080 }
0081 
0082 void ConvolveMatrixEffect::setDivisor(qreal divisor)
0083 {
0084     m_divisor = divisor;
0085 }
0086 
0087 qreal ConvolveMatrixEffect::bias() const
0088 {
0089     return m_bias;
0090 }
0091 
0092 void ConvolveMatrixEffect::setBias(qreal bias)
0093 {
0094     m_bias = bias;
0095 }
0096 
0097 QPoint ConvolveMatrixEffect::target() const
0098 {
0099     return m_target;
0100 }
0101 
0102 void ConvolveMatrixEffect::setTarget(const QPoint &target)
0103 {
0104     m_target = target;
0105 }
0106 
0107 ConvolveMatrixEffect::EdgeMode ConvolveMatrixEffect::edgeMode() const
0108 {
0109     return m_edgeMode;
0110 }
0111 
0112 void ConvolveMatrixEffect::setEdgeMode(EdgeMode edgeMode)
0113 {
0114     m_edgeMode = edgeMode;
0115 }
0116 
0117 bool ConvolveMatrixEffect::isPreserveAlphaEnabled() const
0118 {
0119     return m_preserveAlpha;
0120 }
0121 
0122 void ConvolveMatrixEffect::enablePreserveAlpha(bool on)
0123 {
0124     m_preserveAlpha = on;
0125 }
0126 
0127 QImage ConvolveMatrixEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
0128 {
0129     QImage result = image;
0130 
0131     const int rx = m_order.x();
0132     const int ry = m_order.y();
0133     if( rx == 0 && ry == 0 )
0134         return result;
0135 
0136     const int tx = m_target.x() >= 0 && m_target.x() <= rx ? m_target.x() : rx >> 1;
0137     const int ty = m_target.y() >= 0 && m_target.y() <= ry ? m_target.y() : ry >> 1;
0138 
0139     const int w = result.width();
0140     const int h = result.height();
0141 
0142     // setup mask
0143     const int maskSize = rx*ry;
0144     QVector<QPoint> offset(maskSize);
0145     int index = 0;
0146     for (int y = 0; y < ry; ++y) {
0147         for (int x = 0; x < rx; ++x) {
0148             offset[index] = QPoint(x-tx, y-ty);
0149             index++;
0150         }
0151     }
0152 
0153     qreal divisor = m_divisor;
0154     // if no divisor given, it is the sum of all kernel values
0155     // if sum of kernel values is zero, divisor is set to 1
0156     if (divisor == 0.0) {
0157         foreach(qreal k, m_kernel) {
0158             divisor += k;
0159         }
0160         if (divisor == 0.0)
0161             divisor = 1.0;
0162     }
0163 
0164     int dstPixel, srcPixel;
0165     qreal sumA, sumR, sumG, sumB;
0166     const QRgb * src = (const QRgb*)image.constBits();
0167     QRgb * dst = (QRgb*)result.bits();
0168 
0169     const QRect roi = context.filterRegion().toRect();
0170     const int minX = roi.left();
0171     const int maxX = roi.right();
0172     const int minY = roi.top();
0173     const int maxY = roi.bottom();
0174 
0175     int srcRow, srcCol;
0176     for (int row = minY; row <= maxY; ++row) {
0177         for (int col = minX; col <= maxX; ++col) {
0178             dstPixel = row * w + col;
0179             sumA = sumR = sumG = sumB = 0;
0180             for (int i = 0; i < maskSize; ++i) {
0181                 srcRow = row + offset[i].y();
0182                 srcCol = col + offset[i].x();
0183                 // handle top and bottom edge
0184                 if (srcRow < 0 || srcRow >= h ) {
0185                     switch(m_edgeMode) {
0186                         case Duplicate:
0187                             srcRow = srcRow >= h ? h-1 : 0;
0188                             break;
0189                         case Wrap:
0190                             srcRow = (srcRow+h)%h;
0191                             break;
0192                         case None:
0193                             // zero for all color channels
0194                             continue;
0195                             break;
0196                     }
0197                 }
0198                 // handle left and right edge
0199                 if (srcCol < 0 || srcCol >= w) {
0200                     switch(m_edgeMode) {
0201                         case Duplicate:
0202                             srcCol = srcCol >= w ? w-1 : 0;
0203                             break;
0204                         case Wrap:
0205                             srcCol = (srcCol+w)%w;
0206                             break;
0207                         case None:
0208                             // zero for all color channels
0209                             continue;
0210                             break;
0211                     }
0212                 }
0213                 srcPixel = srcRow * w + srcCol;
0214                 const QRgb &s = src[srcPixel];
0215                 const qreal &k = m_kernel[i];
0216                 if (!m_preserveAlpha)
0217                     sumA += qAlpha(s) * k;
0218                 sumR += qRed(s) * k;
0219                 sumG += qGreen(s) * k;
0220                 sumB += qBlue(s) * k;
0221             }
0222             if (m_preserveAlpha) {
0223                 dst[dstPixel] = qRgba( qBound(0, static_cast<int>(sumR / divisor + m_bias), 255),
0224                                        qBound(0, static_cast<int>(sumG / divisor + m_bias), 255),
0225                                        qBound(0, static_cast<int>(sumB / divisor + m_bias), 255),
0226                                        qAlpha(dst[dstPixel]));
0227             } else {
0228                 dst[dstPixel] = qRgba( qBound(0, static_cast<int>(sumR / divisor + m_bias), 255),
0229                                        qBound(0, static_cast<int>(sumG / divisor + m_bias), 255),
0230                                        qBound(0, static_cast<int>(sumB / divisor + m_bias), 255),
0231                                        qBound(0, static_cast<int>(sumA / divisor + m_bias), 255));
0232             }
0233         }
0234     }
0235 
0236     return result;
0237 }
0238 
0239 bool ConvolveMatrixEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &/*context*/)
0240 {
0241     if (element.tagName() != id())
0242         return false;
0243 
0244     setDefaults();
0245 
0246     if (element.hasAttribute("order")) {
0247         QString orderStr = element.attribute("order");
0248         QStringList params = orderStr.replace(',', ' ').simplified().split(' ');
0249         switch (params.count()) {
0250             case 1:
0251                 m_order.rx() = qMax(1, params[0].toInt());
0252                 m_order.ry() = m_order.x();
0253                 break;
0254             case 2:
0255                 m_order.rx() = qMax(1, params[0].toInt());
0256                 m_order.ry() = qMax(1, params[1].toInt());
0257                 break;
0258         }
0259     }
0260     if (element.hasAttribute("kernelMatrix")) {
0261         QString matrixStr = element.attribute("kernelMatrix");
0262         // values are separated by whitespace and/or comma
0263         QStringList values = matrixStr.replace(',', ' ').simplified().split(' ');
0264         if (values.count() == m_order.x()*m_order.y()) {
0265             m_kernel.resize(values.count());
0266             for (int i = 0; i < values.count(); ++i) {
0267                 m_kernel[i] = values[i].toDouble();
0268             }
0269         } else {
0270             m_kernel.resize(m_order.x()*m_order.y());
0271             for (int i = 0; i < m_kernel.size(); ++i) {
0272                 m_kernel[i] = 0.0;
0273             }
0274         }
0275     }
0276     if (element.hasAttribute("divisor")) {
0277         m_divisor = element.attribute("divisor").toDouble();
0278     }
0279     if (element.hasAttribute("bias")) {
0280         m_bias = element.attribute("bias").toDouble();
0281     }
0282     if (element.hasAttribute("targetX")) {
0283         m_target.rx() = qBound<int>(0, element.attribute("targetX").toInt(), m_order.x());
0284     }
0285     if (element.hasAttribute("targetY")) {
0286         m_target.ry() = qBound<int>(0, element.attribute("targetY").toInt(), m_order.y());
0287     }
0288     if (element.hasAttribute("edgeMode")) {
0289         QString mode = element.attribute("edgeMode");
0290         if (mode == "wrap")
0291             m_edgeMode = Wrap;
0292         else if (mode == "none")
0293             m_edgeMode = None;
0294         else
0295             m_edgeMode = Duplicate;
0296     }
0297     if (element.hasAttribute("kernelUnitLength")) {
0298         QString kernelUnitLengthStr = element.attribute("kernelUnitLength");
0299         QStringList params = kernelUnitLengthStr.replace(',', ' ').simplified().split(' ');
0300         switch (params.count()) {
0301             case 1:
0302                 m_kernelUnitLength.rx() = params[0].toDouble();
0303                 m_kernelUnitLength.ry() = m_kernelUnitLength.x();
0304                 break;
0305             case 2:
0306                 m_kernelUnitLength.rx() = params[0].toDouble();
0307                 m_kernelUnitLength.ry() = params[1].toDouble();
0308                 break;
0309         }
0310     }
0311     if (element.hasAttribute("preserveAlpha")) {
0312         m_preserveAlpha = (element.attribute("preserveAlpha") == "true");
0313     }
0314 
0315     return true;
0316 }
0317 
0318 void ConvolveMatrixEffect::save(KoXmlWriter &writer)
0319 {
0320     writer.startElement(ConvolveMatrixEffectId);
0321 
0322     saveCommonAttributes(writer);
0323 
0324     if (m_order.x() == m_order.y()) {
0325         writer.addAttribute("order", QString("%1").arg(m_order.x()));
0326     } else {
0327         writer.addAttribute("order", QString("%1 %2").arg(m_order.x()).arg(m_order.y()));
0328     }
0329     QString kernel;
0330     for (int i = 0; i < m_kernel.size(); ++i) {
0331         kernel += QString("%1 ").arg(m_kernel[i]);
0332     }
0333     writer.addAttribute("kernelMatrix", kernel);
0334     writer.addAttribute("divisor", QString("%1").arg(m_divisor));
0335     if (m_bias != 0.0)
0336         writer.addAttribute("bias", QString("%1").arg(m_bias));
0337     writer.addAttribute("targetX", QString("%1").arg(m_target.x()));
0338     writer.addAttribute("targetY", QString("%1").arg(m_target.y()));
0339     switch(m_edgeMode) {
0340         case Wrap:
0341             writer.addAttribute("edgeMode", "wrap");
0342             break;
0343         case None:
0344             writer.addAttribute("edgeMode", "none");
0345             break;
0346         case Duplicate:
0347             // fall through as it is the default
0348             break;
0349     }
0350     writer.addAttribute("kernelUnitLength", QString("%1 %2").arg(m_kernelUnitLength.x()).arg(m_kernelUnitLength.y()));
0351     if (m_preserveAlpha)
0352         writer.addAttribute("preserveAlpha", "true");
0353 
0354     writer.endElement();
0355 }