# HG changeset patch # User Benjamin Golinvaux # Date 1586957010 -7200 # Node ID 4287eaabe4909558c1dbff85b348108b3b20745d # Parent 0dc5b8a4b3a01578091dbfe5b7dc2e0ba48512a0 Sdl simple viewer application diff -r 0dc5b8a4b3a0 -r 4287eaabe490 Samples/Sdl/SimpleViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/CMakeLists.txt Wed Apr 15 15:23:30 2020 +0200 @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(SdlViewer) + +set(ALLOW_DOWNLOADS ON) + +set(STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../) + +set(STATIC_BUILD ON) +set(MSVC_MULTIPLE_PROCESSES ON) + +set(ORTHANC_FRAMEWORK_SOURCE "path") +set(ORTHANC_FRAMEWORK_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../orthanc) +include(${STONE_ROOT}/Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_QT OFF) +SET(ENABLE_SDL ON) +SET(ENABLE_DCMTK ON) # <== +SET(ENABLE_OPENGL ON) # <== +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) + +include(${STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +include_directories(${STONE_ROOT}) + +add_definitions( + -DORTHANC_ENABLE_LOGGING=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + -DORTHANC_ENABLE_PUGIXML=0 + -DORTHANC_DEFAULT_DICOM_ENCODING=Encoding_Latin1 + ) + +add_executable(OrthancStone + SdlSimpleViewerApplication.h + SimpleViewer.cpp + ${ORTHANC_STONE_SOURCES} + ) + +target_link_libraries(OrthancStone ${DCMTK_LIBRARIES}) diff -r 0dc5b8a4b3a0 -r 4287eaabe490 Samples/Sdl/SimpleViewer/SdlSimpleViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/SdlSimpleViewerApplication.h Wed Apr 15 15:23:30 2020 +0200 @@ -0,0 +1,146 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + + +using OrthancStone::ILoadersContext; +using OrthancStone::ObserverBase; +using OrthancStone::IViewport; +using OrthancStone::DicomResourcesLoader; +using OrthancStone::SeriesFramesLoader; +using OrthancStone::TextureBaseSceneLayer; +using OrthancStone::DicomSource; +using OrthancStone::SeriesThumbnailsLoader; +using OrthancStone::LoadedDicomResources; +using OrthancStone::SeriesThumbnailType; +using OrthancStone::OracleScheduler; +using OrthancStone::OrthancRestApiCommand; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; + + +class SdlSimpleViewerApplication : public ObserverBase +{ + +public: + static boost::shared_ptr Create(ILoadersContext& context, boost::shared_ptr viewport) + { + boost::shared_ptr application(new SdlSimpleViewerApplication(context, viewport)); + + { + std::auto_ptr lock(context.Lock()); + DicomResourcesLoader::Factory f; + application->dicomLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + +#if 0 + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } +#endif + + void FitContent() + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + +private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + SdlSimpleViewerApplication(ILoadersContext& context, + boost::shared_ptr viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::auto_ptr layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::auto_ptr lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast( + f.Create(*lock)); + + Register( + *framesLoader_, &SdlSimpleViewerApplication::Handle); + + assert(message.HasUserPayload()); + + const Orthanc::SingleValueObject& payload = + dynamic_cast&>( + message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + +}; + diff -r 0dc5b8a4b3a0 -r 4287eaabe490 Samples/Sdl/SimpleViewer/SimpleViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/SimpleViewer.cpp Wed Apr 15 15:23:30 2020 +0200 @@ -0,0 +1,292 @@ + +#include "SdlSimpleViewerApplication.h" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace OrthancStone +{ + static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, + const int scancodeCount) + { + int result = KeyboardModifiers_None; + + if (keyboardState != NULL) + { + if (SDL_SCANCODE_LSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_LSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_RSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_RSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_RCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_RCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + result |= KeyboardModifiers_Alt; + } + + if (SDL_SCANCODE_RALT < scancodeCount && + keyboardState[SDL_SCANCODE_RALT]) + { + result |= KeyboardModifiers_Alt; + } + } + + return static_cast(result); + } + + + static void GetPointerEvent(PointerEvent& p, + const ICompositor& compositor, + SDL_Event event, + const uint8_t* keyboardState, + const int scancodeCount) + { + KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + switch (event.button.button) + { + case SDL_BUTTON_LEFT: + p.SetMouseButton(OrthancStone::MouseButton_Left); + break; + + case SDL_BUTTON_RIGHT: + p.SetMouseButton(OrthancStone::MouseButton_Right); + break; + + case SDL_BUTTON_MIDDLE: + p.SetMouseButton(OrthancStone::MouseButton_Middle); + break; + + default: + p.SetMouseButton(OrthancStone::MouseButton_None); + break; + } + + p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + p.SetAltModifier(modifiers & KeyboardModifiers_Alt); + p.SetControlModifier(modifiers & KeyboardModifiers_Control); + p.SetShiftModifier(modifiers & KeyboardModifiers_Shift); + } + +} + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + try + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + + { + +#if 1 + boost::shared_ptr viewport = + OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); +#else + boost::shared_ptr viewport = + OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); +#endif + + OrthancStone::GenericLoadersContext context(1, 4, 1); + + context.StartOracle(); + + { + + boost::shared_ptr application( + SdlSimpleViewerApplication::Create(context, viewport)); + + OrthancStone::DicomSource source; + + // Default and command-line parameters + const char* instanceId = "285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"; + unsigned int frameIndex = 0; + + if (argc == 1) + { + LOG(ERROR) << "No instanceId supplied. The default of " << instanceId << " will be used. " + << "Please supply the Orthanc instance ID of the frame you wish to display then, optionally, " + << "the zero-based index of the frame (for multi-frame instances)"; + // TODO: frame number as second argument... + } + + if (argc >= 2) + instanceId = argv[1]; + + if (argc >= 3) + frameIndex = atoi(argv[1]); + + if (argc > 3) + { + LOG(ERROR) << "Extra arguments ignored!"; + } + + + application->LoadOrthancFrame(source, instanceId, frameIndex); + + OrthancStone::DefaultViewportInteractor interactor; + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + bool paint = false; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (viewport->IsRefreshEvent(event)) + { + paint = true; + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + viewport->UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_SHOWN || + event.window.event == SDL_WINDOWEVENT_EXPOSED)) + { + paint = true; + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport->ToggleMaximize(); + break; + + case SDLK_s: + application->FitContent(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN || + event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEBUTTONUP) + { + std::auto_ptr lock(viewport->Lock()); + if (lock->HasCompositor()) + { + OrthancStone::PointerEvent p; + OrthancStone::GetPointerEvent(p, lock->GetCompositor(), + event, keyboardState, scancodeCount); + + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + lock->GetController().HandleMousePress(interactor, p, + lock->GetCompositor().GetCanvasWidth(), + lock->GetCompositor().GetCanvasHeight()); + lock->Invalidate(); + break; + + case SDL_MOUSEMOTION: + if (lock->GetController().HandleMouseMove(p)) + { + lock->Invalidate(); + } + break; + + case SDL_MOUSEBUTTONUP: + lock->GetController().HandleMouseRelease(p); + lock->Invalidate(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } + + if (paint) + { + viewport->Paint(); + } + + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + + context.StopOracle(); + } + } + + OrthancStone::StoneFinalize(); + return 0; + } + catch (Orthanc::OrthancException & e) + { + auto test = e.What(); + fprintf(stdout, test); + LOG(ERROR) << "OrthancException: " << e.What(); + return -1; + } + catch (OrthancStone::StoneException & e) + { + LOG(ERROR) << "StoneException: " << e.What(); + return -1; + } + catch (std::runtime_error & e) + { + LOG(ERROR) << "Runtime error: " << e.what(); + return -1; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return -1; + } +}