File indexing completed on 2024-05-19 16:07:25

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 "MorphologyEffect.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 <QImage>
0029 #include <cmath>
0030 
0031 MorphologyEffect::MorphologyEffect()
0032         : KoFilterEffect(MorphologyEffectId, i18n("Morphology"))
0033         , m_radius(0,0), m_operator(Erode)
0034 {
0035 }
0036 
0037 QPointF MorphologyEffect::morphologyRadius() const
0038 {
0039     return m_radius;
0040 }
0041 
0042 void MorphologyEffect::setMorphologyRadius(const QPointF &radius)
0043 {
0044     m_radius = radius;
0045 }
0046 
0047 MorphologyEffect::Operator MorphologyEffect::morphologyOperator() const
0048 {
0049     return m_operator;
0050 }
0051 
0052 void MorphologyEffect::setMorphologyOperator(Operator op)
0053 {
0054     m_operator = op;
0055 }
0056 
0057 QImage MorphologyEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
0058 {
0059     QImage result = image;
0060 
0061     QPointF radius = context.toUserSpace(m_radius);
0062 
0063     const int rx = static_cast<int>(ceil(radius.x()));
0064     const int ry = static_cast<int>(ceil(radius.y()));
0065 
0066     const int w = result.width();
0067     const int h = result.height();
0068 
0069     // setup mask
0070     const int maskSize = (1+2*rx)*(1+2*ry);
0071     int * mask = new int[maskSize];
0072     int index = 0;
0073     for (int y = -ry; y <= ry; ++y) {
0074         for (int x = -rx; x <= rx; ++x) {
0075             mask[index] = y*w+x;
0076             index++;
0077         }
0078     }
0079 
0080     int dstPixel, srcPixel;
0081     uchar s0, s1, s2, s3;
0082     const uchar * src = image.constBits();
0083     uchar * dst = result.bits();
0084 
0085     const QRect roi = context.filterRegion().toRect();
0086     const int minX = qMax(rx, roi.left());
0087     const int maxX = qMin(w-rx, roi.right());
0088     const int minY = qMax(ry, roi.top());
0089     const int maxY = qMin(h-ry, roi.bottom());
0090     const int defValue = m_operator == Erode ? 255 : 0;
0091 
0092     uchar * d = 0;
0093 
0094     for (int row = minY; row < maxY; ++row) {
0095         for (int col = minX; col < maxX; ++col) {
0096             dstPixel = row * w + col;
0097             s0 = s1 = s2 = s3 = defValue;
0098             for (int i = 0; i < maskSize; ++i) {
0099                 srcPixel = dstPixel+mask[i];
0100                 const uchar *s = &src[4*srcPixel];
0101                 if (m_operator == Erode ) {
0102                     s0 = qMin(s0, s[0]);
0103                     s1 = qMin(s1, s[1]);
0104                     s2 = qMin(s2, s[2]);
0105                     s3 = qMin(s3, s[3]);
0106                 } else {
0107                     s0 = qMax(s0, s[0]);
0108                     s1 = qMax(s1, s[1]);
0109                     s2 = qMax(s2, s[2]);
0110                     s3 = qMax(s3, s[3]);
0111                 }
0112             }
0113             d = &dst[4*dstPixel];
0114             d[0] = s0;
0115             d[1] = s1;
0116             d[2] = s2;
0117             d[3] = s3;
0118         }
0119     }
0120 
0121     delete [] mask;
0122 
0123     return result;
0124 }
0125 
0126 bool MorphologyEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context)
0127 {
0128     if (element.tagName() != id())
0129         return false;
0130 
0131     m_radius = QPointF();
0132     m_operator = Erode;
0133 
0134     if (element.hasAttribute("radius")) {
0135         QString radiusStr = element.attribute("radius").trimmed();
0136         QStringList params = radiusStr.replace(',', ' ').simplified().split(' ');
0137         switch (params.count()) {
0138             case 1:
0139                 m_radius.rx() = params[0].toDouble()*72./90.;
0140                 m_radius.ry() = m_radius.x();
0141                 break;
0142             case 2:
0143                 m_radius.rx() = params[0].toDouble()*72./90.;
0144                 m_radius.ry() = params[1].toDouble()*72./90.;
0145                 break;
0146             default:
0147                 m_radius = QPointF();
0148         }
0149     }
0150 
0151     m_radius = context.convertFilterPrimitiveUnits(m_radius);
0152 
0153     if (element.hasAttribute("operator")) {
0154         QString op = element.attribute("operator");
0155         if (op == "dilate")
0156             m_operator = Dilate;
0157     }
0158 
0159     return true;
0160 }
0161 
0162 void MorphologyEffect::save(KoXmlWriter &writer)
0163 {
0164     writer.startElement(MorphologyEffectId);
0165 
0166     saveCommonAttributes(writer);
0167 
0168     if (m_operator != Erode )
0169         writer.addAttribute("operator", "dilate");
0170     if (!m_radius.isNull()) {
0171         if (m_radius.x() == m_radius.y()) {
0172             writer.addAttribute("radius", QString("%1").arg(m_radius.x()));
0173         } else {
0174             writer.addAttribute("radius", QString("%1 %2").arg(m_radius.x()).arg(m_radius.y()));
0175         }
0176     }
0177 
0178     writer.endElement();
0179 }