diff options
Diffstat (limited to 'src/libs/widgets/common/curveswidget.cpp')
-rw-r--r-- | src/libs/widgets/common/curveswidget.cpp | 838 |
1 files changed, 838 insertions, 0 deletions
diff --git a/src/libs/widgets/common/curveswidget.cpp b/src/libs/widgets/common/curveswidget.cpp new file mode 100644 index 00000000..281aecc9 --- /dev/null +++ b/src/libs/widgets/common/curveswidget.cpp @@ -0,0 +1,838 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-12-01 + * Description : a widget to draw histogram curves + * + * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * 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, 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. + * + * ============================================================ */ + +#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) + +// C++ includes. + +#include <cmath> +#include <cstdlib> + +// TQt includes. + +#include <tqpixmap.h> +#include <tqpainter.h> +#include <tqpoint.h> +#include <tqpen.h> +#include <tqevent.h> +#include <tqtimer.h> +#include <tqrect.h> +#include <tqcolor.h> +#include <tqfont.h> +#include <tqfontmetrics.h> + +// KDE includes. + +#include <kcursor.h> +#include <tdelocale.h> + +// Digikam includes. + +#include "ddebug.h" +#include "imagehistogram.h" +#include "imagecurves.h" + +// Local includes. + +#include "curveswidget.h" +#include "curveswidget.moc" + +namespace Digikam +{ + +class CurvesWidgetPriv +{ +public: + + enum RepaintType + { + HistogramDataLoading = 0, // Image Data loading in progress. + HistogramNone, // No current histogram values calculation. + HistogramStarted, // Histogram values calculation started. + HistogramCompleted, // Histogram values calculation completed. + HistogramFailed // Histogram values calculation failed. + }; + + CurvesWidgetPriv() + { + blinkTimer = 0; + curves = 0; + grabPoint = -1; + last = 0; + guideVisible = false; + xMouseOver = -1; + yMouseOver = -1; + clearFlag = HistogramNone; + pos = 0; + } + + int clearFlag; // Clear drawing zone with message. + int leftMost; + int rightMost; + int grabPoint; + int last; + int xMouseOver; + int yMouseOver; + int pos; // Position of animation during loading/calculation. + + bool sixteenBits; + bool readOnlyMode; + bool guideVisible; + + DColor colorGuide; + + TQTimer *blinkTimer; + + ImageCurves *curves; // Curves data instance. + +}; + +CurvesWidget::CurvesWidget(int w, int h, TQWidget *parent, bool readOnly) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new CurvesWidgetPriv; + + setup(w, h, readOnly); +} + +CurvesWidget::CurvesWidget(int w, int h, + uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, + TQWidget *parent, bool readOnly) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new CurvesWidgetPriv; + + setup(w, h, readOnly); + updateData(i_data, i_w, i_h, i_sixteenBits); +} + +CurvesWidget::~CurvesWidget() +{ + d->blinkTimer->stop(); + + if (m_imageHistogram) + delete m_imageHistogram; + + if (d->curves) + delete d->curves; + + delete d; +} + +void CurvesWidget::setup(int w, int h, bool readOnly) +{ + d->readOnlyMode = readOnly; + d->curves = new ImageCurves(true); + m_channelType = ValueHistogram; + m_scaleType = LogScaleHistogram; + m_imageHistogram = 0; + + setMouseTracking(true); + setPaletteBackgroundColor(colorGroup().background()); + setMinimumSize(w, h); + + d->blinkTimer = new TQTimer( this ); + + connect(d->blinkTimer, TQ_SIGNAL(timeout()), + this, TQ_SLOT(slotBlinkTimerDone())); +} + +void CurvesWidget::updateData(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits) +{ + stopHistogramComputation(); + + d->sixteenBits = i_sixteenBits; + + // Remove old histogram data from memory. + if (m_imageHistogram) + delete m_imageHistogram; + + // Calc new histogram data + m_imageHistogram = new ImageHistogram(i_data, i_w, i_h, i_sixteenBits, this); + + if (d->curves) + delete d->curves; + + d->curves = new ImageCurves(i_sixteenBits); + reset(); +} + +void CurvesWidget::reset() +{ + if (d->curves) + d->curves->curvesReset(); + + d->grabPoint = -1; + d->guideVisible = false; + repaint(false); +} + +ImageCurves* CurvesWidget::curves() const +{ + return d->curves; +} + +void CurvesWidget::setDataLoading() +{ + if (d->clearFlag != CurvesWidgetPriv::HistogramDataLoading) + { + setCursor(KCursor::waitCursor()); + d->clearFlag = CurvesWidgetPriv::HistogramDataLoading; + d->pos = 0; + d->blinkTimer->start(100); + } +} + +void CurvesWidget::setLoadingFailed() +{ + d->clearFlag = CurvesWidgetPriv::HistogramFailed; + d->pos = 0; + d->blinkTimer->stop(); + repaint(false); + setCursor(KCursor::arrowCursor()); +} + +void CurvesWidget::setCurveGuide(const DColor& color) +{ + d->guideVisible = true; + d->colorGuide = color; + repaint(false); +} + +void CurvesWidget::curveTypeChanged() +{ + switch (d->curves->getCurveType(m_channelType)) + { + case ImageCurves::CURVE_SMOOTH: + + // pick representative points from the curve and make them control points + + for (int i = 0; i <= 8; i++) + { + int index = CLAMP(i * m_imageHistogram->getHistogramSegment()/8, + 0, m_imageHistogram->getHistogramSegment()-1); + + d->curves->setCurvePoint( m_channelType, + i * 2, TQPoint(index, + d->curves->getCurveValue(m_channelType, + index)) ); + } + + d->curves->curvesCalculateCurve(m_channelType); + break; + + case ImageCurves::CURVE_FREE: + break; + } + + repaint(false); + emit signalCurvesChanged(); +} + +void CurvesWidget::customEvent(TQCustomEvent *event) +{ + if (!event) return; + + ImageHistogram::EventData *ed = (ImageHistogram::EventData*) event->data(); + + if (!ed) return; + + if (ed->starting) + { + setCursor(KCursor::waitCursor()); + d->clearFlag = CurvesWidgetPriv::HistogramStarted; + d->blinkTimer->start(200); + repaint(false); + } + else + { + if (ed->success) + { + // Repaint histogram + d->clearFlag = CurvesWidgetPriv::HistogramCompleted; + d->blinkTimer->stop(); + repaint(false); + setCursor(KCursor::arrowCursor()); + } + else + { + d->clearFlag = CurvesWidgetPriv::HistogramFailed; + d->blinkTimer->stop(); + repaint(false); + setCursor(KCursor::arrowCursor()); + emit signalHistogramComputationFailed(); + } + } + + delete ed; +} + +void CurvesWidget::stopHistogramComputation() +{ + if (m_imageHistogram) + m_imageHistogram->stopCalcHistogramValues(); + + d->blinkTimer->stop(); + d->pos = 0; +} + +void CurvesWidget::slotBlinkTimerDone() +{ + repaint(false); + d->blinkTimer->start(200); +} + +void CurvesWidget::paintEvent(TQPaintEvent*) +{ + if (d->clearFlag == CurvesWidgetPriv::HistogramDataLoading || + d->clearFlag == CurvesWidgetPriv::HistogramStarted) + { + // In first, we draw an animation. + + int asize = 24; + TQPixmap anim(asize, asize); + TQPainter p2; + p2.begin(&anim, this); + p2.fillRect(0, 0, asize, asize, palette().active().background()); + p2.translate(asize/2, asize/2); + + d->pos = (d->pos + 10) % 360; + p2.setPen(TQPen(palette().active().text())); + p2.rotate(d->pos); + for ( int i=0 ; i<12 ; i++ ) + { + p2.drawLine(asize/2-5, 0, asize/2-2, 0); + p2.rotate(30); + } + p2.end(); + + // ... and we render busy text. + + TQPixmap pm(size()); + TQPainter p1; + p1.begin(&pm, this); + p1.fillRect(0, 0, width(), height(), palette().active().background()); + p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine)); + p1.drawRect(0, 0, width(), height()); + p1.drawPixmap(width()/2 - asize /2, asize, anim); + p1.setPen(TQPen(palette().active().text())); + + if (d->clearFlag == CurvesWidgetPriv::HistogramDataLoading) + p1.drawText(0, 0, width(), height(), TQt::AlignCenter, + i18n("Loading image...")); + else + p1.drawText(0, 0, width(), height(), TQt::AlignCenter, + i18n("Histogram calculation...")); + + p1.end(); + bitBlt(this, 0, 0, &pm); + return; + } + + if (d->clearFlag == CurvesWidgetPriv::HistogramFailed) + { + TQPixmap pm(size()); + TQPainter p1; + p1.begin(&pm, this); + p1.fillRect(0, 0, width(), height(), palette().active().background()); + p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine)); + p1.drawRect(0, 0, width(), height()); + p1.setPen(TQPen(palette().active().text())); + p1.drawText(0, 0, width(), height(), TQt::AlignCenter, + i18n("Histogram\ncalculation\nfailed.")); + p1.end(); + bitBlt(this, 0, 0, &pm); + return; + } + + if (!m_imageHistogram) return; + + int x, y; + int wWidth = width(); + int wHeight = height(); + double max; + class ImageHistogram *histogram = m_imageHistogram; + + x = 0; + y = 0; + max = 0.0; + + switch(m_channelType) + { + case CurvesWidget::GreenChannelHistogram: // Green channel. + max = histogram->getMaximum(ImageHistogram::GreenChannel); + break; + + case CurvesWidget::BlueChannelHistogram: // Blue channel. + max = histogram->getMaximum(ImageHistogram::BlueChannel); + break; + + case CurvesWidget::RedChannelHistogram: // Red channel. + max = histogram->getMaximum(ImageHistogram::RedChannel); + break; + + case CurvesWidget::AlphaChannelHistogram: // Alpha channel. + max = histogram->getMaximum(ImageHistogram::AlphaChannel); + break; + + case CurvesWidget::ValueHistogram: // Luminosity. + max = histogram->getMaximum(ImageHistogram::ValueChannel); + break; + } + + switch (m_scaleType) + { + case CurvesWidget::LinScaleHistogram: + break; + + case CurvesWidget::LogScaleHistogram: + if (max > 0.0) + max = log (max); + else + max = 1.0; + break; + } + + // Drawing selection or all histogram values. + // A TQPixmap is used for enable the double buffering. + + TQPixmap pm(size()); + TQPainter p1; + p1.begin(&pm, this); + + int curvePrevVal = 0; + + for (x = 0 ; x < wWidth ; x++) + { + double value = 0.0; + int i, j; + int curveVal; + + i = (x * histogram->getHistogramSegment()) / wWidth; + j = ((x + 1) * histogram->getHistogramSegment()) / wWidth; + + curveVal = d->curves->getCurveValue(m_channelType, i); + + do + { + double v = 0.0; + + switch(m_channelType) + { + case CurvesWidget::RedChannelHistogram: // Red channel. + v = histogram->getValue(ImageHistogram::RedChannel, i++); + break; + + case CurvesWidget::GreenChannelHistogram: // Green channel. + v = histogram->getValue(ImageHistogram::GreenChannel, i++); + break; + + case CurvesWidget::BlueChannelHistogram: // Blue channel. + v = histogram->getValue(ImageHistogram::BlueChannel, i++); + break; + + case CurvesWidget::AlphaChannelHistogram: // Alpha channel. + v = histogram->getValue(ImageHistogram::AlphaChannel, i++); + break; + + case CurvesWidget::ValueHistogram: // Luminosity. + v = histogram->getValue(ImageHistogram::ValueChannel, i++); + break; + } + + if (v > value) + value = v; + } + while (i < j); + + switch (m_scaleType) + { + case CurvesWidget::LinScaleHistogram: + y = (int) ((wHeight * value) / max); + break; + + case CurvesWidget::LogScaleHistogram: + if (value <= 0.0) value = 1.0; + y = (int) ((wHeight * log (value)) / max); + break; + + default: + y = 0; + break; + } + + // Drawing histogram + + p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine)); + p1.drawLine(x, wHeight, x, wHeight - y); + p1.setPen(TQPen(palette().active().background(), 1, TQt::SolidLine)); + p1.drawLine(x, wHeight - y, x, 0); + + // Drawing curves. + + p1.setPen(TQPen(palette().active().link(), 2, TQt::SolidLine)); + p1.drawLine(x - 1, wHeight - ((curvePrevVal * wHeight) / histogram->getHistogramSegment()), + x, wHeight - ((curveVal * wHeight) / histogram->getHistogramSegment())); + + curvePrevVal = curveVal; + } + + // Drawing curves points. + + if (!d->readOnlyMode && d->curves->getCurveType(m_channelType) == ImageCurves::CURVE_SMOOTH) + { + p1.setPen(TQPen(TQt::red, 3, TQt::SolidLine)); + + for (int p = 0 ; p < 17 ; p++) + { + TQPoint curvePoint = d->curves->getCurvePoint(m_channelType, p); + + if (curvePoint.x() >= 0) + { + p1.drawEllipse( ((curvePoint.x() * wWidth) / histogram->getHistogramSegment()) - 2, + wHeight - 2 - ((curvePoint.y() * wHeight) / histogram->getHistogramSegment()), + 4, 4 ); + } + } + } + + // Drawing black/middle/highlight tone grid separators. + + p1.setPen(TQPen(palette().active().base(), 1, TQt::SolidLine)); + p1.drawLine(wWidth/4, 0, wWidth/4, wHeight); + p1.drawLine(wWidth/2, 0, wWidth/2, wHeight); + p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight); + p1.drawLine(0, wHeight/4, wWidth, wHeight/4); + p1.drawLine(0, wHeight/2, wWidth, wHeight/2); + p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4); + + // Drawing X,Y point position dragged by mouse over widget. + + p1.setPen(TQPen(TQt::red, 1, TQt::DotLine)); + + if (d->xMouseOver != -1 && d->yMouseOver != -1) + { + TQString string = i18n("x:%1\ny:%2").arg(d->xMouseOver).arg(d->yMouseOver); + TQFontMetrics fontMt(string); + TQRect rect = fontMt.boundingRect(0, 0, wWidth, wHeight, 0, string); + rect.moveRight(wWidth); + rect.moveBottom(wHeight); + p1.drawText(rect, TQt::AlignLeft||TQt::AlignTop, string); + } + + // Drawing color guide. + + int guidePos; + + if (d->guideVisible) + { + switch(m_channelType) + { + case CurvesWidget::RedChannelHistogram: + guidePos = d->colorGuide.red(); + break; + + case CurvesWidget::GreenChannelHistogram: + guidePos = d->colorGuide.green(); + break; + + case CurvesWidget::BlueChannelHistogram: + guidePos = d->colorGuide.blue(); + break; + + case CurvesWidget::ValueHistogram: + guidePos = TQMAX(TQMAX(d->colorGuide.red(), d->colorGuide.green()), d->colorGuide.blue()); + break; + + default: // Alpha. + guidePos = -1; + break; + } + + if (guidePos != -1) + { + int xGuide = (guidePos * wWidth) / histogram->getHistogramSegment(); + p1.drawLine(xGuide, 0, xGuide, wHeight); + + TQString string = i18n("x:%1").arg(guidePos); + TQFontMetrics fontMt( string ); + TQRect rect = fontMt.boundingRect(0, 0, wWidth, wHeight, 0, string); + p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine)); + rect.moveTop(1); + + if (xGuide < wWidth/2) + { + rect.moveLeft(xGuide); + p1.fillRect(rect, TQBrush(TQColor(250, 250, 255))); + p1.drawRect(rect); + rect.moveLeft(xGuide+3); + p1.drawText(rect, TQt::AlignLeft, string); + } + else + { + rect.moveRight(xGuide); + p1.fillRect(rect, TQBrush(TQColor(250, 250, 255))); + p1.drawRect(rect); + rect.moveRight(xGuide-3); + p1.drawText(rect, TQt::AlignRight, string); + } + } + } + + // Drawing frame. + + p1.setPen(TQPen(palette().active().foreground(), 1, TQt::SolidLine)); + p1.drawRect(0, 0, width(), height()); + + p1.end(); + bitBlt(this, 0, 0, &pm); +} + +void CurvesWidget::mousePressEvent(TQMouseEvent *e) +{ + if (d->readOnlyMode || !m_imageHistogram) return; + + int i; + int closest_point; + int distance; + + if (e->button() != TQt::LeftButton || d->clearFlag == CurvesWidgetPriv::HistogramStarted) + return; + + int x = CLAMP((int)(e->pos().x() * + ((float)(m_imageHistogram->getHistogramSegment()-1) / (float)width())), + 0, m_imageHistogram->getHistogramSegment()-1 ); + int y = CLAMP((int)(e->pos().y() * + ((float)(m_imageHistogram->getHistogramSegment()-1) / (float)height())), + 0, m_imageHistogram->getHistogramSegment()-1 ); + + distance = 65536; + + for (i = 0, closest_point = 0 ; i < 17 ; i++) + { + int xcurvepoint = d->curves->getCurvePointX(m_channelType, i); + + if (xcurvepoint != -1) + { + if (abs (x - xcurvepoint) < distance) + { + distance = abs (x - xcurvepoint); + closest_point = i; + } + } + } + + int delta = m_imageHistogram->getHistogramSegment()/16; + if (distance > 8) + closest_point = (x + delta/2) / delta; + + setCursor(KCursor::crossCursor()); + + switch(d->curves->getCurveType(m_channelType)) + { + case ImageCurves::CURVE_SMOOTH: + { + // Determine the leftmost and rightmost points. + + d->leftMost = -1; + + for (i = closest_point - 1 ; i >= 0 ; i--) + { + if (d->curves->getCurvePointX(m_channelType, i) != -1) + { + d->leftMost = d->curves->getCurvePointX(m_channelType, i); + break; + } + } + + d->rightMost = m_imageHistogram->getHistogramSegment(); + + for (i = closest_point + 1 ; i < 17 ; i++) + { + if (d->curves->getCurvePointX(m_channelType, i) != -1) + { + d->rightMost = d->curves->getCurvePointX(m_channelType, i); + break; + } + } + + d->grabPoint = closest_point; + d->curves->setCurvePoint(m_channelType, d->grabPoint, + TQPoint(x, m_imageHistogram->getHistogramSegment() - y)); + + break; + } + + case ImageCurves::CURVE_FREE: + { + + d->curves->setCurveValue(m_channelType, x, m_imageHistogram->getHistogramSegment() - y); + d->grabPoint = x; + d->last = y; + break; + } + } + + d->curves->curvesCalculateCurve(m_channelType); + repaint(false); +} + +void CurvesWidget::mouseReleaseEvent(TQMouseEvent *e) +{ + if (d->readOnlyMode || !m_imageHistogram) return; + + if (e->button() != TQt::LeftButton || d->clearFlag == CurvesWidgetPriv::HistogramStarted) + return; + + setCursor(KCursor::arrowCursor()); + d->grabPoint = -1; + d->curves->curvesCalculateCurve(m_channelType); + repaint(false); + emit signalCurvesChanged(); +} + +void CurvesWidget::mouseMoveEvent(TQMouseEvent *e) +{ + if (d->readOnlyMode || !m_imageHistogram) return; + + int i; + int closest_point; + int x1, x2, y1, y2; + int distance; + + if (d->clearFlag == CurvesWidgetPriv::HistogramStarted) + return; + + int x = CLAMP( (int)(e->pos().x()*((float)(m_imageHistogram->getHistogramSegment()-1)/(float)width())), + 0, m_imageHistogram->getHistogramSegment()-1 ); + int y = CLAMP( (int)(e->pos().y()*((float)(m_imageHistogram->getHistogramSegment()-1)/(float)height())), + 0, m_imageHistogram->getHistogramSegment()-1 ); + + distance = 65536; + + for (i = 0, closest_point = 0 ; i < 17 ; i++) + { + if (d->curves->getCurvePointX(m_channelType, i) != -1) + { + if (abs (x - d->curves->getCurvePointX(m_channelType, i)) < distance) + { + distance = abs (x - d->curves->getCurvePointX(m_channelType, i)); + closest_point = i; + } + } + } + + int delta = m_imageHistogram->getHistogramSegment()/16; + if (distance > 8) + closest_point = (x + delta/2) / delta; + + switch ( d->curves->getCurveType(m_channelType) ) + { + case ImageCurves::CURVE_SMOOTH: + { + if (d->grabPoint == -1) // If no point is grabbed... + { + if ( d->curves->getCurvePointX(m_channelType, closest_point) != -1 ) + setCursor(KCursor::arrowCursor()); + else + setCursor(KCursor::crossCursor()); + } + else // Else, drag the grabbed point + { + setCursor(KCursor::crossCursor()); + + d->curves->setCurvePointX(m_channelType, d->grabPoint, -1); + + if (x > d->leftMost && x < d->rightMost) + { + closest_point = (x + delta/2) / delta; + + if (d->curves->getCurvePointX(m_channelType, closest_point) == -1) + d->grabPoint = closest_point; + + d->curves->setCurvePoint(m_channelType, d->grabPoint, + TQPoint(x, m_imageHistogram->getHistogramSegment()-1 - y)); + } + + d->curves->curvesCalculateCurve(m_channelType); + emit signalCurvesChanged(); + } + + break; + } + + case ImageCurves::CURVE_FREE: + { + if (d->grabPoint != -1) + { + if (d->grabPoint > x) + { + x1 = x; + x2 = d->grabPoint; + y1 = y; + y2 = d->last; + } + else + { + x1 = d->grabPoint; + x2 = x; + y1 = d->last; + y2 = y; + } + + if (x2 != x1) + { + for (i = x1 ; i <= x2 ; i++) + d->curves->setCurveValue(m_channelType, i, + m_imageHistogram->getHistogramSegment()-1 - (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1))); + } + else + { + d->curves->setCurveValue(m_channelType, x, m_imageHistogram->getHistogramSegment()-1 - y); + } + + d->grabPoint = x; + d->last = y; + } + + emit signalCurvesChanged(); + + break; + } + } + + d->xMouseOver = x; + d->yMouseOver = m_imageHistogram->getHistogramSegment()-1 - y; + emit signalMouseMoved(d->xMouseOver, d->yMouseOver); + repaint(false); +} + +void CurvesWidget::leaveEvent(TQEvent*) +{ + d->xMouseOver = -1; + d->yMouseOver = -1; + emit signalMouseMoved(d->xMouseOver, d->yMouseOver); + repaint(false); +} + +} // NameSpace Digikam |