Mercurial > hg > orthanc-stone
comparison Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp @ 1796:20a0aba0ede5
creation of AnnotationsOverlay
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 19 May 2021 18:59:45 +0200 |
parents | b325761e0bd3 |
children | 013dec434a84 |
comparison
equal
deleted
inserted
replaced
1795:39673d351ef2 | 1796:20a0aba0ede5 |
---|---|
41 #include <SDL.h> | 41 #include <SDL.h> |
42 | 42 |
43 #include <string> | 43 #include <string> |
44 | 44 |
45 | 45 |
46 | |
47 #if 1 | |
48 #include "../../../../OrthancStone/Sources/Scene2D/MacroSceneLayer.h" | |
49 | |
50 #include <boost/math/constants/constants.hpp> | |
51 | |
52 static const double HANDLE_SIZE = 10.0; | |
53 static const double PI = boost::math::constants::pi<double>(); | |
54 | |
55 namespace OrthancStone | |
56 { | |
57 class AnnotationsOverlay : public boost::noncopyable | |
58 { | |
59 private: | |
60 class Measure; | |
61 | |
62 class Primitive : public boost::noncopyable | |
63 { | |
64 private: | |
65 bool modified_; | |
66 Measure& parentMeasure_; | |
67 Color color_; | |
68 Color hoverColor_; | |
69 bool isHover_; | |
70 int depth_; | |
71 | |
72 protected: | |
73 Measure& GetParentMeasure() const | |
74 { | |
75 return parentMeasure_; | |
76 } | |
77 | |
78 public: | |
79 Primitive(Measure& parentMeasure, | |
80 int depth) : | |
81 modified_(true), | |
82 parentMeasure_(parentMeasure), | |
83 color_(192, 192, 192), | |
84 hoverColor_(0, 255, 0), | |
85 isHover_(false), | |
86 depth_(depth) | |
87 { | |
88 } | |
89 | |
90 virtual ~Primitive() | |
91 { | |
92 } | |
93 | |
94 int GetDepth() const | |
95 { | |
96 return depth_; | |
97 } | |
98 | |
99 void SetHover(bool hover) | |
100 { | |
101 if (hover != isHover_) | |
102 { | |
103 isHover_ = hover; | |
104 modified_ = true; | |
105 } | |
106 } | |
107 | |
108 bool IsHover() const | |
109 { | |
110 return isHover_; | |
111 } | |
112 | |
113 void SetModified(bool modified) | |
114 { | |
115 modified_ = modified; | |
116 } | |
117 | |
118 bool IsModified() const | |
119 { | |
120 return modified_; | |
121 } | |
122 | |
123 void SetColor(const Color& color) | |
124 { | |
125 SetModified(true); | |
126 color_ = color; | |
127 } | |
128 | |
129 void SetHoverColor(const Color& color) | |
130 { | |
131 SetModified(true); | |
132 hoverColor_ = color; | |
133 } | |
134 | |
135 const Color& GetColor() const | |
136 { | |
137 return color_; | |
138 } | |
139 | |
140 const Color& GetHoverColor() const | |
141 { | |
142 return hoverColor_; | |
143 } | |
144 | |
145 virtual bool IsHit(const ScenePoint2D& p, | |
146 const Scene2D& scene) const = 0; | |
147 | |
148 // Always called, even if not modified | |
149 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
150 const Scene2D& scene) = 0; | |
151 | |
152 // Only called if modified | |
153 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
154 const Scene2D& scene) = 0; | |
155 | |
156 virtual void MovePreview(const ScenePoint2D& delta) = 0; | |
157 | |
158 virtual void MoveDone(const ScenePoint2D& delta) = 0; | |
159 }; | |
160 | |
161 | |
162 class Measure : public boost::noncopyable | |
163 { | |
164 private: | |
165 typedef std::list<Primitive*> Primitives; | |
166 | |
167 AnnotationsOverlay& that_; | |
168 Primitives primitives_; | |
169 | |
170 public: | |
171 Measure(AnnotationsOverlay& that) : | |
172 that_(that) | |
173 { | |
174 } | |
175 | |
176 virtual ~Measure() | |
177 { | |
178 for (Primitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it) | |
179 { | |
180 assert(*it != NULL); | |
181 assert(that_.primitives_.find(*it) != that_.primitives_.end()); | |
182 that_.primitives_.erase(*it); | |
183 delete *it; | |
184 } | |
185 } | |
186 | |
187 Primitive* AddPrimitive(Primitive* primitive) | |
188 { | |
189 if (primitive == NULL) | |
190 { | |
191 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
192 } | |
193 else | |
194 { | |
195 assert(that_.primitives_.find(primitive) == that_.primitives_.end()); | |
196 primitives_.push_back(primitive); // For automated deallocation | |
197 that_.primitives_.insert(primitive); | |
198 return primitive; | |
199 } | |
200 } | |
201 | |
202 template <typename T> | |
203 T& AddTypedPrimitive(T* primitive) | |
204 { | |
205 AddPrimitive(primitive); | |
206 return *primitive; | |
207 } | |
208 | |
209 virtual void SignalMove(Primitive& primitive) = 0; | |
210 }; | |
211 | |
212 | |
213 class Handle : public Primitive | |
214 { | |
215 private: | |
216 ScenePoint2D center_; | |
217 ScenePoint2D delta_; | |
218 | |
219 public: | |
220 explicit Handle(Measure& parentMeasure, | |
221 const ScenePoint2D& center) : | |
222 Primitive(parentMeasure, 0), // Highest priority | |
223 center_(center), | |
224 delta_(0, 0) | |
225 { | |
226 } | |
227 | |
228 void SetSize(unsigned int size) | |
229 { | |
230 SetModified(true); | |
231 } | |
232 | |
233 void SetCenter(const ScenePoint2D& center) | |
234 { | |
235 SetModified(true); | |
236 center_ = center; | |
237 delta_ = ScenePoint2D(0, 0); | |
238 } | |
239 | |
240 ScenePoint2D GetCenter() const | |
241 { | |
242 return center_ + delta_; | |
243 } | |
244 | |
245 virtual bool IsHit(const ScenePoint2D& p, | |
246 const Scene2D& scene) const | |
247 { | |
248 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom(); | |
249 | |
250 double dx = (center_.GetX() + delta_.GetX() - p.GetX()) * zoom; | |
251 double dy = (center_.GetY() + delta_.GetY() - p.GetY()) * zoom; | |
252 | |
253 return (std::abs(dx) <= HANDLE_SIZE / 2.0 && | |
254 std::abs(dy) <= HANDLE_SIZE / 2.0); | |
255 } | |
256 | |
257 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
258 const Scene2D& scene) ORTHANC_OVERRIDE | |
259 { | |
260 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom(); | |
261 | |
262 // TODO: take DPI into account | |
263 double x1 = center_.GetX() + delta_.GetX() - (HANDLE_SIZE / 2.0) / zoom; | |
264 double y1 = center_.GetY() + delta_.GetY() - (HANDLE_SIZE / 2.0) / zoom; | |
265 double x2 = center_.GetX() + delta_.GetX() + (HANDLE_SIZE / 2.0) / zoom; | |
266 double y2 = center_.GetY() + delta_.GetY() + (HANDLE_SIZE / 2.0) / zoom; | |
267 | |
268 PolylineSceneLayer::Chain chain; | |
269 chain.reserve(4); | |
270 chain.push_back(ScenePoint2D(x1, y1)); | |
271 chain.push_back(ScenePoint2D(x2, y1)); | |
272 chain.push_back(ScenePoint2D(x2, y2)); | |
273 chain.push_back(ScenePoint2D(x1, y2)); | |
274 | |
275 if (IsHover()) | |
276 { | |
277 polyline.AddChain(chain, true /* closed */, GetHoverColor()); | |
278 } | |
279 else | |
280 { | |
281 polyline.AddChain(chain, true /* closed */, GetColor()); | |
282 } | |
283 } | |
284 | |
285 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
286 const Scene2D& scene) ORTHANC_OVERRIDE | |
287 { | |
288 } | |
289 | |
290 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
291 { | |
292 SetModified(true); | |
293 delta_ = delta; | |
294 GetParentMeasure().SignalMove(*this); | |
295 } | |
296 | |
297 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
298 { | |
299 SetModified(true); | |
300 center_ = center_ + delta; | |
301 delta_ = ScenePoint2D(0, 0); | |
302 GetParentMeasure().SignalMove(*this); | |
303 } | |
304 }; | |
305 | |
306 | |
307 class Segment : public Primitive | |
308 { | |
309 private: | |
310 ScenePoint2D p1_; | |
311 ScenePoint2D p2_; | |
312 ScenePoint2D delta_; | |
313 | |
314 public: | |
315 Segment(Measure& parentMeasure, | |
316 const ScenePoint2D& p1, | |
317 const ScenePoint2D& p2) : | |
318 Primitive(parentMeasure, 1), // Can only be selected if no handle matches | |
319 p1_(p1), | |
320 p2_(p2), | |
321 delta_(0, 0) | |
322 { | |
323 } | |
324 | |
325 void SetPosition(const ScenePoint2D& p1, | |
326 const ScenePoint2D& p2) | |
327 { | |
328 SetModified(true); | |
329 p1_ = p1; | |
330 p2_ = p2; | |
331 delta_ = ScenePoint2D(0, 0); | |
332 } | |
333 | |
334 ScenePoint2D GetPosition1() const | |
335 { | |
336 return p1_ + delta_; | |
337 } | |
338 | |
339 ScenePoint2D GetPosition2() const | |
340 { | |
341 return p2_ + delta_; | |
342 } | |
343 | |
344 virtual bool IsHit(const ScenePoint2D& p, | |
345 const Scene2D& scene) const | |
346 { | |
347 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom(); | |
348 return (ScenePoint2D::SquaredDistancePtSegment(p1_ + delta_, p2_ + delta_, p) * zoom * zoom <= | |
349 (HANDLE_SIZE / 2.0) * (HANDLE_SIZE / 2.0)); | |
350 } | |
351 | |
352 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
353 const Scene2D& scene) ORTHANC_OVERRIDE | |
354 { | |
355 PolylineSceneLayer::Chain chain; | |
356 chain.reserve(2); | |
357 chain.push_back(p1_ + delta_); | |
358 chain.push_back(p2_ + delta_); | |
359 | |
360 if (IsHover()) | |
361 { | |
362 polyline.AddChain(chain, false /* closed */, GetHoverColor()); | |
363 } | |
364 else | |
365 { | |
366 polyline.AddChain(chain, false /* closed */, GetColor()); | |
367 } | |
368 } | |
369 | |
370 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
371 const Scene2D& scene) ORTHANC_OVERRIDE | |
372 { | |
373 } | |
374 | |
375 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
376 { | |
377 SetModified(true); | |
378 delta_ = delta; | |
379 GetParentMeasure().SignalMove(*this); | |
380 } | |
381 | |
382 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
383 { | |
384 SetModified(true); | |
385 p1_ = p1_ + delta; | |
386 p2_ = p2_ + delta; | |
387 delta_ = ScenePoint2D(0, 0); | |
388 GetParentMeasure().SignalMove(*this); | |
389 } | |
390 }; | |
391 | |
392 | |
393 class Circle : public Primitive | |
394 { | |
395 private: | |
396 ScenePoint2D p1_; | |
397 ScenePoint2D p2_; | |
398 | |
399 public: | |
400 Circle(Measure& parentMeasure, | |
401 const ScenePoint2D& p1, | |
402 const ScenePoint2D& p2) : | |
403 Primitive(parentMeasure, 2), | |
404 p1_(p1), | |
405 p2_(p2) | |
406 { | |
407 } | |
408 | |
409 void SetPosition(const ScenePoint2D& p1, | |
410 const ScenePoint2D& p2) | |
411 { | |
412 SetModified(true); | |
413 p1_ = p1; | |
414 p2_ = p2; | |
415 } | |
416 | |
417 ScenePoint2D GetPosition1() const | |
418 { | |
419 return p1_; | |
420 } | |
421 | |
422 ScenePoint2D GetPosition2() const | |
423 { | |
424 return p2_; | |
425 } | |
426 | |
427 virtual bool IsHit(const ScenePoint2D& p, | |
428 const Scene2D& scene) const | |
429 { | |
430 return false; | |
431 } | |
432 | |
433 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
434 const Scene2D& scene) ORTHANC_OVERRIDE | |
435 { | |
436 static unsigned int NUM_SEGMENTS = 128; | |
437 | |
438 ScenePoint2D middle((p1_.GetX() + p2_.GetX()) / 2.0, | |
439 (p1_.GetY() + p2_.GetY()) / 2.0); | |
440 | |
441 const double radius = ScenePoint2D::DistancePtPt(middle, p1_); | |
442 | |
443 double increment = 2.0 * PI / static_cast<double>(NUM_SEGMENTS - 1); | |
444 | |
445 PolylineSceneLayer::Chain chain; | |
446 chain.reserve(NUM_SEGMENTS); | |
447 | |
448 double theta = 0; | |
449 for (unsigned int i = 0; i < NUM_SEGMENTS; i++) | |
450 { | |
451 chain.push_back(ScenePoint2D(middle.GetX() + radius * cos(theta), | |
452 middle.GetY() + radius * sin(theta))); | |
453 theta += increment; | |
454 } | |
455 | |
456 if (IsHover()) | |
457 { | |
458 polyline.AddChain(chain, false /* closed */, GetHoverColor()); | |
459 } | |
460 else | |
461 { | |
462 polyline.AddChain(chain, false /* closed */, GetColor()); | |
463 } | |
464 } | |
465 | |
466 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
467 const Scene2D& scene) ORTHANC_OVERRIDE | |
468 { | |
469 } | |
470 | |
471 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
472 { | |
473 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
474 } | |
475 | |
476 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
477 { | |
478 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
479 } | |
480 }; | |
481 | |
482 | |
483 class Arc : public Primitive | |
484 { | |
485 private: | |
486 ScenePoint2D start_; | |
487 ScenePoint2D middle_; | |
488 ScenePoint2D end_; | |
489 double radius_; // in pixels | |
490 | |
491 void ComputeAngles(double& fullAngle, | |
492 double& startAngle, | |
493 double& endAngle) const | |
494 { | |
495 const double x1 = start_.GetX(); | |
496 const double y1 = start_.GetY(); | |
497 const double xc = middle_.GetX(); | |
498 const double yc = middle_.GetY(); | |
499 const double x2 = end_.GetX(); | |
500 const double y2 = end_.GetY(); | |
501 | |
502 startAngle = atan2(y1 - yc, x1 - xc); | |
503 endAngle = atan2(y2 - yc, x2 - xc); | |
504 | |
505 fullAngle = endAngle - startAngle; | |
506 | |
507 while (fullAngle < -PI) | |
508 { | |
509 fullAngle += 2.0 * PI; | |
510 } | |
511 | |
512 while (fullAngle >= PI) | |
513 { | |
514 fullAngle -= 2.0 * PI; | |
515 } | |
516 } | |
517 | |
518 public: | |
519 Arc(Measure& parentMeasure, | |
520 const ScenePoint2D& start, | |
521 const ScenePoint2D& middle, | |
522 const ScenePoint2D& end) : | |
523 Primitive(parentMeasure, 2), | |
524 start_(start), | |
525 middle_(middle), | |
526 end_(end), | |
527 radius_(20) | |
528 { | |
529 } | |
530 | |
531 double GetAngle() const | |
532 { | |
533 double fullAngle, startAngle, endAngle; | |
534 ComputeAngles(fullAngle, startAngle, endAngle); | |
535 return fullAngle; | |
536 } | |
537 | |
538 void SetStart(const ScenePoint2D& p) | |
539 { | |
540 SetModified(true); | |
541 start_ = p; | |
542 } | |
543 | |
544 void SetMiddle(const ScenePoint2D& p) | |
545 { | |
546 SetModified(true); | |
547 middle_ = p; | |
548 } | |
549 | |
550 void SetEnd(const ScenePoint2D& p) | |
551 { | |
552 SetModified(true); | |
553 end_ = p; | |
554 } | |
555 | |
556 virtual bool IsHit(const ScenePoint2D& p, | |
557 const Scene2D& scene) const | |
558 { | |
559 return false; | |
560 } | |
561 | |
562 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
563 const Scene2D& scene) ORTHANC_OVERRIDE | |
564 { | |
565 static unsigned int NUM_SEGMENTS = 64; | |
566 | |
567 const double radius = radius_ / scene.GetSceneToCanvasTransform().ComputeZoom(); | |
568 | |
569 double fullAngle, startAngle, endAngle; | |
570 ComputeAngles(fullAngle, startAngle, endAngle); | |
571 | |
572 double increment = fullAngle / static_cast<double>(NUM_SEGMENTS - 1); | |
573 | |
574 PolylineSceneLayer::Chain chain; | |
575 chain.reserve(NUM_SEGMENTS); | |
576 | |
577 double theta = startAngle; | |
578 for (unsigned int i = 0; i < NUM_SEGMENTS; i++) | |
579 { | |
580 chain.push_back(ScenePoint2D(middle_.GetX() + radius * cos(theta), | |
581 middle_.GetY() + radius * sin(theta))); | |
582 theta += increment; | |
583 } | |
584 | |
585 if (IsHover()) | |
586 { | |
587 polyline.AddChain(chain, false /* closed */, GetHoverColor()); | |
588 } | |
589 else | |
590 { | |
591 polyline.AddChain(chain, false /* closed */, GetColor()); | |
592 } | |
593 } | |
594 | |
595 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
596 const Scene2D& scene) ORTHANC_OVERRIDE | |
597 { | |
598 } | |
599 | |
600 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
601 { | |
602 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
603 } | |
604 | |
605 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
606 { | |
607 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
608 } | |
609 }; | |
610 | |
611 | |
612 class Text : public Primitive | |
613 { | |
614 private: | |
615 bool first_; | |
616 size_t sublayer_; | |
617 std::unique_ptr<TextSceneLayer> content_; | |
618 | |
619 public: | |
620 Text(Measure& parentMeasure) : | |
621 Primitive(parentMeasure, 2), | |
622 first_(true) | |
623 { | |
624 } | |
625 | |
626 void SetContent(const TextSceneLayer& content) | |
627 { | |
628 SetModified(true); | |
629 content_.reset(dynamic_cast<TextSceneLayer*>(content.Clone())); | |
630 } | |
631 | |
632 virtual bool IsHit(const ScenePoint2D& p, | |
633 const Scene2D& scene) const | |
634 { | |
635 return false; | |
636 } | |
637 | |
638 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline, | |
639 const Scene2D& scene) ORTHANC_OVERRIDE | |
640 { | |
641 } | |
642 | |
643 virtual void RenderOtherLayers(MacroSceneLayer& macro, | |
644 const Scene2D& scene) ORTHANC_OVERRIDE | |
645 { | |
646 if (content_.get() != NULL) | |
647 { | |
648 std::unique_ptr<TextSceneLayer> layer(reinterpret_cast<TextSceneLayer*>(content_->Clone())); | |
649 | |
650 layer->SetColor(IsHover() ? GetHoverColor() : GetColor()); | |
651 | |
652 if (first_) | |
653 { | |
654 sublayer_ = macro.AddLayer(layer.release()); | |
655 first_ = false; | |
656 } | |
657 else | |
658 { | |
659 macro.UpdateLayer(sublayer_, layer.release()); | |
660 } | |
661 } | |
662 } | |
663 | |
664 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
665 { | |
666 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
667 } | |
668 | |
669 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE | |
670 { | |
671 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible | |
672 } | |
673 }; | |
674 | |
675 | |
676 class EditPrimitiveTracker : public IFlexiblePointerTracker | |
677 { | |
678 private: | |
679 Primitive& primitive_; | |
680 ScenePoint2D sceneClick_; | |
681 AffineTransform2D canvasToScene_; | |
682 bool alive_; | |
683 | |
684 public: | |
685 EditPrimitiveTracker(Primitive& primitive, | |
686 const ScenePoint2D& sceneClick, | |
687 const AffineTransform2D& canvasToScene) : | |
688 primitive_(primitive), | |
689 sceneClick_(sceneClick), | |
690 canvasToScene_(canvasToScene), | |
691 alive_(true) | |
692 { | |
693 } | |
694 | |
695 virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE | |
696 { | |
697 primitive_.MovePreview(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_); | |
698 } | |
699 | |
700 virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE | |
701 { | |
702 primitive_.MoveDone(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_); | |
703 alive_ = false; | |
704 } | |
705 | |
706 virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE | |
707 { | |
708 } | |
709 | |
710 virtual bool IsAlive() const ORTHANC_OVERRIDE | |
711 { | |
712 return alive_; | |
713 } | |
714 | |
715 virtual void Cancel() ORTHANC_OVERRIDE | |
716 { | |
717 primitive_.MoveDone(ScenePoint2D(0, 0)); | |
718 } | |
719 }; | |
720 | |
721 | |
722 class SegmentMeasure : public Measure | |
723 { | |
724 private: | |
725 Handle& handle1_; | |
726 Handle& handle2_; | |
727 Segment& segment_; | |
728 Text& label_; | |
729 | |
730 void UpdateLabel() | |
731 { | |
732 TextSceneLayer content; | |
733 | |
734 double x1 = handle1_.GetCenter().GetX(); | |
735 double y1 = handle1_.GetCenter().GetY(); | |
736 double x2 = handle2_.GetCenter().GetX(); | |
737 double y2 = handle2_.GetCenter().GetY(); | |
738 | |
739 // Put the label to the right of the right-most handle | |
740 if (x1 < x2) | |
741 { | |
742 content.SetPosition(x2, y2); | |
743 } | |
744 else | |
745 { | |
746 content.SetPosition(x1, y1); | |
747 } | |
748 | |
749 content.SetAnchor(BitmapAnchor_CenterLeft); | |
750 content.SetBorder(10); | |
751 | |
752 double dx = x1 - x2; | |
753 double dy = y1 - y2; | |
754 char buf[32]; | |
755 sprintf(buf, "%0.2f cm", sqrt(dx * dx + dy * dy) / 10.0); | |
756 content.SetText(buf); | |
757 | |
758 label_.SetContent(content); | |
759 } | |
760 | |
761 public: | |
762 SegmentMeasure(AnnotationsOverlay& that, | |
763 const ScenePoint2D& p1, | |
764 const ScenePoint2D& p2) : | |
765 Measure(that), | |
766 handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))), | |
767 handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))), | |
768 segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))), | |
769 label_(AddTypedPrimitive<Text>(new Text(*this))) | |
770 { | |
771 label_.SetColor(Color(255, 0, 0)); | |
772 UpdateLabel(); | |
773 } | |
774 | |
775 virtual void SignalMove(Primitive& primitive) ORTHANC_OVERRIDE | |
776 { | |
777 if (&primitive == &handle1_ || | |
778 &primitive == &handle2_) | |
779 { | |
780 segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter()); | |
781 } | |
782 else if (&primitive == &segment_) | |
783 { | |
784 handle1_.SetCenter(segment_.GetPosition1()); | |
785 handle2_.SetCenter(segment_.GetPosition2()); | |
786 } | |
787 | |
788 UpdateLabel(); | |
789 } | |
790 }; | |
791 | |
792 | |
793 class AngleMeasure : public Measure | |
794 { | |
795 private: | |
796 Handle& startHandle_; | |
797 Handle& middleHandle_; | |
798 Handle& endHandle_; | |
799 Segment& segment1_; | |
800 Segment& segment2_; | |
801 Arc& arc_; | |
802 Text& label_; | |
803 | |
804 void UpdateLabel() | |
805 { | |
806 TextSceneLayer content; | |
807 | |
808 const double x1 = startHandle_.GetCenter().GetX(); | |
809 const double x2 = middleHandle_.GetCenter().GetX(); | |
810 const double y2 = middleHandle_.GetCenter().GetY(); | |
811 const double x3 = endHandle_.GetCenter().GetX(); | |
812 | |
813 if (x2 < x1 && | |
814 x2 < x3) | |
815 { | |
816 content.SetAnchor(BitmapAnchor_CenterRight); | |
817 } | |
818 else | |
819 { | |
820 content.SetAnchor(BitmapAnchor_CenterLeft); | |
821 } | |
822 | |
823 content.SetPosition(x2, y2); | |
824 content.SetBorder(10); | |
825 | |
826 char buf[32]; | |
827 sprintf(buf, "%.01f%c%c", std::abs(arc_.GetAngle()) / PI * 180.0, | |
828 0xc2, 0xb0 /* two bytes corresponding to degree symbol in UTF-8 */); | |
829 content.SetText(buf); | |
830 | |
831 label_.SetContent(content); | |
832 } | |
833 | |
834 public: | |
835 AngleMeasure(AnnotationsOverlay& that, | |
836 const ScenePoint2D& start, | |
837 const ScenePoint2D& middle, | |
838 const ScenePoint2D& end) : | |
839 Measure(that), | |
840 startHandle_(AddTypedPrimitive<Handle>(new Handle(*this, start))), | |
841 middleHandle_(AddTypedPrimitive<Handle>(new Handle(*this, middle))), | |
842 endHandle_(AddTypedPrimitive<Handle>(new Handle(*this, end))), | |
843 segment1_(AddTypedPrimitive<Segment>(new Segment(*this, start, middle))), | |
844 segment2_(AddTypedPrimitive<Segment>(new Segment(*this, middle, end))), | |
845 arc_(AddTypedPrimitive<Arc>(new Arc(*this, start, middle, end))), | |
846 label_(AddTypedPrimitive<Text>(new Text(*this))) | |
847 { | |
848 label_.SetColor(Color(255, 0, 0)); | |
849 UpdateLabel(); | |
850 } | |
851 | |
852 virtual void SignalMove(Primitive& primitive) ORTHANC_OVERRIDE | |
853 { | |
854 if (&primitive == &startHandle_) | |
855 { | |
856 segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter()); | |
857 arc_.SetStart(startHandle_.GetCenter()); | |
858 } | |
859 else if (&primitive == &middleHandle_) | |
860 { | |
861 segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter()); | |
862 segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter()); | |
863 arc_.SetMiddle(middleHandle_.GetCenter()); | |
864 } | |
865 else if (&primitive == &endHandle_) | |
866 { | |
867 segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter()); | |
868 arc_.SetEnd(endHandle_.GetCenter()); | |
869 } | |
870 else if (&primitive == &segment1_) | |
871 { | |
872 startHandle_.SetCenter(segment1_.GetPosition1()); | |
873 middleHandle_.SetCenter(segment1_.GetPosition2()); | |
874 segment2_.SetPosition(segment1_.GetPosition2(), segment2_.GetPosition2()); | |
875 arc_.SetStart(segment1_.GetPosition1()); | |
876 arc_.SetMiddle(segment1_.GetPosition2()); | |
877 } | |
878 else if (&primitive == &segment2_) | |
879 { | |
880 middleHandle_.SetCenter(segment2_.GetPosition1()); | |
881 endHandle_.SetCenter(segment2_.GetPosition2()); | |
882 segment1_.SetPosition(segment1_.GetPosition1(), segment2_.GetPosition1()); | |
883 arc_.SetMiddle(segment2_.GetPosition1()); | |
884 arc_.SetEnd(segment2_.GetPosition2()); | |
885 } | |
886 | |
887 UpdateLabel(); | |
888 } | |
889 }; | |
890 | |
891 | |
892 class CircleMeasure : public Measure | |
893 { | |
894 private: | |
895 Handle& handle1_; | |
896 Handle& handle2_; | |
897 Segment& segment_; | |
898 Circle& circle_; | |
899 Text& label_; | |
900 | |
901 void UpdateLabel() | |
902 { | |
903 TextSceneLayer content; | |
904 | |
905 double x1 = handle1_.GetCenter().GetX(); | |
906 double y1 = handle1_.GetCenter().GetY(); | |
907 double x2 = handle2_.GetCenter().GetX(); | |
908 double y2 = handle2_.GetCenter().GetY(); | |
909 | |
910 // Put the label to the right of the right-most handle | |
911 if (x1 < x2) | |
912 { | |
913 content.SetPosition(x2, y2); | |
914 } | |
915 else | |
916 { | |
917 content.SetPosition(x1, y1); | |
918 } | |
919 | |
920 content.SetAnchor(BitmapAnchor_CenterLeft); | |
921 content.SetBorder(10); | |
922 | |
923 double dx = x1 - x2; | |
924 double dy = y1 - y2; | |
925 double diameter = sqrt(dx * dx + dy * dy); // in millimeters | |
926 | |
927 double area = PI * diameter * diameter / 4.0; | |
928 | |
929 char buf[32]; | |
930 sprintf(buf, "%0.2f cm\n%0.2f cm%c%c", | |
931 diameter / 10.0, | |
932 area / 100.0, | |
933 0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */); | |
934 content.SetText(buf); | |
935 | |
936 label_.SetContent(content); | |
937 } | |
938 | |
939 public: | |
940 CircleMeasure(AnnotationsOverlay& that, | |
941 const ScenePoint2D& p1, | |
942 const ScenePoint2D& p2) : | |
943 Measure(that), | |
944 handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))), | |
945 handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))), | |
946 segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))), | |
947 circle_(AddTypedPrimitive<Circle>(new Circle(*this, p1, p2))), | |
948 label_(AddTypedPrimitive<Text>(new Text(*this))) | |
949 { | |
950 label_.SetColor(Color(255, 0, 0)); | |
951 UpdateLabel(); | |
952 } | |
953 | |
954 virtual void SignalMove(Primitive& primitive) ORTHANC_OVERRIDE | |
955 { | |
956 if (&primitive == &handle1_ || | |
957 &primitive == &handle2_) | |
958 { | |
959 segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter()); | |
960 circle_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter()); | |
961 } | |
962 else if (&primitive == &segment_) | |
963 { | |
964 handle1_.SetCenter(segment_.GetPosition1()); | |
965 handle2_.SetCenter(segment_.GetPosition2()); | |
966 circle_.SetPosition(segment_.GetPosition1(), segment_.GetPosition2()); | |
967 } | |
968 | |
969 UpdateLabel(); | |
970 } | |
971 }; | |
972 | |
973 | |
974 typedef std::set<Primitive*> Primitives; | |
975 typedef std::set<Measure*> Measures; | |
976 | |
977 size_t macroLayerIndex_; | |
978 size_t polylineSublayer_; | |
979 Primitives primitives_; | |
980 Measures measures_; | |
981 | |
982 public: | |
983 AnnotationsOverlay(size_t macroLayerIndex) : | |
984 macroLayerIndex_(macroLayerIndex), | |
985 polylineSublayer_(0) // dummy initialization | |
986 { | |
987 measures_.insert(new SegmentMeasure(*this, ScenePoint2D(0, 0), ScenePoint2D(100, 100))); | |
988 measures_.insert(new AngleMeasure(*this, ScenePoint2D(100, 50), ScenePoint2D(150, 40), ScenePoint2D(200, 50))); | |
989 measures_.insert(new CircleMeasure(*this, ScenePoint2D(50, 200), ScenePoint2D(100, 250))); | |
990 } | |
991 | |
992 ~AnnotationsOverlay() | |
993 { | |
994 for (Measures::iterator it = measures_.begin(); it != measures_.end(); ++it) | |
995 { | |
996 assert(*it != NULL); | |
997 delete *it; | |
998 } | |
999 | |
1000 measures_.clear(); | |
1001 } | |
1002 | |
1003 void Render(Scene2D& scene) | |
1004 { | |
1005 MacroSceneLayer* macro = NULL; | |
1006 | |
1007 if (scene.HasLayer(macroLayerIndex_)) | |
1008 { | |
1009 macro = &dynamic_cast<MacroSceneLayer&>(scene.GetLayer(macroLayerIndex_)); | |
1010 } | |
1011 else | |
1012 { | |
1013 macro = &dynamic_cast<MacroSceneLayer&>(scene.SetLayer(macroLayerIndex_, new MacroSceneLayer)); | |
1014 polylineSublayer_ = macro->AddLayer(new PolylineSceneLayer); | |
1015 } | |
1016 | |
1017 std::unique_ptr<PolylineSceneLayer> polyline(new PolylineSceneLayer); | |
1018 | |
1019 for (Primitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it) | |
1020 { | |
1021 assert(*it != NULL); | |
1022 Primitive& primitive = **it; | |
1023 | |
1024 primitive.RenderPolylineLayer(*polyline, scene); | |
1025 | |
1026 if (primitive.IsModified()) | |
1027 { | |
1028 primitive.RenderOtherLayers(*macro, scene); | |
1029 primitive.SetModified(false); | |
1030 } | |
1031 } | |
1032 | |
1033 macro->UpdateLayer(polylineSublayer_, polyline.release()); | |
1034 } | |
1035 | |
1036 bool ClearHover() | |
1037 { | |
1038 bool needsRefresh = false; | |
1039 | |
1040 for (Primitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it) | |
1041 { | |
1042 assert(*it != NULL); | |
1043 if ((*it)->IsHover()) | |
1044 { | |
1045 (*it)->SetHover(false); | |
1046 needsRefresh = true; | |
1047 } | |
1048 } | |
1049 | |
1050 return needsRefresh; | |
1051 } | |
1052 | |
1053 bool SetMouseHover(const ScenePoint2D& p /* expressed in canvas coordinates */, | |
1054 const Scene2D& scene) | |
1055 { | |
1056 bool needsRefresh = false; | |
1057 | |
1058 const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform()); | |
1059 | |
1060 for (Primitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it) | |
1061 { | |
1062 assert(*it != NULL); | |
1063 bool hover = (*it)->IsHit(s, scene); | |
1064 | |
1065 if ((*it)->IsHover() != hover) | |
1066 { | |
1067 needsRefresh = true; | |
1068 } | |
1069 | |
1070 (*it)->SetHover(hover); | |
1071 } | |
1072 | |
1073 return needsRefresh; | |
1074 } | |
1075 | |
1076 IFlexiblePointerTracker* CreateTracker(const ScenePoint2D& p /* expressed in canvas coordinates */, | |
1077 const Scene2D& scene) | |
1078 { | |
1079 const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform()); | |
1080 | |
1081 int bestDepth; | |
1082 std::unique_ptr<IFlexiblePointerTracker> tracker; | |
1083 | |
1084 for (Primitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it) | |
1085 { | |
1086 assert(*it != NULL); | |
1087 if ((*it)->IsHit(s, scene)) | |
1088 { | |
1089 if (tracker.get() == NULL || | |
1090 bestDepth > (*it)->GetDepth()) | |
1091 { | |
1092 tracker.reset(new EditPrimitiveTracker(**it, s, scene.GetCanvasToSceneTransform())); | |
1093 bestDepth = (*it)->GetDepth(); | |
1094 } | |
1095 } | |
1096 } | |
1097 | |
1098 return tracker.release(); | |
1099 } | |
1100 }; | |
1101 } | |
1102 #endif | |
1103 | |
1104 | |
1105 | |
46 std::string orthancUrl; | 1106 std::string orthancUrl; |
47 std::string instanceId; | 1107 std::string instanceId; |
48 int frameIndex = 0; | 1108 int frameIndex = 0; |
49 | 1109 |
50 | 1110 |
180 lineMeasureTool->Disable(); | 1240 lineMeasureTool->Disable(); |
181 | 1241 |
182 boost::shared_ptr<OrthancStone::AngleMeasureTool> angleMeasureTool(OrthancStone::AngleMeasureTool::Create(viewport)); | 1242 boost::shared_ptr<OrthancStone::AngleMeasureTool> angleMeasureTool(OrthancStone::AngleMeasureTool::Create(viewport)); |
183 bool angleMeasureFirst = true; | 1243 bool angleMeasureFirst = true; |
184 angleMeasureTool->Disable(); | 1244 angleMeasureTool->Disable(); |
1245 | |
1246 OrthancStone::AnnotationsOverlay overlay(10); | |
185 | 1247 |
186 boost::shared_ptr<SdlSimpleViewerApplication> application( | 1248 boost::shared_ptr<SdlSimpleViewerApplication> application( |
187 SdlSimpleViewerApplication::Create(context, viewport)); | 1249 SdlSimpleViewerApplication::Create(context, viewport)); |
188 | 1250 |
189 OrthancStone::DicomSource source; | 1251 OrthancStone::DicomSource source; |
342 switch (event.type) | 1404 switch (event.type) |
343 { | 1405 { |
344 case SDL_MOUSEBUTTONDOWN: | 1406 case SDL_MOUSEBUTTONDOWN: |
345 { | 1407 { |
346 boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; | 1408 boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; |
347 switch (activeTool) | 1409 |
1410 t.reset(overlay.CreateTracker(p.GetMainPosition(), lock->GetController().GetScene())); | |
1411 if (t.get() == NULL) | |
348 { | 1412 { |
349 case ActiveTool_Angle: | 1413 switch (activeTool) |
350 t = angleMeasureTool->CreateEditionTracker(p); | 1414 { |
351 break; | 1415 case ActiveTool_Angle: |
352 | 1416 t = angleMeasureTool->CreateEditionTracker(p); |
353 case ActiveTool_Line: | 1417 break; |
354 t = lineMeasureTool->CreateEditionTracker(p); | 1418 |
355 break; | 1419 case ActiveTool_Line: |
356 | 1420 t = lineMeasureTool->CreateEditionTracker(p); |
357 case ActiveTool_None: | 1421 break; |
358 break; | 1422 |
1423 case ActiveTool_None: | |
1424 break; | |
359 | 1425 |
360 default: | 1426 default: |
361 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | 1427 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); |
1428 } | |
362 } | 1429 } |
363 | 1430 |
364 if (t.get() != NULL) | 1431 if (t.get() != NULL) |
365 { | 1432 { |
366 lock->GetController().AcquireActiveTracker(t); | 1433 lock->GetController().AcquireActiveTracker(t); |
377 | 1444 |
378 case SDL_MOUSEMOTION: | 1445 case SDL_MOUSEMOTION: |
379 if (lock->GetController().HandleMouseMove(p)) | 1446 if (lock->GetController().HandleMouseMove(p)) |
380 { | 1447 { |
381 lock->Invalidate(); | 1448 lock->Invalidate(); |
1449 if (overlay.ClearHover()) | |
1450 { | |
1451 paint = true; | |
1452 } | |
1453 } | |
1454 else | |
1455 { | |
1456 if (overlay.SetMouseHover(p.GetMainPosition(), lock->GetController().GetScene())) | |
1457 { | |
1458 paint = true; | |
1459 } | |
382 } | 1460 } |
383 break; | 1461 break; |
384 | 1462 |
385 case SDL_MOUSEBUTTONUP: | 1463 case SDL_MOUSEBUTTONUP: |
386 lock->GetController().HandleMouseRelease(p); | 1464 lock->GetController().HandleMouseRelease(p); |
394 } | 1472 } |
395 } | 1473 } |
396 | 1474 |
397 if (paint) | 1475 if (paint) |
398 { | 1476 { |
1477 { | |
1478 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock()); | |
1479 overlay.Render(lock->GetController().GetScene()); | |
1480 } | |
1481 | |
399 viewport->Paint(); | 1482 viewport->Paint(); |
400 } | 1483 } |
401 | 1484 |
402 // Small delay to avoid using 100% of CPU | 1485 // Small delay to avoid using 100% of CPU |
403 SDL_Delay(1); | 1486 SDL_Delay(1); |