File indexing completed on 2024-05-12 04:19:47

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "redeyereductionimageoperation.h"
0023 
0024 // STL
0025 #include <cmath>
0026 
0027 // Qt
0028 #include <QImage>
0029 #include <QPainter>
0030 
0031 // KF
0032 #include <KLocalizedString>
0033 
0034 // Local
0035 #include "document/abstractdocumenteditor.h"
0036 #include "document/document.h"
0037 #include "document/documentjob.h"
0038 #include "gwenview_lib_debug.h"
0039 #include "ramp.h"
0040 
0041 namespace Gwenview
0042 {
0043 class RedEyeReductionJob : public ThreadedDocumentJob
0044 {
0045 public:
0046     RedEyeReductionJob(const QRectF &rectF)
0047         : mRectF(rectF)
0048     {
0049     }
0050 
0051     void threadedStart() override
0052     {
0053         if (!checkDocumentEditor()) {
0054             return;
0055         }
0056         QImage img = document()->image();
0057         RedEyeReductionImageOperation::apply(&img, mRectF);
0058         document()->editor()->setImage(img);
0059         setError(NoError);
0060     }
0061 
0062 private:
0063     QRectF mRectF;
0064 };
0065 
0066 struct RedEyeReductionImageOperationPrivate {
0067     QRectF mRectF;
0068     QImage mOriginalImage;
0069 };
0070 
0071 RedEyeReductionImageOperation::RedEyeReductionImageOperation(const QRectF &rectF)
0072     : d(new RedEyeReductionImageOperationPrivate)
0073 {
0074     d->mRectF = rectF;
0075     setText(i18n("Reduce Red Eye"));
0076 }
0077 
0078 RedEyeReductionImageOperation::~RedEyeReductionImageOperation()
0079 {
0080     delete d;
0081 }
0082 
0083 void RedEyeReductionImageOperation::redo()
0084 {
0085     const QImage img = document()->image();
0086     const QRect rect = d->mRectF.toAlignedRect();
0087     d->mOriginalImage = img.copy(rect);
0088     redoAsDocumentJob(new RedEyeReductionJob(d->mRectF));
0089 }
0090 
0091 void RedEyeReductionImageOperation::undo()
0092 {
0093     if (!document()->editor()) {
0094         qCWarning(GWENVIEW_LIB_LOG) << "!document->editor()";
0095         return;
0096     }
0097     QImage img = document()->image();
0098     {
0099         QPainter painter(&img);
0100         painter.setCompositionMode(QPainter::CompositionMode_Source);
0101         const QRect rect = d->mRectF.toAlignedRect();
0102         painter.drawImage(rect.topLeft(), d->mOriginalImage);
0103     }
0104     document()->editor()->setImage(img);
0105     finish(true);
0106 }
0107 
0108 /**
0109  * This code is inspired from code found in a Paint.net plugin:
0110  * http://paintdotnet.forumer.com/viewtopic.php?f=27&t=26193&p=205954&hilit=red+eye#p205954
0111  */
0112 inline qreal computeRedEyeAlpha(const QColor &src)
0113 {
0114     int hue, sat, value;
0115     src.getHsv(&hue, &sat, &value);
0116 
0117     qreal axs = 1.0;
0118     if (hue > 259) {
0119         static const Ramp ramp(30, 35, 0., 1.);
0120         axs = ramp(sat);
0121     } else {
0122         const Ramp ramp(hue * 2 + 29, hue * 2 + 40, 0., 1.);
0123         axs = ramp(sat);
0124     }
0125 
0126     return qBound(qreal(0.), src.alphaF() * axs, qreal(1.));
0127 }
0128 
0129 void RedEyeReductionImageOperation::apply(QImage *img, const QRectF &rectF)
0130 {
0131     const QRect rect = rectF.toAlignedRect();
0132     const qreal radius = rectF.width() / 2;
0133     const qreal centerX = rectF.x() + radius;
0134     const qreal centerY = rectF.y() + radius;
0135     const Ramp radiusRamp(qMin(qreal(radius * 0.7), qreal(radius - 1)), radius, qreal(1.), qreal(0.));
0136 
0137     uchar *line = img->scanLine(rect.top()) + rect.left() * 4;
0138     for (int y = rect.top(); y < rect.bottom(); ++y, line += img->bytesPerLine()) {
0139         QRgb *ptr = (QRgb *)line;
0140 
0141         for (int x = rect.left(); x < rect.right(); ++x, ++ptr) {
0142             const qreal currentRadius = sqrt(pow(y - centerY, 2) + pow(x - centerX, 2));
0143             qreal alpha = radiusRamp(currentRadius);
0144             if (qFuzzyCompare(alpha, 0)) {
0145                 continue;
0146             }
0147 
0148             const QColor src(*ptr);
0149             alpha *= computeRedEyeAlpha(src);
0150             int r = src.red();
0151             int g = src.green();
0152             int b = src.blue();
0153             QColor dst;
0154             // Replace red with green, and blend according to alpha
0155             dst.setRed(int((1 - alpha) * r + alpha * g));
0156             dst.setGreen(g);
0157             dst.setBlue(b);
0158             *ptr = dst.rgba();
0159         }
0160     }
0161 }
0162 
0163 } // namespace