164 lines
4.5 KiB
C++

// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// Self
#include "redeyereductionimageoperation.h"
// STL
#include <cmath>
// Qt
#include <QImage>
#include <QPainter>
// KF
#include <KLocalizedString>
// Local
#include "document/abstractdocumenteditor.h"
#include "document/document.h"
#include "document/documentjob.h"
#include "gwenview_lib_debug.h"
#include "ramp.h"
namespace Gwenview
{
class RedEyeReductionJob : public ThreadedDocumentJob
{
public:
RedEyeReductionJob(const QRectF &rectF)
: mRectF(rectF)
{
}
void threadedStart() override
{
if (!checkDocumentEditor()) {
return;
}
QImage img = document()->image();
RedEyeReductionImageOperation::apply(&img, mRectF);
document()->editor()->setImage(img);
setError(NoError);
}
private:
QRectF mRectF;
};
struct RedEyeReductionImageOperationPrivate {
QRectF mRectF;
QImage mOriginalImage;
};
RedEyeReductionImageOperation::RedEyeReductionImageOperation(const QRectF &rectF)
: d(new RedEyeReductionImageOperationPrivate)
{
d->mRectF = rectF;
setText(i18n("Reduce Red Eye"));
}
RedEyeReductionImageOperation::~RedEyeReductionImageOperation()
{
delete d;
}
void RedEyeReductionImageOperation::redo()
{
const QImage img = document()->image();
const QRect rect = d->mRectF.toAlignedRect();
d->mOriginalImage = img.copy(rect);
redoAsDocumentJob(new RedEyeReductionJob(d->mRectF));
}
void RedEyeReductionImageOperation::undo()
{
if (!document()->editor()) {
qCWarning(GWENVIEW_LIB_LOG) << "!document->editor()";
return;
}
QImage img = document()->image();
{
QPainter painter(&img);
painter.setCompositionMode(QPainter::CompositionMode_Source);
const QRect rect = d->mRectF.toAlignedRect();
painter.drawImage(rect.topLeft(), d->mOriginalImage);
}
document()->editor()->setImage(img);
finish(true);
}
/**
* This code is inspired from code found in a Paint.net plugin:
* http://paintdotnet.forumer.com/viewtopic.php?f=27&t=26193&p=205954&hilit=red+eye#p205954
*/
inline qreal computeRedEyeAlpha(const QColor &src)
{
int hue, sat, value;
src.getHsv(&hue, &sat, &value);
qreal axs = 1.0;
if (hue > 259) {
static const Ramp ramp(30, 35, 0., 1.);
axs = ramp(sat);
} else {
const Ramp ramp(hue * 2 + 29, hue * 2 + 40, 0., 1.);
axs = ramp(sat);
}
return qBound(qreal(0.), src.alphaF() * axs, qreal(1.));
}
void RedEyeReductionImageOperation::apply(QImage *img, const QRectF &rectF)
{
const QRect rect = rectF.toAlignedRect();
const qreal radius = rectF.width() / 2;
const qreal centerX = rectF.x() + radius;
const qreal centerY = rectF.y() + radius;
const Ramp radiusRamp(qMin(qreal(radius * 0.7), qreal(radius - 1)), radius, qreal(1.), qreal(0.));
uchar *line = img->scanLine(rect.top()) + rect.left() * 4;
for (int y = rect.top(); y < rect.bottom(); ++y, line += img->bytesPerLine()) {
QRgb *ptr = (QRgb *)line;
for (int x = rect.left(); x < rect.right(); ++x, ++ptr) {
const qreal currentRadius = sqrt(pow(y - centerY, 2) + pow(x - centerX, 2));
qreal alpha = radiusRamp(currentRadius);
if (qFuzzyCompare(alpha, 0)) {
continue;
}
const QColor src(*ptr);
alpha *= computeRedEyeAlpha(src);
int r = src.red();
int g = src.green();
int b = src.blue();
QColor dst;
// Replace red with green, and blend according to alpha
dst.setRed(int((1 - alpha) * r + alpha * g));
dst.setGreen(g);
dst.setBlue(b);
*ptr = dst.rgba();
}
}
}
} // namespace