comparison Applications/Samples/SingleFrameEditorApplication.h @ 354:f806779bd40f am-2

UndoRedoStack
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 29 Oct 2018 13:53:02 +0100
parents 92a159481900
children d2468dd75b3f
comparison
equal deleted inserted replaced
353:92a159481900 354:f806779bd40f
55 Corner_TopLeft, 55 Corner_TopLeft,
56 Corner_TopRight, 56 Corner_TopRight,
57 Corner_BottomLeft, 57 Corner_BottomLeft,
58 Corner_BottomRight 58 Corner_BottomRight
59 }; 59 };
60 60
61 61
62 class Bitmap : public boost::noncopyable 62 class Bitmap : public boost::noncopyable
63 { 63 {
64 private: 64 private:
65 size_t index_; 65 size_t index_;
596 596
597 597
598 class BitmapAccessor : public boost::noncopyable 598 class BitmapAccessor : public boost::noncopyable
599 { 599 {
600 private: 600 private:
601 size_t index_; 601 BitmapStack& stack_;
602 Bitmap* bitmap_; 602 size_t index_;
603 Bitmap* bitmap_;
603 604
604 public: 605 public:
605 BitmapAccessor(BitmapStack& stack, 606 BitmapAccessor(BitmapStack& stack,
606 size_t index) : 607 size_t index) :
608 stack_(stack),
607 index_(index) 609 index_(index)
608 { 610 {
609 Bitmaps::iterator bitmap = stack.bitmaps_.find(index); 611 Bitmaps::iterator bitmap = stack.bitmaps_.find(index);
610 if (bitmap == stack.bitmaps_.end()) 612 if (bitmap == stack.bitmaps_.end())
611 { 613 {
619 } 621 }
620 622
621 BitmapAccessor(BitmapStack& stack, 623 BitmapAccessor(BitmapStack& stack,
622 double x, 624 double x,
623 double y) : 625 double y) :
626 stack_(stack),
624 index_(0) // Dummy initialization 627 index_(0) // Dummy initialization
625 { 628 {
626 if (stack.LookupBitmap(index_, x, y)) 629 if (stack.LookupBitmap(index_, x, y))
627 { 630 {
628 Bitmaps::iterator bitmap = stack.bitmaps_.find(index_); 631 Bitmaps::iterator bitmap = stack.bitmaps_.find(index_);
651 bool IsValid() const 654 bool IsValid() const
652 { 655 {
653 return bitmap_ != NULL; 656 return bitmap_ != NULL;
654 } 657 }
655 658
659 BitmapStack& GetStack() const
660 {
661 if (IsValid())
662 {
663 return stack_;
664 }
665 else
666 {
667 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
668 }
669 }
670
656 size_t GetIndex() const 671 size_t GetIndex() const
657 { 672 {
658 if (IsValid()) 673 if (IsValid())
659 { 674 {
660 return index_; 675 return index_;
675 { 690 {
676 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 691 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
677 } 692 }
678 } 693 }
679 }; 694 };
680
681
682 private:
683 class DicomBitmap : public Bitmap
684 {
685 private:
686 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData
687 std::auto_ptr<DicomFrameConverter> converter_;
688 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32 or RGB24
689
690 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag)
691 {
692 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
693 }
694
695
696 void ApplyConverter()
697 {
698 if (source_.get() != NULL &&
699 converter_.get() != NULL)
700 {
701 converted_.reset(converter_->ConvertFrame(*source_));
702 }
703 }
704
705 public:
706 DicomBitmap(size_t index) :
707 Bitmap(index)
708 {
709 }
710
711 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
712 {
713 converter_.reset(new DicomFrameConverter);
714 converter_->ReadParameters(dataset);
715 ApplyConverter();
716
717 std::string tmp;
718 Vector pixelSpacing;
719
720 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
721 LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
722 pixelSpacing.size() == 2)
723 {
724 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
725 }
726
727 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY());
728
729 static unsigned int c = 0;
730 if (c == 0)
731 {
732 SetPan(400, 0);
733 c ++;
734 }
735
736 OrthancPlugins::DicomDatasetReader reader(dataset);
737
738 unsigned int width, height;
739 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
740 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
741 {
742 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
743 }
744 else
745 {
746 SetSize(width, height);
747 }
748 }
749
750
751 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership
752 {
753 std::auto_ptr<Orthanc::ImageAccessor> raii(image);
754
755 if (image == NULL)
756 {
757 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
758 }
759
760 SetSize(image->GetWidth(), image->GetHeight());
761
762 source_ = raii;
763 ApplyConverter();
764 }
765
766
767 virtual void Render(Orthanc::ImageAccessor& buffer,
768 const ViewportGeometry& view,
769 ImageInterpolation interpolation) const
770 {
771 if (converted_.get() != NULL)
772 {
773 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
774 {
775 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
776 }
777
778 unsigned int cropX, cropY, cropWidth, cropHeight;
779 GetCrop(cropX, cropY, cropWidth, cropHeight);
780
781 Matrix m = LinearAlgebra::Product(view.GetMatrix(),
782 GetTransform(),
783 CreateOffsetMatrix(cropX, cropY));
784
785 Orthanc::ImageAccessor cropped;
786 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
787
788 ApplyProjectiveTransform(buffer, cropped, m, interpolation, false);
789 }
790 }
791
792
793 virtual bool GetDefaultWindowing(float& center,
794 float& width) const
795 {
796 if (converter_.get() != NULL &&
797 converter_->HasDefaultWindow())
798 {
799 center = static_cast<float>(converter_->GetDefaultWindowCenter());
800 width = static_cast<float>(converter_->GetDefaultWindowWidth());
801 return true;
802 }
803 else
804 {
805 return false;
806 }
807 }
808 };
809
810
811 695
812 696
813 class AlphaBitmap : public Bitmap 697 class AlphaBitmap : public Bitmap
814 { 698 {
815 private: 699 private:
913 } 797 }
914 } 798 }
915 }; 799 };
916 800
917 801
802
803 private:
804 class DicomBitmap : public Bitmap
805 {
806 private:
807 std::auto_ptr<Orthanc::ImageAccessor> source_; // Content of PixelData
808 std::auto_ptr<DicomFrameConverter> converter_;
809 std::auto_ptr<Orthanc::ImageAccessor> converted_; // Float32 or RGB24
810
811 static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag)
812 {
813 return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement());
814 }
815
816
817 void ApplyConverter()
818 {
819 if (source_.get() != NULL &&
820 converter_.get() != NULL)
821 {
822 converted_.reset(converter_->ConvertFrame(*source_));
823 }
824 }
825
826 public:
827 DicomBitmap(size_t index) :
828 Bitmap(index)
829 {
830 }
831
832 void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset)
833 {
834 converter_.reset(new DicomFrameConverter);
835 converter_->ReadParameters(dataset);
836 ApplyConverter();
837
838 std::string tmp;
839 Vector pixelSpacing;
840
841 if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) &&
842 LinearAlgebra::ParseVector(pixelSpacing, tmp) &&
843 pixelSpacing.size() == 2)
844 {
845 SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]);
846 }
847
848 //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY());
849
850 static unsigned int c = 0;
851 if (c == 0)
852 {
853 SetPan(400, 0);
854 c ++;
855 }
856
857 OrthancPlugins::DicomDatasetReader reader(dataset);
858
859 unsigned int width, height;
860 if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) ||
861 !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS)))
862 {
863 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
864 }
865 else
866 {
867 SetSize(width, height);
868 }
869 }
870
871
872 void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership
873 {
874 std::auto_ptr<Orthanc::ImageAccessor> raii(image);
875
876 if (image == NULL)
877 {
878 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
879 }
880
881 SetSize(image->GetWidth(), image->GetHeight());
882
883 source_ = raii;
884 ApplyConverter();
885 }
886
887
888 virtual void Render(Orthanc::ImageAccessor& buffer,
889 const ViewportGeometry& view,
890 ImageInterpolation interpolation) const
891 {
892 if (converted_.get() != NULL)
893 {
894 if (converted_->GetFormat() != Orthanc::PixelFormat_Float32)
895 {
896 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
897 }
898
899 unsigned int cropX, cropY, cropWidth, cropHeight;
900 GetCrop(cropX, cropY, cropWidth, cropHeight);
901
902 Matrix m = LinearAlgebra::Product(view.GetMatrix(),
903 GetTransform(),
904 CreateOffsetMatrix(cropX, cropY));
905
906 Orthanc::ImageAccessor cropped;
907 converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight);
908
909 ApplyProjectiveTransform(buffer, cropped, m, interpolation, false);
910 }
911 }
912
913
914 virtual bool GetDefaultWindowing(float& center,
915 float& width) const
916 {
917 if (converter_.get() != NULL &&
918 converter_->HasDefaultWindow())
919 {
920 center = static_cast<float>(converter_->GetDefaultWindowCenter());
921 width = static_cast<float>(converter_->GetDefaultWindowWidth());
922 return true;
923 }
924 else
925 {
926 return false;
927 }
928 }
929 };
930
931
932
918 933
919 typedef std::map<size_t, Bitmap*> Bitmaps; 934 typedef std::map<size_t, Bitmap*> Bitmaps;
920 935
921 OrthancApiClient& orthanc_; 936 OrthancApiClient& orthanc_;
922 size_t countBitmaps_; 937 size_t countBitmaps_;
1058 1073
1059 return *ptr; 1074 return *ptr;
1060 } 1075 }
1061 1076
1062 1077
1063 size_t LoadFrame(const std::string& instance, 1078 Bitmap& LoadFrame(const std::string& instance,
1064 unsigned int frame, 1079 unsigned int frame,
1065 bool httpCompression) 1080 bool httpCompression)
1066 { 1081 {
1067 size_t bitmap = countBitmaps_++; 1082 size_t bitmap = countBitmaps_++;
1068 1083
1069 bitmaps_[bitmap] = new DicomBitmap(bitmap); 1084 bitmaps_[bitmap] = new DicomBitmap(bitmap);
1070 1085
1071
1072 { 1086 {
1073 IWebService::Headers headers; 1087 IWebService::Headers headers;
1074 std::string uri = "/instances/" + instance + "/tags"; 1088 std::string uri = "/instances/" + instance + "/tags";
1075 orthanc_.GetBinaryAsync(uri, headers, 1089 orthanc_.GetBinaryAsync(uri, headers,
1076 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage> 1090 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
1092 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage> 1106 new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
1093 (*this, &BitmapStack::OnFrameReceived), NULL, 1107 (*this, &BitmapStack::OnFrameReceived), NULL,
1094 new Orthanc::SingleValueObject<size_t>(bitmap)); 1108 new Orthanc::SingleValueObject<size_t>(bitmap));
1095 } 1109 }
1096 1110
1097 return bitmap; 1111 return *bitmaps_[bitmap];
1098 } 1112 }
1099 1113
1100 1114
1101 void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) 1115 void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message)
1102 { 1116 {
1223 1237
1224 context.SetSourceColor(255, 0, 0); 1238 context.SetSourceColor(255, 0, 0);
1225 view.ApplyTransform(context); 1239 view.ApplyTransform(context);
1226 bitmap->second->DrawBorders(context, view.GetZoom()); 1240 bitmap->second->DrawBorders(context, view.GetZoom());
1227 } 1241 }
1242 }
1243 }
1244 };
1245
1246
1247 class UndoRedoStack : public boost::noncopyable
1248 {
1249 public:
1250 class ICommand : public boost::noncopyable
1251 {
1252 public:
1253 virtual ~ICommand()
1254 {
1255 }
1256
1257 virtual void Undo() const = 0;
1258
1259 virtual void Redo() const = 0;
1260 };
1261
1262 private:
1263 typedef std::list<ICommand*> Stack;
1264
1265 Stack stack_;
1266 Stack::iterator current_;
1267
1268 void Clear(Stack::iterator from)
1269 {
1270 for (Stack::iterator it = from; it != stack_.end(); ++it)
1271 {
1272 assert(*it != NULL);
1273 delete *it;
1274 }
1275
1276 stack_.erase(from, stack_.end());
1277 }
1278
1279 public:
1280 UndoRedoStack() :
1281 current_(stack_.end())
1282 {
1283 }
1284
1285 ~UndoRedoStack()
1286 {
1287 Clear(stack_.begin());
1288 }
1289
1290 void Add(ICommand* command)
1291 {
1292 if (command == NULL)
1293 {
1294 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1295 }
1296
1297 Clear(current_);
1298
1299 stack_.push_back(command);
1300 current_ = stack_.end();
1301 }
1302
1303 void Undo()
1304 {
1305 if (current_ != stack_.begin())
1306 {
1307 --current_;
1308
1309 assert(*current_ != NULL);
1310 (*current_)->Undo();
1311 }
1312 }
1313
1314 void Redo()
1315 {
1316 if (current_ != stack_.end())
1317 {
1318 assert(*current_ != NULL);
1319 (*current_)->Redo();
1320
1321 ++current_;
1322 }
1323 }
1324 };
1325
1326
1327 class BitmapCommandBase : public UndoRedoStack::ICommand
1328 {
1329 private:
1330 BitmapStack& stack_;
1331 size_t bitmap_;
1332
1333 protected:
1334 virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const = 0;
1335
1336 virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const = 0;
1337
1338 public:
1339 BitmapCommandBase(BitmapStack& stack,
1340 size_t bitmap) :
1341 stack_(stack),
1342 bitmap_(bitmap)
1343 {
1344 }
1345
1346 BitmapCommandBase(BitmapStack::BitmapAccessor& accessor) :
1347 stack_(accessor.GetStack()),
1348 bitmap_(accessor.GetIndex())
1349 {
1350 }
1351
1352 virtual void Undo() const
1353 {
1354 BitmapStack::BitmapAccessor accessor(stack_, bitmap_);
1355
1356 if (accessor.IsValid())
1357 {
1358 UndoInternal(accessor.GetBitmap());
1359 }
1360 }
1361
1362 virtual void Redo() const
1363 {
1364 BitmapStack::BitmapAccessor accessor(stack_, bitmap_);
1365
1366 if (accessor.IsValid())
1367 {
1368 RedoInternal(accessor.GetBitmap());
1369 }
1370 }
1371 };
1372
1373
1374 class RotateBitmapTracker : public IWorldSceneMouseTracker
1375 {
1376 private:
1377 UndoRedoStack& undoRedoStack_;
1378 BitmapStack::BitmapAccessor accessor_;
1379 double centerX_;
1380 double centerY_;
1381 double originalAngle_;
1382 double clickAngle_;
1383 bool roundAngles_;
1384
1385 bool ComputeAngle(double& angle /* out */,
1386 double sceneX,
1387 double sceneY) const
1388 {
1389 Vector u;
1390 LinearAlgebra::AssignVector(u, sceneX - centerX_, sceneY - centerY_);
1391
1392 double nu = boost::numeric::ublas::norm_2(u);
1393
1394 if (!LinearAlgebra::IsCloseToZero(nu))
1395 {
1396 u /= nu;
1397 angle = atan2(u[1], u[0]);
1398 return true;
1399 }
1400 else
1401 {
1402 return false;
1403 }
1404 }
1405
1406
1407 class UndoRedoCommand : public BitmapCommandBase
1408 {
1409 private:
1410 double sourceAngle_;
1411 double targetAngle_;
1412
1413 static int ToDegrees(double angle)
1414 {
1415 return static_cast<int>(round(angle * 180.0 / boost::math::constants::pi<double>()));
1416 }
1417
1418 protected:
1419 virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
1420 {
1421 LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
1422 bitmap.SetAngle(sourceAngle_);
1423 }
1424
1425 virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
1426 {
1427 LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
1428 bitmap.SetAngle(targetAngle_);
1429 }
1430
1431 public:
1432 UndoRedoCommand(BitmapStack::BitmapAccessor& accessor,
1433 double sourceAngle,
1434 double targetAngle) :
1435 BitmapCommandBase(accessor),
1436 sourceAngle_(sourceAngle),
1437 targetAngle_(targetAngle)
1438 {
1439 }
1440 };
1441
1442
1443 public:
1444 RotateBitmapTracker(UndoRedoStack& undoRedoStack,
1445 BitmapStack& stack,
1446 const ViewportGeometry& view,
1447 size_t bitmap,
1448 double x,
1449 double y,
1450 bool roundAngles) :
1451 undoRedoStack_(undoRedoStack),
1452 accessor_(stack, bitmap),
1453 roundAngles_(roundAngles)
1454 {
1455 if (accessor_.IsValid())
1456 {
1457 accessor_.GetBitmap().GetCenter(centerX_, centerY_);
1458 originalAngle_ = accessor_.GetBitmap().GetAngle();
1459
1460 double sceneX, sceneY;
1461 view.MapDisplayToScene(sceneX, sceneY, x, y);
1462
1463 if (!ComputeAngle(clickAngle_, x, y))
1464 {
1465 accessor_.Invalidate();
1466 }
1467 }
1468 }
1469
1470 virtual bool HasRender() const
1471 {
1472 return false;
1473 }
1474
1475 virtual void Render(CairoContext& context,
1476 double zoom)
1477 {
1478 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1479 }
1480
1481 virtual void MouseUp()
1482 {
1483 if (accessor_.IsValid())
1484 {
1485 undoRedoStack_.Add(new UndoRedoCommand(accessor_, originalAngle_,
1486 accessor_.GetBitmap().GetAngle()));
1487 }
1488 }
1489
1490 virtual void MouseMove(int displayX,
1491 int displayY,
1492 double sceneX,
1493 double sceneY)
1494 {
1495 static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>();
1496
1497 double angle;
1498
1499 if (accessor_.IsValid() &&
1500 ComputeAngle(angle, sceneX, sceneY))
1501 {
1502 angle = angle - clickAngle_ + originalAngle_;
1503
1504 if (roundAngles_)
1505 {
1506 angle = round(angle / ROUND_ANGLE) * ROUND_ANGLE;
1507 }
1508
1509 accessor_.GetBitmap().SetAngle(angle);
1510 }
1511 }
1512 };
1513
1514
1515 class MoveBitmapTracker : public IWorldSceneMouseTracker
1516 {
1517 private:
1518 UndoRedoStack& undoRedoStack_;
1519 BitmapStack::BitmapAccessor accessor_;
1520 double clickX_;
1521 double clickY_;
1522 double panX_;
1523 double panY_;
1524 bool oneAxis_;
1525
1526 class UndoRedoCommand : public BitmapCommandBase
1527 {
1528 private:
1529 double sourceX_;
1530 double sourceY_;
1531 double targetX_;
1532 double targetY_;
1533
1534 protected:
1535 virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
1536 {
1537 bitmap.SetPan(sourceX_, sourceY_);
1538 }
1539
1540 virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
1541 {
1542 bitmap.SetPan(targetX_, targetY_);
1543 }
1544
1545 public:
1546 UndoRedoCommand(BitmapStack::BitmapAccessor& accessor,
1547 double sourceX,
1548 double sourceY,
1549 double targetX,
1550 double targetY) :
1551 BitmapCommandBase(accessor),
1552 sourceX_(sourceX),
1553 sourceY_(sourceY),
1554 targetX_(targetX),
1555 targetY_(targetY)
1556 {
1557 }
1558 };
1559
1560
1561 public:
1562 MoveBitmapTracker(UndoRedoStack& undoRedoStack,
1563 BitmapStack& stack,
1564 size_t bitmap,
1565 double x,
1566 double y,
1567 bool oneAxis) :
1568 undoRedoStack_(undoRedoStack),
1569 accessor_(stack, bitmap),
1570 clickX_(x),
1571 clickY_(y),
1572 oneAxis_(oneAxis)
1573 {
1574 if (accessor_.IsValid())
1575 {
1576 panX_ = accessor_.GetBitmap().GetPanX();
1577 panY_ = accessor_.GetBitmap().GetPanY();
1578 }
1579 }
1580
1581 virtual bool HasRender() const
1582 {
1583 return false;
1584 }
1585
1586 virtual void Render(CairoContext& context,
1587 double zoom)
1588 {
1589 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1590 }
1591
1592 virtual void MouseUp()
1593 {
1594 if (accessor_.IsValid())
1595 {
1596 undoRedoStack_.Add(new UndoRedoCommand(accessor_, panX_, panY_,
1597 accessor_.GetBitmap().GetPanX(),
1598 accessor_.GetBitmap().GetPanY()));
1599 }
1600 }
1601
1602 virtual void MouseMove(int displayX,
1603 int displayY,
1604 double sceneX,
1605 double sceneY)
1606 {
1607 if (accessor_.IsValid())
1608 {
1609 double dx = sceneX - clickX_;
1610 double dy = sceneY - clickY_;
1611
1612 if (oneAxis_)
1613 {
1614 if (fabs(dx) > fabs(dy))
1615 {
1616 accessor_.GetBitmap().SetPan(dx + panX_, panY_);
1617 }
1618 else
1619 {
1620 accessor_.GetBitmap().SetPan(panX_, dy + panY_);
1621 }
1622 }
1623 else
1624 {
1625 accessor_.GetBitmap().SetPan(dx + panX_, dy + panY_);
1626 }
1627 }
1628 }
1629 };
1630
1631
1632 class CropBitmapTracker : public IWorldSceneMouseTracker
1633 {
1634 private:
1635 BitmapStack::BitmapAccessor accessor_;
1636 BitmapStack::Corner corner_;
1637 unsigned int cropX_;
1638 unsigned int cropY_;
1639 unsigned int cropWidth_;
1640 unsigned int cropHeight_;
1641
1642 public:
1643 CropBitmapTracker(BitmapStack& stack,
1644 const ViewportGeometry& view,
1645 size_t bitmap,
1646 double x,
1647 double y,
1648 BitmapStack::Corner corner) :
1649 accessor_(stack, bitmap),
1650 corner_(corner)
1651 {
1652 if (accessor_.IsValid())
1653 {
1654 accessor_.GetBitmap().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);
1655 }
1656 }
1657
1658 virtual bool HasRender() const
1659 {
1660 return false;
1661 }
1662
1663 virtual void Render(CairoContext& context,
1664 double zoom)
1665 {
1666 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1667 }
1668
1669 virtual void MouseUp()
1670 {
1671 }
1672
1673 virtual void MouseMove(int displayX,
1674 int displayY,
1675 double sceneX,
1676 double sceneY)
1677 {
1678 if (accessor_.IsValid())
1679 {
1680 unsigned int x, y;
1681
1682 BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
1683 if (bitmap.GetPixel(x, y, sceneX, sceneY))
1684 {
1685 unsigned int targetX, targetWidth;
1686
1687 if (corner_ == BitmapStack::Corner_TopLeft ||
1688 corner_ == BitmapStack::Corner_BottomLeft)
1689 {
1690 targetX = std::min(x, cropX_ + cropWidth_);
1691 targetWidth = cropX_ + cropWidth_ - targetX;
1692 }
1693 else
1694 {
1695 targetX = cropX_;
1696 targetWidth = std::max(x, cropX_) - cropX_;
1697 }
1698
1699 unsigned int targetY, targetHeight;
1700
1701 if (corner_ == BitmapStack::Corner_TopLeft ||
1702 corner_ == BitmapStack::Corner_TopRight)
1703 {
1704 targetY = std::min(y, cropY_ + cropHeight_);
1705 targetHeight = cropY_ + cropHeight_ - targetY;
1706 }
1707 else
1708 {
1709 targetY = cropY_;
1710 targetHeight = std::max(y, cropY_) - cropY_;
1711 }
1712
1713 bitmap.SetCrop(targetX, targetY, targetWidth, targetHeight);
1714 }
1715 }
1716 }
1717 };
1718
1719
1720 class ResizeBitmapTracker : public IWorldSceneMouseTracker
1721 {
1722 private:
1723 BitmapStack::BitmapAccessor accessor_;
1724 bool roundScaling_;
1725 double originalSpacingX_;
1726 double originalSpacingY_;
1727 BitmapStack::Corner oppositeCorner_;
1728 double oppositeX_;
1729 double oppositeY_;
1730 double baseScaling_;
1731
1732 static double ComputeDistance(double x1,
1733 double y1,
1734 double x2,
1735 double y2)
1736 {
1737 double dx = x1 - x2;
1738 double dy = y1 - y2;
1739 return sqrt(dx * dx + dy * dy);
1740 }
1741
1742 public:
1743 ResizeBitmapTracker(BitmapStack& stack,
1744 size_t bitmap,
1745 double x,
1746 double y,
1747 BitmapStack::Corner corner,
1748 bool roundScaling) :
1749 accessor_(stack, bitmap),
1750 roundScaling_(roundScaling)
1751 {
1752 if (accessor_.IsValid() &&
1753 accessor_.GetBitmap().IsResizeable())
1754 {
1755 originalSpacingX_ = accessor_.GetBitmap().GetPixelSpacingX();
1756 originalSpacingY_ = accessor_.GetBitmap().GetPixelSpacingY();
1757
1758 switch (corner)
1759 {
1760 case BitmapStack::Corner_TopLeft:
1761 oppositeCorner_ = BitmapStack::Corner_BottomRight;
1762 break;
1763
1764 case BitmapStack::Corner_TopRight:
1765 oppositeCorner_ = BitmapStack::Corner_BottomLeft;
1766 break;
1767
1768 case BitmapStack::Corner_BottomLeft:
1769 oppositeCorner_ = BitmapStack::Corner_TopRight;
1770 break;
1771
1772 case BitmapStack::Corner_BottomRight:
1773 oppositeCorner_ = BitmapStack::Corner_TopLeft;
1774 break;
1775
1776 default:
1777 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1778 }
1779
1780 accessor_.GetBitmap().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
1781
1782 double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
1783 if (d >= std::numeric_limits<float>::epsilon())
1784 {
1785 baseScaling_ = 1.0 / d;
1786 }
1787 else
1788 {
1789 // Avoid division by zero in extreme cases
1790 accessor_.Invalidate();
1791 }
1792 }
1793 }
1794
1795 virtual bool HasRender() const
1796 {
1797 return false;
1798 }
1799
1800 virtual void Render(CairoContext& context,
1801 double zoom)
1802 {
1803 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1804 }
1805
1806 virtual void MouseUp()
1807 {
1808 }
1809
1810 virtual void MouseMove(int displayX,
1811 int displayY,
1812 double sceneX,
1813 double sceneY)
1814 {
1815 static const double ROUND_SCALING = 0.1;
1816
1817 if (accessor_.IsValid() &&
1818 accessor_.GetBitmap().IsResizeable())
1819 {
1820 double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
1821
1822 if (roundScaling_)
1823 {
1824 scaling = round(scaling / ROUND_SCALING) * ROUND_SCALING;
1825 }
1826
1827 BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
1828 bitmap.SetPixelSpacing(scaling * originalSpacingX_,
1829 scaling * originalSpacingY_);
1830
1831 // Keep the opposite corner at a fixed location
1832 double ox, oy;
1833 bitmap.GetCorner(ox, oy, oppositeCorner_);
1834 bitmap.SetPan(bitmap.GetPanX() + oppositeX_ - ox,
1835 bitmap.GetPanY() + oppositeY_ - oy);
1228 } 1836 }
1229 } 1837 }
1230 }; 1838 };
1231 1839
1232 1840
1245 { 1853 {
1246 return 10.0; 1854 return 10.0;
1247 } 1855 }
1248 1856
1249 1857
1250 BitmapStack& stack_; 1858 BitmapStack& stack_;
1251 Tool tool_; 1859 UndoRedoStack undoRedoStack_;
1252 1860 Tool tool_;
1253 1861
1254 class RotateBitmapTracker : public IWorldSceneMouseTracker
1255 {
1256 private:
1257 BitmapStack::BitmapAccessor accessor_;
1258 double centerX_;
1259 double centerY_;
1260 double originalAngle_;
1261 double clickAngle_;
1262 bool roundAngles_;
1263
1264 bool ComputeAngle(double& angle /* out */,
1265 double sceneX,
1266 double sceneY) const
1267 {
1268 Vector u;
1269 LinearAlgebra::AssignVector(u, sceneX - centerX_, sceneY - centerY_);
1270
1271 double nu = boost::numeric::ublas::norm_2(u);
1272
1273 if (!LinearAlgebra::IsCloseToZero(nu))
1274 {
1275 u /= nu;
1276 angle = atan2(u[1], u[0]);
1277 return true;
1278 }
1279 else
1280 {
1281 return false;
1282 }
1283 }
1284
1285
1286 public:
1287 RotateBitmapTracker(BitmapStack& stack,
1288 const ViewportGeometry& view,
1289 size_t bitmap,
1290 double x,
1291 double y,
1292 bool roundAngles) :
1293 accessor_(stack, bitmap),
1294 roundAngles_(roundAngles)
1295 {
1296 if (accessor_.IsValid())
1297 {
1298 accessor_.GetBitmap().GetCenter(centerX_, centerY_);
1299 originalAngle_ = accessor_.GetBitmap().GetAngle();
1300
1301 double sceneX, sceneY;
1302 view.MapDisplayToScene(sceneX, sceneY, x, y);
1303
1304 if (!ComputeAngle(clickAngle_, x, y))
1305 {
1306 accessor_.Invalidate();
1307 }
1308 }
1309 }
1310
1311 virtual bool HasRender() const
1312 {
1313 return false;
1314 }
1315
1316 virtual void Render(CairoContext& context,
1317 double zoom)
1318 {
1319 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1320 }
1321
1322 virtual void MouseUp()
1323 {
1324 }
1325
1326 virtual void MouseMove(int displayX,
1327 int displayY,
1328 double sceneX,
1329 double sceneY)
1330 {
1331 static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>();
1332
1333 double angle;
1334
1335 if (accessor_.IsValid() &&
1336 ComputeAngle(angle, sceneX, sceneY))
1337 {
1338 angle = angle - clickAngle_ + originalAngle_;
1339
1340 if (roundAngles_)
1341 {
1342 angle = round(angle / ROUND_ANGLE) * ROUND_ANGLE;
1343 }
1344
1345 accessor_.GetBitmap().SetAngle(angle);
1346 }
1347 }
1348 };
1349
1350
1351 class MoveBitmapTracker : public IWorldSceneMouseTracker
1352 {
1353 private:
1354 BitmapStack::BitmapAccessor accessor_;
1355 double clickX_;
1356 double clickY_;
1357 double panX_;
1358 double panY_;
1359 bool oneAxis_;
1360
1361 public:
1362 MoveBitmapTracker(BitmapStack& stack,
1363 size_t bitmap,
1364 double x,
1365 double y,
1366 bool oneAxis) :
1367 accessor_(stack, bitmap),
1368 clickX_(x),
1369 clickY_(y),
1370 oneAxis_(oneAxis)
1371 {
1372 if (accessor_.IsValid())
1373 {
1374 panX_ = accessor_.GetBitmap().GetPanX();
1375 panY_ = accessor_.GetBitmap().GetPanY();
1376 }
1377 }
1378
1379 virtual bool HasRender() const
1380 {
1381 return false;
1382 }
1383
1384 virtual void Render(CairoContext& context,
1385 double zoom)
1386 {
1387 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1388 }
1389
1390 virtual void MouseUp()
1391 {
1392 }
1393
1394 virtual void MouseMove(int displayX,
1395 int displayY,
1396 double sceneX,
1397 double sceneY)
1398 {
1399 if (accessor_.IsValid())
1400 {
1401 double dx = sceneX - clickX_;
1402 double dy = sceneY - clickY_;
1403
1404 if (oneAxis_)
1405 {
1406 if (fabs(dx) > fabs(dy))
1407 {
1408 accessor_.GetBitmap().SetPan(dx + panX_, panY_);
1409 }
1410 else
1411 {
1412 accessor_.GetBitmap().SetPan(panX_, dy + panY_);
1413 }
1414 }
1415 else
1416 {
1417 accessor_.GetBitmap().SetPan(dx + panX_, dy + panY_);
1418 }
1419 }
1420 }
1421 };
1422
1423
1424 class CropBitmapTracker : public IWorldSceneMouseTracker
1425 {
1426 private:
1427 BitmapStack::BitmapAccessor accessor_;
1428 BitmapStack::Corner corner_;
1429 unsigned int cropX_;
1430 unsigned int cropY_;
1431 unsigned int cropWidth_;
1432 unsigned int cropHeight_;
1433
1434 public:
1435 CropBitmapTracker(BitmapStack& stack,
1436 const ViewportGeometry& view,
1437 size_t bitmap,
1438 double x,
1439 double y,
1440 BitmapStack::Corner corner) :
1441 accessor_(stack, bitmap),
1442 corner_(corner)
1443 {
1444 if (accessor_.IsValid())
1445 {
1446 accessor_.GetBitmap().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);
1447 }
1448 }
1449
1450 virtual bool HasRender() const
1451 {
1452 return false;
1453 }
1454
1455 virtual void Render(CairoContext& context,
1456 double zoom)
1457 {
1458 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1459 }
1460
1461 virtual void MouseUp()
1462 {
1463 }
1464
1465 virtual void MouseMove(int displayX,
1466 int displayY,
1467 double sceneX,
1468 double sceneY)
1469 {
1470 if (accessor_.IsValid())
1471 {
1472 unsigned int x, y;
1473
1474 BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
1475 if (bitmap.GetPixel(x, y, sceneX, sceneY))
1476 {
1477 unsigned int targetX, targetWidth;
1478
1479 if (corner_ == BitmapStack::Corner_TopLeft ||
1480 corner_ == BitmapStack::Corner_BottomLeft)
1481 {
1482 targetX = std::min(x, cropX_ + cropWidth_);
1483 targetWidth = cropX_ + cropWidth_ - targetX;
1484 }
1485 else
1486 {
1487 targetX = cropX_;
1488 targetWidth = std::max(x, cropX_) - cropX_;
1489 }
1490
1491 unsigned int targetY, targetHeight;
1492
1493 if (corner_ == BitmapStack::Corner_TopLeft ||
1494 corner_ == BitmapStack::Corner_TopRight)
1495 {
1496 targetY = std::min(y, cropY_ + cropHeight_);
1497 targetHeight = cropY_ + cropHeight_ - targetY;
1498 }
1499 else
1500 {
1501 targetY = cropY_;
1502 targetHeight = std::max(y, cropY_) - cropY_;
1503 }
1504
1505 bitmap.SetCrop(targetX, targetY, targetWidth, targetHeight);
1506 }
1507 }
1508 }
1509 };
1510
1511
1512 class ResizeBitmapTracker : public IWorldSceneMouseTracker
1513 {
1514 private:
1515 BitmapStack::BitmapAccessor accessor_;
1516 bool roundScaling_;
1517 double originalSpacingX_;
1518 double originalSpacingY_;
1519 BitmapStack::Corner oppositeCorner_;
1520 double oppositeX_;
1521 double oppositeY_;
1522 double baseScaling_;
1523
1524 static double ComputeDistance(double x1,
1525 double y1,
1526 double x2,
1527 double y2)
1528 {
1529 double dx = x1 - x2;
1530 double dy = y1 - y2;
1531 return sqrt(dx * dx + dy * dy);
1532 }
1533
1534 public:
1535 ResizeBitmapTracker(BitmapStack& stack,
1536 size_t bitmap,
1537 double x,
1538 double y,
1539 BitmapStack::Corner corner,
1540 bool roundScaling) :
1541 accessor_(stack, bitmap),
1542 roundScaling_(roundScaling)
1543 {
1544 if (accessor_.IsValid() &&
1545 accessor_.GetBitmap().IsResizeable())
1546 {
1547 originalSpacingX_ = accessor_.GetBitmap().GetPixelSpacingX();
1548 originalSpacingY_ = accessor_.GetBitmap().GetPixelSpacingY();
1549
1550 switch (corner)
1551 {
1552 case BitmapStack::Corner_TopLeft:
1553 oppositeCorner_ = BitmapStack::Corner_BottomRight;
1554 break;
1555
1556 case BitmapStack::Corner_TopRight:
1557 oppositeCorner_ = BitmapStack::Corner_BottomLeft;
1558 break;
1559
1560 case BitmapStack::Corner_BottomLeft:
1561 oppositeCorner_ = BitmapStack::Corner_TopRight;
1562 break;
1563
1564 case BitmapStack::Corner_BottomRight:
1565 oppositeCorner_ = BitmapStack::Corner_TopLeft;
1566 break;
1567
1568 default:
1569 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1570 }
1571
1572 accessor_.GetBitmap().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
1573
1574 double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
1575 if (d >= std::numeric_limits<float>::epsilon())
1576 {
1577 baseScaling_ = 1.0 / d;
1578 }
1579 else
1580 {
1581 // Avoid division by zero in extreme cases
1582 accessor_.Invalidate();
1583 }
1584 }
1585 }
1586
1587 virtual bool HasRender() const
1588 {
1589 return false;
1590 }
1591
1592 virtual void Render(CairoContext& context,
1593 double zoom)
1594 {
1595 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1596 }
1597
1598 virtual void MouseUp()
1599 {
1600 }
1601
1602 virtual void MouseMove(int displayX,
1603 int displayY,
1604 double sceneX,
1605 double sceneY)
1606 {
1607 static const double ROUND_SCALING = 0.1;
1608
1609 if (accessor_.IsValid() &&
1610 accessor_.GetBitmap().IsResizeable())
1611 {
1612 double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
1613
1614 if (roundScaling_)
1615 {
1616 scaling = round(scaling / ROUND_SCALING) * ROUND_SCALING;
1617 }
1618
1619 BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
1620 bitmap.SetPixelSpacing(scaling * originalSpacingX_,
1621 scaling * originalSpacingY_);
1622
1623 // Keep the opposite corner at a fixed location
1624 double ox, oy;
1625 bitmap.GetCorner(ox, oy, oppositeCorner_);
1626 bitmap.SetPan(bitmap.GetPanX() + oppositeX_ - ox,
1627 bitmap.GetPanY() + oppositeY_ - oy);
1628 }
1629 }
1630 };
1631
1632 1862
1633 public: 1863 public:
1634 BitmapStackInteractor(BitmapStack& stack) : 1864 BitmapStackInteractor(BitmapStack& stack) :
1635 stack_(stack), 1865 stack_(stack),
1636 tool_(Tool_Move) 1866 tool_(Tool_Move)
1701 bitmap == selected) 1931 bitmap == selected)
1702 { 1932 {
1703 switch (tool_) 1933 switch (tool_)
1704 { 1934 {
1705 case Tool_Move: 1935 case Tool_Move:
1706 return new MoveBitmapTracker(stack_, bitmap, x, y, 1936 return new MoveBitmapTracker(undoRedoStack_, stack_, bitmap, x, y,
1707 (modifiers & KeyboardModifiers_Shift)); 1937 (modifiers & KeyboardModifiers_Shift));
1708 1938
1709 case Tool_Rotate: 1939 case Tool_Rotate:
1710 return new RotateBitmapTracker(stack_, view, bitmap, x, y, 1940 return new RotateBitmapTracker(undoRedoStack_, stack_, view, bitmap, x, y,
1711 (modifiers & KeyboardModifiers_Shift)); 1941 (modifiers & KeyboardModifiers_Shift));
1712 1942
1713 default: 1943 default:
1714 break; 1944 break;
1715 } 1945 }
1795 tool_ = Tool_Rotate; 2025 tool_ = Tool_Rotate;
1796 break; 2026 break;
1797 2027
1798 case 's': 2028 case 's':
1799 tool_ = Tool_Resize; 2029 tool_ = Tool_Resize;
2030 break;
2031
2032 case 'z':
2033 if (modifiers & KeyboardModifiers_Control)
2034 {
2035 undoRedoStack_.Undo();
2036 widget.NotifyContentChanged();
2037 }
2038 break;
2039
2040 case 'y':
2041 if (modifiers & KeyboardModifiers_Control)
2042 {
2043 undoRedoStack_.Redo();
2044 widget.NotifyContentChanged();
2045 }
1800 break; 2046 break;
1801 2047
1802 default: 2048 default:
1803 break; 2049 break;
1804 } 2050 }
2105 2351
2106 Orthanc::FontRegistry fonts; 2352 Orthanc::FontRegistry fonts;
2107 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); 2353 fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
2108 2354
2109 stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_)); 2355 stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_));
2110 stack_->LoadFrame(instance, frame, false); 2356 //stack_->LoadFrame(instance, frame, false);
2111 stack_->LoadFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", frame, false); 2357 //stack_->LoadFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", frame, false);
2112 stack_->LoadText(fonts.GetFont(0), "Hello\nworld\nBonjour, Alain").SetResizeable(true); 2358
2113 stack_->LoadTestBlock(100, 50).SetResizeable(true); 2359 {
2360 BitmapStack::Bitmap& bitmap = stack_->LoadText(fonts.GetFont(0), "Hello\nworld\nBonjour, Alain");
2361 dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetForegroundValue(256);
2362 dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetResizeable(true);
2363 }
2364
2365 {
2366 BitmapStack::Bitmap& bitmap = stack_->LoadTestBlock(100, 50);
2367 dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetForegroundValue(256);
2368 dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetResizeable(true);
2369 }
2370
2114 2371
2115 mainWidget_ = new BitmapStackWidget(IObserver::broker_, *stack_, "main-widget"); 2372 mainWidget_ = new BitmapStackWidget(IObserver::broker_, *stack_, "main-widget");
2116 mainWidget_->SetTransmitMouseOver(true); 2373 mainWidget_->SetTransmitMouseOver(true);
2117 2374
2118 //stack_->SetWindowing(128, 256); 2375 //stack_->SetWindowing(128, 256);