Mercurial > hg > orthanc-stone
comparison Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp @ 1538:d1806b4e4839
moving OrthancStone/Samples/ as Applications/Samples/
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 11 Aug 2020 13:24:38 +0200 |
parents | OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp@301571299212 |
children | 6e0da8370270 |
comparison
equal
deleted
inserted
replaced
1537:de8cf5859e84 | 1538:d1806b4e4839 |
---|---|
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. This must be supplied.") | |
105 | |
106 ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"), | |
107 "Orthanc ID of the RTDOSE instance to load. This may be an empty string.") | |
108 | |
109 ("rtstruct", po::value<std::string>()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"), | |
110 "Orthanc ID of the RTSTRUCT instance to load. This may be an empty string.") | |
111 ; | |
112 | |
113 std::cout << desc << std::endl; | |
114 | |
115 po::variables_map vm; | |
116 try | |
117 { | |
118 po::store(po::parse_command_line(argc, argv, desc), vm); | |
119 po::notify(vm); | |
120 } | |
121 catch (std::exception& e) | |
122 { | |
123 std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; | |
124 } | |
125 | |
126 for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it) | |
127 { | |
128 std::string key = it->first; | |
129 const po::variable_value& value = it->second; | |
130 const std::string& strValue = value.as<std::string>(); | |
131 SetArgument(key, strValue); | |
132 } | |
133 } | |
134 | |
135 void RtViewerApp::RunSdl(int argc, char* argv[]) | |
136 { | |
137 ProcessOptions(argc, argv); | |
138 | |
139 /** | |
140 Create the shared loaders context | |
141 */ | |
142 loadersContext_.reset(new GenericLoadersContext(1, 4, 1)); | |
143 | |
144 // we are in SDL --> downcast to concrete type | |
145 boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_); | |
146 | |
147 /** | |
148 Url of the Orthanc instance | |
149 Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In | |
150 wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative | |
151 URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders | |
152 plugin that serves the main web application from an URL like "http://localhost:8042/stone-rtviewer" (with ".." | |
153 leading to the main Orthanc root URL) | |
154 */ | |
155 std::string orthancUrl = arguments_["orthanc"]; | |
156 | |
157 { | |
158 Orthanc::WebServiceParameters p; | |
159 if (HasArgument("orthanc")) | |
160 { | |
161 p.SetUrl(orthancUrl); | |
162 } | |
163 if (HasArgument("user")) | |
164 { | |
165 ORTHANC_ASSERT(HasArgument("password")); | |
166 p.SetCredentials(GetArgument("user"), GetArgument("password")); | |
167 } | |
168 else | |
169 { | |
170 ORTHANC_ASSERT(!HasArgument("password")); | |
171 } | |
172 loadersContext->SetOrthancParameters(p); | |
173 } | |
174 | |
175 loadersContext->StartOracle(); | |
176 | |
177 CreateLoaders(); | |
178 | |
179 /** | |
180 Create viewports | |
181 */ | |
182 CreateView("RtViewer Axial", VolumeProjection_Axial); | |
183 CreateView("RtViewer Coronal", VolumeProjection_Coronal); | |
184 CreateView("RtViewer Sagittal", VolumeProjection_Sagittal); | |
185 | |
186 for (size_t i = 0; i < views_.size(); ++i) | |
187 { | |
188 views_[i]->PrepareViewport(); | |
189 views_[i]->EnableGLDebugOutput(); | |
190 } | |
191 | |
192 DefaultViewportInteractor interactor; | |
193 | |
194 /** | |
195 It is very important that the Oracle (responsible for network I/O) be started before creating and firing the | |
196 loaders, for any command scheduled by the loader before the oracle is started will be lost. | |
197 */ | |
198 StartLoaders(); | |
199 | |
200 | |
201 SdlRunLoop(views_, interactor); | |
202 loadersContext->StopOracle(); | |
203 } | |
204 | |
205 void RtViewerView::TakeScreenshot(const std::string& target, | |
206 unsigned int canvasWidth, | |
207 unsigned int canvasHeight) | |
208 { | |
209 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); | |
210 ViewportController& controller = lock->GetController(); | |
211 Scene2D& scene = controller.GetScene(); | |
212 | |
213 std::string ttf; | |
214 Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT); | |
215 | |
216 CairoCompositor compositor(canvasWidth, canvasHeight); | |
217 compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1); | |
218 compositor.Refresh(scene); | |
219 | |
220 Orthanc::ImageAccessor canvas; | |
221 compositor.GetCanvas().GetReadOnlyAccessor(canvas); | |
222 | |
223 Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); | |
224 Orthanc::ImageProcessing::Convert(png, canvas); | |
225 | |
226 Orthanc::PngWriter writer; | |
227 writer.WriteToFile(target, png); | |
228 } | |
229 | |
230 static boost::shared_ptr<OrthancStone::RtViewerView> GetViewFromWindowId( | |
231 const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, | |
232 Uint32 windowID) | |
233 { | |
234 using namespace OrthancStone; | |
235 for (size_t i = 0; i < views.size(); ++i) | |
236 { | |
237 boost::shared_ptr<OrthancStone::RtViewerView> view = views[i]; | |
238 boost::shared_ptr<IViewport> viewport = view->GetViewport(); | |
239 boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport); | |
240 Uint32 curWindowID = sdlViewport->GetSdlWindowId(); | |
241 if (windowID == curWindowID) | |
242 return view; | |
243 } | |
244 return boost::shared_ptr<OrthancStone::RtViewerView>(); | |
245 } | |
246 | |
247 void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, | |
248 OrthancStone::DefaultViewportInteractor& interactor) | |
249 { | |
250 using namespace OrthancStone; | |
251 | |
252 // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views | |
253 std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports; | |
254 for (size_t i = 0; i < views.size(); ++i) | |
255 { | |
256 boost::shared_ptr<RtViewerView> view = views[i]; | |
257 boost::shared_ptr<IViewport> viewport = view->GetViewport(); | |
258 boost::shared_ptr<SdlViewport> sdlViewport = | |
259 boost::dynamic_pointer_cast<SdlViewport>(viewport); | |
260 viewports.push_back(sdlViewport); | |
261 } | |
262 | |
263 { | |
264 int scancodeCount = 0; | |
265 const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); | |
266 | |
267 bool stop = false; | |
268 while (!stop) | |
269 { | |
270 std::vector<SDL_Event> sdlEvents; | |
271 std::map<Uint32,SDL_Event> userEventsMap; | |
272 SDL_Event sdlEvent; | |
273 | |
274 // FIRST: collect all pending events | |
275 while (SDL_PollEvent(&sdlEvent) != 0) | |
276 { | |
277 if ( (sdlEvent.type >= SDL_USEREVENT) && | |
278 (sdlEvent.type < SDL_LASTEVENT) ) | |
279 { | |
280 // we don't want to have multiple refresh events , | |
281 // and since every refresh event is a user event with a special type, | |
282 // we use a map | |
283 userEventsMap[sdlEvent.type] = sdlEvent; | |
284 } | |
285 else | |
286 { | |
287 sdlEvents.push_back(sdlEvent); | |
288 } | |
289 } | |
290 | |
291 // SECOND: add all user events to sdlEvents | |
292 for (std::map<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) | |
293 sdlEvents.push_back(it->second); | |
294 | |
295 // now process the events | |
296 for (std::vector<SDL_Event>::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it) | |
297 { | |
298 const SDL_Event& sdlEvent = *it; | |
299 | |
300 if (sdlEvent.type == SDL_QUIT) | |
301 { | |
302 stop = true; | |
303 break; | |
304 } | |
305 else if (sdlEvent.type == SDL_WINDOWEVENT && | |
306 (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED || | |
307 sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) | |
308 { | |
309 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( | |
310 views, sdlEvent.window.windowID); | |
311 | |
312 boost::shared_ptr<SdlViewport> sdlViewport = | |
313 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); | |
314 | |
315 sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2); | |
316 } | |
317 else if (sdlEvent.type == SDL_WINDOWEVENT && | |
318 (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN || | |
319 sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED)) | |
320 { | |
321 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( | |
322 views, sdlEvent.window.windowID); | |
323 boost::shared_ptr<SdlViewport> sdlViewport = | |
324 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); | |
325 sdlViewport->Paint(); | |
326 } | |
327 else if (sdlEvent.type == SDL_KEYDOWN && | |
328 sdlEvent.key.repeat == 0 /* Ignore key bounce */) | |
329 { | |
330 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( | |
331 views, sdlEvent.window.windowID); | |
332 | |
333 switch (sdlEvent.key.keysym.sym) | |
334 { | |
335 case SDLK_f: | |
336 { | |
337 boost::shared_ptr<SdlViewport> sdlViewport = | |
338 boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); | |
339 sdlViewport->ToggleMaximize(); | |
340 } | |
341 break; | |
342 | |
343 case SDLK_s: | |
344 { | |
345 std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock()); | |
346 lock->GetCompositor().FitContent(lock->GetController().GetScene()); | |
347 lock->Invalidate(); | |
348 } | |
349 break; | |
350 | |
351 case SDLK_q: | |
352 stop = true; | |
353 break; | |
354 | |
355 default: | |
356 break; | |
357 } | |
358 } | |
359 else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN || | |
360 sdlEvent.type == SDL_MOUSEMOTION || | |
361 sdlEvent.type == SDL_MOUSEBUTTONUP) | |
362 { | |
363 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( | |
364 views, sdlEvent.window.windowID); | |
365 | |
366 std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock()); | |
367 if (lock->HasCompositor()) | |
368 { | |
369 OrthancStone::PointerEvent p; | |
370 OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), | |
371 sdlEvent, keyboardState, scancodeCount); | |
372 | |
373 switch (sdlEvent.type) | |
374 { | |
375 case SDL_MOUSEBUTTONDOWN: | |
376 interactor.SetWindowingLayer(view->GetCtLayerIndex()); | |
377 lock->GetController().HandleMousePress(interactor, p, | |
378 lock->GetCompositor().GetCanvasWidth(), | |
379 lock->GetCompositor().GetCanvasHeight()); | |
380 lock->Invalidate(); | |
381 break; | |
382 | |
383 case SDL_MOUSEMOTION: | |
384 if (lock->GetController().HandleMouseMove(p)) | |
385 { | |
386 lock->Invalidate(); | |
387 } | |
388 break; | |
389 | |
390 case SDL_MOUSEBUTTONUP: | |
391 lock->GetController().HandleMouseRelease(p); | |
392 lock->Invalidate(); | |
393 break; | |
394 | |
395 default: | |
396 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
397 } | |
398 } | |
399 } | |
400 else if (sdlEvent.type == SDL_MOUSEWHEEL) | |
401 { | |
402 boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( | |
403 views, sdlEvent.window.windowID); | |
404 | |
405 int delta = 0; | |
406 if (sdlEvent.wheel.y < 0) | |
407 delta = -1; | |
408 if (sdlEvent.wheel.y > 0) | |
409 delta = 1; | |
410 | |
411 view->Scroll(delta); | |
412 } | |
413 else | |
414 { | |
415 for (size_t i = 0; i < views.size(); ++i) | |
416 { | |
417 boost::shared_ptr<SdlViewport> sdlViewport = | |
418 boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport()); | |
419 if (sdlViewport->IsRefreshEvent(sdlEvent)) | |
420 { | |
421 sdlViewport->Paint(); | |
422 } | |
423 } | |
424 } | |
425 } | |
426 // Small delay to avoid using 100% of CPU | |
427 SDL_Delay(1); | |
428 } | |
429 } | |
430 } | |
431 } | |
432 | |
433 boost::weak_ptr<OrthancStone::RtViewerApp> g_app; | |
434 | |
435 /** | |
436 * IMPORTANT: The full arguments to "main()" are needed for SDL on | |
437 * Windows. Otherwise, one gets the linking error "undefined reference | |
438 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows | |
439 **/ | |
440 int main(int argc, char* argv[]) | |
441 { | |
442 using namespace OrthancStone; | |
443 | |
444 StoneInitialize(); | |
445 | |
446 try | |
447 { | |
448 boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create(); | |
449 g_app = app; | |
450 app->RunSdl(argc,argv); | |
451 } | |
452 catch (Orthanc::OrthancException& e) | |
453 { | |
454 LOG(ERROR) << "EXCEPTION: " << e.What(); | |
455 } | |
456 | |
457 StoneFinalize(); | |
458 | |
459 return 0; | |
460 } | |
461 |