1 
2 
3 #include "toonzqt/tonecurvefield.h"
4 #include "toonzqt/fxhistogramrender.h"
5 #include "toonzqt/doublepairfield.h"
6 #include "toonzqt/checkbox.h"
7 #include "toonzqt/doublefield.h"
8 #include <QPainter>
9 #include <QPainterPath>
10 #include <QStackedWidget>
11 #include <QMouseEvent>
12 #include <QApplication>
13 #include <QHBoxLayout>
14 #include <QComboBox>
15 #include <QLabel>
16 
17 using namespace DVGui;
18 
19 //-----------------------------------------------------------------------------
20 namespace {
21 
22 // minimum distance between control points
23 const double cpMargin = 4.0;
24 
25 //-----------------------------------------------------------------------------
26 
getPercentAtPoint(QPointF point,QPainterPath path)27 double getPercentAtPoint(QPointF point, QPainterPath path) {
28   int i;
29   for (i = 1; i < 100; i++) {
30     double p          = double(i) * 0.01;
31     QPointF pathPoint = path.pointAtPercent(p);
32     if (std::abs(pathPoint.x() - point.x()) < 3 &&
33         std::abs(pathPoint.y() - point.y()) < 3)
34       return p;
35   }
36   return 0;
37 }
38 
39 //-----------------------------------------------------------------------------
40 
getIntersectedPoint(QRectF rect,QPainterPath path)41 QList<QPointF> getIntersectedPoint(QRectF rect, QPainterPath path) {
42   int y0 = rect.top();
43   int y1 = rect.bottom();
44   QList<QPointF> points;
45   double g      = 1.0 / 256.0;
46   QPointF prec  = path.pointAtPercent(0);
47   QPointF point = path.pointAtPercent(g);
48   int j         = 0;
49   for (j = 2; j < 256; j++) {
50     QPointF next = path.pointAtPercent(double(j) * g);
51     if (prec.y() >= y0 && prec.y() <= y1) {
52       if (point.y() < y0)
53         points.push_back(QPointF(prec.x(), y0));
54       else if (point.y() > y1)
55         points.push_back(QPointF(prec.x(), y1));
56     }
57     if (next.y() >= y0 && next.y() <= y1) {
58       if (point.y() < y0)
59         points.push_back(QPointF(next.x(), y0));
60       else if (point.y() > y1)
61         points.push_back(QPointF(next.x(), y1));
62     }
63     prec  = point;
64     point = next;
65   }
66   return points;
67 }
68 
69 //-----------------------------------------------------------------------------
70 
qtNorm2(const QPointF & p)71 double qtNorm2(const QPointF &p) { return p.x() * p.x() + p.y() * p.y(); }
72 
qtDistance2(const QPointF & p1,const QPointF & p2)73 double qtDistance2(const QPointF &p1, const QPointF &p2) {
74   return qtNorm2(p2 - p1);
75 }
76 
77 //---------------------------------------------------------
78 // referred to the truncateSpeeds() in tdoubleparam.cpp
truncateSpeeds(double aFrame,double bFrame,QVector2D & aSpeedTrunc,QVector2D & bSpeedTrunc)79 void truncateSpeeds(double aFrame, double bFrame, QVector2D &aSpeedTrunc,
80                     QVector2D &bSpeedTrunc) {
81   double deltaX = bFrame - aFrame;
82   if (aSpeedTrunc.x() < 0) aSpeedTrunc.setX(0);
83   if (bSpeedTrunc.x() > 0) bSpeedTrunc.setX(0);
84 
85   if (aFrame + aSpeedTrunc.x() > bFrame) {
86     if (aSpeedTrunc.x() != 0) {
87       aSpeedTrunc = aSpeedTrunc * (deltaX / aSpeedTrunc.x());
88     }
89   }
90 
91   if (bFrame + bSpeedTrunc.x() < aFrame) {
92     if (bSpeedTrunc.x() != 0) {
93       bSpeedTrunc = -bSpeedTrunc * (deltaX / bSpeedTrunc.x());
94     }
95   }
96 }
97 
98 }  // anonymous namespace
99 //-----------------------------------------------------------------------------
100 
101 //=============================================================================
102 // ChennelCurveEditor
103 //=============================================================================
104 
ChennelCurveEditor(QWidget * parent,HistogramView * histogramView)105 ChennelCurveEditor::ChennelCurveEditor(QWidget *parent,
106                                        HistogramView *histogramView)
107     : QWidget(parent)
108     , m_histogramView(histogramView)
109     , m_currentControlPointIndex(-1)
110     , m_mouseButton(Qt::NoButton)
111     , m_curveHeight(256)
112     , m_LeftRightMargin(42)
113     , m_TopMargin(9)
114     , m_BottomMargin(48)
115     , m_isLinear(false)
116     , m_isEnlarged(false) {
117   setFixedSize(m_curveHeight + 2 * m_LeftRightMargin + 2,
118                m_curveHeight + m_TopMargin + m_BottomMargin);
119   setAttribute(Qt::WA_KeyCompression);
120   setFocusPolicy(Qt::StrongFocus);
121   setMouseTracking(true);
122 
123   m_histogramView->setDrawnWidget(this);
124   m_histogramView->setGraphHeight(m_curveHeight);
125   m_histogramView->setGraphAlphaMask(120);
126   m_verticalChannelBar =
127       new ChannelBar(0, m_histogramView->getChannelBarColor(), false);
128 }
129 
130 //-----------------------------------------------------------------------------
131 
setPoints(QList<TPointD> points)132 void ChennelCurveEditor::setPoints(QList<TPointD> points) {
133   if (!m_points.isEmpty()) m_points.clear();
134   for (const TPointD &point : points)
135     m_points.push_back(QPointF(point.x, point.y));
136   // update slider position according to the first and the last control points
137   int firstIndex = 3;
138   int lastIndex  = m_points.size() - 4;
139   emit firstLastXPostionChanged(m_points.at(firstIndex).x(),
140                                 m_points.at(lastIndex).x());
141   update();
142 }
143 
144 //-----------------------------------------------------------------------------
145 
getPoints()146 QList<TPointD> ChennelCurveEditor::getPoints() {
147   QList<TPointD> points;
148   if (m_points.isEmpty()) return points;
149   for (const QPointF &point : m_points)
150     points.push_back(TPointD(point.x(), point.y()));
151   return points;
152 }
153 
154 //-----------------------------------------------------------------------------
155 
setFirstLastXPosition(std::pair<double,double> values,bool isDragging)156 void ChennelCurveEditor::setFirstLastXPosition(std::pair<double, double> values,
157                                                bool isDragging) {
158   QPointF newX0(values.first, 0);
159   QPointF newX1(values.second, 0);
160 
161   int indexX0 = 3;
162   int indexX1 = m_points.size() - 4;
163   QPointF x0  = m_points.at(indexX0);
164   QPointF x1  = m_points.at(indexX1);
165   if (x0.x() != newX0.x()) {
166     QPointF delta(newX0.x() - x0.x(), 0);
167     moveCentralControlPoint(indexX0, delta);
168     update();
169   }
170   if (x1.x() != newX1.x()) {
171     QPointF delta(newX1.x() - x1.x(), 0);
172     moveCentralControlPoint(indexX1, delta);
173     update();
174   }
175   m_currentControlPointIndex = -1;
176 
177   if (!isDragging) emit controlPointChanged(false);
178 }
179 
180 //-----------------------------------------------------------------------------
181 
setLinear(bool isLinear)182 void ChennelCurveEditor::setLinear(bool isLinear) {
183   if (m_isLinear == isLinear) return;
184   m_isLinear = isLinear;
185   update();
186 }
187 
188 //-----------------------------------------------------------------------------
189 
setEnlarged(bool isEnlarged)190 void ChennelCurveEditor::setEnlarged(bool isEnlarged) {
191   if (m_isEnlarged == isEnlarged) return;
192   m_isEnlarged     = isEnlarged;
193   int widgetHeight = (m_isEnlarged) ? m_curveHeight * 2 : m_curveHeight;
194   setFixedSize(widgetHeight + 2 * m_LeftRightMargin + 2,
195                widgetHeight + m_TopMargin + m_BottomMargin);
196   m_histogramView->setGraphHeight(widgetHeight);
197   m_verticalChannelBar->setFixedHeight(widgetHeight + 22);
198   update();
199 }
200 
201 //-----------------------------------------------------------------------------
202 
setLabelRange(ChannelBar::Range range)203 void ChennelCurveEditor::setLabelRange(ChannelBar::Range range) {
204   m_histogramView->channelBar()->setLabelRange(range);
205   m_verticalChannelBar->setLabelRange(range);
206 }
207 
208 //-----------------------------------------------------------------------------
209 
checkPoint(const QPointF p)210 QPointF ChennelCurveEditor::checkPoint(const QPointF p) {
211   QPointF checkedP = p;
212   if (p.x() < 0)
213     checkedP.setX(0);
214   else if (p.x() > m_curveHeight)
215     checkedP.setX(m_curveHeight);
216   if (p.y() < 0)
217     checkedP.setY(0);
218   else if (p.y() > m_curveHeight)
219     checkedP.setY(m_curveHeight);
220   return checkedP;
221 }
222 
223 //-----------------------------------------------------------------------------
224 
getVisibleHandlePos(int index) const225 QPointF ChennelCurveEditor::getVisibleHandlePos(int index) const {
226   QRectF rect(0.0, 0.0, m_curveHeight, m_curveHeight);
227   QPointF handlePos(m_points.at(index));
228   if (isCentralControlPoint(index) || rect.contains(handlePos))
229     return handlePos;
230 
231   if (isLeftControlPoint(index)) {
232     QPointF cp = m_points.at(index + 1);
233     QVector2D lHandle(handlePos - cp);
234     if (handlePos.x() < 0) {
235       float ratio = -cp.x() / lHandle.x();
236       handlePos   = cp + lHandle.toPointF() * ratio;
237     }
238     if (handlePos.y() < 0) {
239       float ratio = -cp.y() / lHandle.y();
240       handlePos   = cp + lHandle.toPointF() * ratio;
241     } else if (handlePos.y() > 256) {
242       float ratio = (256 - cp.y()) / lHandle.y();
243       handlePos   = cp + lHandle.toPointF() * ratio;
244     }
245   } else {  // isRightControlPoint
246     QPointF cp = m_points.at(index - 1);
247     QVector2D rHandle(handlePos - cp);
248     if (handlePos.x() > 256) {
249       float ratio = (256 - cp.x()) / rHandle.x();
250       handlePos   = cp + rHandle.toPointF() * ratio;
251     }
252     if (handlePos.y() < 0) {
253       float ratio = -cp.y() / rHandle.y();
254       handlePos   = cp + rHandle.toPointF() * ratio;
255     } else if (handlePos.y() > 256) {
256       float ratio = (256 - cp.y()) / rHandle.y();
257       handlePos   = cp + rHandle.toPointF() * ratio;
258     }
259   }
260   return handlePos;
261 }
262 
263 //-----------------------------------------------------------------------------
264 
viewToStrokePoint(const QPointF & p)265 QPointF ChennelCurveEditor::viewToStrokePoint(const QPointF &p) {
266   double x = p.x() - m_LeftRightMargin - 1;
267   double y = p.y() - m_TopMargin;
268   if (m_isEnlarged) {
269     x *= 0.5;
270     y *= 0.5;
271   }
272   return QPointF(x, m_curveHeight - y);
273 }
274 
275 //-----------------------------------------------------------------------------
276 
getClosestPointIndex(const QPointF & pos,double & minDistance2) const277 int ChennelCurveEditor::getClosestPointIndex(const QPointF &pos,
278                                              double &minDistance2) const {
279   int closestPointIndex = -1;
280   minDistance2          = 0;
281   enum pointType { Handle = 0, ControlPoint, PseudoHandle } closestPointType;
282   QRectF rect(0, 0, m_curveHeight, m_curveHeight);
283   int i;
284   for (i = 3; i < (int)m_points.size() - 3; i++) {
285     if (m_isLinear && !isCentralControlPoint(i)) continue;
286     QPointF visiblePoint = getVisibleHandlePos(i);
287 
288     pointType type =
289         (isCentralControlPoint(i))
290             ? ControlPoint
291             : (visiblePoint == m_points.at(i)) ? Handle : PseudoHandle;
292 
293     double distance2 = qtDistance2(pos, visiblePoint);
294     if (closestPointIndex < 0 || distance2 < minDistance2 ||
295         (distance2 == minDistance2 && type < closestPointType)) {
296       minDistance2      = distance2;
297       closestPointIndex = i;
298       closestPointType  = type;
299     }
300   }
301   return closestPointIndex;
302 }
303 
304 //-----------------------------------------------------------------------------
305 
movePoint(int index,const QPointF delta)306 void ChennelCurveEditor::movePoint(int index, const QPointF delta) {
307   QPointF p = m_points.at(index);
308   p += delta;
309   setPoint(index, p);
310 
311   int firstIndex = 3;
312   int lastIndex  = m_points.size() - 4;
313   if (index == firstIndex)
314     emit firstLastXPostionChanged(p.x(), m_points.at(lastIndex).x());
315   if (index == lastIndex)
316     emit firstLastXPostionChanged(m_points.at(firstIndex).x(), p.x());
317 }
318 
319 //-----------------------------------------------------------------------------
320 
setPoint(int index,const QPointF p)321 void ChennelCurveEditor::setPoint(int index, const QPointF p) {
322   m_points.removeAt(index);
323   m_points.insert(index, p);
324 
325   int firstIndex = 3;
326   int lastIndex  = m_points.size() - 4;
327   if (index == firstIndex)
328     emit firstLastXPostionChanged(p.x(), m_points.at(lastIndex).x());
329   if (index == lastIndex)
330     emit firstLastXPostionChanged(m_points.at(firstIndex).x(), p.x());
331 }
332 
333 //-----------------------------------------------------------------------------
334 
moveCurrentControlPoint(QPointF delta)335 void ChennelCurveEditor::moveCurrentControlPoint(QPointF delta) {
336   assert(m_currentControlPointIndex != -1);
337   int pointCount = m_points.size();
338   // in case moving the control point
339   if (isCentralControlPoint(m_currentControlPointIndex))
340     moveCentralControlPoint(m_currentControlPointIndex, delta);
341   // in case moving the left handle
342   else if (isLeftControlPoint(m_currentControlPointIndex)) {
343     QPointF cp   = m_points.at(m_currentControlPointIndex + 1);
344     QPointF left = m_points.at(m_currentControlPointIndex);
345 
346     // handle should not move across the control point
347     if (left.x() + delta.x() > cp.x()) delta.setX(cp.x() - left.x());
348 
349     left += delta;
350     setPoint(m_currentControlPointIndex, left);
351 
352     // rotate the opposite handle keeping the handle length unchanged
353     if (m_currentControlPointIndex < pointCount - 5) {
354       QVector2D lHandle(cp - left);
355       if (!lHandle.isNull()) {
356         QPointF right = m_points.at(m_currentControlPointIndex + 2);
357         QVector2D rHandle(right - cp);
358         QVector2D newRHandle = lHandle.normalized() * rHandle.length();
359         setPoint(m_currentControlPointIndex + 2, cp + newRHandle.toPointF());
360       }
361     }
362     emit controlPointChanged(true);
363   }
364   // in case moving the right handle
365   else {
366     assert(isRightControlPoint(m_currentControlPointIndex));
367     QPointF cp    = m_points.at(m_currentControlPointIndex - 1);
368     QPointF right = m_points.at(m_currentControlPointIndex);
369 
370     // handle should not move across the control point
371     if (right.x() + delta.x() < cp.x()) delta.setX(cp.x() - right.x());
372 
373     right += delta;
374     setPoint(m_currentControlPointIndex, right);
375 
376     // rotate the opposite handle keeping the handle length unchanged
377     if (m_currentControlPointIndex > 4) {
378       QVector2D rHandle(cp - right);
379       if (!rHandle.isNull()) {
380         QPointF left = m_points.at(m_currentControlPointIndex - 2);
381         QVector2D lHandle(left - cp);
382         QVector2D newLHandle = rHandle.normalized() * lHandle.length();
383         setPoint(m_currentControlPointIndex - 2, cp + newLHandle.toPointF());
384       }
385     }
386     emit controlPointChanged(true);
387   }
388   update();
389   emit updateCurrentPosition(m_currentControlPointIndex,
390                              m_points.at(m_currentControlPointIndex));
391 }
392 
393 //-----------------------------------------------------------------------------
394 
moveCentralControlPoint(int index,QPointF delta)395 void ChennelCurveEditor::moveCentralControlPoint(int index, QPointF delta) {
396   int pointCount = m_points.size();
397 
398   assert(index < pointCount - 3 && index > 2);
399 
400   QPointF p = m_points.at(index);
401   QPointF d = delta;
402   // Trovo il valore di delta im modo tale che il punto di controllo non sia
403   // trascinato fuori dal range consentito
404   QPointF newPoint = checkPoint(p + delta);
405   d                = newPoint - p;
406 
407   QPointF nextP       = m_points.at(index + 3);
408   QPointF precP       = m_points.at(index - 3);
409   double nextDistance = nextP.x() - (p.x() + d.x());
410   double precDistance = (p.x() + d.x()) - precP.x();
411 
412   if (nextDistance < cpMargin)
413     d = QPointF(nextP.x() - p.x() - cpMargin, d.y());
414   else if (precDistance < cpMargin)
415     d = QPointF(precP.x() - p.x() + cpMargin, d.y());
416 
417   if (d.isNull()) return;
418 
419   // Punto di controllo speciale: il primo visualizzato.
420   if (index == 3) {
421     QPointF dY = QPointF(0, d.y());
422     movePoint(index - 1, dY);
423     movePoint(index - 2, dY);
424     movePoint(index - 3, dY);
425   }
426   // Punto di controllo speciale: l'ultimo visualizzato.
427   if (index == pointCount - 4) {
428     QPointF dY = QPointF(0, d.y());
429     movePoint(index + 1, dY);
430     movePoint(index + 2, dY);
431     movePoint(index + 3, dY);
432   }
433   if (index > 3) movePoint(index - 1, d);
434   if (index < pointCount - 4) movePoint(index + 1, d);
435   movePoint(index, d);
436   emit controlPointChanged(true);
437 }
438 
439 //-----------------------------------------------------------------------------
440 
addControlPoint(double percent)441 void ChennelCurveEditor::addControlPoint(double percent) {
442   QPainterPath path = getPainterPath();
443   QPointF p         = path.pointAtPercent(percent);
444 
445   // Cerco il punto di controllo precedente
446   int pointCount = m_points.size();
447   int beforeControlPointIndex;
448   for (beforeControlPointIndex = pointCount - 1; beforeControlPointIndex >= 0;
449        beforeControlPointIndex--) {
450     QPointF point = m_points.at(beforeControlPointIndex);
451     if (isCentralControlPoint(beforeControlPointIndex) && point.x() < p.x())
452       break;
453   }
454 
455   if (beforeControlPointIndex == 0 || beforeControlPointIndex == pointCount - 4)
456     return;
457 
458   QPointF p0 = checkPoint(m_points.at(beforeControlPointIndex));
459   // Se sono troppo vicino al punto di controllo precedente ritorno
460   if (std::abs(p.x() - p0.x()) <= cpMargin) return;
461   double beforeControlPointPercent = getPercentAtPoint(p0, path);
462   QPointF p1 = checkPoint(m_points.at(beforeControlPointIndex + 1));
463   QPointF p2 = checkPoint(m_points.at(beforeControlPointIndex + 2));
464   QPointF p3 = checkPoint(m_points.at(beforeControlPointIndex + 3));
465   // Se sono troppo vicino al punto di controllo successivo ritorno
466   if (std::abs(p3.x() - p.x()) <= cpMargin) return;
467   double nextControlPointPercent = getPercentAtPoint(p3, path);
468 
469   // Calcolo la velocita' e quindi il coiffciente angolare.
470   double t =
471       percent * 100 / (nextControlPointPercent - beforeControlPointPercent);
472   double s = t - 1;
473   QPointF speed =
474       3.0 * ((p1 - p0) * s * s + 2 * (p2 - p0) * s * t + (p3 - p2) * t * t);
475   double m = speed.y() / speed.x();
476 
477   int newControlPointIndex = beforeControlPointIndex + 3;
478   m_points.insert(newControlPointIndex - 1,
479                   QPointF(p.x() - 16, p.y() - 16 * m));
480   m_points.insert(newControlPointIndex, p);
481   m_points.insert(newControlPointIndex + 1,
482                   QPointF(p.x() + 16, p.y() + 16 * m));
483 
484   m_currentControlPointIndex = newControlPointIndex;
485   m_preMousePos              = p;
486   emit controlPointAdded(newControlPointIndex);
487   update();
488 }
489 
490 //-----------------------------------------------------------------------------
491 
removeCurrentControlPoint()492 void ChennelCurveEditor::removeCurrentControlPoint() {
493   removeControlPoint(m_currentControlPointIndex);
494 }
495 
496 //-----------------------------------------------------------------------------
497 
selectNextControlPoint()498 void ChennelCurveEditor::selectNextControlPoint() {
499   int controlPointCount = (int)m_points.size();
500 
501   if (controlPointCount == 0) return;
502 
503   int firstVisibleControlPoint = 3;
504   int lastVisibleControlPoint  = m_points.size() - 4;
505 
506   m_currentControlPointIndex++;
507   if (m_currentControlPointIndex < firstVisibleControlPoint ||
508       m_currentControlPointIndex > lastVisibleControlPoint)
509     m_currentControlPointIndex = firstVisibleControlPoint;
510 
511   emit updateCurrentPosition(m_currentControlPointIndex,
512                              m_points.at(m_currentControlPointIndex));
513   update();
514 }
515 
516 //-----------------------------------------------------------------------------
517 
selectPreviousControlPoint()518 void ChennelCurveEditor::selectPreviousControlPoint() {
519   int controlPointCount = (int)m_points.size();
520 
521   if (controlPointCount == 0) return;
522 
523   int firstVisibleControlPoint = 3;
524   int lastVisibleControlPoint  = m_points.size() - 4;
525 
526   m_currentControlPointIndex--;
527   if (m_currentControlPointIndex < firstVisibleControlPoint ||
528       m_currentControlPointIndex > lastVisibleControlPoint)
529     m_currentControlPointIndex = lastVisibleControlPoint;
530 
531   emit updateCurrentPosition(m_currentControlPointIndex,
532                              m_points.at(m_currentControlPointIndex));
533   update();
534 }
535 
536 //-----------------------------------------------------------------------------
537 
removeControlPoint(int index)538 void ChennelCurveEditor::removeControlPoint(int index) {
539   // Non posso eliminare il primo punto di controllo visibile quindi lo rimetto
540   // in condizione iniziale
541   if (index <= 4) {
542     setPoint(0, QPointF(-40, 0));
543     setPoint(1, QPointF(-20, 0));
544     setPoint(2, QPointF(-20, 0));
545     setPoint(3, QPointF(0, 0));
546     setPoint(4, QPointF(16, 16));
547     update();
548     emit controlPointChanged(false);
549     return;
550   }
551   // Non posso eliminare il l'ultimo punto di controllo visibile quindi lo
552   // rimetto in condizione iniziale
553   if (index >= m_points.size() - 5) {
554     int i = m_points.size() - 5;
555     setPoint(i, QPointF(239, 239));
556     setPoint(i + 1, QPointF(255, 255));
557     setPoint(i + 2, QPointF(275, 255));
558     setPoint(i + 3, QPointF(275, 255));
559     setPoint(i + 4, QPointF(295, 255));
560     update();
561     emit controlPointChanged(false);
562     return;
563   }
564 
565   int firstIndex = 0;
566   if (isCentralControlPoint(index))
567     firstIndex = index - 1;
568   else if (isLeftControlPoint(index))
569     firstIndex = index;
570   else
571     firstIndex = index - 2;
572 
573   m_points.removeAt(firstIndex);
574   m_points.removeAt(firstIndex);
575   m_points.removeAt(firstIndex);
576 
577   emit controlPointRemoved(firstIndex + 1);
578   m_currentControlPointIndex = firstIndex - 2;
579 
580   emit updateCurrentPosition(m_currentControlPointIndex,
581                              m_points.at(m_currentControlPointIndex));
582 
583   update();
584 }
585 
586 //-----------------------------------------------------------------------------
587 
getPainterPath()588 QPainterPath ChennelCurveEditor::getPainterPath() {
589   int pointCount = m_points.size();
590   if (pointCount == 0) return QPainterPath();
591 
592   QPointF p0 = m_points.at(0);
593   QPainterPath path(p0);
594   int i;
595   for (i = 1; i < pointCount; i++) {
596     QPointF p1 = m_points.at(i);
597     QPointF p2 = m_points.at(++i);
598     QPointF p3 = m_points.at(++i);
599     path.moveTo(p0);
600     if (!m_isLinear) {
601       // truncate speed
602       QVector2D aSpeed(p1 - p0);
603       QVector2D bSpeed(p2 - p3);
604       truncateSpeeds(p0.x(), p3.x(), aSpeed, bSpeed);
605       path.cubicTo(p0 + aSpeed.toPointF(), p3 + bSpeed.toPointF(), p3);
606     } else
607       path.lineTo(p3);
608     p0 = p3;
609   }
610 
611   // Cerco le eventuali intersezioni con il bordo.
612   QRectF rect(0, 0, m_curveHeight, m_curveHeight);
613   // QRectF rect(m_LeftRightMargin, m_TopMargin, m_curveHeight, m_curveHeight);
614   QRectF r = path.boundingRect();
615   if (!rect.contains(QRect(rect.left(), r.top(), rect.width(), r.height()))) {
616     QList<QPointF> points = getIntersectedPoint(rect, path);
617     // Se trovo punti di intersezione (per come e' definita la curva devono
618     // essere pari)
619     // faccio l'unione del path calcolato e di nuovi path lineari.
620     int j = 0;
621     for (j = 0; j < points.size(); j++) {
622       QPointF p0 = points.at(j);
623       QPointF p1 = points.at(++j);
624       QPainterPath line(p0);
625       line.lineTo(p1);
626       path.addPath(line);
627     }
628   }
629 
630   return path;
631 }
632 
633 //-----------------------------------------------------------------------------
634 
paintEvent(QPaintEvent * e)635 void ChennelCurveEditor::paintEvent(QPaintEvent *e) {
636   QPainter painter(this);
637 
638   double scale = (m_isEnlarged) ? 2.0 : 1.0;
639   // Disegno il reticolato
640   painter.setRenderHint(QPainter::Antialiasing, false);
641   painter.setPen(QColor(250, 250, 250));
642 
643   // Disegno l'histogram.
644   m_histogramView->draw(&painter, QPoint(m_LeftRightMargin - 10, 0),
645                         m_curveHeight * scale);
646 
647   // Disegno la barra verticale a sinistra.
648   m_verticalChannelBar->draw(
649       &painter,
650       QPoint(0, -2));  //-1 == m_topMargin- il margine della barra(=10+1).
651 
652   QRectF r = rect().adjusted(m_LeftRightMargin, m_TopMargin, -m_LeftRightMargin,
653                              -m_BottomMargin);
654   // Disegno la curva entro i limiti del grafo
655   painter.setClipRect(r, Qt::IntersectClip);
656 
657   painter.translate(m_LeftRightMargin + 1, height() - m_BottomMargin);
658   painter.scale(scale, -scale);
659 
660   QPainterPath path = getPainterPath();
661   if (path.isEmpty()) return;
662   painter.setRenderHint(QPainter::Antialiasing, true);
663   QPen blackPen(Qt::black);
664   QPen bluePen(Qt::blue);
665   blackPen.setWidthF(1.0 / scale);
666   bluePen.setWidthF(1.0 / scale);
667 
668   painter.setPen(blackPen);
669   painter.setBrush(Qt::NoBrush);
670   painter.drawPath(path);
671 
672   int n     = m_points.size();
673   QPointF p = m_points.at(3);
674   for (int i = 3; i < n - 3; i++) {
675     QBrush brush(Qt::white);
676     int rad       = (m_isEnlarged) ? 1 : 2;
677     QPointF nextP = m_points.at(i + 1);
678     if (isCentralControlPoint(i))
679       rad = (m_isEnlarged) ? 2 : 3;
680     else if (m_isLinear) {
681       p = nextP;
682       continue;
683     }
684     painter.setPen(blackPen);
685 
686     QPointF handlePos = p;
687     if (!m_isLinear) {
688       if (isLeftControlPoint(i)) {
689         painter.drawLine(p, nextP);
690       } else if (isCentralControlPoint(i) && i < n - 4)
691         painter.drawLine(p, nextP);
692 
693       handlePos = getVisibleHandlePos(i);
694     }
695 
696     painter.setBrush((m_currentControlPointIndex != i)
697                          ? Qt::white
698                          : (p == handlePos) ? Qt::black : Qt::blue);
699     painter.setPen((p == handlePos) ? blackPen : bluePen);
700 
701     QRectF pointRect(handlePos.x() - rad, handlePos.y() - rad, 2 * rad,
702                      2 * rad);
703     painter.drawEllipse(pointRect);
704     p = nextP;
705   }
706 }
707 
708 //-----------------------------------------------------------------------------
709 
mouseMoveEvent(QMouseEvent * e)710 void ChennelCurveEditor::mouseMoveEvent(QMouseEvent *e) {
711   QPointF posF = viewToStrokePoint(QPointF(e->pos()));
712   if (m_mouseButton == Qt::LeftButton && m_currentControlPointIndex != -1) {
713     moveCurrentControlPoint(posF - m_preMousePos);
714     m_preMousePos = posF;
715   } else if (m_currentControlPointIndex == -1)
716     emit updateCurrentPosition(-1, posF);
717 }
718 
719 //-----------------------------------------------------------------------------
720 
mousePressEvent(QMouseEvent * e)721 void ChennelCurveEditor::mousePressEvent(QMouseEvent *e) {
722   m_mouseButton = e->button();
723   setFocus();
724   if (m_mouseButton == Qt::LeftButton) {
725     QPointF posF = viewToStrokePoint(QPointF(e->pos()));
726     double minDistance;
727     int controlPointIndex = getClosestPointIndex(posF, minDistance);
728 
729     // Se la distanza e' piccola seleziono il control point corrente
730     if (minDistance < 20) {
731       m_currentControlPointIndex = controlPointIndex;
732       m_preMousePos              = posF;
733     } else {
734       m_currentControlPointIndex = -1;
735       // Se sono sufficentemente lontano da un punto di controllo, ma abbastanza
736       // vicino alla curva
737       // aggiungo un punto di controllo
738       double percent = getPercentAtPoint(posF, getPainterPath());
739       if (percent != 0 && minDistance > 20) addControlPoint(percent);
740     }
741     QPointF currentPointPos = (m_currentControlPointIndex == -1)
742                                   ? posF
743                                   : m_points.at(m_currentControlPointIndex);
744 
745     emit updateCurrentPosition(m_currentControlPointIndex, currentPointPos);
746     update();
747   }
748 }
749 
750 //-----------------------------------------------------------------------------
751 
mouseReleaseEvent(QMouseEvent * e)752 void ChennelCurveEditor::mouseReleaseEvent(QMouseEvent *e) {
753   // fx preview updates here ( it does not update while mouse dragging )
754   if (m_mouseButton == Qt::LeftButton && m_currentControlPointIndex != -1 &&
755       e->button() == Qt::LeftButton)
756     emit controlPointChanged(false);
757   m_mouseButton = Qt::NoButton;
758 }
759 
760 //-----------------------------------------------------------------------------
761 
keyPressEvent(QKeyEvent * e)762 void ChennelCurveEditor::keyPressEvent(QKeyEvent *e) {
763   if (m_currentControlPointIndex == -1) return;
764 
765   if (e->key() == Qt::Key_Delete) {
766     removeCurrentControlPoint();
767     return;
768   }
769 
770   bool controlPressed = e->modifiers() & Qt::ControlModifier;
771   bool shiftPressed   = e->modifiers() & Qt::ShiftModifier;
772   float delta         = (shiftPressed) ? 10.0 : 1.0;
773 
774   if (e->key() == Qt::Key_Right) {
775     if (controlPressed)
776       selectNextControlPoint();
777     else
778       moveCurrentControlPoint(QPointF(delta, 0.0));
779   } else if (e->key() == Qt::Key_Left) {
780     if (controlPressed)
781       selectPreviousControlPoint();
782     else
783       moveCurrentControlPoint(QPointF(-delta, 0.0));
784   } else if (e->key() == Qt::Key_Up)
785     moveCurrentControlPoint(QPointF(0.0, delta));
786   else if (e->key() == Qt::Key_Down)
787     moveCurrentControlPoint(QPointF(0.0, -delta));
788 }
789 
790 //--------------------------------------------------------
791 
eventFilter(QObject * object,QEvent * event)792 bool ChennelCurveEditor::eventFilter(QObject *object, QEvent *event) {
793   if (event->type() == QEvent::Shortcut ||
794       event->type() == QEvent::ShortcutOverride) {
795     if (!object->inherits("FxSettings")) {
796       event->accept();
797       return true;
798     }
799   }
800   return false;
801 }
802 
803 //--------------------------------------------------------
804 
focusInEvent(QFocusEvent * fe)805 void ChennelCurveEditor::focusInEvent(QFocusEvent *fe) {
806   QWidget::focusInEvent(fe);
807   qApp->installEventFilter(this);
808 }
809 
810 //--------------------------------------------------------
811 
focusOutEvent(QFocusEvent * fe)812 void ChennelCurveEditor::focusOutEvent(QFocusEvent *fe) {
813   emit focusOut();
814   QWidget::focusOutEvent(fe);
815   qApp->removeEventFilter(this);
816   update();
817 }
818 
819 //=============================================================================
820 // ToneCurveField
821 //=============================================================================
822 
ToneCurveField(QWidget * parent,FxHistogramRender * fxHistogramRender)823 ToneCurveField::ToneCurveField(QWidget *parent,
824                                FxHistogramRender *fxHistogramRender)
825     : QWidget(parent), m_toneCurveStackedWidget(0), m_currentPointIndex(-1) {
826   setFixedWidth(400);
827   // setFixedWidth(368);
828 
829   QStringList channels;
830   channels << "RGBA"
831            << "RGB"
832            << "Red"
833            << "Green"
834            << "Blue"
835            << "Alpha";
836   int channelCount        = channels.size();
837   int currentChannelIndex = 0;
838 
839   m_channelListChooser = new QComboBox(this);
840   m_channelListChooser->setFixedSize(100, 20);
841   m_channelListChooser->addItems(channels);
842   m_channelListChooser->setCurrentIndex(currentChannelIndex);
843 
844   m_rangeMode = new QComboBox(this);
845   m_rangeMode->addItems({"0-255", "0.0-1.0"});
846   m_rangeMode->setCurrentIndex(0);
847 
848   // stack widget dei grafi
849   m_toneCurveStackedWidget = new QStackedWidget(this);
850   Histograms *histograms   = new Histograms(0, true);
851   fxHistogramRender->setHistograms(histograms);
852   int i;
853   for (i = 0; i < channelCount; i++) {
854     ChennelCurveEditor *c =
855         new ChennelCurveEditor(this, histograms->getHistogramView(i));
856     m_toneCurveStackedWidget->addWidget(c);
857     connect(c, SIGNAL(firstLastXPostionChanged(double, double)), this,
858             SLOT(onFirstLastXPostionChanged(double, double)));
859     connect(c, SIGNAL(updateCurrentPosition(int, QPointF)), this,
860             SLOT(onUpdateCurrentPosition(int, QPointF)));
861   }
862   m_toneCurveStackedWidget->setCurrentIndex(currentChannelIndex);
863 
864   // stack widget degli slider
865   m_sliderStackedWidget = new QStackedWidget(this);
866   for (i = 0; i < channelCount; i++) {
867     DoublePairField *doublePairSlider = new DoublePairField(this);
868     doublePairSlider->setFixedHeight(20);
869     doublePairSlider->setLabelsEnabled(false);
870     doublePairSlider->setRange(0.0, 255.0);
871     doublePairSlider->setValues(std::make_pair(0.0, 255.0));
872     m_sliderStackedWidget->addWidget(doublePairSlider);
873     connect(doublePairSlider, SIGNAL(valuesChanged(bool)), this,
874             SLOT(sliderValueChanged(bool)));
875   }
876   m_sliderStackedWidget->setCurrentIndex(currentChannelIndex);
877 
878   m_isLinearCheckBox   = new CheckBox(QString("Linear"), this);
879   m_isEnlargedCheckBox = new CheckBox(QString("Enlarge"), this);
880   m_isEnlargedCheckBox->setChecked(false);
881   m_currentInput  = new DoubleLineEdit(this);
882   m_currentOutput = new DoubleLineEdit(this);
883   m_currentInput->setFixedWidth(60);
884   m_currentOutput->setFixedWidth(60);
885   m_currentInput->setDecimals(2);
886   m_currentOutput->setDecimals(2);
887   m_currentInput->setDisabled(true);
888   m_currentOutput->setDisabled(true);
889 
890   //------ layout
891 
892   QVBoxLayout *mainLayout = new QVBoxLayout(this);
893   mainLayout->setMargin(0);
894   mainLayout->setSpacing(0);
895   {
896     QHBoxLayout *channelListLayout = new QHBoxLayout();
897     channelListLayout->setMargin(0);
898     channelListLayout->setSpacing(0);
899     channelListLayout->setAlignment(Qt::AlignCenter);
900     {
901       // lista canali: label+comboBox
902       channelListLayout->addWidget(new QLabel(tr("Channel:"), this));
903       channelListLayout->addWidget(m_channelListChooser);
904       channelListLayout->addSpacing(20);
905       channelListLayout->addWidget(new QLabel(tr("Range:"), this));
906       channelListLayout->addWidget(m_rangeMode);
907       channelListLayout->addSpacing(20);
908       channelListLayout->addWidget(m_isEnlargedCheckBox);
909     }
910     mainLayout->addLayout(channelListLayout, 0);
911 
912     QGridLayout *bottomLayout = new QGridLayout();
913     bottomLayout->setMargin(0);
914     bottomLayout->setHorizontalSpacing(5);
915     bottomLayout->setVerticalSpacing(0);
916     {
917       QVBoxLayout *currentValLayout = new QVBoxLayout();
918       currentValLayout->setMargin(0);
919       currentValLayout->setSpacing(0);
920       currentValLayout->setAlignment(Qt::AlignLeft);
921       {
922         currentValLayout->addStretch(1);
923         currentValLayout->addWidget(new QLabel(tr("Output:"), this));
924         currentValLayout->addWidget(m_currentOutput);
925         currentValLayout->addSpacing(10);
926         currentValLayout->addWidget(new QLabel(tr("Input:"), this));
927         currentValLayout->addWidget(m_currentInput);
928         currentValLayout->addSpacing(10);
929       }
930       bottomLayout->addLayout(currentValLayout, 0, 0);
931 
932       bottomLayout->addWidget(m_toneCurveStackedWidget, 0, 1, Qt::AlignHCenter);
933       bottomLayout->addWidget(m_sliderStackedWidget, 1, 1);
934     }
935     bottomLayout->setColumnStretch(1, 1);
936     bottomLayout->setRowStretch(0, 1);
937 
938     mainLayout->addLayout(bottomLayout);
939     mainLayout->addSpacing(10);
940     mainLayout->addWidget(m_isLinearCheckBox, 0, Qt::AlignHCenter);
941   }
942   setLayout(mainLayout);
943 
944   connect(m_isLinearCheckBox, SIGNAL(clicked(bool)),
945           SLOT(setLinearManually(bool)));
946   connect(m_isLinearCheckBox, SIGNAL(toggled(bool)), SLOT(setLinear(bool)));
947   connect(m_isEnlargedCheckBox, SIGNAL(toggled(bool)), SLOT(setEnlarged(bool)));
948 
949   connect(m_channelListChooser, SIGNAL(currentIndexChanged(int)), this,
950           SLOT(onCurrentChannelSwitched(int)));
951   connect(m_rangeMode, SIGNAL(currentIndexChanged(int)), this,
952           SLOT(onRangeModeSwitched(int)));
953   connect(m_currentInput, SIGNAL(editingFinished()), this,
954           SLOT(onCurrentPointEditted()));
955   connect(m_currentOutput, SIGNAL(editingFinished()), this,
956           SLOT(onCurrentPointEditted()));
957 }
958 
959 //-----------------------------------------------------------------------------
960 
setCurrentChannel(int currentChannel)961 void ToneCurveField::setCurrentChannel(int currentChannel) {
962   m_channelListChooser->setCurrentIndex(currentChannel);
963 }
964 
965 //-----------------------------------------------------------------------------
966 
getChannelEditor(int channel) const967 ChennelCurveEditor *ToneCurveField::getChannelEditor(int channel) const {
968   ChennelCurveEditor *c = dynamic_cast<ChennelCurveEditor *>(
969       m_toneCurveStackedWidget->widget(channel));
970   assert(c);
971   return c;
972 }
973 
974 //-----------------------------------------------------------------------------
975 
getCurrentChannelEditor() const976 ChennelCurveEditor *ToneCurveField::getCurrentChannelEditor() const {
977   return getChannelEditor(m_toneCurveStackedWidget->currentIndex());
978 }
979 
980 //-----------------------------------------------------------------------------
981 
getCurrentSlider() const982 DoublePairField *ToneCurveField::getCurrentSlider() const {
983   return dynamic_cast<DoublePairField *>(
984       m_sliderStackedWidget->currentWidget());
985 }
986 
987 //-----------------------------------------------------------------------------
988 
sliderValueChanged(bool isDragging)989 void ToneCurveField::sliderValueChanged(bool isDragging) {
990   std::pair<double, double> values = getCurrentSlider()->getValues();
991 
992   if (m_rangeMode->currentIndex() == 1) {
993     values.first *= 255.0;
994     values.second *= 255.0;
995   }
996 
997   getCurrentChannelEditor()->setFirstLastXPosition(values, isDragging);
998 }
999 
1000 //-----------------------------------------------------------------------------
1001 
onFirstLastXPostionChanged(double x0,double x1)1002 void ToneCurveField::onFirstLastXPostionChanged(double x0, double x1) {
1003   if (m_rangeMode->currentIndex() == 1) {
1004     x0 /= 255.0;
1005     x1 /= 255.0;
1006   }
1007   std::pair<double, double> values = getCurrentSlider()->getValues();
1008   if (values.first != x0 || values.second != x1)
1009     getCurrentSlider()->setValues(std::make_pair(x0, x1));
1010 }
1011 
1012 //-----------------------------------------------------------------------------
1013 // index = -1 when no point is selected
onUpdateCurrentPosition(int index,QPointF pos)1014 void ToneCurveField::onUpdateCurrentPosition(int index, QPointF pos) {
1015   auto modify = [&](double val) {
1016     return (m_rangeMode->currentIndex() == 0) ? val : val / 255.0;
1017   };
1018 
1019   QList<TPointD> points = getCurrentChannelEditor()->getPoints();
1020   assert(index == -1 || (index >= 3 && index < points.size() - 3));
1021   if (index != -1 && (index < 3 || index >= points.size() - 3)) return;
1022 
1023   double maxChanVal = 255.0;
1024 
1025   // if no point is selected
1026   if (index == -1) {
1027     if (m_currentPointIndex != index) {
1028       m_currentInput->setDisabled(true);
1029       m_currentOutput->setDisabled(true);
1030       m_currentInput->setRange(-(std::numeric_limits<double>::max)(),
1031                                (std::numeric_limits<double>::max)());
1032       m_currentOutput->setRange(-(std::numeric_limits<double>::max)(),
1033                                 (std::numeric_limits<double>::max)());
1034       m_currentPointIndex = index;
1035     }
1036     if (pos.x() < 0.0 || pos.x() > maxChanVal || pos.y() < 0.0 ||
1037         pos.y() > maxChanVal) {
1038       m_currentInput->setText("");
1039       m_currentOutput->setText("");
1040     } else {
1041       m_currentInput->setValue(modify(pos.x()));
1042       m_currentOutput->setValue(modify(pos.y()));
1043     }
1044   } else {  // if any point is selected
1045     if (m_currentPointIndex != index) {
1046       m_currentInput->setEnabled(true);
1047       m_currentOutput->setEnabled(true);
1048       if (index % 3 == 0) {  // control point case
1049         // 前後のポイントと4離す
1050         double xmin = (index == 3) ? 0 : points.at(index - 3).x + cpMargin;
1051         double xmax = (index == points.size() - 4)
1052                           ? maxChanVal
1053                           : points.at(index + 3).x - cpMargin;
1054         m_currentInput->setRange(modify(xmin), modify(xmax));
1055         m_currentOutput->setRange(0.0, modify(maxChanVal));
1056       } else if (index % 3 == 2) {  // left handle
1057         TPointD cp = points.at(index + 1);
1058         m_currentInput->setRange(-(std::numeric_limits<double>::max)(),
1059                                  modify(cp.x));
1060         m_currentOutput->setRange(-(std::numeric_limits<double>::max)(),
1061                                   (std::numeric_limits<double>::max)());
1062       } else {  // right handle
1063         TPointD cp = points.at(index - 1);
1064         m_currentInput->setRange(modify(cp.x),
1065                                  (std::numeric_limits<double>::max)());
1066         m_currentOutput->setRange(-(std::numeric_limits<double>::max)(),
1067                                   (std::numeric_limits<double>::max)());
1068       }
1069       m_currentPointIndex = index;
1070     }
1071     m_currentInput->setValue(modify(pos.x()));
1072     m_currentOutput->setValue(modify(pos.y()));
1073   }
1074 }
1075 
1076 //-----------------------------------------------------------------------------
1077 
onCurrentPointEditted()1078 void ToneCurveField::onCurrentPointEditted() {
1079   TPointD oldPos =
1080       getCurrentChannelEditor()->getPoints().at(m_currentPointIndex);
1081   double factor = (m_rangeMode->currentIndex() == 0) ? 1.0 : 255.0;
1082   QPointF newPos(m_currentInput->getValue() * factor,
1083                  m_currentOutput->getValue() * factor);
1084   getCurrentChannelEditor()->moveCurrentControlPoint(
1085       newPos - QPointF(oldPos.x, oldPos.y));
1086 }
1087 
1088 //-----------------------------------------------------------------------------
1089 
onCurrentChannelSwitched(int channelIndex)1090 void ToneCurveField::onCurrentChannelSwitched(int channelIndex) {
1091   getCurrentChannelEditor()->setCurrentControlPointIndex(-1);
1092   m_toneCurveStackedWidget->setCurrentIndex(channelIndex);
1093   m_sliderStackedWidget->setCurrentIndex(channelIndex);
1094   emit currentChannelIndexChanged(channelIndex);
1095   onUpdateCurrentPosition(-1, QPointF(-1, -1));
1096 }
1097 
1098 //-----------------------------------------------------------------------------
1099 
onRangeModeSwitched(int index)1100 void ToneCurveField::onRangeModeSwitched(int index) {
1101   double maxVal = (index == 0) ? 255.0 : 1.0;
1102   double factor = (index == 0) ? 255.0 : 1.0 / 255.0;
1103   ChannelBar::Range range =
1104       (index == 0) ? ChannelBar::Range_0_255 : ChannelBar::Range_0_1;
1105 
1106   for (int i = 0; i < m_toneCurveStackedWidget->count(); i++) {
1107     ChennelCurveEditor *c =
1108         dynamic_cast<ChennelCurveEditor *>(m_toneCurveStackedWidget->widget(i));
1109     if (c) c->setLabelRange(range);
1110 
1111     DoublePairField *doublePairSlider =
1112         dynamic_cast<DoublePairField *>(m_sliderStackedWidget->widget(i));
1113     if (doublePairSlider) {
1114       doublePairSlider->setRange(0.0, maxVal);
1115       std::pair<double, double> val = doublePairSlider->getValues();
1116       doublePairSlider->setValues(
1117           std::make_pair(val.first * factor, val.second * factor));
1118     }
1119   }
1120 
1121   if (m_currentPointIndex == -1) return;
1122 
1123   int pointIndex = m_currentPointIndex;
1124   // in order to force updating the value range
1125   m_currentPointIndex = -1;
1126   TPointD point       = getCurrentChannelEditor()->getPoints().at(pointIndex);
1127   onUpdateCurrentPosition(pointIndex, QPointF(point.x, point.y));
1128 }
1129 
1130 //-----------------------------------------------------------------------------
1131 
setIsLinearCheckBox(bool isChecked)1132 void ToneCurveField::setIsLinearCheckBox(bool isChecked) {
1133   if (m_isLinearCheckBox->isChecked() == isChecked) return;
1134   m_isLinearCheckBox->setChecked(isChecked);
1135 }
1136 
1137 //-----------------------------------------------------------------------------
1138 
setLinear(bool isLinear)1139 void ToneCurveField::setLinear(bool isLinear) {
1140   int i;
1141   for (i = 0; i < m_sliderStackedWidget->count(); i++)
1142     getChannelEditor(i)->setLinear(isLinear);
1143 }
1144 
1145 //-----------------------------------------------------------------------------
1146 
setEnlarged(bool isEnlarged)1147 void ToneCurveField::setEnlarged(bool isEnlarged) {
1148   int i;
1149   for (i = 0; i < m_sliderStackedWidget->count(); i++)
1150     getChannelEditor(i)->setEnlarged(isEnlarged);
1151   setFixedWidth((isEnlarged) ? 400 + 256 : 400);
1152   emit sizeChanged();
1153 }
1154 
1155 //-----------------------------------------------------------------------------
1156 
setLinearManually(bool isLinear)1157 void ToneCurveField::setLinearManually(bool isLinear) {
1158   emit isLinearChanged(isLinear);
1159 }
1160 
1161 //-----------------------------------------------------------------------------
1162 
isEnlarged()1163 bool ToneCurveField::isEnlarged() { return m_isEnlargedCheckBox->isChecked(); }