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