Mercurial > hg > orthanc-stone
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 |