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 }