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 "ColorMatrixEffect.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 const int MatrixSize = 20;
0031 const int MatrixRows = 4;
0032 const int MatrixCols = 5;
0033 
0034 ColorMatrixEffect::ColorMatrixEffect()
0035         : KoFilterEffect(ColorMatrixEffectId, i18n("Color Matrix"))
0036         , m_type(Matrix)
0037 {
0038     setIdentity();
0039 }
0040 
0041 void ColorMatrixEffect::setIdentity()
0042 {
0043     // set identity matrix
0044     m_matrix.resize(MatrixSize);
0045     for (int r = 0; r < MatrixRows; ++r) {
0046         for (int c = 0; c < MatrixCols; ++c) {
0047             m_matrix[r*MatrixCols+c] = r == c ? 1.0 : 0.0;
0048         }
0049     }
0050 }
0051 
0052 ColorMatrixEffect::Type ColorMatrixEffect::type() const
0053 {
0054     return m_type;
0055 }
0056 
0057 int ColorMatrixEffect::colorMatrixSize()
0058 {
0059     return MatrixSize;
0060 }
0061 
0062 int ColorMatrixEffect::colorMatrixRowCount()
0063 {
0064     return MatrixRows;
0065 }
0066 
0067 int ColorMatrixEffect::colorMatrixColumnCount()
0068 {
0069     return MatrixCols;
0070 }
0071 
0072 QVector<qreal> ColorMatrixEffect::colorMatrix() const
0073 {
0074     return m_matrix;
0075 }
0076 
0077 void ColorMatrixEffect::setColorMatrix(const QVector<qreal> &colorMatrix)
0078 {
0079     if (colorMatrix.count() == MatrixSize)
0080         m_matrix = colorMatrix;
0081     m_type = Matrix;
0082 }
0083 
0084 void ColorMatrixEffect::setSaturate(qreal value)
0085 {
0086     m_type = Saturate;
0087     m_value = qBound(qreal(0.0), value, qreal(1.0));
0088 
0089     setIdentity();
0090 
0091     m_matrix[0] = 0.213 + 0.787 * value;
0092     m_matrix[1] = 0.715 - 0.715 * value;
0093     m_matrix[2] = 0.072 - 0.072 * value;
0094 
0095     m_matrix[5] = 0.213 - 0.213 * value;
0096     m_matrix[6] = 0.715 + 0.285 * value;
0097     m_matrix[7] = 0.072 - 0.072 * value;
0098 
0099     m_matrix[10] = 0.213 - 0.213 * value;
0100     m_matrix[11] = 0.715 - 0.715 * value;
0101     m_matrix[12] = 0.072 + 0.928 * value;
0102 }
0103 
0104 qreal ColorMatrixEffect::saturate() const
0105 {
0106     if (m_type == Saturate)
0107         return m_value;
0108     else
0109         return 1.0;
0110 }
0111 
0112 void ColorMatrixEffect::setHueRotate(qreal value)
0113 {
0114     m_type = HueRotate;
0115     m_value = value;
0116 
0117     const qreal rad = m_value * M_PI / 180.0;
0118     const qreal c = cos(rad);
0119     const qreal s = sin(rad);
0120 
0121     setIdentity();
0122 
0123     m_matrix[0] = 0.213 + 0.787 * c - 0.213 * s;
0124     m_matrix[1] = 0.715 - 0.715 * c - 0.715 * s;
0125     m_matrix[2] = 0.072 - 0.072 * c + 0.928 * s;
0126 
0127     m_matrix[5] = 0.213 - 0.213 * c + 0.143 * s;
0128     m_matrix[6] = 0.715 + 0.285 * c + 0.140 * s;
0129     m_matrix[7] = 0.072 - 0.072 * c - 0.283 * s;
0130 
0131     m_matrix[10] = 0.213 - 0.213 * c - 0.787 * s;
0132     m_matrix[11] = 0.715 - 0.715 * c + 0.715 * s;
0133     m_matrix[12] = 0.072 + 0.928 * c + 0.072 * s;
0134 }
0135 
0136 qreal ColorMatrixEffect::hueRotate() const
0137 {
0138     if (m_type == HueRotate)
0139         return m_value;
0140     else
0141         return 0.0;
0142 }
0143 
0144 void ColorMatrixEffect::setLuminanceAlpha()
0145 {
0146     m_type = LuminanceAlpha;
0147 
0148     memset(m_matrix.data(), 0, MatrixSize*sizeof(qreal));
0149 
0150     m_matrix[15] = 0.2125;
0151     m_matrix[16] = 0.7154;
0152     m_matrix[17] = 0.0721;
0153     m_matrix[18] = 0.0;
0154 }
0155 
0156 QImage ColorMatrixEffect::processImage(const QImage &image, const KoFilterEffectRenderContext &context) const
0157 {
0158     QImage result = image;
0159 
0160     const QRgb *src = (const QRgb*)image.constBits();
0161     QRgb *dst = (QRgb*)result.bits();
0162     int w = result.width();
0163 
0164     const qreal * m = m_matrix.data();
0165     qreal sa, sr, sg, sb;
0166     qreal da, dr, dg, db;
0167 
0168     QRect roi = context.filterRegion().toRect();
0169     for (int row = roi.top(); row < roi.bottom(); ++row) {
0170         for (int col = roi.left(); col < roi.right(); ++col) {
0171             const QRgb &s = src[row*w+col];
0172             sa = fromIntColor[qAlpha(s)];
0173             sr = fromIntColor[qRed(s)];
0174             sg = fromIntColor[qGreen(s)];
0175             sb = fromIntColor[qBlue(s)];
0176             // the matrix is applied to non-premultiplied color values
0177             // so we have to convert colors by dividing by alpha value
0178             if (sa > 0.0 && sa < 1.0) {
0179                 sr /= sa;
0180                 sb /= sa;
0181                 sg /= sa;
0182             }
0183 
0184             // apply matrix to color values
0185             dr = m[ 0] * sr + m[ 1] * sg + m[ 2] * sb + m[ 3] * sa + m[ 4];
0186             dg = m[ 5] * sr + m[ 6] * sg + m[ 7] * sb + m[ 8] * sa + m[ 9];
0187             db = m[10] * sr + m[11] * sg + m[12] * sb + m[13] * sa + m[14];
0188             da = m[15] * sr + m[16] * sg + m[17] * sb + m[18] * sa + m[19];
0189 
0190             // the new alpha value
0191             da *= 255.0;
0192 
0193             // set pre-multiplied color values on destination image
0194             dst[row*w+col] = qRgba(static_cast<quint8>(qBound(qreal(0.0), dr * da, qreal(255.0))),
0195                                    static_cast<quint8>(qBound(qreal(0.0), dg * da, qreal(255.0))),
0196                                    static_cast<quint8>(qBound(qreal(0.0), db * da, qreal(255.0))),
0197                                    static_cast<quint8>(qBound(qreal(0.0), da, qreal(255.0))));
0198         }
0199     }
0200 
0201     return result;
0202 }
0203 
0204 bool ColorMatrixEffect::load(const KoXmlElement &element, const KoFilterEffectLoadingContext &)
0205 {
0206     if (element.tagName() != id())
0207         return false;
0208 
0209     QString typeStr = element.attribute("type");
0210     if (typeStr.isEmpty())
0211         return false;
0212 
0213     QString valueStr = element.attribute("values");
0214 
0215     setIdentity();
0216     m_type = Matrix;
0217 
0218     if (typeStr == "matrix") {
0219         // values are separated by whitespace and/or comma
0220         QStringList values = valueStr.trimmed().split(QRegExp("(\\s+|,)"), QString::SkipEmptyParts);
0221         if (values.count() == MatrixSize) {
0222             for (int i = 0; i < MatrixSize; ++i) {
0223                 m_matrix[i] = values[i].toDouble();
0224             }
0225         }
0226     } else if (typeStr == "saturate") {
0227         if (!valueStr.isEmpty()) {
0228             setSaturate(valueStr.toDouble());
0229         }
0230     } else if (typeStr == "hueRotate") {
0231         if (!valueStr.isEmpty()) {
0232             setHueRotate(valueStr.toDouble());
0233         }
0234     } else if (typeStr == "luminanceToAlpha") {
0235         setLuminanceAlpha();
0236     } else {
0237         return false;
0238     }
0239 
0240     return true;
0241 }
0242 
0243 void ColorMatrixEffect::save(KoXmlWriter &writer)
0244 {
0245     writer.startElement(ColorMatrixEffectId);
0246 
0247     saveCommonAttributes(writer);
0248 
0249     switch (m_type) {
0250     case Matrix: {
0251         writer.addAttribute("type", "matrix");
0252         QString matrix;
0253         for (int r = 0; r < MatrixRows; ++r) {
0254             for (int c = 0; c < MatrixCols; ++c) {
0255                 matrix += QString("%1 ").arg(m_matrix[r*MatrixCols+c]);
0256             }
0257         }
0258         writer.addAttribute("values", matrix);
0259     }
0260     break;
0261     case Saturate:
0262         writer.addAttribute("type", "saturate");
0263         writer.addAttribute("values", QString("%1").arg(m_value));
0264         break;
0265     case HueRotate:
0266         writer.addAttribute("type", "hueRotate");
0267         writer.addAttribute("values", QString("%1").arg(m_value));
0268         break;
0269     case LuminanceAlpha:
0270         writer.addAttribute("type", "luminanceToAlpha");
0271         break;
0272     }
0273 
0274     writer.endElement();
0275 }