1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkCompositeControlPointsItem.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 
16 #include "vtkCompositeControlPointsItem.h"
17 #include "vtkBrush.h"
18 #include "vtkCallbackCommand.h"
19 #include "vtkColorTransferFunction.h"
20 #include "vtkContext2D.h"
21 #include "vtkContextScene.h"
22 #include "vtkIdTypeArray.h"
23 #include "vtkObjectFactory.h"
24 #include "vtkPen.h"
25 #include "vtkPiecewiseFunction.h"
26 #include "vtkPiecewisePointHandleItem.h"
27 #include "vtkPoints2D.h"
28 
29 // to handle mouse.GetButton
30 #include "vtkContextMouseEvent.h"
31 
32 #include <algorithm>
33 #include <cassert>
34 #include <limits>
35 
36 //------------------------------------------------------------------------------
37 vtkStandardNewMacro(vtkCompositeControlPointsItem);
38 
39 //------------------------------------------------------------------------------
vtkCompositeControlPointsItem()40 vtkCompositeControlPointsItem::vtkCompositeControlPointsItem()
41 {
42   this->SetColorFill(true);
43 }
44 
45 //------------------------------------------------------------------------------
~vtkCompositeControlPointsItem()46 vtkCompositeControlPointsItem::~vtkCompositeControlPointsItem()
47 {
48   if (this->OpacityFunction)
49   {
50     this->OpacityFunction->RemoveObserver(this->Callback);
51     this->OpacityFunction->Delete();
52     this->OpacityFunction = nullptr;
53   }
54   if (this->OpacityPointHandle)
55   {
56     this->OpacityPointHandle->Delete();
57     this->OpacityPointHandle = nullptr;
58   }
59 }
60 
61 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)62 void vtkCompositeControlPointsItem::PrintSelf(ostream& os, vtkIndent indent)
63 {
64   this->Superclass::PrintSelf(os, indent);
65   os << indent << "OpacityFunction: ";
66   if (this->OpacityFunction)
67   {
68     os << endl;
69     this->OpacityFunction->PrintSelf(os, indent.GetNextIndent());
70   }
71   else
72   {
73     os << "(none)" << endl;
74   }
75   os << indent << "OpacityFunction: ";
76   if (this->OpacityPointHandle)
77   {
78     os << endl;
79     this->OpacityPointHandle->PrintSelf(os, indent.GetNextIndent());
80   }
81   else
82   {
83     os << "(none)" << endl;
84   }
85   os << indent << "UseOpacityPointHandles: " << this->UseOpacityPointHandles << endl;
86 }
87 
88 //------------------------------------------------------------------------------
emitEvent(unsigned long event,void * params)89 void vtkCompositeControlPointsItem::emitEvent(unsigned long event, void* params)
90 {
91   if (this->OpacityFunction)
92   {
93     this->OpacityFunction->InvokeEvent(event, params);
94   }
95   this->Superclass::emitEvent(event, params);
96 }
97 
98 //------------------------------------------------------------------------------
GetControlPointsMTime()99 vtkMTimeType vtkCompositeControlPointsItem::GetControlPointsMTime()
100 {
101   vtkMTimeType mTime = this->Superclass::GetControlPointsMTime();
102   if (this->OpacityFunction)
103   {
104     mTime = std::max(mTime, this->OpacityFunction->GetMTime());
105   }
106   return mTime;
107 }
108 
109 //------------------------------------------------------------------------------
SetOpacityFunction(vtkPiecewiseFunction * function)110 void vtkCompositeControlPointsItem::SetOpacityFunction(vtkPiecewiseFunction* function)
111 {
112   if (function == this->OpacityFunction)
113   {
114     return;
115   }
116   if (this->OpacityFunction)
117   {
118     this->OpacityFunction->RemoveObserver(this->Callback);
119   }
120   vtkSetObjectBodyMacro(OpacityFunction, vtkPiecewiseFunction, function);
121   if (this->PointsFunction == ColorAndOpacityPointsFunction)
122   {
123     this->SilentMergeTransferFunctions();
124   }
125   if (this->OpacityFunction)
126   {
127     this->OpacityFunction->AddObserver(vtkCommand::StartEvent, this->Callback);
128     this->OpacityFunction->AddObserver(vtkCommand::ModifiedEvent, this->Callback);
129     this->OpacityFunction->AddObserver(vtkCommand::EndEvent, this->Callback);
130   }
131   this->ResetBounds();
132   this->ComputePoints();
133 }
134 
135 //------------------------------------------------------------------------------
SetColorTransferFunction(vtkColorTransferFunction * c)136 void vtkCompositeControlPointsItem::SetColorTransferFunction(vtkColorTransferFunction* c)
137 {
138   if (c == this->ColorTransferFunction)
139   {
140     return;
141   }
142   // We need to set the color transfer function here (before
143   // Superclass::SetPiecewiseFunction) to be able to have a valid
144   // color transfer function for MergeColorTransferFunction().
145   this->Superclass::SetColorTransferFunction(c);
146   if (this->PointsFunction == ColorAndOpacityPointsFunction)
147   {
148     this->SilentMergeTransferFunctions();
149   }
150 }
151 
152 //------------------------------------------------------------------------------
DrawPoint(vtkContext2D * painter,vtkIdType index)153 void vtkCompositeControlPointsItem::DrawPoint(vtkContext2D* painter, vtkIdType index)
154 {
155   if (this->PointsFunction == ColorPointsFunction ||
156     this->PointsFunction == ColorAndOpacityPointsFunction)
157   {
158     this->Superclass::DrawPoint(painter, index);
159     return;
160   }
161   if (this->PointsFunction == OpacityPointsFunction && this->ColorFill &&
162     this->ColorTransferFunction)
163   {
164     double xvms[4];
165     this->OpacityFunction->GetNodeValue(index, xvms);
166     const unsigned char* rgb = this->ColorTransferFunction->MapValue(xvms[0]);
167     painter->GetBrush()->SetColorF(rgb[0] / 255., rgb[1] / 255., rgb[2] / 255., 0.55);
168   }
169   // The superclass logic was already considered at the beginning of this method.
170   // As such, the grandparent class must be called here in order to avoid applying
171   // the color transfer function a second time.
172   // NOLINTNEXTLINE(bugprone-parent-virtual-call)
173   this->Superclass::Superclass::DrawPoint(painter, index);
174 }
175 
176 //------------------------------------------------------------------------------
GetNumberOfPoints() const177 vtkIdType vtkCompositeControlPointsItem::GetNumberOfPoints() const
178 {
179   if (this->ColorTransferFunction &&
180     (this->PointsFunction == ColorPointsFunction ||
181       this->PointsFunction == ColorAndOpacityPointsFunction))
182   {
183     return this->Superclass::GetNumberOfPoints();
184   }
185   if (this->OpacityFunction &&
186     (this->PointsFunction == OpacityPointsFunction ||
187       this->PointsFunction == ColorAndOpacityPointsFunction))
188   {
189     return static_cast<vtkIdType>(this->OpacityFunction->GetSize());
190   }
191   return 0;
192 }
193 
194 //------------------------------------------------------------------------------
SetControlPoint(vtkIdType index,double * newPos)195 void vtkCompositeControlPointsItem::SetControlPoint(vtkIdType index, double* newPos)
196 {
197   if (this->PointsFunction == ColorPointsFunction ||
198     this->PointsFunction == ColorAndOpacityPointsFunction)
199   {
200     this->Superclass::SetControlPoint(index, newPos);
201   }
202   if (this->OpacityFunction &&
203     (this->PointsFunction == OpacityPointsFunction ||
204       this->PointsFunction == ColorAndOpacityPointsFunction))
205   {
206     this->StartChanges();
207     this->OpacityFunction->SetNodeValue(index, newPos);
208     this->EndChanges();
209   }
210 }
211 
212 //------------------------------------------------------------------------------
GetControlPoint(vtkIdType index,double * pos) const213 void vtkCompositeControlPointsItem::GetControlPoint(vtkIdType index, double* pos) const
214 {
215   if (!this->OpacityFunction || this->PointsFunction == ColorPointsFunction)
216   {
217     this->Superclass::GetControlPoint(index, pos);
218     if (this->OpacityFunction)
219     {
220       pos[1] = const_cast<vtkPiecewiseFunction*>(this->OpacityFunction)->GetValue(pos[0]);
221     }
222     return;
223   }
224   const_cast<vtkPiecewiseFunction*>(this->OpacityFunction)->GetNodeValue(index, pos);
225 }
226 
227 //------------------------------------------------------------------------------
EditPoint(float tX,float tY)228 void vtkCompositeControlPointsItem::EditPoint(float tX, float tY)
229 {
230   if (this->PointsFunction == ColorPointsFunction ||
231     this->PointsFunction == ColorAndOpacityPointsFunction)
232   {
233     this->Superclass::EditPoint(tX, tY);
234   }
235   if (this->OpacityFunction &&
236     (this->PointsFunction == ColorPointsFunction ||
237       this->PointsFunction == ColorAndOpacityPointsFunction))
238   {
239     this->StartChanges();
240     double xvms[4];
241     this->OpacityFunction->GetNodeValue(this->CurrentPoint, xvms);
242     xvms[2] += tX;
243     xvms[3] += tY;
244     this->OpacityFunction->SetNodeValue(this->CurrentPoint, xvms);
245     // Not sure why we move the point before too
246     if (this->CurrentPoint > 0)
247     {
248       this->OpacityFunction->GetNodeValue(this->CurrentPoint - 1, xvms);
249       xvms[2] += tX;
250       xvms[3] += tY;
251       this->OpacityFunction->SetNodeValue(this->CurrentPoint - 1, xvms);
252     }
253     this->EndChanges();
254   }
255 }
256 
257 //------------------------------------------------------------------------------
AddPoint(double * newPos)258 vtkIdType vtkCompositeControlPointsItem::AddPoint(double* newPos)
259 {
260   vtkIdType addedPoint = -1;
261   this->StartChanges();
262   if (this->OpacityFunction &&
263     (this->PointsFunction == OpacityPointsFunction ||
264       this->PointsFunction == ColorAndOpacityPointsFunction))
265   {
266     addedPoint = this->OpacityFunction->AddPoint(newPos[0], newPos[1]);
267     if (this->PointsFunction == OpacityPointsFunction)
268     {
269       this->vtkControlPointsItem::AddPointId(addedPoint);
270     }
271   }
272   if (this->PointsFunction == ColorPointsFunction ||
273     this->PointsFunction == ColorAndOpacityPointsFunction)
274   {
275     addedPoint = this->Superclass::AddPoint(newPos);
276   }
277   this->EndChanges();
278   return addedPoint;
279 }
280 
281 //------------------------------------------------------------------------------
RemovePoint(double * currentPoint)282 vtkIdType vtkCompositeControlPointsItem::RemovePoint(double* currentPoint)
283 {
284   vtkIdType removedPoint = -1;
285   if (!this->IsPointRemovable(this->GetControlPointId(currentPoint)))
286   {
287     return removedPoint;
288   }
289 
290   this->StartChanges();
291   if (this->PointsFunction == ColorPointsFunction ||
292     this->PointsFunction == ColorAndOpacityPointsFunction)
293   {
294     removedPoint = this->Superclass::RemovePoint(currentPoint);
295   }
296   if (this->OpacityFunction &&
297     (this->PointsFunction == OpacityPointsFunction ||
298       this->PointsFunction == ColorAndOpacityPointsFunction))
299   {
300     removedPoint = this->OpacityFunction->RemovePoint(currentPoint[0], currentPoint[1]);
301   }
302 
303   if (this->CurrentPoint > removedPoint || this->CurrentPoint == this->GetNumberOfPoints() - 1)
304   {
305     this->SetCurrentPoint(this->CurrentPoint - 1);
306   }
307 
308   this->EndChanges();
309   return removedPoint;
310 }
311 
312 //------------------------------------------------------------------------------
MergeTransferFunctions()313 void vtkCompositeControlPointsItem::MergeTransferFunctions()
314 {
315   if (!this->ColorTransferFunction || !this->OpacityFunction)
316   {
317     return;
318   }
319   // Naive implementation that does the work but can be a bit slow
320   // Copy OpacityFunction points into the ColorTransferFunction
321   const int piecewiseFunctionCount = this->OpacityFunction->GetSize();
322   for (int i = 0; i < piecewiseFunctionCount; ++i)
323   {
324     double piecewisePoint[4];
325     this->OpacityFunction->GetNodeValue(i, piecewisePoint);
326     double rgb[3];
327     this->ColorTransferFunction->GetColor(piecewisePoint[0], rgb);
328     // note that we might lose the midpoint/sharpness of the point if any
329     this->ColorTransferFunction->RemovePoint(piecewisePoint[0]);
330     this->ColorTransferFunction->AddRGBPoint(
331       piecewisePoint[0], rgb[0], rgb[1], rgb[2], piecewisePoint[2], piecewisePoint[3]);
332   }
333   // Copy ColorTransferFunction points into the OpacityFunction
334   const int colorFunctionCount = this->ColorTransferFunction->GetSize();
335   for (int i = 0; i < colorFunctionCount; ++i)
336   {
337     double xrgbms[6];
338     this->ColorTransferFunction->GetNodeValue(i, xrgbms);
339     double value = this->OpacityFunction->GetValue(xrgbms[0]);
340     // note that we might lose the midpoint/sharpness of the point if any
341     this->OpacityFunction->RemovePoint(xrgbms[0]);
342     this->OpacityFunction->AddPoint(xrgbms[0], value, xrgbms[4], xrgbms[5]);
343   }
344 }
345 
346 //------------------------------------------------------------------------------
SilentMergeTransferFunctions()347 void vtkCompositeControlPointsItem::SilentMergeTransferFunctions()
348 {
349   this->StartChanges();
350   this->MergeTransferFunctions();
351   this->EndChanges();
352 }
353 
354 //------------------------------------------------------------------------------
MouseButtonPressEvent(const vtkContextMouseEvent & mouse)355 bool vtkCompositeControlPointsItem::MouseButtonPressEvent(const vtkContextMouseEvent& mouse)
356 {
357   bool result = false;
358   if (this->OpacityPointHandle && this->OpacityPointHandle->GetVisible())
359   {
360     result = this->OpacityPointHandle->MouseButtonPressEvent(mouse);
361   }
362   if (!result)
363   {
364     result = this->Superclass::MouseButtonPressEvent(mouse);
365     if (result && this->OpacityPointHandle && this->OpacityPointHandle->GetVisible() &&
366       this->OpacityPointHandle->GetCurrentPointIndex() != this->GetCurrentPoint())
367     {
368       this->OpacityPointHandle->SetVisible(false);
369     }
370   }
371   return result;
372 }
373 
374 //------------------------------------------------------------------------------
MouseDoubleClickEvent(const vtkContextMouseEvent & mouse)375 bool vtkCompositeControlPointsItem::MouseDoubleClickEvent(const vtkContextMouseEvent& mouse)
376 {
377   bool superRes = this->Superclass::MouseDoubleClickEvent(mouse);
378   if (superRes)
379   {
380     vtkIdType curIdx = this->GetCurrentPoint();
381     this->EditPointCurve(curIdx);
382   }
383   return superRes;
384 }
385 
386 //------------------------------------------------------------------------------
MouseMoveEvent(const vtkContextMouseEvent & mouse)387 bool vtkCompositeControlPointsItem::MouseMoveEvent(const vtkContextMouseEvent& mouse)
388 {
389   bool result = false;
390   if (this->OpacityPointHandle && this->OpacityPointHandle->GetVisible())
391   {
392     result = this->OpacityPointHandle->MouseMoveEvent(mouse);
393   }
394   if (!result)
395   {
396     result = this->Superclass::MouseMoveEvent(mouse);
397   }
398   return result;
399 }
400 
401 //------------------------------------------------------------------------------
EditPointCurve(vtkIdType index)402 void vtkCompositeControlPointsItem::EditPointCurve(vtkIdType index)
403 {
404   if (index < 0 || index >= this->GetNumberOfPoints())
405   {
406     return;
407   }
408   if (this->GetUseOpacityPointHandles())
409   {
410     if (!this->OpacityPointHandle)
411     {
412       this->OpacityPointHandle = vtkPiecewisePointHandleItem::New();
413       this->AddItem(this->OpacityPointHandle);
414       this->OpacityPointHandle->SetPiecewiseFunction(this->GetOpacityFunction());
415     }
416     else
417     {
418       this->OpacityPointHandle->SetVisible(!this->OpacityPointHandle->GetVisible());
419       this->GetScene()->SetDirty(true);
420     }
421   }
422 }
423