// vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 // Qt #include #include // KF #include // 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