# HG changeset patch # User Sebastien Jodogne # Date 1540210839 -7200 # Node ID 712acc87fa2eb1bdb3e4e0a90338cf15b2a04ff3 # Parent 4297d6c5eef986867ee077b30656b3e2b763b134 text layer diff -r 4297d6c5eef9 -r 712acc87fa2e Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Mon Oct 22 09:44:52 2018 +0200 +++ b/Applications/Samples/SingleFrameEditorApplication.h Mon Oct 22 14:20:39 2018 +0200 @@ -28,8 +28,10 @@ #include "../../Framework/Layers/OrthancFrameLayerSource.h" #include +#include #include #include +#include //TODO #include #include #include @@ -51,23 +53,20 @@ class Bitmap : public boost::noncopyable { private: - bool visible_; - std::auto_ptr source_; - std::auto_ptr converted_; // Float32 or RGB24 - std::auto_ptr alpha_; // Grayscale8 (if any) - std::auto_ptr converter_; - unsigned int width_; - unsigned int height_; - bool hasCrop_; - unsigned int cropX_; - unsigned int cropY_; - unsigned int cropWidth_; - unsigned int cropHeight_; - Matrix transform_; - double pixelSpacingX_; - double pixelSpacingY_; - double panX_; - double panY_; + bool visible_; + bool hasSize_; + unsigned int width_; + unsigned int height_; + bool hasCrop_; + unsigned int cropX_; + unsigned int cropY_; + unsigned int cropWidth_; + unsigned int cropHeight_; + Matrix transform_; + double pixelSpacingX_; + double pixelSpacingY_; + double panX_; + double panY_; void UpdateTransform() @@ -92,22 +91,6 @@ } - void ApplyConverter() - { - if (source_.get() != NULL && - converter_.get() != NULL) - { - if (width_ != source_->GetWidth() || - height_ != source_->GetHeight()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - printf("CONVERTED! %dx%d\n", width_, height_); - converted_.reset(converter_->ConvertFrame(*source_)); - } - } - void AddToExtent(Extent2D& extent, double x, double y) const @@ -131,6 +114,7 @@ public: Bitmap() : visible_(true), + hasSize_(false), width_(0), height_(0), hasCrop_(false), @@ -142,6 +126,10 @@ UpdateTransform(); } + virtual ~Bitmap() + { + } + void ResetCrop() { hasCrop_ = false; @@ -191,72 +179,33 @@ } - static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) - { - return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); - } - - void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) + void SetSize(unsigned int width, + unsigned int height) { - converter_.reset(new DicomFrameConverter); - converter_->ReadParameters(dataset); - ApplyConverter(); - - transform_ = LinearAlgebra::IdentityMatrix(3); - - std::string tmp; - Vector pixelSpacing; - - if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && - LinearAlgebra::ParseVector(pixelSpacing, tmp) && - pixelSpacing.size() == 2) + if (hasSize_ && + (width != width_ || + height != height_)) { - pixelSpacingX_ = pixelSpacing[0]; - pixelSpacingY_ = pixelSpacing[1]; - } - - static unsigned int c = 0; - if (c == 0) - { - panX_ = 400; - c ++; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); } - OrthancPlugins::DicomDatasetReader reader(dataset); - - if (!reader.GetUnsignedIntegerValue(width_, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || - !reader.GetUnsignedIntegerValue(height_, ConvertTag(Orthanc::DICOM_TAG_ROWS))) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - UpdateTransform(); - } - - - void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership - { - source_.reset(image); - ApplyConverter(); + hasSize_ = true; + width_ = width; + height_ = height; } - - bool GetDefaultWindowing(float& center, - float& width) const + + void CheckSize(unsigned int width, + unsigned int height) { - if (converter_.get() != NULL && - converter_->HasDefaultWindow()) + if (hasSize_ && + (width != width_ || + height != height_)) { - center = static_cast(converter_->GetDefaultWindowCenter()); - width = static_cast(converter_->GetDefaultWindowWidth()); - return true; - } - else - { - return false; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); } } - + Extent2D GetExtent() const { @@ -270,8 +219,6 @@ double dwidth = static_cast(width); double dheight = static_cast(height); - LinearAlgebra::Print(transform_); - AddToExtent(extent, dx, dy); AddToExtent(extent, dx + dwidth, dy); AddToExtent(extent, dx, dy + dheight); @@ -281,16 +228,8 @@ } - void Render(Orthanc::ImageAccessor& buffer, - const ViewportGeometry& view) const - { - if (converted_.get() != NULL) - { - Matrix m = LinearAlgebra::Product(view.GetMatrix(), transform_); - //ApplyProjectiveTransform(buffer, *converted_, m, ImageInterpolation_Bilinear, false); - ApplyProjectiveTransform(buffer, *converted_, m, ImageInterpolation_Nearest, false); - } - } + virtual void Render(Orthanc::ImageAccessor& buffer, + const ViewportGeometry& view) const = 0; bool Contains(double x, @@ -328,6 +267,15 @@ } + void SetPixelSpacing(double x, + double y) + { + pixelSpacingX_ = x; + pixelSpacingY_ = y; + UpdateTransform(); + } + + double GetPanX() const { return panX_; @@ -338,9 +286,227 @@ { return panY_; } + + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + return false; + } + + + const Matrix& GetTransform() const + { + return transform_; + } }; + + class DicomBitmap : public Bitmap + { + private: + std::auto_ptr source_; // Content of PixelData + std::auto_ptr converter_; + std::auto_ptr converted_; // Float32 or RGB24 + + + void ApplyConverter() + { + if (source_.get() != NULL && + converter_.get() != NULL) + { + converted_.reset(converter_->ConvertFrame(*source_)); + } + } + + public: + static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) + { + return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); + } + + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) + { + converter_.reset(new DicomFrameConverter); + converter_->ReadParameters(dataset); + ApplyConverter(); + + std::string tmp; + Vector pixelSpacing; + + if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && + LinearAlgebra::ParseVector(pixelSpacing, tmp) && + pixelSpacing.size() == 2) + { + SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); + } + + static unsigned int c = 0; + if (c == 0) + { + SetPan(400, 0); + c ++; + } + + OrthancPlugins::DicomDatasetReader reader(dataset); + + unsigned int width, height; + if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || + !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SetSize(width, height); + } + } + + + void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership + { + std::auto_ptr raii(image); + + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + SetSize(image->GetWidth(), image->GetHeight()); + + source_ = raii; + ApplyConverter(); + } + + + virtual void Render(Orthanc::ImageAccessor& buffer, + const ViewportGeometry& view) const + { + if (converted_.get() != NULL) + { + Matrix m = LinearAlgebra::Product(view.GetMatrix(), GetTransform()); + //ApplyProjectiveTransform(buffer, *converted_, m, ImageInterpolation_Bilinear, false); + ApplyProjectiveTransform(buffer, *converted_, m, ImageInterpolation_Nearest, false); + } + } + + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + if (converter_.get() != NULL && + converter_->HasDefaultWindow()) + { + center = static_cast(converter_->GetDefaultWindowCenter()); + width = static_cast(converter_->GetDefaultWindowWidth()); + return true; + } + else + { + return false; + } + } + }; + + + + + class AlphaBitmap : public Bitmap + { + private: + const BitmapStack& stack_; + std::auto_ptr alpha_; // Grayscale8 + bool useWindowing_; + float foreground_; + + public: + AlphaBitmap(const BitmapStack& stack) : + stack_(stack), + useWindowing_(true), + foreground_(0) + { + } + + + void SetForegroundValue(float foreground) + { + useWindowing_ = false; + foreground_ = foreground; + } + + + void SetAlpha(Orthanc::ImageAccessor* image) + { + std::auto_ptr raii(image); + + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + SetSize(image->GetWidth(), image->GetHeight()); + alpha_ = raii; + } + + + void LoadText(const Orthanc::Font& font, + const std::string& utf8) + { + SetAlpha(font.RenderAlpha(utf8)); + } + + + virtual void Render(Orthanc::ImageAccessor& buffer, + const ViewportGeometry& view) const + { + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + Matrix m = LinearAlgebra::Product(view.GetMatrix(), GetTransform()); + + Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); + ApplyProjectiveTransform(tmp, *alpha_, m, ImageInterpolation_Nearest, true /* clear */); + + // Blit + const unsigned int width = buffer.GetWidth(); + const unsigned int height = buffer.GetHeight(); + + float value = foreground_; + + if (useWindowing_) + { + float center, width; + if (stack_.GetWindowing(center, width)) + { + value = center + width / 2.0f; + } + } + + for (unsigned int y = 0; y < height; y++) + { + float *q = reinterpret_cast(buffer.GetRow(y)); + const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q++) + { + float a = static_cast(*p) / 255.0f; + + *q = (a * value + (1.0f - a) * (*q)); + } + } + } + }; + + + typedef std::map Bitmaps; OrthancApiClient& orthanc_; @@ -374,29 +540,45 @@ } - void GetWindowing(float& center, - float& width) + bool GetWindowing(float& center, + float& width) const { if (hasWindowing_) { center = windowingCenter_; width = windowingWidth_; + return true; } else { - center = 128; - width = 256; + return false; } } + size_t LoadText(const Orthanc::Font& font, + const std::string& utf8, + double x, + double y) + { + std::auto_ptr alpha(new AlphaBitmap(*this)); + alpha->LoadText(font, utf8); + + size_t bitmap = countBitmaps_++; + + bitmaps_[bitmap] = alpha.release(); + + return bitmap; + } + + size_t LoadFrame(const std::string& instance, unsigned int frame, bool httpCompression) { size_t bitmap = countBitmaps_++; - bitmaps_[bitmap] = new Bitmap; + bitmaps_[bitmap] = new DicomBitmap; { @@ -441,7 +623,7 @@ assert(bitmap->second != NULL); OrthancPlugins::FullOrthancDataset dicom(message.Answer, message.AnswerSize); - bitmap->second->SetDicomTags(dicom); + dynamic_cast(bitmap->second)->SetDicomTags(dicom); float c, w; if (!hasWindowing_ && @@ -477,7 +659,7 @@ std::auto_ptr reader(new Orthanc::PamReader); reader->ReadFromMemory(content); - bitmap->second->SetSourceImage(reader.release()); + dynamic_cast(bitmap->second)->SetSourceImage(reader.release()); EmitMessage(ContentChangedMessage(*this)); } @@ -574,28 +756,27 @@ class MoveBitmapTracker : public IWorldSceneMouseTracker { private: - WorldSceneWidget& widget_; BitmapStack& stack_; size_t bitmap_; double clickX_; double clickY_; + double panX_; + double panY_; + bool oneAxis_; public: - MoveBitmapTracker(WorldSceneWidget& widget, - BitmapStack& stack, + MoveBitmapTracker(BitmapStack& stack, size_t bitmap, double x, - double y) : - widget_(widget), + double y, + bool oneAxis) : stack_(stack), bitmap_(bitmap), clickX_(x), - clickY_(y) + clickY_(y), + oneAxis_(oneAxis) { - double panX, panY; - stack.GetPan(panX, panY, bitmap_); - clickX_ -= panX; - clickY_ -= panY; + stack.GetPan(panX_, panY_, bitmap_); } virtual bool HasRender() const @@ -618,7 +799,24 @@ double sceneX, double sceneY) { - stack_.SetPan(bitmap_, sceneX - clickX_, sceneY - clickY_); + double dx = sceneX - clickX_; + double dy = sceneY - clickY_; + + if (oneAxis_) + { + if (fabs(dx) > fabs(dy)) + { + stack_.SetPan(bitmap_, dx + panX_, panY_); + } + else + { + stack_.SetPan(bitmap_, panX_, dy + panY_); + } + } + else + { + stack_.SetPan(bitmap_, dx + panX_, dy + panY_); + } } }; @@ -643,7 +841,8 @@ if (stack_.LookupBitmap(bitmap, x, y)) { printf("CLICK on bitmap %d\n", bitmap); - return new MoveBitmapTracker(widget, stack_, bitmap, x, y); + return new MoveBitmapTracker(stack_, bitmap, x, y, + (modifiers & KeyboardModifiers_Shift)); } else { @@ -745,7 +944,11 @@ // As in GrayscaleFrameRenderer => TODO MERGE float windowCenter, windowWidth; - stack_.GetWindowing(windowCenter, windowWidth); + if (!stack_.GetWindowing(windowCenter, windowWidth)) + { + windowCenter = 128; + windowWidth = 256; + } float x0 = windowCenter - windowWidth / 2.0f; float x1 = windowCenter + windowWidth / 2.0f; @@ -965,9 +1168,13 @@ orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); + Orthanc::FontRegistry fonts; + fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); + stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_)); stack_->LoadFrame(instance, frame, false); stack_->LoadFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", frame, false); + stack_->LoadText(fonts.GetFont(0), "Hello\nworld\nBonjour, Alain", 0, -50); mainWidget_ = new BitmapStackWidget(IObserver::broker_, *stack_, "main-widget"); mainWidget_->SetTransmitMouseOver(true); diff -r 4297d6c5eef9 -r 712acc87fa2e Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Oct 22 09:44:52 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Mon Oct 22 14:20:39 2018 +0200 @@ -131,6 +131,8 @@ # "OrthancStoneParameters.cmake" ${DCMTK_DICTIONARIES} + FONT_UBUNTU_MONO_BOLD_16 ${ORTHANC_ROOT}/Resources/Fonts/UbuntuMonoBold-16.json + # Resources specific to the Stone of Orthanc COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut COLORMAP_JET ${ORTHANC_STONE_ROOT}/Resources/Colormaps/jet.lut