comparison OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Samples/Sdl/RtViewer/RtViewerSdl.cpp@169adf9090a6
children 307a805d0587
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21 #include "../../Common/RtViewerApp.h"
22 #include "../../Common/RtViewerView.h"
23 #include "../SdlHelpers.h"
24
25 #include <EmbeddedResources.h>
26
27 // Stone of Orthanc includes
28 #include "../../../Sources/Loaders/GenericLoadersContext.h"
29 #include "../../../Sources/OpenGL/OpenGLIncludes.h"
30 #include "../../../Sources/OpenGL/SdlOpenGLContext.h"
31 #include "../../../Sources/StoneException.h"
32 #include "../../../Sources/StoneInitialization.h"
33
34 // Orthanc (a.o. for screenshot capture)
35 #include <Compatibility.h> // For std::unique_ptr<>
36 #include <Images/Image.h>
37 #include <Images/ImageProcessing.h>
38 #include <Images/PngWriter.h>
39
40
41 #include <boost/program_options.hpp>
42 #include <boost/shared_ptr.hpp>
43
44 // #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions
45
46 #include <SDL.h>
47
48 #include <string>
49
50
51 #if !defined(__APPLE__)
52 /**
53 * OpenGL: "OS X does not seem to support debug output functionality
54 * (as gathered online)."
55 * https://learnopengl.com/In-Practice/Debugging
56 **/
57 static void GLAPIENTRY
58 OpenGLMessageCallback(GLenum source,
59 GLenum type,
60 GLuint id,
61 GLenum severity,
62 GLsizei length,
63 const GLchar* message,
64 const void* userParam)
65 {
66 if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
67 {
68 fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
69 (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
70 type, severity, message);
71 }
72 }
73 #endif
74
75 namespace OrthancStone
76 {
77 void RtViewerView::EnableGLDebugOutput()
78 {
79 #if !defined(__APPLE__)
80 glEnable(GL_DEBUG_OUTPUT);
81 glDebugMessageCallback(OpenGLMessageCallback, 0);
82 #endif
83 }
84
85 boost::shared_ptr<IViewport> RtViewerView::CreateViewport(const std::string& canvasId)
86 {
87 // False means we do NOT let Windows treat this as a legacy application that needs to be scaled
88 return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false);
89 }
90
91 void RtViewerApp::ProcessOptions(int argc, char* argv[])
92 {
93 namespace po = boost::program_options;
94 po::options_description desc("Usage:");
95
96 desc.add_options()
97 ("loglevel", po::value<std::string>()->default_value("WARNING"),
98 "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)")
99
100 ("orthanc", po::value<std::string>()->default_value("http://localhost:8042"),
101 "Base URL of the Orthanc instance")
102
103 ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"),
104 "Orthanc ID of the CT series to load")
105
106 ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"),
107 "Orthanc ID of the RTDOSE instance to load")
108
109 ("rtstruct", po::value<std::string>()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"),
110 "Orthanc ID of the RTSTRUCT instance to load")
111 ;
112
113 po::variables_map vm;
114 try
115 {
116 po::store(po::parse_command_line(argc, argv, desc), vm);
117 po::notify(vm);
118 }
119 catch (std::exception& e)
120 {
121 std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl;
122 }
123
124 for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
125 {
126 std::string key = it->first;
127 const po::variable_value& value = it->second;
128 const std::string& strValue = value.as<std::string>();
129 SetArgument(key, strValue);
130 }
131 }
132
133 void RtViewerApp::RunSdl(int argc, char* argv[])
134 {
135 ProcessOptions(argc, argv);
136
137 /**
138 Create the shared loaders context
139 */
140 loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
141
142 // we are in SDL --> downcast to concrete type
143 boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);
144
145 /**
146 Url of the Orthanc instance
147 Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In
148 wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative
149 URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders
150 plugin that serves the main web application from an URL like "http://localhost:8042/rtviewer" (with ".." leading
151 to the main Orthanc root URL)
152 */
153 std::string orthancUrl = arguments_["orthanc"];
154
155 {
156 Orthanc::WebServiceParameters p;
157 if (HasArgument("orthanc"))
158 {
159 p.SetUrl(orthancUrl);
160 }
161 if (HasArgument("user"))
162 {
163 ORTHANC_ASSERT(HasArgument("password"));
164 p.SetCredentials(GetArgument("user"), GetArgument("password"));
165 }
166 else
167 {
168 ORTHANC_ASSERT(!HasArgument("password"));
169 }
170 loadersContext->SetOrthancParameters(p);
171 }
172
173 loadersContext->StartOracle();
174
175 CreateLoaders();
176
177 /**
178 Create viewports
179 */
180 CreateView("RtViewer Axial", VolumeProjection_Axial);
181 CreateView("RtViewer Coronal", VolumeProjection_Coronal);
182 CreateView("RtViewer Sagittal", VolumeProjection_Sagittal);
183
184 for (size_t i = 0; i < views_.size(); ++i)
185 {
186 views_[i]->PrepareViewport();
187 views_[i]->EnableGLDebugOutput();
188 }
189
190 DefaultViewportInteractor interactor;
191
192 /**
193 It is very important that the Oracle (responsible for network I/O) be started before creating and firing the
194 loaders, for any command scheduled by the loader before the oracle is started will be lost.
195 */
196 StartLoaders();
197
198
199 SdlRunLoop(views_, interactor);
200 loadersContext->StopOracle();
201 }
202
203 void RtViewerView::TakeScreenshot(const std::string& target,
204 unsigned int canvasWidth,
205 unsigned int canvasHeight)
206 {
207 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
208 ViewportController& controller = lock->GetController();
209 Scene2D& scene = controller.GetScene();
210
211 std::string ttf;
212 Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT);
213
214 CairoCompositor compositor(canvasWidth, canvasHeight);
215 compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1);
216 compositor.Refresh(scene);
217
218 Orthanc::ImageAccessor canvas;
219 compositor.GetCanvas().GetReadOnlyAccessor(canvas);
220
221 Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
222 Orthanc::ImageProcessing::Convert(png, canvas);
223
224 Orthanc::PngWriter writer;
225 writer.WriteToFile(target, png);
226 }
227
228 static boost::shared_ptr<OrthancStone::RtViewerView> GetViewFromWindowId(
229 const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
230 Uint32 windowID)
231 {
232 using namespace OrthancStone;
233 for (size_t i = 0; i < views.size(); ++i)
234 {
235 boost::shared_ptr<OrthancStone::RtViewerView> view = views[i];
236 boost::shared_ptr<IViewport> viewport = view->GetViewport();
237 boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
238 Uint32 curWindowID = sdlViewport->GetSdlWindowId();
239 if (windowID == curWindowID)
240 return view;
241 }
242 return boost::shared_ptr<OrthancStone::RtViewerView>();
243 }
244
245 void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
246 OrthancStone::IViewportInteractor& interactor)
247 {
248 using namespace OrthancStone;
249
250 // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views
251 std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports;
252 for (size_t i = 0; i < views.size(); ++i)
253 {
254 boost::shared_ptr<RtViewerView> view = views[i];
255 boost::shared_ptr<IViewport> viewport = view->GetViewport();
256 boost::shared_ptr<SdlViewport> sdlViewport =
257 boost::dynamic_pointer_cast<SdlViewport>(viewport);
258 viewports.push_back(sdlViewport);
259 }
260
261 {
262 int scancodeCount = 0;
263 const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
264
265 bool stop = false;
266 while (!stop)
267 {
268 std::vector<SDL_Event> sdlEvents;
269 std::map<Uint32,SDL_Event> userEventsMap;
270 SDL_Event sdlEvent;
271
272 // FIRST: collect all pending events
273 while (SDL_PollEvent(&sdlEvent) != 0)
274 {
275 if ( (sdlEvent.type >= SDL_USEREVENT) &&
276 (sdlEvent.type < SDL_LASTEVENT) )
277 {
278 // we don't want to have multiple refresh events ,
279 // and since every refresh event is a user event with a special type,
280 // we use a map
281 userEventsMap[sdlEvent.type] = sdlEvent;
282 }
283 else
284 {
285 sdlEvents.push_back(sdlEvent);
286 }
287 }
288
289 // SECOND: add all user events to sdlEvents
290 for (std::map<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it)
291 sdlEvents.push_back(it->second);
292
293 // now process the events
294 for (std::vector<SDL_Event>::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it)
295 {
296 const SDL_Event& sdlEvent = *it;
297
298 if (sdlEvent.type == SDL_QUIT)
299 {
300 stop = true;
301 break;
302 }
303 else if (sdlEvent.type == SDL_WINDOWEVENT &&
304 (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED ||
305 sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
306 {
307 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
308 views, sdlEvent.window.windowID);
309
310 boost::shared_ptr<SdlViewport> sdlViewport =
311 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
312
313 sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2);
314 }
315 else if (sdlEvent.type == SDL_WINDOWEVENT &&
316 (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN ||
317 sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED))
318 {
319 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
320 views, sdlEvent.window.windowID);
321 boost::shared_ptr<SdlViewport> sdlViewport =
322 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
323 sdlViewport->Paint();
324 }
325 else if (sdlEvent.type == SDL_KEYDOWN &&
326 sdlEvent.key.repeat == 0 /* Ignore key bounce */)
327 {
328 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
329 views, sdlEvent.window.windowID);
330
331 switch (sdlEvent.key.keysym.sym)
332 {
333 case SDLK_f:
334 {
335 boost::shared_ptr<SdlViewport> sdlViewport =
336 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
337 sdlViewport->ToggleMaximize();
338 }
339 break;
340
341 case SDLK_s:
342 {
343 std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
344 lock->GetCompositor().FitContent(lock->GetController().GetScene());
345 lock->Invalidate();
346 }
347 break;
348
349 case SDLK_q:
350 stop = true;
351 break;
352
353 default:
354 break;
355 }
356 }
357 else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
358 sdlEvent.type == SDL_MOUSEMOTION ||
359 sdlEvent.type == SDL_MOUSEBUTTONUP)
360 {
361 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
362 views, sdlEvent.window.windowID);
363
364 std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
365 if (lock->HasCompositor())
366 {
367 OrthancStone::PointerEvent p;
368 OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
369 sdlEvent, keyboardState, scancodeCount);
370
371 switch (sdlEvent.type)
372 {
373 case SDL_MOUSEBUTTONDOWN:
374 lock->GetController().HandleMousePress(interactor, p,
375 lock->GetCompositor().GetCanvasWidth(),
376 lock->GetCompositor().GetCanvasHeight());
377 lock->Invalidate();
378 break;
379
380 case SDL_MOUSEMOTION:
381 if (lock->GetController().HandleMouseMove(p))
382 {
383 lock->Invalidate();
384 }
385 break;
386
387 case SDL_MOUSEBUTTONUP:
388 lock->GetController().HandleMouseRelease(p);
389 lock->Invalidate();
390 break;
391
392 default:
393 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
394 }
395 }
396 }
397 else if (sdlEvent.type == SDL_MOUSEWHEEL)
398 {
399 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
400 views, sdlEvent.window.windowID);
401
402 int delta = 0;
403 if (sdlEvent.wheel.y < 0)
404 delta = -1;
405 if (sdlEvent.wheel.y > 0)
406 delta = 1;
407
408 view->Scroll(delta);
409 }
410 else
411 {
412 for (size_t i = 0; i < views.size(); ++i)
413 {
414 boost::shared_ptr<SdlViewport> sdlViewport =
415 boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport());
416 if (sdlViewport->IsRefreshEvent(sdlEvent))
417 {
418 sdlViewport->Paint();
419 }
420 }
421 }
422 }
423 // Small delay to avoid using 100% of CPU
424 SDL_Delay(1);
425 }
426 }
427 }
428 }
429
430 boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
431
432 /**
433 * IMPORTANT: The full arguments to "main()" are needed for SDL on
434 * Windows. Otherwise, one gets the linking error "undefined reference
435 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
436 **/
437 int main(int argc, char* argv[])
438 {
439 using namespace OrthancStone;
440
441 StoneInitialize();
442
443 try
444 {
445 boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
446 g_app = app;
447 app->RunSdl(argc,argv);
448 }
449 catch (Orthanc::OrthancException& e)
450 {
451 LOG(ERROR) << "EXCEPTION: " << e.What();
452 }
453
454 StoneFinalize();
455
456 return 0;
457 }
458