1 /*=========================================================================
2 
3   Library:   CTK
4 
5   Copyright (c) Kitware Inc.
6 
7   Licensed under the Apache License, Version 2.0 (the "License");
8   you may not use this file except in compliance with the License.
9   You may obtain a copy of the License at
10 
11       http://www.apache.org/licenses/LICENSE-2.0.txt
12 
13   Unless required by applicable law or agreed to in writing, software
14   distributed under the License is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18 
19 =========================================================================*/
20 /// Qt includes
21 #include <QGraphicsScene>
22 #include <QLinearGradient>
23 #include <QResizeEvent>
24 #include <QDebug>
25 
26 /// CTK includes
27 #include "ctkTransferFunction.h"
28 #include "ctkTransferFunctionRepresentation.h"
29 
30 /// STL includes
31 #include <limits>
32 
33 //-----------------------------------------------------------------------------
34 class ctkTransferFunctionRepresentationPrivate
35 {
36 public:
37   ctkTransferFunctionRepresentationPrivate();
38 
39   ctkTransferFunction* TransferFunction;
40   QColor               VerticalGradientColor;
41 
42   QPainterPath         Path;
43   QLinearGradient      Gradient;
44   QList<QPointF>       Points;
45 
46   QRectF       rect()const;
47   qreal        width()const;
48   qreal        height()const;
49 
50   qreal        WorldRangeX[2];
51   QVariant     WorldRangeY[2];
52   qreal        RangeXDiff;
53   qreal        RangeXOffSet;
54   qreal        RangeYDiff;
55   qreal        RangeYOffSet;
56 };
57 
58 //-----------------------------------------------------------------------------
ctkTransferFunctionRepresentationPrivate()59 ctkTransferFunctionRepresentationPrivate::ctkTransferFunctionRepresentationPrivate()
60 {
61   this->TransferFunction = 0;
62   this->VerticalGradientColor = QColor::fromRgbF(1., 0., 0., 1. );
63   this->RangeXDiff = 0.;
64   this->RangeXOffSet = 0.;
65   this->RangeYDiff = 0.;
66   this->RangeYOffSet = 0.;
67 }
68 
69 //-----------------------------------------------------------------------------
rect() const70 QRectF ctkTransferFunctionRepresentationPrivate::rect()const
71 {
72   return QRectF(0.,0.,1.,1.);
73 }
74 
75 //-----------------------------------------------------------------------------
width() const76 qreal ctkTransferFunctionRepresentationPrivate::width()const
77 {
78   return 1.;
79 }
80 
81 //-----------------------------------------------------------------------------
height() const82 qreal ctkTransferFunctionRepresentationPrivate::height()const
83 {
84   return 1.;
85 }
86 
87 //-----------------------------------------------------------------------------
ctkTransferFunctionRepresentation(QObject * parentObject)88 ctkTransferFunctionRepresentation::ctkTransferFunctionRepresentation(QObject* parentObject)
89   :QObject(parentObject)
90   , d_ptr(new ctkTransferFunctionRepresentationPrivate)
91 {
92 }
93 
94 //-----------------------------------------------------------------------------
ctkTransferFunctionRepresentation(ctkTransferFunction * transferFunction,QObject * parentObject)95 ctkTransferFunctionRepresentation::ctkTransferFunctionRepresentation(
96   ctkTransferFunction* transferFunction, QObject* parentObject)
97   :QObject(parentObject)
98   , d_ptr(new ctkTransferFunctionRepresentationPrivate)
99 {
100   this->setTransferFunction(transferFunction);
101 }
102 
103 //-----------------------------------------------------------------------------
~ctkTransferFunctionRepresentation()104 ctkTransferFunctionRepresentation::~ctkTransferFunctionRepresentation()
105 {
106 }
107 
108 //-----------------------------------------------------------------------------
verticalGradientColor() const109 QColor ctkTransferFunctionRepresentation::verticalGradientColor() const
110 {
111   Q_D( const ctkTransferFunctionRepresentation );
112   return d->VerticalGradientColor;
113 }
114 //-----------------------------------------------------------------------------
setVerticalGradientColor(QColor verticalGradientColor)115 void ctkTransferFunctionRepresentation::setVerticalGradientColor( QColor verticalGradientColor )
116 {
117   Q_D( ctkTransferFunctionRepresentation );
118   d->VerticalGradientColor = verticalGradientColor;
119 }
120 
121 //-----------------------------------------------------------------------------
setTransferFunction(ctkTransferFunction * transferFunction)122 void ctkTransferFunctionRepresentation::setTransferFunction(ctkTransferFunction* transferFunction)
123 {
124   Q_D(ctkTransferFunctionRepresentation);
125   if (d->TransferFunction == transferFunction)
126     {
127     return;
128     }
129   d->TransferFunction = transferFunction;
130   connect( d->TransferFunction, SIGNAL(changed()),
131            this, SLOT(onTransferFunctionChanged()),
132            Qt::UniqueConnection);
133   this->onTransferFunctionChanged();
134 }
135 
136 //-----------------------------------------------------------------------------
transferFunction() const137 ctkTransferFunction* ctkTransferFunctionRepresentation::transferFunction()const
138 {
139   Q_D(const ctkTransferFunctionRepresentation);
140   return d->TransferFunction;
141 }
142 
143 //-----------------------------------------------------------------------------
onTransferFunctionChanged()144 void ctkTransferFunctionRepresentation::onTransferFunctionChanged()
145 {
146   Q_D(ctkTransferFunctionRepresentation);
147   // delete cache here
148   d->Path = QPainterPath();
149   d->Points.clear();
150 }
151 
152 //-----------------------------------------------------------------------------
curve() const153 const QPainterPath& ctkTransferFunctionRepresentation::curve()const
154 {
155   Q_D(const ctkTransferFunctionRepresentation);
156   if (d->Path.isEmpty())
157     {
158     const_cast<ctkTransferFunctionRepresentation*>(this)->computeCurve();
159     const_cast<ctkTransferFunctionRepresentation*>(this)->computeGradient();
160     }
161   return d->Path;
162 }
163 
164 //-----------------------------------------------------------------------------
points() const165 const QList<QPointF>& ctkTransferFunctionRepresentation::points()const
166 {
167   Q_D(const ctkTransferFunctionRepresentation);
168   if (d->Path.isEmpty())
169     {
170     const_cast<ctkTransferFunctionRepresentation*>(this)->computeCurve();
171     const_cast<ctkTransferFunctionRepresentation*>(this)->computeGradient();
172     }
173   return d->Points;
174 }
175 
176 //-----------------------------------------------------------------------------
gradient() const177 const QGradient& ctkTransferFunctionRepresentation::gradient()const
178 {
179   Q_D(const ctkTransferFunctionRepresentation);
180   if (d->Path.isEmpty())
181     {
182     const_cast<ctkTransferFunctionRepresentation*>(this)->computeCurve();
183     const_cast<ctkTransferFunctionRepresentation*>(this)->computeGradient();
184     }
185   return d->Gradient;
186 }
187 
188 //-----------------------------------------------------------------------------
computeCurve()189 void ctkTransferFunctionRepresentation::computeCurve()
190 {
191   Q_D(ctkTransferFunctionRepresentation);
192 
193   int count = d->TransferFunction ? d->TransferFunction->count() : 0;
194   if (count <= 0)
195     {
196     return;
197     }
198 
199   d->TransferFunction->range(d->WorldRangeX[0], d->WorldRangeX[1]);
200   d->WorldRangeY[0] = this->posY(d->TransferFunction->minValue());
201   d->WorldRangeY[1] = this->posY(d->TransferFunction->maxValue());
202 
203   d->RangeXDiff   = this->computeRangeXDiff(d->rect(), d->WorldRangeX);
204   d->RangeXOffSet = this->computeRangeXOffset(d->WorldRangeX);
205 
206   d->RangeYDiff   = this->computeRangeYDiff(d->rect(), d->WorldRangeY);
207   d->RangeYOffSet = this->computeRangeYOffset(d->WorldRangeY);
208 
209   ctkControlPoint* startCP = d->TransferFunction->controlPoint(0);
210   ctkControlPoint* nextCP = 0;
211 
212   QPointF startPos = this->mapPointToScene(startCP);
213 
214   d->Points.clear();
215   d->Points << startPos;
216 
217   d->Path = QPainterPath();
218   d->Path.moveTo(startPos);
219   for(int i = 1; i < count; ++i)
220     {
221     nextCP = d->TransferFunction->controlPoint(i);
222     if (this->transferFunction()->isDiscrete())
223       {
224       QPointF nextPos = this->mapPointToScene(nextCP);
225       qreal midPosX = (startPos.x() + nextPos.x()) / 2.;
226 
227       d->Path.lineTo(QPointF(midPosX, startPos.y()));
228       d->Path.lineTo(QPointF(midPosX, nextPos.y()));
229 
230       d->Points << nextPos;
231       startPos = nextPos;
232       if (i == count -1)
233         {
234         d->Path.lineTo(nextPos);
235         }
236       }
237     else if (dynamic_cast<ctkNonLinearControlPoint*>(startCP))
238       {
239       QList<ctkPoint> points = this->nonLinearPoints(startCP, nextCP);
240       int j;
241       for (j = 1; j < points.count(); ++j)
242         {
243         d->Path.lineTo(this->mapPointToScene(points[j]));
244         }
245       j = points.count() - 1;
246       d->Points << this->mapPointToScene(points[j]);
247       }
248     else //dynamic_cast<ctkBezierControlPoint*>(startCP))
249       {
250       QList<ctkPoint> points = this->bezierParams(startCP, nextCP);
251       QList<QPointF> bezierPoints;
252       foreach(const ctkPoint& p, points)
253         {
254         bezierPoints << this->mapPointToScene(p);
255         }
256       d->Path.cubicTo(bezierPoints[1], bezierPoints[2], bezierPoints[3]);
257       d->Points << bezierPoints[3];
258       }
259     //qDebug() << i << points[0] << points[1] << points[2] << points[3];
260     delete startCP;
261     startCP = nextCP;
262     }
263   if (startCP)
264     {
265     delete startCP;
266     }
267 }
268 
269 //-----------------------------------------------------------------------------
computeGradient()270 void ctkTransferFunctionRepresentation::computeGradient()
271 {
272   Q_D(ctkTransferFunctionRepresentation);
273 
274   int count = d->TransferFunction ? d->TransferFunction->count() : 0;
275   if (count <= 0)
276     {
277     return;
278     }
279   d->TransferFunction->range(d->WorldRangeX[0], d->WorldRangeX[1]);
280   d->WorldRangeY[0] = this->posY(d->TransferFunction->minValue());
281   d->WorldRangeY[1] = this->posY(d->TransferFunction->maxValue());
282 
283   d->RangeXDiff   = this->computeRangeXDiff(QRectF(0.,0.,1.,1.), d->WorldRangeX);
284   d->RangeXOffSet = this->computeRangeXOffset(d->WorldRangeX);
285 
286   d->RangeYDiff   = this->computeRangeYDiff(QRectF(0.,0.,1.,1.), d->WorldRangeY);
287   d->RangeYOffSet = this->computeRangeYOffset(d->WorldRangeY);
288 
289   ctkControlPoint* startCP = d->TransferFunction->controlPoint(0);
290   ctkControlPoint* nextCP = 0;
291 
292   qreal startPos = this->mapXToScene(this->posX(startCP->x()));
293   qreal nextPos;
294 
295   //
296   //if we have no colors in value (i.e. can't convert value to color)
297   if (! d->TransferFunction->value(0).canConvert<QColor>())
298     {
299     // create vertical gradient
300     d->Gradient = QLinearGradient(0., 0., 0., 1.);
301     // red
302     d->Gradient.setColorAt(0, d->VerticalGradientColor );
303     // to black
304     d->Gradient.setColorAt(1, QColor::fromRgbF(0., 0., 0., 1. ));
305     return;
306     }
307 
308   // classic gradient if we have colors in value
309   d->Gradient = QLinearGradient(0., 0., 1., 0.);
310   d->Gradient.setColorAt(startPos, this->color(startCP));
311   for(int i = 1; i < count; ++i)
312     {
313     nextCP = d->TransferFunction->controlPoint(i);
314     nextPos = this->mapXToScene(this->posX(nextCP));
315     if (this->transferFunction()->isDiscrete())
316       {
317       qreal midPoint = (startPos + nextPos)  / 2;
318       d->Gradient.setColorAt(midPoint, this->color(startCP));
319       d->Gradient.setColorAt(midPoint + std::numeric_limits<qreal>::epsilon(), this->color(nextCP));
320       }
321     else if (dynamic_cast<ctkNonLinearControlPoint*>(startCP))
322       {
323       QList<ctkPoint> points = this->nonLinearPoints(startCP, nextCP);
324       foreach(const ctkPoint& p, points)
325         {
326         d->Gradient.setColorAt(this->mapXToScene(this->posX(p)), this->color(p));
327         }
328       //no need, d->Gradient.setColorAt(nextPos, this->color(nextCP));
329       }
330     else //dynamic_cast<ctkBezierControlPoint*>(startCP))
331       { // TODO handle bezier points with color
332       QList<ctkPoint> points = this->bezierParams(startCP, nextCP);
333       QList<QPointF> bezierPoints;
334       foreach(const ctkPoint& p, points)
335         {
336         d->Gradient.setColorAt(this->mapXToScene(this->posX(p)), this->color(p));
337         }
338       nextPos = this->mapXToScene(this->posX(points[points.size() - 1]));
339       }
340     //qDebug() << i << points[0] << points[1] << points[2] << points[3];
341     delete startCP;
342     startCP = nextCP;
343     startPos = nextPos;
344     }
345   d->Gradient.setColorAt(startPos, this->color(startCP));
346   if (startCP)
347     {
348     delete startCP;
349     }
350 }
351 
352 //-----------------------------------------------------------------------------
bezierParams(ctkControlPoint * start,ctkControlPoint * end) const353 QList<ctkPoint> ctkTransferFunctionRepresentation::bezierParams(
354   ctkControlPoint* start, ctkControlPoint* end) const
355 {
356   Q_ASSERT(start);
357   Q_ASSERT(end);
358   QList<ctkPoint> points;
359 
360   ctkBezierControlPoint* bezierCP = dynamic_cast<ctkBezierControlPoint*>(start);
361   if (!bezierCP)
362     {// just duplicate start and end into p1 and p2
363     points << start->P;
364     points << start->P;
365     points << end->P;
366     points << end->P;
367     return points;
368     }
369 
370   points << start->P;
371   points << bezierCP->P1;
372   points << bezierCP->P2;
373   points << end->P;
374   return points;
375 }
376 
377 //-----------------------------------------------------------------------------
nonLinearPoints(ctkControlPoint * start,ctkControlPoint * end) const378 QList<ctkPoint> ctkTransferFunctionRepresentation::nonLinearPoints(
379   ctkControlPoint* start, ctkControlPoint* end) const
380 {
381   Q_ASSERT(start);
382 
383   ctkNonLinearControlPoint* nonLinearCP =
384     dynamic_cast<ctkNonLinearControlPoint*>(start);
385   if (!nonLinearCP)
386     {
387     QList<ctkPoint> points;
388     points << start->P;
389     points << end->P;
390     return points;
391     }
392   return nonLinearCP->SubPoints;
393 }
394 
395 //-----------------------------------------------------------------------------
color(const QVariant & v) const396 QColor ctkTransferFunctionRepresentation::color(const QVariant& v) const
397 {
398   //Q_ASSERT(v.canConvert<QColor>());
399   if (v.canConvert<QColor>())
400     {
401     return v.value<QColor>();
402     }
403   else
404     {
405     //black background
406     QColor defaultColor(0., 0., 0.);
407     return defaultColor;
408     }
409   return QColor();
410 }
411 
412 //-----------------------------------------------------------------------------
computeRangeXDiff(const QRectF & rect,qreal rangeX[2])413 qreal ctkTransferFunctionRepresentation::computeRangeXDiff(const QRectF& rect, qreal rangeX[2])
414 {
415   return rect.width() / (rangeX[1] - rangeX[0]);
416 }
417 
418 //-----------------------------------------------------------------------------
computeRangeXOffset(qreal rangeX[2])419 qreal ctkTransferFunctionRepresentation::computeRangeXOffset(qreal rangeX[2])
420 {
421   return rangeX[0];
422 }
423 
424 //-----------------------------------------------------------------------------
computeRangeYDiff(const QRectF & rect,const QVariant rangeY[2])425 qreal ctkTransferFunctionRepresentation::computeRangeYDiff(const QRectF& rect, const QVariant rangeY[2])
426 {
427   qreal rangeYDiff = rect.height();
428   qreal rangePosY[2];
429   rangePosY[0] = this->posY(rangeY[0]);
430   rangePosY[1] = this->posY(rangeY[1]);
431   if (rangePosY[1] == rangePosY[0])
432     {
433     rangeYDiff /= rangePosY[0];
434     return rangeYDiff;
435     }
436   rangeYDiff /= rangePosY[1] - rangePosY[0];
437   return rangeYDiff;
438 }
439 
440 //-----------------------------------------------------------------------------
computeRangeYOffset(const QVariant rangeY[2])441 qreal ctkTransferFunctionRepresentation::computeRangeYOffset(const QVariant rangeY[2])
442 {
443   qreal rangePosY[2];
444   rangePosY[0] = this->posY(rangeY[0]);
445   rangePosY[1] = this->posY(rangeY[1]);
446 
447   if (rangePosY[1] == rangePosY[0])
448     {
449     return 0.;
450     }
451   return rangePosY[0];
452 }
453 
454 //-----------------------------------------------------------------------------
posX(const qreal & x) const455 qreal ctkTransferFunctionRepresentation::posX(const qreal& x)const
456 {
457   return x;
458 }
459 
460 //-----------------------------------------------------------------------------
posY(const QVariant & value) const461 qreal ctkTransferFunctionRepresentation::posY(const QVariant& value)const
462 {
463   Q_ASSERT(value.canConvert<qreal>() || value.canConvert<QColor>());
464   if (value.canConvert<QColor>())
465     {
466     return value.value<QColor>().alphaF();
467     }
468   return value.toReal();
469 }
470 
471 //-----------------------------------------------------------------------------
mapPointToScene(const ctkControlPoint * cp) const472 QPointF ctkTransferFunctionRepresentation::mapPointToScene(const ctkControlPoint* cp)const
473 {
474   return QPointF(this->mapXToScene(this->posX(cp->x())),
475                  this->mapYToScene(this->posY(cp->value())));
476 }
477 
478 //-----------------------------------------------------------------------------
mapPointToScene(const ctkPoint & point) const479 QPointF ctkTransferFunctionRepresentation::mapPointToScene(const ctkPoint& point)const
480 {
481   return QPointF( this->mapXToScene(this->posX(point.X)),
482                   this->mapYToScene(this->posY(point.Value)));
483 }
484 
485 //-----------------------------------------------------------------------------
mapXToScene(qreal xPos) const486 qreal ctkTransferFunctionRepresentation::mapXToScene(qreal xPos)const
487 {
488   Q_D(const ctkTransferFunctionRepresentation);
489   return (xPos - d->RangeXOffSet) * d->RangeXDiff;
490 }
491 
492 //-----------------------------------------------------------------------------
mapYToScene(qreal yPos) const493 qreal ctkTransferFunctionRepresentation::mapYToScene(qreal yPos)const
494 {
495   Q_D(const ctkTransferFunctionRepresentation);
496   return d->height() - (yPos - d->RangeYOffSet) * d->RangeYDiff;
497 }
498 
499 //-----------------------------------------------------------------------------
mapXFromScene(qreal scenePosX) const500 qreal ctkTransferFunctionRepresentation::mapXFromScene(qreal scenePosX)const
501 {
502   Q_D(const ctkTransferFunctionRepresentation);
503   return (scenePosX / d->RangeXDiff) + d->RangeXOffSet;
504 }
505 
506 //-----------------------------------------------------------------------------
mapYFromScene(qreal scenePosY) const507 qreal ctkTransferFunctionRepresentation::mapYFromScene(qreal scenePosY)const
508 {
509   Q_D(const ctkTransferFunctionRepresentation);
510   return ((d->height() - scenePosY) / d->RangeYDiff) + d->RangeYOffSet ;
511 }
512