Compare commits

..

434 Commits
0.5.2 ... 0.6.1

Author SHA1 Message Date
Bruno Herbelin
dd7a63413c Fixed keyboard arrows control 2021-10-09 23:40:18 +02:00
Bruno Herbelin
6d0c2301c1 Keyboard input for Source Controller
SPACE toggle play/pause, B for begin restart
2021-10-09 00:16:55 +02:00
Bruno Herbelin
8bf8f05add compilation warnings 2021-09-24 00:46:20 +02:00
Bruno Herbelin
bd773d54c6 Improve stability FPS measure in timecounter 2021-09-24 00:45:47 +02:00
Bruno Herbelin
f4c52b7ed3 Fixed output monitor disablling 2021-09-19 11:07:13 +02:00
Bruno Herbelin
5b1504c8f6 Added general DISABLE output action menu
Makes sure the output is black, unrelated to session openning or opacity
2021-09-17 11:31:52 +02:00
Bruno
06187b9a1a work-in progress Helper and keyboard shortcuts 2021-08-26 15:51:07 +02:00
Bruno
7fb6e57829 Added 1200px height selection (e.g. for WUXGA displays) 2021-08-20 21:32:12 +02:00
Bruno
5ec954dbb5 UI fix 2021-08-16 22:55:21 +02:00
Bruno
a6bc30cf62 Fixed Frame grabber 2021-08-16 22:26:59 +02:00
Bruno
df165252fa Fixed OSX vtenc_h264_hw support for Recording 2021-08-16 15:52:58 +02:00
Bruno
da9c94f675 Temporarily disable v4l loopback: not working anymore 2021-08-15 00:32:20 +02:00
Bruno
031cef6357 optimize jpegenc 2021-08-15 00:30:28 +02:00
Bruno
ef5f3efd2e BugFix changing resolution of session 2021-08-14 23:15:18 +02:00
Bruno
bc8c4e3c7b Cleanup UI for centralized Recording settings 2021-08-14 21:57:59 +02:00
Bruno
f5da4c8bc2 Recording: support for NVIDIA nvenc and improved stability
Let gstreamer appsrc generate PTS automatically (seems to fix crash of encoding after long duration). Added test for GPU encoders and switch if enabled and available.
2021-08-14 13:41:53 +02:00
Bruno
644741a1ab Attempt at improving recoding when buffer if full 2021-08-12 00:05:41 +02:00
Bruno
09f46e7a27 Minor GUI layout improvement 2021-08-12 00:05:22 +02:00
Bruno
db4e1d214f BugFix drop Session file 2021-08-11 22:18:08 +02:00
Bruno
79433dd45c Improved Log message Video recording 2021-08-11 22:17:43 +02:00
Bruno
fe72c9b829 Cleanup and improve stability of FrameGrabber 2021-08-11 20:48:18 +02:00
Bruno
b37d22ba47 Improved FrameGrabber with clock duration and priority strategies
Keep track of actual FrameGrabber duration (different from timestamp). Two strategies for frame PTS: clock and framerate priorities. Implemented variable Framerate selection for VideoRecorder.  Integration of all this in UserInterface and Settings.
2021-08-11 00:20:28 +02:00
Bruno
7fb08e618f Added a READABLE time string conversion 2021-08-11 00:17:07 +02:00
Bruno
63c6f1169b Add icons on info and warning Logs 2021-08-11 00:16:19 +02:00
Bruno
0eff8fd24d Minor compilation warning fixed 2021-08-09 10:08:32 +02:00
Bruno
818e554d35 removing debuging info 2021-08-08 23:58:54 +02:00
Bruno
5a18dbaf37 Video Recoding Buffer management
Implemented methods to supervise encoding in FrameGrabber, avoid running out of buffer, and give user a selection of buffer sizes for recording.
2021-08-08 23:58:35 +02:00
Bruno
ddd9bb4e99 minor compilation fix 2021-08-07 20:26:59 +02:00
Bruno
38a2aa90e0 Improved FrameBufferSuface API 2021-08-07 20:26:30 +02:00
Bruno
139137770c BugFix: prevent missing symbol when attach 2021-08-07 20:25:52 +02:00
Bruno
789bf1bd00 BugFix prevent failed FrameBuffer fill 2021-08-07 20:23:20 +02:00
Bruno
563e762dde Store mask after fill 2021-08-07 20:22:07 +02:00
Bruno
28da5f8f39 BugFix Restore fading when cross fading 2021-08-07 20:21:37 +02:00
Bruno
5c42061fd9 Work in progress - towards display initials on sources 2021-08-07 20:21:37 +02:00
Bruno
843224ca35 oops.. correct Mesh file parse 2021-08-07 14:14:16 +02:00
Bruno
e47e76962b Fixed Recording (timing and UI)
Improved frame grabber timing and fixed UserInterface to show the "saving file" info.
2021-08-07 12:34:05 +02:00
Bruno
2f0e4e3212 Improved recording time acuracy 2021-08-07 01:02:39 +02:00
Bruno
fb3e1d0d25 Detecting EOF recording and unexpected termination 2021-08-06 21:23:01 +02:00
Bruno
e9b7e55570 work in progress recording probe 2021-08-06 17:56:48 +02:00
Bruno
8c206898f0 Dialog media include more formats
Integrate exotic file extensions and uppercase equivalent of all possible files to select with dialogs. 
Code cleanup
2021-08-06 16:43:25 +02:00
Bruno
a9c9683b8b longer recording timeout 2021-08-06 13:26:19 +02:00
Bruno
4f43ddf088 Draw a glyph in IMGUI
Proof of concept to show how to access Font texture in opengl to draw one glygh
2021-08-06 13:25:19 +02:00
Bruno
a9c8b67975 Implementation of custom Masks
FrameBuffer accepts to fill any size of FrameBufferImage as input, and a Dialog in TextureView allows to select a JPG or PNG.
2021-08-06 13:23:59 +02:00
Bruno
d1b7073ff9 Reimplementation of Dialogs
Cleanup code to integrate multithreading process for dialogs into the DialogToolkit (avoid poluting UserInterfaceManager and improves reliability)
2021-08-06 13:21:16 +02:00
Bruno
58afcacab9 BugFix thumbnailing
1. avoid crash by cathing the correct exception and 2. ensure we capture a frame by waiting a little
2021-08-04 12:55:51 +02:00
Bruno
5eddfcf196 Add tag icon in thumbnail to identify as user defined 2021-08-04 00:29:31 +02:00
Bruno
9a87764949 Improved UI for inactive videos
Display Player for videos even in disabled state, but with disabled controls
2021-08-03 20:02:04 +02:00
Bruno
fc4e40fba3 Display mixing source original texture when inactive
Re-using activesurface_ for manipulation and display of the source's input texture in the Mixer icon when inactive.
2021-08-02 22:27:06 +02:00
Bruno
e8acfc1c26 New Media Player option to Rewind on Disabled 2021-08-01 19:10:46 +02:00
Bruno
eaadc210ae Performance improvement for transliteration
Tracing CPU usage identified the cost of ICU transliteration: using a static dictionnary to improve performance
2021-08-01 16:44:46 +02:00
Bruno
8002f3164c Confirmed performance improvement without glBufferSubData 2021-08-01 16:43:25 +02:00
Bruno
48f92bc52b Cleanup session properties panel 2021-08-01 12:13:38 +02:00
Bruno
d1e833e0a1 Properties pannel of Session
Also added custom thumbnail of session.
2021-08-01 00:29:44 +02:00
Bruno
c5f0be2b32 Compilation Linux 2021-07-30 21:36:47 +02:00
Bruno
dbcf3bb0ea backward compatibility title window 2021-07-30 16:48:26 +02:00
Bruno
e7a79f6cdc Cleanup path_relative_to_path and path_absolute_from_path 2021-07-30 16:08:24 +02:00
Bruno
63b043dc4b Improved windows titles management
Display filename (no path) before APP_NAME, clean APP_TITLE when no file, bugs fixed.
2021-07-30 16:08:00 +02:00
Bruno
d2a576c99c Support for relative path for files in mix
File path in mix session file add a relative reference to the location of the session mix file. If SessionCreator cannot find the absolute path, it tries to load the file at the relative location. Done for MediaSource, SessionFileSource and SequenceSource.
2021-07-30 00:22:44 +02:00
Bruno
fc91e7cbdd Draft Function path_relative_to_path 2021-07-28 19:03:38 +02:00
Bruno
0555361a57 BugFix glfw set Window Title
Function is not thread safe, causing crash when saving.
2021-07-27 20:05:39 +02:00
Bruno
c923815a01 Added Apple Code signing script in cmake 2021-07-27 20:05:01 +02:00
Bruno
7a4d2ac027 Merge remote-tracking branch 'origin/master' 2021-07-27 09:15:47 +02:00
Bruno
442e1096be Compilation and packaging OSX 10.14 2021-07-27 09:12:53 +02:00
Bruno
6eaf8852ae OSX compilation 2021-07-27 09:06:53 +02:00
Bruno
3612fca707 Add keyboard shortcut play/pause
Space bar to toggle play/pause current source, B  for 'beginning' and N for 'next frame'
2021-07-26 12:51:07 +02:00
Bruno
4736d403a1 bugfix save as 2021-07-26 12:21:06 +02:00
Bruno
a18fd3177c Follow clang-tidy and clazy suggestions
variables non-POD should not be 'static' outside a class. Always use and init variables. Delete useless classes.
2021-07-17 16:45:01 +02:00
Bruno
5930b0f8fe UI bugfix locked source in texture view 2021-07-17 11:55:23 +02:00
Bruno
1c7c64db59 Merge remote-tracking branch 'origin/master' 2021-07-17 10:33:53 +02:00
Bruno
9bc780bcda Update CMAKE for OSX
minor text message changes
2021-07-17 10:33:43 +02:00
Bruno
b3f89e0464 for OSX 2021-07-17 10:32:13 +02:00
Bruno
1de4822c67 OSX compile 2021-07-03 11:27:08 +02:00
Bruno
c846e4072a postponing the dev of snapshot interpolation 2021-07-03 10:19:00 +02:00
Bruno
c4f26bd500 added -v and -t command line options 2021-07-02 22:16:55 +02:00
Bruno
041c01135a Small improvement timing fade in and out
Adding a buffer of 0 opacity before or after fading to avoid jumps to previous or next frame of a segment
2021-06-28 23:43:44 +02:00
Bruno
aa904f26ad Recording timeout with timing slider
Changed timout recording in uint milisecond.
2021-06-28 21:33:17 +02:00
Bruno
ff99d37eb6 Player Video Fading dialog
New dialog to apply fade in & out with parameters. Fixed Timeline fading functions. New ImGuiToolkit items to draw icons in Combo boxes.
2021-06-28 00:21:29 +02:00
Bruno
e8a500dc99 BugFix negative play speed Selection 2021-06-24 23:15:50 +02:00
Bruno
9f4f247cd2 Bugfix jump gaps MediaPlayer 2
Timeline copy should not overwrite pts of first frame.
2021-06-23 20:34:00 +02:00
Bruno
e1ac930dd6 Pedantic tooltip 2021-06-21 23:15:17 +02:00
Bruno
c20ed94f46 Bugfix jump gaps MediaPlayer
now that we use first frame time, testing jumps should be done with beginning time
2021-06-21 23:14:43 +02:00
Bruno
4efe754a8d MediaPlayer decoder information improved 2021-06-20 23:54:19 +02:00
Bruno
bb83f7fcb7 Timeline fade in and fade out 2021-06-20 23:53:52 +02:00
Bruno
79fa6082b0 Player: shoft slider on first frame of MediaPlayer 2021-06-20 18:50:12 +02:00
Bruno
f2ecc88955 Clean string from infor visitor on media player 2021-06-20 18:49:39 +02:00
Bruno
7253c1ec1a Merge remote-tracking branch 'origin/master' 2021-06-19 09:49:53 +02:00
BHBN
cf32c9fc12 Merge pull request #39 from brunoherbelin/dev
Accept new Player (dev)
2021-06-19 09:49:11 +02:00
Bruno
3086735be1 Merge branch 'HEAD' 2021-06-19 01:04:11 +02:00
Bruno
5a54e84dd8 Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 01:03:21 +02:00
Bruno
1ef26c0c95 Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-19 01:03:21 +02:00
Bruno
887142079b Fixed Timeline display 2021-06-19 01:03:21 +02:00
Bruno
b75ea00c0d Unique play/pause button for multiple sources 2021-06-19 01:03:21 +02:00
Bruno
319fbfa84d Bugfix display STRING time 2021-06-19 01:03:21 +02:00
Bruno
3874252797 Bugfix computation time with gaps 2021-06-19 01:03:21 +02:00
Bruno
5dfc45af5f Fixed Timeline ticks display 2021-06-19 01:03:21 +02:00
Bruno
1f203801db Player: reset selection on session change 2021-06-19 01:03:21 +02:00
Bruno
291410a2b3 Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-19 01:03:21 +02:00
Bruno
dfc4937688 Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-19 01:03:21 +02:00
Bruno
a0b763ab71 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-19 01:03:21 +02:00
Bruno
ad36ac5cd9 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-19 01:03:21 +02:00
Bruno
cd40d6d7e8 Improved management of selection in Player 2021-06-19 01:03:21 +02:00
Bruno
ec4214ebf8 improved quality realtime recorder h264 2021-06-19 01:03:21 +02:00
Bruno
a403d40b6c Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-19 01:03:21 +02:00
Bruno
7dcfc97f33 UI details 2021-06-19 01:03:21 +02:00
Bruno
5ea056a483 Bugfix timeline display array 2021-06-19 01:03:21 +02:00
Bruno
cd1702bb53 Define UNICODE symbols 2021-06-19 01:03:21 +02:00
Bruno
6ff266581a work in progress selection timeline 2021-06-19 01:03:21 +02:00
Bruno
6b7d108407 Minor improvements timeline display 2021-06-19 01:03:21 +02:00
Bruno
473e24bcd7 Fixed and improved TimeCounter 2021-06-19 01:03:21 +02:00
Bruno
1f5056bf15 BugFix IconButton (pop id) 2021-06-19 01:03:21 +02:00
Bruno
61fa062794 Correct implementation of DeviceSource resize
previous commit was bad...
2021-06-19 01:03:21 +02:00
Bruno
0e48cf4505 Bugfix and cleanup Info on source in UI 2021-06-19 01:03:21 +02:00
Bruno
b606f479e9 Bugfix change device source resolution 2021-06-19 01:03:21 +02:00
Bruno
2ccbf1ec12 Bugfix closing single frame stream 2021-06-19 01:03:21 +02:00
Bruno
ac6e84bb1c New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-06-19 01:03:21 +02:00
Bruno
11d12c1f29 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-06-19 01:03:21 +02:00
Bruno
c9707e7335 Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-06-19 01:03:21 +02:00
Bruno
2c0be68a3c Cleaup UI Selection source Player 2021-06-19 01:03:21 +02:00
Bruno
2add317106 Improved UI media player (info media) 2021-06-19 01:03:21 +02:00
Bruno
60ec11982a OSX compilation 2021-06-19 01:03:21 +02:00
Bruno
b2284cf1b4 Improved cursor EditPlotHistoLines 2021-06-19 01:02:12 +02:00
Bruno
e87ef2774b New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-06-19 01:02:12 +02:00
Bruno
7a9fcaefd6 Minor code improvements 2021-06-19 01:02:12 +02:00
Bruno
2333a7a11a Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-06-19 01:02:12 +02:00
Bruno
2a7857c499 Bugfix; verify frame grabbers before use 2021-06-19 01:02:12 +02:00
Bruno
e892dc1eb5 Implemented delayed start of recording 2021-06-19 01:02:12 +02:00
Bruno
9c8d1f31f6 bugfix linux shared webcam ui 2021-06-19 01:02:12 +02:00
Bruno
048db7a44b Merge remote-tracking branch 'origin/master' 2021-06-19 01:01:59 +02:00
Bruno
1d94d494b6 Merge remote-tracking branch 'origin/dev' into dev 2021-06-19 00:50:31 +02:00
Bruno
c6ac35addb Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 00:49:20 +02:00
Bruno
9ec279754b Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-19 00:49:20 +02:00
Bruno
bdcf28c5da Fixed Timeline display 2021-06-19 00:49:20 +02:00
Bruno
edf0f8074a Unique play/pause button for multiple sources 2021-06-19 00:49:20 +02:00
Bruno
c83a946cbd Bugfix display STRING time 2021-06-19 00:49:20 +02:00
Bruno
8604babeb6 Bugfix computation time with gaps 2021-06-19 00:49:20 +02:00
Bruno
7252b74539 Fixed Timeline ticks display 2021-06-19 00:49:20 +02:00
Bruno
08fbaa039f Player: reset selection on session change 2021-06-19 00:49:20 +02:00
Bruno
30f9fb50eb Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-19 00:49:20 +02:00
Bruno
e3578df8a0 Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-19 00:49:20 +02:00
Bruno
37445b8857 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-19 00:49:20 +02:00
Bruno
99ea14fab0 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-19 00:49:20 +02:00
Bruno
1717c143b2 Improved management of selection in Player 2021-06-19 00:49:20 +02:00
Bruno
53223d0876 improved quality realtime recorder h264 2021-06-19 00:49:20 +02:00
Bruno
096bcb4132 Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-19 00:49:20 +02:00
Bruno
05cc70bdbd UI details 2021-06-19 00:49:20 +02:00
Bruno
45653b52b5 Bugfix timeline display array 2021-06-19 00:49:20 +02:00
Bruno
d1841f2863 Define UNICODE symbols 2021-06-19 00:49:20 +02:00
Bruno
f85de11711 work in progress selection timeline 2021-06-19 00:49:20 +02:00
Bruno
9d81a105ee Minor improvements timeline display 2021-06-19 00:49:20 +02:00
Bruno
deb6af9dea Fixed and improved TimeCounter 2021-06-19 00:49:20 +02:00
Bruno
ab512b76aa BugFix IconButton (pop id) 2021-06-19 00:49:20 +02:00
Bruno
bcbeee7247 Correct implementation of DeviceSource resize
previous commit was bad...
2021-06-19 00:49:20 +02:00
Bruno
bcdc94c3b9 Bugfix and cleanup Info on source in UI 2021-06-19 00:49:20 +02:00
Bruno
6ebcf49758 Bugfix change device source resolution 2021-06-19 00:49:20 +02:00
Bruno
a936ab6851 Bugfix closing single frame stream 2021-06-19 00:49:20 +02:00
Bruno
95378660dd New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-06-19 00:49:20 +02:00
Bruno
e49bdac3e8 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-06-19 00:49:20 +02:00
Bruno
48380fab7e Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-06-19 00:49:20 +02:00
Bruno
ce92529a84 Cleaup UI Selection source Player 2021-06-19 00:49:20 +02:00
Bruno
8e29a555c8 Improved UI media player (info media) 2021-06-19 00:49:20 +02:00
Bruno
a1b6ec066b OSX compilation 2021-06-19 00:49:20 +02:00
Bruno
fb59bf491f Improved cursor EditPlotHistoLines 2021-06-19 00:48:11 +02:00
Bruno
86aec7d2ba New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-06-19 00:48:11 +02:00
Bruno
579f7d5609 Minor code improvements 2021-06-19 00:48:11 +02:00
Bruno
c87b1ac363 Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-06-19 00:48:11 +02:00
Bruno
543648112b Bugfix; verify frame grabbers before use 2021-06-19 00:48:11 +02:00
Bruno
daa3b9e978 Implemented delayed start of recording 2021-06-19 00:48:11 +02:00
Bruno
fb8da181da bugfix linux shared webcam ui 2021-06-19 00:48:11 +02:00
Bruno
6cc5a8af9e Player slight improvements
tick marks count adapted to fps, clamped refresh frequency computation, listing of all sources playable from menu
2021-06-19 00:47:47 +02:00
Bruno
1a1956962a Warning on failed discovery of MediaPlayer framerate
Default to 30fps
2021-06-18 23:58:23 +02:00
Bruno
e422a1b403 Fixed Timeline display 2021-06-18 23:57:42 +02:00
Bruno
295ece79ae Unique play/pause button for multiple sources 2021-06-18 00:22:07 +02:00
Bruno
08f8ee159a Bugfix display STRING time 2021-06-18 00:21:36 +02:00
Bruno
e2d416b3fb Bugfix computation time with gaps 2021-06-18 00:21:09 +02:00
Bruno
16ed97b4cb Fixed Timeline ticks display 2021-06-17 21:42:15 +02:00
Bruno
82739702bd Player: reset selection on session change 2021-06-16 23:47:42 +02:00
Bruno
0010c9e3d5 Player UI: video menu and speed reset icon
+ rename private variables to follow usual style
2021-06-16 23:12:31 +02:00
Bruno
f59d4af92b Player: move up timeline and adjust size
keep play button bar at the bottom for all configurations, avoid text and buttons overlay when Player is small. Fix cut timing in selection
2021-06-15 23:51:59 +02:00
Bruno
a7df619a05 Timeline management in Player
Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut)
2021-06-14 23:42:20 +02:00
Bruno
f3759c2ef5 Player timeline for selection
Selection of media sources now displays in a list with proportional timelines, showing actual play time and cursor on effective timeline with opacity curve.
2021-06-13 00:24:45 +02:00
Bruno
d547f3a8a8 Improved management of selection in Player 2021-06-07 22:53:29 +02:00
Bruno
583e53d8a8 improved quality realtime recorder h264 2021-06-07 00:04:23 +02:00
Bruno
a27bf08ce0 Stick window on current view
menu item for media player and output preview to pin the window in current view
2021-06-07 00:04:06 +02:00
Bruno
060fb5ad2d UI details 2021-06-07 00:03:06 +02:00
Bruno
ffc00d9035 Bugfix timeline display array 2021-06-06 14:54:55 +02:00
Bruno
83a9d281c2 Define UNICODE symbols 2021-06-06 14:54:21 +02:00
Bruno
b6c853c308 work in progress selection timeline 2021-06-06 00:17:19 +02:00
Bruno
552c09d377 Minor improvements timeline display 2021-05-30 22:56:13 +02:00
Bruno
61164e627b Fixed and improved TimeCounter 2021-05-26 23:31:34 +02:00
Bruno
3c71ee1ff2 BugFix IconButton (pop id) 2021-05-25 22:17:57 +02:00
Bruno
07f610e84a Correct implementation of DeviceSource resize
previous commit was bad...
2021-05-25 20:20:43 +02:00
Bruno
faf344bc03 Bugfix and cleanup Info on source in UI 2021-05-25 09:09:23 +02:00
Bruno
e9482a3dfc Bugfix change device source resolution 2021-05-25 09:08:51 +02:00
Bruno
d4a7ce3487 Bugfix closing single frame stream 2021-05-25 09:08:24 +02:00
Bruno
3ef2737d82 New InfoVisitor: get string to describe sources
Unified code in ImGui visitor and Player
2021-05-24 20:39:56 +02:00
BHBN
582b67f4e1 Add ref to GLP 3 or later 2021-05-24 12:10:51 +02:00
Bruno
5dd6c0af78 New Timeline actions
Smooth and auto cut actions added on the side of the timeline UI.
2021-05-24 00:58:21 +02:00
Bruno
893e4f4723 Improved link between ImGuiVisitor and SourcePlayer
Source panel shows description and icon to open player UI. Changed icon player, and fixed source selection.
2021-05-23 12:32:32 +02:00
Bruno
d137e87f0e Cleaup UI Selection source Player 2021-05-23 00:55:24 +02:00
Bruno
7f152077e5 Improved UI media player (info media) 2021-05-22 01:34:19 +02:00
Bruno
74a9b229d0 OSX compilation 2021-05-19 23:30:07 +02:00
Bruno
80cf57a979 Improved cursor EditPlotHistoLines 2021-05-19 23:29:57 +02:00
Bruno
19ba943075 New SourcePlayer
Work in progress; Sources now have play/pause and associated play functions. Media player can play all playable sources, and adapts to control a media player when possible. Selection of play groups (to finalize)
2021-05-19 00:31:37 +02:00
Bruno
ba0e25a272 Minor code improvements 2021-05-17 16:13:21 +02:00
Bruno
575c487fa6 Bugfix Do not lock session in action manager
This was causing mutex deadlock
2021-05-17 16:10:46 +02:00
Bruno
1e91b2aa29 Bugfix; verify frame grabbers before use 2021-05-13 23:15:47 +02:00
Bruno
e10cf40f38 Implemented delayed start of recording 2021-05-13 22:15:50 +02:00
Bruno
23defd2117 bugfix linux shared webcam ui 2021-05-12 21:01:19 +02:00
Bruno
1227b87565 Merge remote-tracking branch 'origin/master' 2021-05-08 23:19:23 +02:00
Bruno
424ef16831 ignore local OSX files 2021-05-08 12:34:47 +02:00
Bruno
87059df28a Merge remote-tracking branch 'origin/master' 2021-05-08 12:33:41 +02:00
BHBN
520ff24ac2 Merge pull request #33 from brunoherbelin/dev
Dev
2021-05-08 12:32:48 +02:00
Bruno
920304773b Merge remote-tracking branch 'origin/dev' 2021-05-08 12:27:07 +02:00
Bruno
e55059c82c OSX update 2021-05-08 12:24:37 +02:00
Bruno
6a96c91fe1 Merge branch 'dev' 2021-05-08 12:19:13 +02:00
Bruno
f995e1cf72 Reordering source type in UI 2021-05-08 12:17:35 +02:00
Bruno
3be09553cf Multiple file selection for OSX
and finetuning creation UI for MultiFile source
2021-05-07 18:56:54 +02:00
Bruno
ee28a03a6c clean MultiFileSource 2021-05-06 23:56:31 +02:00
Bruno
d56700a9d5 Saving, undo and dynamic change of MultiFileSource 2021-05-06 23:44:15 +02:00
Bruno
250df8b651 Bugfix: allow changing resolution of stream 2021-05-06 22:18:46 +02:00
Bruno
e071ffe590 Create new Source type MultiFile
MultiFileSource plays a sequence of numbered images.
2021-05-06 00:24:01 +02:00
brunoherbelin
737b45a18c bugfix test initialization before render source 2021-05-04 21:40:24 +02:00
Bruno
1d2b7b17e8 CTRL + clic to lock/unlock 2021-05-01 20:03:42 +02:00
Bruno
bd71a4b581 restoring copy of transform_ matrix
would be possible to optimize and avoid this copy, but should be verified everywhere that this change of paradigm is taken into account (i.e. run update after copy transform if needed)
2021-05-01 19:12:09 +02:00
Bruno
451ff64b6f Cosmetics
Moved string truncate to BaseToolkit, fixed SystemToolkit max memory, clean left panel UI
2021-05-01 16:39:01 +02:00
Bruno
5e0dd60adb re-open should not change gl texture
rename 'ready' to 'opened' to avoid confusion
2021-05-01 15:35:36 +02:00
Bruno
a05cd380fb Using source mode UNINITIALIZED to replace initialized_ bool
info about source initialization was duplicated; using the mode state machine only avoids to keep a flag
2021-05-01 10:29:34 +02:00
Bruno
fd3102c0d3 memory monitor on latest value (not average) 2021-05-01 09:38:21 +02:00
Bruno
1bdd52232c compile fix 2021-05-01 00:36:02 +02:00
Bruno
381f68aaae Change READY state of source
a source is ready after rendering one frame (which is after being initialized)
2021-05-01 00:34:58 +02:00
Bruno
058fde19ce Fixed merging and action manager bug 2021-04-30 12:33:23 +02:00
Bruno
63544c603d Fixed Session thumbnail
wait for all sources to be ready before creating the thumbnail.
2021-04-30 10:17:10 +02:00
Bruno
92cd8f945e align behavior with stream 2021-04-29 23:13:30 +02:00
Bruno
5244941d9b remove commented code 2021-04-29 23:13:03 +02:00
Bruno
877fb09fa3 more elegant session source init 2021-04-29 23:12:46 +02:00
Bruno
34827ab068 ensure execute open immediately
and various code cleanup
2021-04-29 23:12:05 +02:00
Bruno
9bbe875690 Cleanup and bigsfix UI for thumbnail and preview source 2021-04-29 23:11:04 +02:00
Bruno
c7f09fb12d Prevent crash on exit
Global objects are deleted before  glfw terminates, thus crashing opengl access. Omitting glfw terminate on program exit is harmless, so...
2021-04-29 23:08:23 +02:00
Bruno
3f6d2bb6e3 Unifying list selection UI in session panel 2021-04-28 13:52:38 +02:00
Bruno
bd2500314f decide for byte aligned opengl textures all over 2021-04-28 13:18:01 +02:00
Bruno
a28f46a9eb perform render thumbnail only if framebuffer available 2021-04-28 11:23:11 +02:00
Bruno
7b7bd763c9 clear code copy vec4 2021-04-28 11:22:27 +02:00
Bruno
1118e4bfe0 add source propose a name, does not change it yet
and use filename only if provided
2021-04-28 11:21:47 +02:00
Bruno
b0bc88e9c2 unified behavior stream and media player 2021-04-28 11:19:59 +02:00
Bruno
038fea3539 opengl good practice: unbind texture after set 2021-04-28 11:18:45 +02:00
Bruno
ee20718c51 remove debug code 2021-04-27 23:21:33 +02:00
Bruno
f46ffc004a Validate list of filenames 2021-04-27 23:20:18 +02:00
Bruno
d2f0f42c2d imgui code cleanup 2021-04-27 23:19:58 +02:00
Bruno
be36662efc Fixed unique source name algorithm 2021-04-26 23:51:47 +02:00
Bruno
c7d6d37a8e oops 2021-04-26 22:06:06 +02:00
Bruno
7ba87fcee8 Fixed ButtonOpenUrl 2021-04-26 18:49:01 +02:00
Bruno
c3713c9ce7 work in progress tooltips with thumbnails of sessions 2021-04-26 13:48:20 +02:00
Bruno
98861cea6c Fixed Mixer delete and rename 2021-04-26 13:47:48 +02:00
brunoherbelin
d89290f9b2 alphabetically sort list of files in a dir 2021-04-26 09:22:22 +02:00
Bruno
1b6b2ecd4d Bugfix undo of multiple sources delete 2021-04-26 00:29:26 +02:00
Bruno
0e3575c1ca Using new basetoolkit function for unique naming
applied to source and snapshot names
2021-04-25 23:59:18 +02:00
Bruno
055f5c4c4e Creating a base toolkit for functions independent from other toolkits 2021-04-25 20:09:22 +02:00
Bruno
129d5445c3 Implementation of UI for snapshot manipulation
with thumbnails appearing on mouse over and in edit context menu
2021-04-25 14:02:06 +02:00
Bruno
6e202def1e Bugfix Action Snapshot replace 2021-04-25 11:39:45 +02:00
Bruno
0c51b16353 use framebuffer blit for thumbnail of RenderView 2021-04-25 01:27:51 +02:00
Bruno
c5ee77e969 Fixed blit of framebuffer 2021-04-25 01:27:32 +02:00
Bruno
c7962abfbb Save thumbnail of session when storing history or snapshot 2021-04-25 00:27:59 +02:00
Bruno
d371f6ae8e Cleanup FrameBufferImage API 2021-04-25 00:27:32 +02:00
Bruno
8336f6a595 Fixed RenderView thumbnailer 2021-04-25 00:26:38 +02:00
Bruno
c3a24a6d7f Cleanup XML read & write utility functions 2021-04-25 00:26:05 +02:00
Bruno
f643e80de7 Using std future promises to get thumbnail of render view 2021-04-24 13:14:57 +02:00
Bruno
5473c0b38b Store image size in XML
and discard loaded image if size does not match
2021-04-24 13:14:03 +02:00
Bruno
eb54b67caa New function to get thumbnail of render view 2021-04-22 23:38:00 +02:00
Bruno
97e7e5f4a1 Cleanup image saving and loading in xml session 2021-04-22 23:37:39 +02:00
brunoherbelin
da64172848 Work-in-progress: Interpolation of snapshot in Action manager 2021-04-21 23:35:34 +02:00
Bruno
e2d2e6ddd8 prefix ++View::need_deep_update_ 2021-04-19 19:24:50 +02:00
Bruno
896cae2d07 Fixed window resize 2021-04-19 18:29:10 +02:00
Bruno
409870dddb OSX cmake fix 2021-04-19 18:28:17 +02:00
Bruno
c19acda62d Added Snapshot Replace 2021-04-18 21:02:23 +02:00
Bruno
a50a6e129c Better implementation of SessionSnapshots saving 2021-04-18 18:22:21 +02:00
Bruno
d68987be0f C++ improved declaration of singleton managers 2021-04-18 13:27:19 +02:00
Bruno
bb64579fa5 Cleanup FileDialog (unused) 2021-04-18 13:15:18 +02:00
Bruno
2392d844d9 Making classes non-assignable
Following CppCheck recomendation, all classes that should not be manipulated by value are made non-assignable to ensure no mistake is made.
2021-04-18 13:04:16 +02:00
Bruno
c6d01c1420 Optimizing iteration
prefix ++i is faster than post i++
2021-04-18 11:38:03 +02:00
Bruno
8389010002 CoreSource complement
The CORE of a source is all what can be interpolated
2021-04-18 10:59:39 +02:00
Bruno
31eb13e16d Unused =operator: copy function instead 2021-04-18 10:58:03 +02:00
Bruno
7cafbc032b Working on source interpolation 2021-04-18 10:56:37 +02:00
brunoherbelin
cc752050f8 SourceCore to isolate core properties of a source 2021-04-17 14:40:00 +02:00
Bruno
c90bec36c5 clean useless ptr 2021-04-17 14:10:42 +02:00
Bruno
c1415b021a convert linklist to list of sources 2021-04-17 14:10:14 +02:00
brunoherbelin
1081b4a54d allow undo of trigger snapshot 2021-04-17 14:09:09 +02:00
brunoherbelin
ebb5fd16bb Draft implementation of Snapshots, with saving and UI 2021-04-17 10:28:12 +02:00
brunoherbelin
2d4a6d1fe6 Snapshots in Action manager 2021-04-15 22:50:28 +02:00
brunoherbelin
11df7c28b4 More robust clone XML mechanism: use ID 2021-04-13 22:41:57 +02:00
brunoherbelin
17d2a63132 Important change: sources keep their id all lifelong.
This simplifies a lot history and testing in session.
2021-04-13 22:26:26 +02:00
brunoherbelin
268486b652 std::string compatible imgui text input 2021-04-13 21:28:36 +02:00
brunoherbelin
f5af24b384 Update settings 2021-04-13 21:27:51 +02:00
brunoherbelin
6d522876ad Loading session with menu do not use smooth transition 2021-04-13 21:24:56 +02:00
brunoherbelin
2814763b97 std::string compatible imgui text input 2021-04-13 21:23:42 +02:00
brunoherbelin
765133a3bd BugFix 2021-04-11 15:24:24 +02:00
brunoherbelin
ab41a0c5d8 First implementation of Sticky Notes 2021-04-11 15:13:46 +02:00
brunoherbelin
eee9f49c05 Refurbishing the left panel
Toggle settings to show whole panel of settings
More space for main session panel (added notes)
2021-04-11 01:27:21 +02:00
Bruno
d7102809fc Auto stash before checking out "origin/dev" 2021-04-10 00:47:05 +02:00
Bruno
e792c119ea Merge branch 'master' into origin/dev 2021-04-10 00:45:00 +02:00
brunoherbelin
6e4ced8dcb Unified undo history messages 2021-04-10 00:22:16 +02:00
brunoherbelin
e69be79aed Compilation defines to cleanup old code 2021-04-09 22:50:16 +02:00
brunoherbelin
87dc282fb7 Improved MediaPlayer memory consumption:
Avoid duplicating Timeline object and limit number of URI discoverers to
two parallel threads.
2021-04-09 11:23:05 +02:00
brunoherbelin
38f7fb7c16 Bugfix timeline (prevent zero div) 2021-04-09 11:22:06 +02:00
brunoherbelin
6173e8279f Improved dev toolbox (saving statistics of memory) 2021-04-09 11:21:43 +02:00
brunoherbelin
4f661c5c05 Merge branch 'master' of github.com:brunoherbelin/vimix 2021-04-08 00:27:20 +02:00
brunoherbelin
28172430dc Added 'Source' to metrics (moved to UserInterfaceManager) 2021-04-08 00:26:52 +02:00
brunoherbelin
5a9d4dd55e Temporary disabling feature 'follow image processing' 2021-04-07 23:00:09 +02:00
brunoherbelin
b3880ad380 Limiting memory for media player 2021-04-07 22:57:29 +02:00
BHBN
b45c846a85 Update README.md 2021-04-07 13:23:22 +02:00
brunoherbelin
788fa693fd Draft implementation of Following mechanism for Image processing 2021-04-06 23:20:13 +02:00
brunoherbelin
1d45ab1d20 Cleanup DummySource (bad bad bad) 2021-04-05 14:55:21 +02:00
brunoherbelin
ae1c3d85ab bugfix: store mask after applying effect 2021-04-05 13:31:31 +02:00
brunoherbelin
c22df2eb2a (continue) Migrating clipboard manipulation to Session XML management 2021-04-05 13:06:31 +02:00
brunoherbelin
d3a130d9ba (continue) Migrating clipboard manipulation to Session XML management 2021-04-05 13:05:38 +02:00
brunoherbelin
8a57b52fcc Migrating clipboard manipulation to Session XML management 2021-04-05 13:04:44 +02:00
brunoherbelin
dbc9803f9e center on source only if source is not visible 2021-04-04 22:21:42 +02:00
brunoherbelin
3e376eb166 ensure output and media player window are visible 2021-04-04 20:55:55 +02:00
brunoherbelin
12aca05aef prevent potential memoryleak 2021-04-04 14:38:04 +02:00
brunoherbelin
ce1de27618 Improved README 2021-04-04 14:19:36 +02:00
brunoherbelin
66d5148e3a Prevent potential memory leak 2021-04-04 13:40:17 +02:00
brunoherbelin
d2b4a825eb avoid pedantic compilation warning 2021-04-04 13:27:56 +02:00
brunoherbelin
f443720319 Programming style improvement: following Cppcheck suggestions. 2021-04-04 13:13:06 +02:00
brunoherbelin
b4627a1613 Various potential memory leak fixed 2021-04-04 01:25:35 +02:00
brunoherbelin
ceea9c10d5 gstreamer memory cleanup in mediaplayer and stream 2021-04-04 01:24:13 +02:00
brunoherbelin
a143360497 Memory leak fix 2021-04-02 22:38:34 +02:00
brunoherbelin
163757cb69 Improved layout and menu Media Player UI 2021-04-02 13:57:11 +02:00
brunoherbelin
4537b986ca Support to forced software decoding option
With reload of media player when option is changed
2021-04-02 12:14:20 +02:00
Bruno
aafac2a7a8 merge 2021-04-01 20:46:46 +02:00
brunoherbelin
7344258b2f fixed hardware decoding detection OSX 2021-04-01 09:12:41 +02:00
brunoherbelin
c59994b7e5 Implemented a detection of hardware decoding used in pipeline
Simple check for names of decoder inside uridecodebin and cross check
with the list of known hardware Decoder Names
2021-04-01 00:14:02 +02:00
brunoherbelin
b089f59e2a Implemented a detection of hardware decoding used in pipeline
Simple check for names of decoder inside uridecodebin and cross check
with the list of known hardware Decoder Names
2021-04-01 00:11:05 +02:00
Bruno
649d2b7ef7 Merge remote-tracking branch 'origin/master' 2021-03-31 19:24:48 +02:00
brunoherbelin
8bef575e8b minor changes to media player pipeline (improved performance?) 2021-03-31 15:40:02 +02:00
brunoherbelin
559a036e6d BugFix: reload list of recent sessions after any change to history 2021-03-30 23:51:06 +02:00
brunoherbelin
0b845591f9 Improved transition view
Responsive buttons placement and clarified actions.
2021-03-30 23:02:24 +02:00
brunoherbelin
a8ef68ed59 Thought of the day. 2021-03-30 19:14:34 +02:00
brunoherbelin
7293b8b9dd BugFix: clean interrupt stream when ending abruptly 2021-03-30 19:03:33 +02:00
brunoherbelin
8eef5465c9 eyecandy: better icons for file menu 2021-03-29 23:18:14 +02:00
brunoherbelin
c08cb95dc1 Bugfix: restating correct order session loading properties 2021-03-29 23:17:54 +02:00
brunoherbelin
010166e7b0 renaming mediaplayer attribute force software decoding 2021-03-29 23:17:16 +02:00
brunoherbelin
4c7fb94616 Small update of webpage (link to installation guide) 2021-03-29 22:23:00 +02:00
Bruno
46f486a367 Documenting installation for wiki 2021-03-29 22:05:41 +02:00
brunoherbelin
ea195dcf11 add link to vimeo 2021-03-28 21:03:43 +02:00
brunoherbelin
e6979acded Revert "Try to integrate embedded video in Jekyll webpage"
This reverts commit 43b4fc81b9.
2021-03-28 20:59:46 +02:00
brunoherbelin
43b4fc81b9 Try to integrate embedded video in Jekyll webpage 2021-03-28 20:56:30 +02:00
brunoherbelin
d4ce6ebee6 trying snap eextensions: [gnome-3-28] 2021-03-28 18:37:29 +02:00
brunoherbelin
1c9a5ece83 setLocale in C (not std C++) 2021-03-27 23:36:51 +01:00
brunoherbelin
8a75664264 Preventing display glitch from invalid scaling of view 2021-03-27 23:31:18 +01:00
brunoherbelin
6687bdd258 BugFix: mixed-up Locale for XML I/O caused by GTK Dialogs 2021-03-27 23:17:19 +01:00
brunoherbelin
e525ecad36 Cleanup main 2021-03-27 23:15:49 +01:00
brunoherbelin
e8b5dc0649 BugFix: not using GST g_main_context to avoid GTK conflict 2021-03-27 19:21:18 +01:00
brunoherbelin
3a0f96c1ec fixed add extension on saved filename 2021-03-27 18:23:54 +01:00
brunoherbelin
e4c06ba1bb Fixed #ifdef compilation 2021-03-27 18:21:13 +01:00
brunoherbelin
bc4eadfd08 Bugfix view config loading 2021-03-27 18:13:09 +01:00
Bruno
ee2ce3802f Linux Dialogs in GTK for SNAP compatibility
Discarding use of ZENITY under linux (previously used with the tinyfiledialog) because snapcraft makes  it impossible to use :(. Reimplementation of GTK+ dialogs directly inside vimix code. Note: no changes for OSX. Complete cleanup of cmake file.
2021-03-27 13:03:22 +01:00
brunoherbelin
43d44634f7 Trying to fix the tinyfiledialog zenity integration 2021-03-22 16:25:51 +01:00
brunoherbelin
41bd7fc958 problem snap and zenity 2021-03-22 14:34:47 +01:00
brunoherbelin
1e458a642c Merge branch 'master' of github.com:brunoherbelin/vimix 2021-03-22 13:59:05 +01:00
brunoherbelin
b255e41091 Work in progress: force sofware decoder for a media player 2021-03-22 13:58:55 +01:00
brunoherbelin
bc22832ad6 information on tinyfiledialog in about. 2021-03-22 13:35:17 +01:00
brunoherbelin
f59ac505b7 shift grab source (even on rotation) 2021-03-21 14:23:30 +01:00
brunoherbelin
e15b962221 Cosmetics: improve reordering source in left panel 2021-03-21 14:09:11 +01:00
brunoherbelin
28d4d4acc4 Bugfix: prevent crash with current source when reordering 2021-03-21 13:24:54 +01:00
brunoherbelin
5f800f3723 Creating texture only on draw 2021-03-20 23:29:30 +01:00
brunoherbelin
2537ca03c8 fix 2021-03-20 22:05:30 +01:00
brunoherbelin
1860402452 Bugfix un-understandable crash on texture mixing quadratic. 2021-03-20 22:03:57 +01:00
brunoherbelin
bec9385c68 BugFix (apparently problematic memmove under OSX) 2021-03-20 18:38:46 +01:00
brunoherbelin
798139e097 Cosmetic: add label to button in source imgui visitor 2021-03-20 14:57:13 +01:00
brunoherbelin
6683d76222 OSX package name with patch version 2021-03-20 13:49:56 +01:00
brunoherbelin
6d2d16b644 updated OSX icon 2021-03-20 13:31:09 +01:00
brunoherbelin
4e83cdf30f hide history with ESC 2021-03-20 13:01:00 +01:00
brunoherbelin
71891292b4 Cosmetics: improved naming and actions on SessionSources (Groups and
File)
2021-03-20 11:51:46 +01:00
brunoherbelin
10ac384e7e Cosmetics: mouse over MixingCircle global opacity slider. 2021-03-20 10:46:06 +01:00
brunoherbelin
6e7df60f2c Minor bugfix and Actionmanager undo message improvement. 2021-03-20 10:03:54 +01:00
brunoherbelin
112b583379 Entire cleanup of ActionManager
History of previous id of sources is now obtained in SessionLoader; no
need to store the id of the source in action manager XML (so also
removed in View current id).
2021-03-19 23:09:49 +01:00
brunoherbelin
b07009f3ce BugFix: SessionGroupSource creation and update in ActionManager and
SessionCreator.
2021-03-19 22:53:08 +01:00
brunoherbelin
74e9553d56 bugfix textureview lock source 2021-03-19 17:52:02 +01:00
brunoherbelin
2c3d6ff02e Implement lock source mechanism in TextureView 2021-03-19 13:30:58 +01:00
brunoherbelin
8dd47b3a41 Flatten selection to lower depth 2021-03-19 00:21:47 +01:00
brunoherbelin
09f052a5d6 Added undo-redo to locking of sources. 2021-03-18 21:56:06 +01:00
brunoherbelin
ac5e885fb3 Brute force implementation of undo-redo of mixing group 2021-03-18 21:44:38 +01:00
brunoherbelin
dd9c8ac0b8 Added option to choose mirror or repeat texture on source 2021-03-18 19:43:51 +01:00
brunoherbelin
0de1b87028 reorder ESC sequence 2021-03-18 14:42:51 +01:00
brunoherbelin
e830a6eefe Mixing Group improved UI feedback 2021-03-18 14:42:33 +01:00
brunoherbelin
3c875a064e Active usual function keys without focus on workspace 2021-03-17 23:08:17 +01:00
brunoherbelin
2227c97a57 New action when clic on source symbol in Mixing or Layer views: open
editor in UI
2021-03-17 22:50:21 +01:00
brunoherbelin
ea211cb8ab Prevent action on source after locking it from side panel 2021-03-17 22:20:49 +01:00
brunoherbelin
6d2112fcd9 missing initialization layer view default appearance 2021-03-17 22:19:54 +01:00
brunoherbelin
05cb1db020 Minor UX improvement when mixing group rotation does not have the
expected effect
2021-03-17 22:03:19 +01:00
brunoherbelin
cd4d8f02cb Fixed source picking problems
Allow unlock of source in geometry, do not allow selection of locked
source with CTRL
2021-03-17 21:56:35 +01:00
brunoherbelin
b8fe0d7c69 Improved selection action in mixing and layer views 2021-03-17 21:13:22 +01:00
brunoherbelin
81c173e9c3 prevent null scale texture UV 2021-03-17 20:03:40 +01:00
brunoherbelin
63c954dedc Improved overlay grid 2021-03-17 18:49:20 +01:00
brunoherbelin
91d1ff1eb1 Mixing center action takes barycenter 2021-03-17 05:18:27 +01:00
brunoherbelin
41efc572e0 Improved keyboard manipulation of selection of sources in Views. 2021-03-17 05:12:00 +01:00
brunoherbelin
77764248b5 Added ALT modifier to selection rotation 2021-03-16 22:09:15 +01:00
brunoherbelin
ca0058c741 Action manager for undo action of keyboard arrow keys 2021-03-16 21:29:38 +01:00
brunoherbelin
8bd74ec725 Geometry selection Mirror action 2021-03-15 23:39:39 +01:00
brunoherbelin
9a5983d6de Selection pick bug fix 2021-03-15 23:25:54 +01:00
brunoherbelin
ce38bf72b8 Action manager for undo of context menu actions 2021-03-15 22:55:22 +01:00
brunoherbelin
ecba54196f Mixing and Geometry Selection menu actions 2021-03-15 21:26:30 +01:00
brunoherbelin
3b09bc877c Introducing Oriented bounding box for GeometryView selection
First implementation of MixingView selection manipulation (scale and
rotate)
2021-03-15 11:56:16 +01:00
brunoherbelin
92663aa171 Select group sources with ctrl+clic 2021-03-13 09:23:39 +01:00
brunoherbelin
c41d7ee067 BugFix select current 2021-03-12 23:56:49 +01:00
brunoherbelin
5ab5f1b60f bruteforce and efficient implementation of mixing groups management in
session.
2021-03-12 20:25:36 +01:00
brunoherbelin
10f9c1b329 Work in progress Implementation of mixing group
link and unlink methods, integration in MixingView, update groups on
source change, undo-redo improved.
2021-03-10 23:38:09 +01:00
brunoherbelin
2d62ab969c Work in progress: undo & redo of mixing group creation and delete
actions.
2021-03-10 00:16:49 +01:00
Bruno
7656113dcc Large commit for implementation of load&save of MixingGroups 2021-03-07 19:27:00 +01:00
Bruno
56f0165d75 Implementation of mixing group actions 2021-03-06 11:40:00 +01:00
Bruno
d79c4cbfe1 Fixed rendering LineStrip 2021-03-06 11:39:01 +01:00
Bruno
a55765c100 Add Symbol rotation 2021-03-06 11:38:17 +01:00
Bruno
134617bbd1 Created new Object MixingGoup 2021-03-03 22:39:36 +01:00
Bruno
2ccedd42e4 Cleanup code and includes 2021-03-03 22:39:17 +01:00
Bruno
d6d1ab5099 Clean code and includes 2021-03-03 22:37:56 +01:00
Bruno
b8d323ad59 Longer notification time 2021-03-03 22:37:18 +01:00
Bruno
737269bf5a New Primitive LineLoop (and cleanup associated visitors) 2021-03-03 22:36:59 +01:00
brunoherbelin
e54389b79c Improve context menu (icons and labels) 2021-02-28 17:38:18 +01:00
brunoherbelin
2906c50642 Change terminology sub-session 2021-02-28 14:10:55 +01:00
brunoherbelin
8123e61e34 Cleanup depth management 2021-02-28 14:10:32 +01:00
brunoherbelin
70cc66a7f4 Added edit menu in New source panel 2021-02-28 10:19:30 +01:00
brunoherbelin
13672a9d01 Use dichotomic algorithm to converge to new Alpha 2021-02-28 10:18:42 +01:00
brunoherbelin
f2cd18f754 Cleanup and unify views interface (combo) 2021-02-28 10:18:10 +01:00
brunoherbelin
7e723f4142 Use SourceTrail to analyse structure 2021-02-28 10:15:50 +01:00
brunoherbelin
adcd735127 Clean include tree for view cpp 2021-02-26 23:33:50 +01:00
brunoherbelin
70c28d4226 Renamed Appearance view to Texture view. 2021-02-26 23:15:14 +01:00
Bruno
004e1aaead Compile Views in separate source files 2021-02-26 23:09:51 +01:00
Bruno
e7a5d341e4 Dispatch code of Views in separate source files 2021-02-26 23:09:22 +01:00
brunoherbelin
f7b93478ed Reimplementation of LineStrip primitive using DYNAMIC vertext array and
triangle strips (basic mesh).
2021-02-26 17:01:24 +01:00
brunoherbelin
afc0c7af0e Fixed FPS stable computation 2021-02-23 23:44:04 +01:00
brunoherbelin
0ee5eebf91 Linux compilation fix 2021-02-23 23:43:41 +01:00
brunoherbelin
d0fdbeb14f Changed dt and fps computation in mixer 2021-02-23 23:11:16 +01:00
brunoherbelin
38f1288571 Reading version from git 2021-02-23 20:04:37 +01:00
brunoherbelin
4093170599 New blending: hard light 2021-02-22 18:26:14 +01:00
brunoherbelin
27112a2b57 AlphaShader for mapping alpha in pre-render 2021-02-22 18:26:00 +01:00
154 changed files with 18638 additions and 8370 deletions

6
.gitignore vendored
View File

@@ -18,3 +18,9 @@ rules.ninja
/CMakeLists.txt.user.*
rsc/shaders/paint.fs
osx/.DS_Store
.DS_Store
osx/runvimix

View File

@@ -4,10 +4,14 @@
#include "Log.h"
#include "View.h"
#include "Mixer.h"
#include "MixingGroup.h"
#include "tinyxml2Toolkit.h"
#include "ImageProcessingShader.h"
#include "SessionVisitor.h"
#include "SessionCreator.h"
#include "Settings.h"
#include "BaseToolkit.h"
#include "Interpolator.h"
#include "ActionManager.h"
@@ -15,241 +19,379 @@
#define ACTION_DEBUG
#endif
#define HISTORY_NODE(i) std::to_string(i).insert(0,1,'H')
#define SNAPSHOT_NODE(i) std::to_string(i).insert(0,1,'S')
using namespace tinyxml2;
Action::Action(): step_(0), max_step_(0)
void captureMixerSession(tinyxml2::XMLDocument *doc, std::string node, std::string label)
{
}
void Action::clear()
{
// clean the history
xmlDoc_.Clear();
step_ = 0;
max_step_ = 0;
// start fresh
store("Session start");
}
void Action::store(const std::string &label, uint64_t id)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
step_++;
std::string nodename = "H" + std::to_string(step_);
// erase future
for (uint e = step_; e <= max_step_; e++) {
std::string name = "H" + std::to_string(e);
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
if ( node )
xmlDoc_.DeleteChild(node);
}
max_step_ = step_;
// create history node
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
xmlDoc_.InsertEndChild(sessionNode);
// create node
XMLElement *sessionNode = doc->NewElement( node.c_str() );
doc->InsertEndChild(sessionNode);
// label describes the action
sessionNode->SetAttribute("label", label.c_str());
// id indicates which object was modified
sessionNode->SetAttribute("id", id);
sessionNode->SetAttribute("label", label.c_str() );
// view indicates the view when this action occured
sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode());
// get session to operate on
Session *se = Mixer::manager().session();
// get the thumbnail (requires one opengl update to render)
FrameBufferImage *thumbnail = se->renderThumbnail();
if (thumbnail) {
XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc);
if (imageelement)
sessionNode->InsertEndChild(imageelement);
delete thumbnail;
}
// save all sources using source visitor
SessionVisitor sv(&xmlDoc_, sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
SessionVisitor sv(doc, sessionNode);
for (auto iter = se->begin(); iter != se->end(); ++iter, sv.setRoot(sessionNode) )
(*iter)->accept(sv);
// debug
}
Action::Action(): history_step_(0), history_max_step_(0), locked_(false),
snapshot_id_(0), snapshot_node_(nullptr), interpolator_(nullptr), interpolator_node_(nullptr)
{
}
void Action::init()
{
// clean the history
history_doc_.Clear();
history_step_ = 0;
history_max_step_ = 0;
// reset snapshot
snapshot_id_ = 0;
snapshot_node_ = nullptr;
store("Session start");
}
void Action::store(const std::string &label)
{
// ignore if locked or if no label is given
if (locked_ || label.empty())
return;
// incremental naming of history nodes
history_step_++;
// erase future
for (uint e = history_step_; e <= history_max_step_; e++) {
XMLElement *node = history_doc_.FirstChildElement( HISTORY_NODE(e).c_str() );
if ( node )
history_doc_.DeleteChild(node);
}
history_max_step_ = history_step_;
// threaded capturing state of current session
std::thread(captureMixerSession, &history_doc_, HISTORY_NODE(history_step_), label).detach();
#ifdef ACTION_DEBUG
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
Log::Info("Action stored %d '%s'", history_step_, label.c_str());
// XMLSaveDoc(&history_doc_, "/home/bhbn/history.xml");
#endif
}
void Action::undo()
{
// not possible to go to 1 -1 = 0
if (step_ <= 1)
if (history_step_ <= 1)
return;
// what id was modified to get to this step ?
// get history node of current step
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ - 1
restore( step_ - 1, id);
restore( history_step_ - 1);
}
void Action::redo()
{
// not possible to go to max_step_ + 1
if (step_ >= max_step_)
if (history_step_ >= history_max_step_)
return;
// what id to modify to go to next step ?
std::string nodename = "H" + std::to_string(step_ + 1);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
uint64_t id = 0;
sessionNode->QueryUnsigned64Attribute("id", &id);
// restore always changes step_ to step_ + 1
restore( step_ + 1, id);
restore( history_step_ + 1);
}
void Action::stepTo(uint target)
{
// get reasonable target
uint t = CLAMP(target, 1, max_step_);
uint t = CLAMP(target, 1, history_max_step_);
// going backward
if ( t < step_ ) {
// go back one step at a time
while (t < step_)
undo();
}
// step forward
else if ( t > step_ ) {
// go forward one step at a time
while (t > step_)
redo();
}
// ignore t == step_
if (t != history_step_)
restore(t);
}
std::string Action::label(uint s) const
{
std::string l = "";
if (s > 0 && s <= max_step_) {
std::string nodename = "H" + std::to_string(s);
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
l = sessionNode->Attribute("label");
if (s > 0 && s <= history_max_step_) {
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
if (sessionNode)
l = sessionNode->Attribute("label");
}
return l;
}
void Action::restore(uint target, uint64_t id)
FrameBufferImage *Action::thumbnail(uint s) const
{
FrameBufferImage *img = nullptr;
if (s > 0 && s <= history_max_step_) {
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
if (sessionNode)
img = SessionLoader::XMLToImage(sessionNode);
}
return img;
}
void Action::restore(uint target)
{
// lock
locked_ = true;
// get history node of target step
step_ = CLAMP(target, 1, max_step_);
std::string nodename = "H" + std::to_string(step_);
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
history_step_ = CLAMP(target, 1, history_max_step_);
XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(history_step_).c_str() );
// ask view to refresh, and switch to action view if user prefers
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
if (sessionNode) {
#ifdef ACTION_DEBUG
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
#endif
// ask view to refresh, and switch to action view if user prefers
int view = Settings::application.current_view ;
if (Settings::application.action_history_follow_view)
sessionNode->QueryIntAttribute("view", &view);
Mixer::manager().setView( (View::Mode) view);
// we operate on the current session
Session *se = Mixer::manager().session();
if (se == nullptr)
return;
// sessionsources contains list of ids of all sources currently in the session
std::list<uint64_t> sessionsources = se->getIdList();
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
// load history status:
// - if a source exists, its attributes are updated, and that's all
// - if a source does not exists (in session), it is created in the session
SessionLoader loader( se );
loader.load( sessionNode );
// loadersources contains list of ids of all sources generated by loader
std::list<uint64_t> loadersources = loader.getIdList();
// for( auto it = loadersources.begin(); it != loadersources.end(); it++)
// Log::Info("loadersources id %s", std::to_string(*it).c_str());
// remove intersect of both lists (sources were updated by SessionLoader)
for( auto lsit = loadersources.begin(); lsit != loadersources.end(); ){
auto ssit = std::find(sessionsources.begin(), sessionsources.end(), (*lsit));
if ( ssit != sessionsources.end() ) {
lsit = loadersources.erase(lsit);
sessionsources.erase(ssit);
}
else
lsit++;
}
// remaining ids in list sessionsources : to remove
while ( !sessionsources.empty() ){
Source *s = Mixer::manager().findSource( sessionsources.front() );
if (s!=nullptr) {
#ifdef ACTION_DEBUG
Log::Info("Delete id %s", std::to_string(sessionsources.front() ).c_str());
#endif
// remove the source from the mixer
Mixer::manager().detach( s );
// delete source from session
se->deleteSource( s );
}
sessionsources.pop_front();
}
// remaining ids in list loadersources : to add
while ( !loadersources.empty() ){
#ifdef ACTION_DEBUG
Log::Info("Recreate id %s to %s", std::to_string(id).c_str(), std::to_string(loadersources.front()).c_str());
#endif
// change the history to match the new id
replaceSourceId(id, loadersources.front());
// add the source to the mixer
Mixer::manager().attach( Mixer::manager().findSource( loadersources.front() ) );
loadersources.pop_front();
// actually restore
Mixer::manager().restore(sessionNode);
}
// free
locked_ = false;
}
void Action::replaceSourceId(uint64_t previousid, uint64_t newid)
{
// loop over every session history step
XMLElement* historyNode = xmlDoc_.FirstChildElement("H1");
for( ; historyNode ; historyNode = historyNode->NextSiblingElement())
{
// check if this history node references this id
uint64_t id_history_ = 0;
historyNode->QueryUnsigned64Attribute("id", &id_history_);
if ( id_history_ == previousid )
// change to new id
historyNode->SetAttribute("id", newid);
// loop over every source in session history
XMLElement* sourceNode = historyNode->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
// check if this source node has this id
uint64_t id_source_ = 0;
sourceNode->QueryUnsigned64Attribute("id", &id_source_);
if ( id_source_ == previousid )
// change to new id
sourceNode->SetAttribute("id", newid);
void Action::snapshot(const std::string &label)
{
// ignore if locked
if (locked_)
return;
std::string snap_label = BaseToolkit::uniqueName(label, labels());
// create snapshot id
u_int64_t id = BaseToolkit::uniqueId();
// get session to operate on
Session *se = Mixer::manager().session();
se->snapshots()->keys_.push_back(id);
// threaded capture state of current session
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label).detach();
#ifdef ACTION_DEBUG
Log::Info("Snapshot stored %d '%s'", id, snap_label.c_str());
#endif
}
void Action::open(uint64_t snapshotid)
{
if ( snapshot_id_ != snapshotid )
{
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
snapshot_node_ = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snapshot_node_)
snapshot_id_ = snapshotid;
else
snapshot_id_ = 0;
interpolator_node_ = nullptr;
}
}
void Action::replace(uint64_t snapshotid)
{
// ignore if locked or if no label is given
if (locked_)
return;
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
// remember label
std::string label = snapshot_node_->Attribute("label");
// remove previous node
Session *se = Mixer::manager().session();
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
// threaded capture state of current session
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(snapshot_id_), label).detach();
#ifdef ACTION_DEBUG
Log::Info("Snapshot replaced %d '%s'", snapshot_id_, label.c_str());
#endif
}
}
std::list<uint64_t> Action::snapshots() const
{
return Mixer::manager().session()->snapshots()->keys_;
}
std::list<std::string> Action::labels() const
{
std::list<std::string> names;
tinyxml2::XMLDocument *doc = Mixer::manager().session()->snapshots()->xmlDoc_;
for ( XMLElement *snap = doc->FirstChildElement(); snap ; snap = snap->NextSiblingElement() )
names.push_back( snap->Attribute("label"));
return names;
}
std::string Action::label(uint64_t snapshotid) const
{
std::string label = "";
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snap)
label = snap->Attribute("label");
return label;
}
void Action::setLabel (uint64_t snapshotid, const std::string &label)
{
open(snapshotid);
if (snapshot_node_)
snapshot_node_->SetAttribute("label", label.c_str());
}
FrameBufferImage *Action::thumbnail(uint64_t snapshotid) const
{
FrameBufferImage *img = nullptr;
// get snapshot node of target in current session
Session *se = Mixer::manager().session();
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
if (snap){
img = SessionLoader::XMLToImage(snap);
}
return img;
}
void Action::remove(uint64_t snapshotid)
{
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
// remove
Session *se = Mixer::manager().session();
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
se->snapshots()->keys_.remove( snapshot_id_ );
}
snapshot_node_ = nullptr;
snapshot_id_ = 0;
}
void Action::restore(uint64_t snapshotid)
{
// lock
locked_ = true;
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_)
// actually restore
Mixer::manager().restore(snapshot_node_);
// free
locked_ = false;
store("Snapshot " + label(snapshot_id_));
}
float Action::interpolation()
{
float ret = 0.f;
if ( interpolator_node_ == snapshot_node_ && interpolator_)
ret = interpolator_->current();
return ret;
}
void Action::interpolate(float val, uint64_t snapshotid)
{
if (snapshotid > 0)
open(snapshotid);
if (snapshot_node_) {
if ( interpolator_node_ != snapshot_node_ ) {
// change interpolator
if (interpolator_)
delete interpolator_;
// create new interpolator
interpolator_ = new Interpolator;
// current session
Session *se = Mixer::manager().session();
XMLElement* N = snapshot_node_->FirstChildElement("Source");
for( ; N ; N = N->NextSiblingElement()) {
// check if a source with the given id exists in the session
uint64_t id_xml_ = 0;
N->QueryUnsigned64Attribute("id", &id_xml_);
SourceList::iterator sit = se->find(id_xml_);
// a source with this id exists
if ( sit != se->end() ) {
// read target in the snapshot xml
SourceCore target;
SessionLoader::XMLToSourcecore(N, target);
// add an interpolator for this source
interpolator_->add(*sit, target);
}
}
// operate interpolation on opened snapshot
interpolator_node_ = snapshot_node_;
}
if (interpolator_) {
// Log::Info("Action::interpolate %f", val);
interpolator_->apply( val );
}
}
}

View File

@@ -1,48 +1,76 @@
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include <list>
#include <atomic>
#include <tinyxml2.h>
class Interpolator;
class FrameBufferImage;
class Action
{
// Private Constructor
Action();
Action(Action const& copy); // Not Implemented
Action& operator=(Action const& copy); // Not Implemented
Action(Action const& copy) = delete;
Action& operator=(Action const& copy) = delete;
public:
static Action& manager()
static Action& manager ()
{
// The only instance
static Action _instance;
return _instance;
}
void init ();
void store(const std::string &label, uint64_t id = 0);
// Undo History
void store (const std::string &label);
void undo ();
void redo ();
void stepTo (uint target);
void clear();
void undo();
void redo();
void stepTo(uint target);
inline uint current () const { return history_step_; }
inline uint max () const { return history_max_step_; }
std::string label (uint s) const;
FrameBufferImage *thumbnail (uint s) const;
inline uint current() const { return step_; }
inline uint max() const { return max_step_; }
// Snapshots
void snapshot (const std::string &label = "");
std::string label(uint s) const;
std::list<uint64_t> snapshots () const;
uint64_t currentSnapshot () const { return snapshot_id_; }
void open (uint64_t snapshotid);
void replace (uint64_t snapshotid = 0);
void restore (uint64_t snapshotid = 0);
void remove (uint64_t snapshotid = 0);
std::string label (uint64_t snapshotid) const;
std::list<std::string> labels () const;
void setLabel (uint64_t snapshotid, const std::string &label);
FrameBufferImage *thumbnail (uint64_t snapshotid) const;
float interpolation ();
void interpolate (float val, uint64_t snapshotid = 0);
private:
void restore(uint target, uint64_t id);
void replaceSourceId(uint64_t previousid, uint64_t newid);
tinyxml2::XMLDocument xmlDoc_;
uint step_;
uint max_step_;
tinyxml2::XMLDocument history_doc_;
uint history_step_;
uint history_max_step_;
std::atomic<bool> locked_;
void restore(uint target);
uint64_t snapshot_id_;
tinyxml2::XMLElement *snapshot_node_;
Interpolator *interpolator_;
tinyxml2::XMLElement *interpolator_node_;
};
#endif // ACTIONMANAGER_H

259
BaseToolkit.cpp Normal file
View File

@@ -0,0 +1,259 @@
#include "BaseToolkit.h"
#include <chrono>
#include <ctime>
#include <sstream>
#include <list>
#include <iomanip>
#include <algorithm>
#include <climits>
#include <map>
#include <locale>
#include <unicode/ustream.h>
#include <unicode/translit.h>
uint64_t BaseToolkit::uniqueId()
{
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
// 64-bit int 18446744073709551615UL
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
}
std::string BaseToolkit::uniqueName(const std::string &basename, std::list<std::string> existingnames)
{
std::string tentativename = basename;
int count = 1;
int max = 100;
// while tentativename can be found in the list of existingnames
while ( std::find( existingnames.begin(), existingnames.end(), tentativename ) != existingnames.end() )
{
for( auto it = existingnames.cbegin(); it != existingnames.cend(); ++it) {
if ( it->find(tentativename) != std::string::npos)
++count;
}
if (count > 1)
tentativename = basename + "_" + std::to_string( count );
else
tentativename += "_";
if ( --max < 0 ) // for safety only, should never be needed
break;
}
return tentativename;
}
// Using ICU transliteration :
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
std::string BaseToolkit::transliterate(const std::string &input)
{
// because icu::Transliterator is slow, we keep a dictionnary of already
// transliterated texts to be faster during repeated calls (update of user interface)
static std::map<std::string, std::string> dictionnary_;
std::map<std::string, std::string>::const_iterator existingentry = dictionnary_.find(input);
if (existingentry == dictionnary_.cend()) {
auto ucs = icu::UnicodeString::fromUTF8(input);
UErrorCode status = U_ZERO_ERROR;
icu::Transliterator *firstTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status);
firstTrans->transliterate(ucs);
delete firstTrans;
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
secondTrans->transliterate(ucs);
delete secondTrans;
std::ostringstream output;
output << ucs;
// remember for future
dictionnary_[input] = output.str();
}
// return remembered transliterated text
return dictionnary_[input];
}
std::string BaseToolkit::byte_to_string(long b)
{
double numbytes = static_cast<double>(b);
std::ostringstream oss;
std::list<std::string> list = {" Bytes", " KB", " MB", " GB", " TB"};
std::list<std::string>::iterator i = list.begin();
while(numbytes >= 1024.0 && i != list.end())
{
++i;
numbytes /= 1024.0;
}
oss << std::fixed << std::setprecision(2) << numbytes;
if (i != list.end()) oss << *i;
return oss.str();
}
std::string BaseToolkit::bits_to_string(long b)
{
double numbytes = static_cast<double>(b);
std::ostringstream oss;
std::list<std::string> list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"};
std::list<std::string>::iterator i = list.begin();
while(numbytes >= 1000.0 && i != list.end())
{
++i;
numbytes /= 1000.0;
}
oss << std::fixed << std::setprecision(2) << numbytes;
if (i != list.end()) oss << *i;
return oss.str();
}
std::string BaseToolkit::trunc_string(const std::string& path, int N)
{
std::string trunc = path;
int l = path.size();
if ( l > N ) {
trunc = std::string("...") + path.substr( l - N + 3 );
}
return trunc;
}
std::string BaseToolkit::common_prefix( const std::list<std::string> & allStrings )
{
if (allStrings.empty())
return std::string();
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
}
return std::string(s0.cbegin(), _end);
}
std::string BaseToolkit::common_suffix(const std::list<std::string> & allStrings)
{
if (allStrings.empty())
return std::string();
const std::string &s0 = allStrings.front();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
return suffix;
}
std::string BaseToolkit::common_pattern(const std::list<std::string> &allStrings)
{
if (allStrings.empty())
return std::string();
// find common prefix and suffix
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
return std::string(s0.cbegin(), _end) + "*" + suffix;
}
std::string BaseToolkit::common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max)
{
if (allStrings.empty())
return std::string();
// find common prefix and suffix
const std::string &s0 = allStrings.front();
auto _end = s0.cend();
auto r_end = s0.crend();
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
{
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
if (std::distance(_loc.first, _end) > 0)
_end = _loc.first;
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
if (std::distance(r_loc.first, r_end) > 0)
r_end = r_loc.first;
}
// range of middle string, after prefix and before suffix
size_t pos_prefix = std::distance(s0.cbegin(), _end);
size_t pos_suffix = s0.size() - pos_prefix - std::distance(s0.crbegin(), r_end);
int n = -1;
*max = 0;
*min = INT_MAX;
// loop over all strings to verify there are numbers between prefix and suffix
for (auto it = allStrings.cbegin(); it != allStrings.cend(); ++it)
{
// get middle string, after prefix and before suffix
std::string s = it->substr(pos_prefix, pos_suffix);
// is this central string ONLY made of digits?
if (s.end() == std::find_if(s.begin(), s.end(), [](unsigned char c)->bool { return !isdigit(c); })) {
// yes, validate
*max = std::max(*max, std::atoi(s.c_str()) );
*min = std::min(*min, std::atoi(s.c_str()) );
if (n < 0)
n = s.size();
else if ( n != s.size() ) {
n = 0;
break;
}
}
else {
n = 0;
break;
}
}
if ( n < 1 )
return std::string();
std::string suffix = std::string(s0.crbegin(), r_end);
std::reverse(suffix.begin(), suffix.end());
std::string pattern = std::string(s0.cbegin(), _end);
pattern += "%0" + std::to_string(n) + "d";
pattern += suffix;
return pattern;
}

41
BaseToolkit.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef BASETOOLKIT_H
#define BASETOOLKIT_H
#include <list>
#include <string>
namespace BaseToolkit
{
// get integer with unique id
uint64_t uniqueId();
// proposes a name that is not already in the list
std::string uniqueName(const std::string &basename, std::list<std::string> existingnames);
// get a transliteration to Latin of any string
std::string transliterate(const std::string &input);
// get a string to display memory size with unit KB, MB, GB, TB
std::string byte_to_string(long b);
// get a string to display bit size with unit Kbit, MBit, Gbit, Tbit
std::string bits_to_string(long b);
// Truncate a string to display the right most N characters (e.g. ./home/me/toto.mpg -> ...ome/me/toto.mpg)
std::string trunc_string(const std::string& path, int N);
// find common parts in a list of strings
std::string common_prefix(const std::list<std::string> &allStrings);
std::string common_suffix(const std::list<std::string> &allStrings);
// form a pattern "prefix*suffix" (e.g. file list)
std::string common_pattern(const std::list<std::string> &allStrings);
// form a pattern "prefix%03dsuffix" (e.g. numbered file list)
std::string common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max);
}
#endif // BASETOOLKIT_H

View File

@@ -1,14 +1,18 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#include <map>
#include "Log.h"
#include "View.h"
#include "Primitives.h"
#include "Source.h"
#include "Decorations.h"
#include "BoundingBoxVisitor.h"
#include "Log.h"
#include "Primitives.h"
#include "Decorations.h"
BoundingBoxVisitor::BoundingBoxVisitor(bool force): force_(force)
BoundingBoxVisitor::BoundingBoxVisitor(bool force): force_(force), modelview_(glm::identity<glm::mat4>())
{
modelview_ = glm::identity<glm::mat4>();
}
@@ -34,7 +38,7 @@ void BoundingBoxVisitor::visit(Group &n)
if (!n.visible_)
return;
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;
@@ -64,3 +68,47 @@ void BoundingBoxVisitor::visit(Scene &n)
n.ws()->accept(*this);
}
GlmToolkit::AxisAlignedBoundingBox BoundingBoxVisitor::AABB(SourceList l, View *view)
{
// calculate bbox on selection
BoundingBoxVisitor selection_visitor_bbox;
for (auto it = l.begin(); it != l.end(); ++it) {
// calculate bounding box of area covered by selection
selection_visitor_bbox.setModelview( view->scene.ws()->transform_ );
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
}
return selection_visitor_bbox.bbox();
}
GlmToolkit::OrientedBoundingBox BoundingBoxVisitor::OBB(SourceList l, View *view)
{
GlmToolkit::OrientedBoundingBox obb_;
// try the orientation of each source in the list
for (auto source_it = l.begin(); source_it != l.end(); ++source_it) {
float angle = (*source_it)->group( view->mode() )->rotation_.z;
glm::mat4 transform = view->scene.ws()->transform_;
transform = glm::rotate(transform, -angle, glm::vec3(0.f, 0.f, 1.f) );
// calculate bbox of the list in this orientation
BoundingBoxVisitor selection_visitor_bbox;
for (auto it = l.begin(); it != l.end(); ++it) {
// calculate bounding box of area covered by sources' nodes
selection_visitor_bbox.setModelview( transform );
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
}
// if not initialized or if new bbox is smaller than previous
if ( obb_.aabb.isNull() || selection_visitor_bbox.bbox() < obb_.aabb) {
// keep this bbox as candidate
obb_.aabb = selection_visitor_bbox.bbox();
obb_.orientation = glm::vec3(0.f, 0.f, angle);
}
}
return obb_;
}

View File

@@ -3,7 +3,8 @@
#include "GlmToolkit.h"
#include "Visitor.h"
#include "SourceList.h"
class View;
class BoundingBoxVisitor: public Visitor
{
@@ -11,6 +12,7 @@ class BoundingBoxVisitor: public Visitor
bool force_;
GlmToolkit::AxisAlignedBoundingBox bbox_;
public:
BoundingBoxVisitor(bool force = false);
@@ -24,6 +26,8 @@ public:
void visit(Switch& n) override;
void visit(Primitive& n) override;
static GlmToolkit::AxisAlignedBoundingBox AABB(SourceList l, View *view);
static GlmToolkit::OrientedBoundingBox OBB(SourceList l, View *view);
};
#endif // BOUNDINGBOXVISITOR_H

View File

@@ -1,11 +1,37 @@
cmake_minimum_required(VERSION 3.8.0)
cmake_minimum_required(VERSION 3.8.2)
project(vimix VERSION 0.0.1 LANGUAGES CXX C)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# use git
find_package (Git)
if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT GIT_DESCRIBE_ERROR_CODE)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 0 1 VIMIX_VERSION_MAJOR)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 2 1 VIMIX_VERSION_MINOR)
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 4 1 VIMIX_VERSION_PATCH)
add_definitions(-DVIMIX_VERSION_MAJOR=${VIMIX_VERSION_MAJOR})
add_definitions(-DVIMIX_VERSION_MINOR=${VIMIX_VERSION_MINOR})
add_definitions(-DVIMIX_VERSION_PATCH=${VIMIX_VERSION_PATCH})
message(STATUS "Compiling vimix version ${VIMIX_VERSION_MAJOR}.${VIMIX_VERSION_MINOR}.${VIMIX_VERSION_PATCH}")
else()
message(STATUS "Compiling vimix (unknown version)")
endif()
endif()
set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON")
set(CMAKE_INCLUDE_CURRENTDIR ON)
# Find the cmake modules
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules )
include(MacroLogFeature)
include(MacroFindGStreamerLibrary)
if(UNIX)
if (APPLE)
@@ -14,15 +40,22 @@ if(UNIX)
# the RPATH to be used when installing
set(CMAKE_SKIP_RPATH TRUE)
set(OpenGL_DIR /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/)
set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
# set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment")
set(CMAKE_OSX_ARCHITECTURES "x86_64")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
# find icu4c in OSX (pretty well hidden...)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/icu4c/lib/pkgconfig")
else()
add_definitions(-DLINUX)
# linux opengl
set(OpenGL_GL_PREFERENCE "GLVND")
# linux dialogs use GTK
find_package(GTK 3.0 REQUIRED)
macro_log_feature(GTK_FOUND "GTK" "GTK cross-platform widget toolkit" "http://www.gtk.org" TRUE)
endif()
add_definitions(-DUNIX)
elseif(WIN32)
@@ -31,8 +64,18 @@ elseif(WIN32)
endif()
# Include the CMake RC module
include(CMakeRC)
# Basics
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
find_package(GLIB2)
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org" TRUE)
find_package(GObject)
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org" TRUE)
find_package(PNG REQUIRED)
macro_log_feature(PNG_FOUND "PNG" "Portable Network Graphics" "http://www.libpng.org" TRUE)
#
# GSTREAMER
@@ -42,68 +85,48 @@ find_package(GStreamer 1.0.0 COMPONENTS base)
macro_log_feature(GSTREAMER_FOUND "GStreamer"
"Open Source Multiplatform Multimedia Framework"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_BASE_LIBRARY_FOUND "GStreamer base library"
"${GSTREAMER_BASE_LIBRARY}"
"http://gstreamer.freedesktop.org/" FALSE "1.0.0")
find_package(GStreamerPluginsBase 1.0.0 COMPONENTS app audio video pbutils gl)
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamer app library"
"${GSTREAMER_APP_LIBRARY}"
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer app library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamer audio library"
"${GSTREAMER_AUDIO_LIBRARY}"
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer audio library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamer video library"
"${GSTREAMER_VIDEO_LIBRARY}"
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer video library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamer pbutils library"
"${GSTREAMER_PBUTILS_LIBRARY}"
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer pbutils library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamer opengl library"
"${GSTREAMER_GL_LIBRARY}"
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer opengl library"
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
#find_package(GStreamerPluginsBad 1.0.0 COMPONENTS player)
#macro_log_feature(GSTREAMER_PLAYER_LIBRARY_FOUND "GStreamer player library"
#"${GSTREAMER_PLAYER_LIBRARY}"
#"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
# Various preprocessor definitions for GST
add_definitions(-DGST_DISABLE_XML -DGST_DISABLE_LOADSAVE)
# Basics
find_package(GLIB2)
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org/" TRUE)
find_package(GObject)
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org/" TRUE)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
set(THREAD_LIBRARY Threads::Threads)
find_package(PNG REQUIRED)
set(PNG_LIBRARY PNG::PNG)
find_package(ICU REQUIRED COMPONENTS i18n io uc)
#
# ICU4C
#
if (PKG_CONFIG_FOUND)
pkg_check_modules(ICU REQUIRED icu-i18n icu-uc icu-io)
else ()
find_package(ICU REQUIRED COMPONENTS i18n io uc)
endif ()
macro_log_feature(ICU_FOUND "ICU" "International Components for Unicode" "http://site.icu-project.org" TRUE)
#
# GLFW3
# NB: set glfw3_PATH to /usr/local/Cellar/glfw/3.3.2/lib/cmake/glfw3
#
find_package(glfw3 3.2 REQUIRED)
macro_log_feature(glfw3_FOUND "GLFW3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org/" TRUE)
set(GLFW_LIBRARY glfw)
if (PKG_CONFIG_FOUND)
pkg_check_modules(GLFW3 REQUIRED glfw3>=3.2)
else ()
find_package(glfw3 3.2 REQUIRED)
endif()
macro_log_feature(GLFW3_FOUND "glfw3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org" TRUE)
#find_package(OpenGL REQUIRED)
macro_display_feature_log()
# static sub packages in ext
set(BUILD_STATIC_LIBS ON)
@@ -112,14 +135,14 @@ set(BUILD_STATIC_LIBS ON)
# GLM
#
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/glm)
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net.")
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net -- ${CMAKE_CURRENT_SOURCE_DIR}/ext/glm")
#
# GLAD
#
set(GLAD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/include)
add_library(GLAD "${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/src/glad.c")
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de/ -- ${GLAD_INCLUDE_DIR}.")
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de -- ${GLAD_INCLUDE_DIR}")
#
# DEAR IMGUI
@@ -135,23 +158,14 @@ set(IMGUI_SRCS
set(IMGUI_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/imgui)
add_library(IMGUI "${IMGUI_SRCS}")
target_compile_definitions(IMGUI PRIVATE "IMGUI_IMPL_OPENGL_LOADER_GLAD")
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}.")
#
# ImGui Color Text Editor
#
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
set(IMGUITEXTEDIT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
)
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}.")
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}")
#
# TINY XML 2
#
set(TINYXML2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2)
add_library(TINYXML2 "${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2/tinyxml2.cpp")
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}.")
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}")
#
# OSCPack
@@ -172,40 +186,47 @@ set(OSCPACK_SRCS
)
set(OSCPACK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack)
add_library(OSCPACK "${OSCPACK_SRCS}")
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}.")
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}")
#
# FILE DIALOG: use tinyfiledialog for all except Linux
#
if(UNIX)
if (APPLE)
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
set(TINYFD_LIBRARY TINYFD)
endif()
else()
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
set(TINYFD_LIBRARY TINYFD)
endif()
#
# ImGui Color Text Editor
#
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
set(IMGUITEXTEDIT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
)
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}")
#
# STB
#
set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/stb)
add_definitions(-DIMGUI_USE_STB_SPRINTF)
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}.")
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}")
#
# DIRENT
#
# DIRENT (windows only)
if(WIN32)
set(DIRENT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/Dirent/include)
message(STATUS "Including 'Dirent' from https://github.com/tronkko/dirent -- ${DIRENT_INCLUDE_DIR}.")
endif( WIN32 )
#
# TINY FILE DIALOG
#
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
#
# OBJ LOADER
#
#set(OBJLOADER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/obj)
#add_library(OBJLOADER "${CMAKE_CURRENT_SOURCE_DIR}/ext/obj/ObjLoader.cpp")
#message(STATUS "Compiling 'ObjLoader' from https://github.com/mortennobel/OpenGL_3_2_Utils -- ${OBJLOADER_INCLUDE_DIR}.")
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
endif()
#
# Application
@@ -220,9 +241,11 @@ include_directories(
${GSTREAMER_APP_INCLUDE_DIR}
${GSTREAMER_PBUTILS_INCLUDE_DIR}
${GSTREAMER_GL_INCLUDE_DIR}
${GLFW3_INCLUDE_DIRS}
${ICU_INCLUDE_DIRS}
${GLM_INCLUDE_DIRS}
${GLIB2_INCLUDE_DIR}
${GLAD_INCLUDE_DIR}
${GLM_INCLUDE_DIRS}
${IMGUI_INCLUDE_DIR}
${IMGUI_INCLUDE_DIR}/examples
${IMGUITEXTEDIT_INCLUDE_DIR}
@@ -231,7 +254,11 @@ include_directories(
${STB_INCLUDE_DIR}
${DIRENT_INCLUDE_DIR}
${OSCPACK_INCLUDE_DIR}
${ICU_INCLUDE_DIRS}
)
link_directories(
${GLFW3_LIBRARY_DIRS}
${ICU_LIBRARY_DIRS}
)
@@ -239,6 +266,7 @@ set(VMIX_BINARY "vimix")
set(VMIX_SRCS
main.cpp
Log.cpp
BaseToolkit.cpp
Shader.cpp
ImageShader.cpp
ImageProcessingShader.cpp
@@ -248,13 +276,22 @@ set(VMIX_SRCS
Mesh.cpp
Decorations.cpp
View.cpp
RenderView.cpp
GeometryView.cpp
MixingView.cpp
MixingGroup.cpp
LayerView.cpp
TextureView.cpp
TransitionView.cpp
Source.cpp
SourceList.cpp
Session.cpp
Selection.cpp
SessionSource.cpp
SessionVisitor.cpp
GarbageVisitor.cpp
Interpolator.cpp
SessionCreator.cpp
SessionParser.cpp
Mixer.cpp
FrameGrabber.cpp
Recorder.cpp
@@ -263,7 +300,6 @@ set(VMIX_SRCS
Settings.cpp
Screenshot.cpp
Resource.cpp
FileDialog.cpp
Timeline.cpp
Stream.cpp
MediaPlayer.cpp
@@ -272,6 +308,7 @@ set(VMIX_SRCS
PatternSource.cpp
DeviceSource.cpp
NetworkSource.cpp
MultiFileSource.cpp
FrameBuffer.cpp
RenderingManager.cpp
UserInterfaceManager.cpp
@@ -281,13 +318,16 @@ set(VMIX_SRCS
SearchVisitor.cpp
ImGuiToolkit.cpp
ImGuiVisitor.cpp
InfoVisitor.cpp
GstToolkit.cpp
GlmToolkit.cpp
SystemToolkit.cpp
DialogToolkit.cpp
tinyxml2Toolkit.cpp
NetworkToolkit.cpp
Connection.cpp
ActionManager.cpp
Overlay.cpp
)
@@ -390,10 +430,16 @@ set(VMIX_RSC_FILES
./rsc/mesh/icon_eye_slash.ply
./rsc/mesh/icon_vector_square_slash.ply
./rsc/mesh/icon_cube.ply
./rsc/mesh/icon_sequence.ply
./rsc/mesh/h_line.ply
./rsc/mesh/h_mark.ply
)
# Include the CMake RC module
include(CMakeRC)
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
### DEFINE THE TARGET (OS specific)
IF(APPLE)
@@ -407,7 +453,6 @@ IF(APPLE)
# create the application
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
${VMIX_SRCS}
./osx/CustomDelegate.m
${IMGUITEXTEDIT_SRC}
${MACOSX_BUNDLE_ICON_FILE}
)
@@ -423,17 +468,19 @@ IF(APPLE)
ELSE(APPLE)
link_directories (${GTK3_LIBRARY_DIRS})
add_executable(${VMIX_BINARY}
${VMIX_SRCS}
${IMGUITEXTEDIT_SRC}
)
set(PLATFORM_LIBS ""
set(PLATFORM_LIBS
GTK::GTK
)
ENDIF(APPLE)
### COMPILE THE TARGET (all OS)
set_property(TARGET ${VMIX_BINARY} PROPERTY CXX_STANDARD 17)
@@ -441,14 +488,16 @@ set_property(TARGET ${VMIX_BINARY} PROPERTY C_STANDARD 11)
target_compile_definitions(${VMIX_BINARY} PUBLIC "IMGUI_IMPL_OPENGL_LOADER_GLAD")
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
${GLFW_LIBRARY}
GLAD
vmix::rc
glm::glm
GLAD
TINYXML2
IMGUI
OSCPACK
${TINYFD_LIBRARY}
${GLFW3_LIBRARIES}
${ICU_LIBRARIES}
${CMAKE_DL_LIBS}
${GOBJECT_LIBRARIES}
${GSTREAMER_LIBRARY}
@@ -458,33 +507,23 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
${GSTREAMER_VIDEO_LIBRARY}
${GSTREAMER_PBUTILS_LIBRARY}
${GSTREAMER_GL_LIBRARY}
${GSTREAMER_PLAYER_LIBRARY}
${NFD_LIBRARY}
${PNG_LIBRARY}
${THREAD_LIBRARY}
TINYXML2
TINYFD
IMGUI
OSCPACK
vmix::rc
ICU::i18n
ICU::io
ICU::uc
Threads::Threads
PNG::PNG
${PLATFORM_LIBS}
)
macro_display_feature_log()
### DEFINE THE PACKAGING (all OS)
SET(CPACK_PACKAGE_NAME "vimix")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\n Real-time video mixing for live performance.")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\nReal-time video mixing for live performance.")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt")
SET(CPACK_PACKAGE_VERSION_MAJOR "0")
SET(CPACK_PACKAGE_VERSION_MINOR "5")
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
SET(CPACK_PACKAGE_HOMEPAGE_URL "https://brunoherbelin.github.io/vimix")
SET(CPACK_PACKAGE_VERSION_MAJOR "${VIMIX_VERSION_MAJOR}")
SET(CPACK_PACKAGE_VERSION_MINOR "${VIMIX_VERSION_MINOR}")
SET(CPACK_PACKAGE_VERSION_PATCH "${VIMIX_VERSION_PATCH}")
SET(CPACK_PACKAGE_VENDOR "Bruno Herbelin")
SET(CPACK_SOURCE_IGNORE_FILES
"/\\\\.git/"
@@ -493,23 +532,19 @@ SET(CPACK_SOURCE_IGNORE_FILES
)
# optimize size ?
SET(CPACK_STRIP_FILES TRUE)
### DEFINE THE PACKAGING (OS specific)
IF(APPLE)
# include( InstallRequiredSystemLibraries )
# Bundle target
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_GENERATOR DragNDrop)
set(CPACK_BINARY_DRAGNDROP ON)
# OSX cpack info
set(CPACK_SYSTEM_NAME "OSX_${CMAKE_OSX_DEPLOYMENT_TARGET}_${CMAKE_OSX_ARCHITECTURES}")
set(CPACK_BUNDLE_NAME ${CPACK_PACKAGE_NAME})
set(CPACK_BUNDLE_ICON ${MACOSX_BUNDLE_ICON_FILE})
set(CPACK_BUNDLE_PLIST ${MACOSX_BUNDLE_PLIST_FILE})
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
# set( APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/vimix${CMAKE_EXECUTABLE_SUFFIX}")
install(TARGETS ${VMIX_BINARY}
CONFIGURATIONS Release RelWithDebInfo
@@ -522,34 +557,49 @@ IF(APPLE)
### TODO configure auto to find installation dir of gst
message(STATUS "install gst-plugins ${PKG_GSTREAMER_PLUGIN_DIR}")
message(STATUS "install gst-plugins-base ${PKG_GSTREAMER_BASE_PLUGIN_DIR}")
if (PKG_CONFIG_FOUND)
pkg_check_modules(PKG_GSTREAMER_PLUGINS_BAD gstreamer-plugins-bad-${GSTREAMER_ABI_VERSION})
set(PKG_GSTREAMER_BAD_PLUGIN_DIR ${PKG_GSTREAMER_PLUGINS_BAD_LIBDIR}/gstreamer-${GSTREAMER_ABI_VERSION})
message(STATUS "install gst-plugins-bad ${PKG_GSTREAMER_BAD_PLUGIN_DIR}")
endif()
# intall the gst-plugin-scanner program (used by plugins at load time)
install(FILES "/usr/local/Cellar/gstreamer/1.18.1/libexec/gstreamer-1.0/gst-plugin-scanner"
set(PKG_GSTREAMER_SCANNER "${PKG_GSTREAMER_PREFIX}/libexec/gstreamer-1.0/gst-plugin-scanner")
message(STATUS "install gst-plugin-scanner ${PKG_GSTREAMER_SCANNER}")
install(FILES "${PKG_GSTREAMER_SCANNER}"
DESTINATION "${plugin_dest_dir}"
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
COMPONENT Runtime
)
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
install(FILES "/usr/local/Cellar/icu4c/67.1/lib/libicudata.67.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.67.dylib" COMPONENT Runtime)
# Install the gst-plugins (all those installed with brew )
install(DIRECTORY "${PKG_GSTREAMER_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-base/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.18.1_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "${PKG_GSTREAMER_BASE_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "${PKG_GSTREAMER_BAD_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled gst-plugins (because not included in brew package)
set(LOCAL_BUILD_BAD "/Users/herbelin/Development/gst/gst-plugins-bad-1.18.0/build")
install(FILES "${LOCAL_BUILD_BAD}/sys/applemedia/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "${LOCAL_BUILD_BAD}/ext/x265/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.4_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
# install locally recompiled & installed gst-plugins (because not included in brew package)
install(FILES "/usr/local/lib/gstreamer-1.0/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/usr/local/lib/gstreamer-1.0/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/usr/local/lib/gstreamer-1.0/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
# install(FILES "${ICU_LINK_LIBRARIES}" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
install(FILES "/usr/local/Cellar/icu4c/69.1/lib/libicudata.69.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.69.dylib" COMPONENT Runtime)
message(STATUS "install ${ICU_LINK_LIBRARIES} from ${ICU_LIBRARY_DIRS}")
# package runtime fixup bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
install(CODE "
file(GLOB_RECURSE GSTPLUGINS \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/gstreamer-1.0/*.dylib\")
list(APPEND LIBS_PATH \"/usr/local/Cellar/icu4c/67.1/lib\")
list(APPEND LIBS_PATH \"\${ICU_LIBRARY_DIRS}\")
include(BundleUtilities)
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
fixup_bundle(\"${APPS}\" \"\${GSTPLUGINS}\" \"${LIBS_PATH}\")
@@ -557,22 +607,43 @@ IF(APPLE)
COMPONENT Runtime
)
set(CPACK_BINARY_DRAGNDROP ON)
set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
set(APPLE_CODESIGN_IDENTITY "" CACHE STRING "")
string(LENGTH "${APPLE_CODESIGN_IDENTITY}" APPLE_CODESIGN_IDENTITY_LENGHT)
if( ${APPLE_CODESIGN_IDENTITY_LENGHT} LESS 40 )
message(STATUS "Not signing bundle. Specify APPLE_CODESIGN_IDENTITY to cmake before running cpack to sign")
else()
install(CODE "
execute_process(COMMAND
codesign --verbose=4 --deep --force
--entitlements \"${APPLE_CODESIGN_ENTITLEMENTS}\"
--sign \"${APPLE_CODESIGN_IDENTITY}\"
\"${APPS}\" )
"
COMPONENT Runtime
)
endif()
# # package runtime fixup bundle and codesign
# set(BUNDLE_NAME "vimix.app")
# set(BUNDLE_LIBS_DIR "${plugin_dest_dir}/gstreamer-1.0")
# set(BUNDLE_DIRS "${ICU_LIBRARY_DIRS}")
# set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
# configure_file(cmake/modules/BundleInstall.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" @ONLY)
# install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" COMPONENT Runtime)
ELSE(APPLE)
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/vimix")
install(TARGETS ${VMIX_BINARY}
CONFIGURATIONS Release RelWithDebInfo
RUNTIME DESTINATION bin COMPONENT Runtime
)
ENDIF(APPLE)
# Package full name
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}_${CPACK_SYSTEM_NAME}")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_SYSTEM_NAME}")
# To Create a package, run "cpack"
include(CPack)

View File

@@ -114,10 +114,10 @@ ConnectionInfo Connection::info(int index)
struct hasName: public std::unary_function<ConnectionInfo, bool>
{
inline bool operator()(const ConnectionInfo elem) const {
inline bool operator()(const ConnectionInfo &elem) const {
return (elem.name.compare(_a) == 0);
}
hasName(std::string a) : _a(a) { }
explicit hasName(const std::string &a) : _a(a) { }
private:
std::string _a;
};
@@ -157,7 +157,8 @@ void Connection::listen()
#ifdef CONNECTION_DEBUG
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
#endif
Connection::manager().receiver_->Run();
if (Connection::manager().receiver_)
Connection::manager().receiver_->Run();
}
void Connection::ask()
@@ -185,7 +186,7 @@ void Connection::ask()
// check the list of connections for non responding (disconnected)
std::vector< ConnectionInfo >::iterator it = Connection::manager().connections_.begin();
for(it++; it!=Connection::manager().connections_.end(); ) {
for(++it; it!=Connection::manager().connections_.end(); ) {
// decrease life score
(*it).alive--;
// erase connection if its life score is negative (not responding too many times)
@@ -201,7 +202,7 @@ void Connection::ask()
}
// loop
else
it++;
++it;
}
}

View File

@@ -62,8 +62,8 @@ class Connection
// Private Constructor
Connection();
Connection(Connection const& copy); // Not Implemented
Connection& operator=(Connection const& copy); // Not Implemented
Connection(Connection const& copy) = delete;
Connection& operator=(Connection const& copy) = delete;
public:
static Connection& manager()

85
CopyVisitor.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "GlmToolkit.h"
#include "Scene.h"
#include "Source.h"
#include "CopyVisitor.h"
Node *CopyVisitor::deepCopy(Node *node)
{
CopyVisitor cv;
node->accept(cv);
return cv.current_;
}
CopyVisitor::CopyVisitor() : current_(nullptr)
{
}
void CopyVisitor::visit(Node &n)
{
}
void CopyVisitor::visit(Group &n)
{
Group *here = new Group;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
// loop
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
here->attach( current_ );
current_ = here;
}
}
void CopyVisitor::visit(Switch &n)
{
Switch *here = new Switch;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
// switch properties
here->setActive( n.active() );
// loop
for (uint i = 0; i < n.numChildren(); ++i) {
n.child(i)->accept(*this);
here->attach( current_ );
current_ = here;
}
}
void CopyVisitor::visit(Scene &n)
{
Scene *here = new Scene;
current_ = here->root();
n.root()->accept(*this);
}
void CopyVisitor::visit(Primitive &n)
{
Primitive *here = new Primitive;
// node
current_ = here;
current_->copyTransform(&n);
current_->visible_ = n.visible_;
}

24
CopyVisitor.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef COPYVISITOR_H
#define COPYVISITOR_H
#include "Visitor.h"
class SourceCore;
class CopyVisitor : public Visitor
{
Node *current_;
CopyVisitor();
public:
static Node *deepCopy(Node *node);
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Primitive& n) override;
void visit(Group& n) override;
void visit(Switch& n) override;
};
#endif // COPYVISITOR_H

View File

@@ -11,6 +11,8 @@
#include "Resource.h"
#include "Log.h"
#include "imgui.h"
#include <glad/glad.h>
Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
right_(nullptr), left_(nullptr), top_(nullptr), shadow_(nullptr), square_(nullptr)
@@ -240,7 +242,6 @@ void Handles::update( float dt )
void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
{
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
if ( !initialized() ) {
if(handle_ && !handle_->initialized())
@@ -250,7 +251,8 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
init();
}
if ( visible_ ) {
if ( visible_ && handle_) {
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
// set color
handle_->shader()->color = color;
@@ -404,6 +406,8 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
shadows[SQUARE_POINT] = nullptr;
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
shadows[IMAGE] = shadow;
icons[SEQUENCE] = new Mesh("mesh/icon_sequence.ply");
shadows[SEQUENCE]= shadow;
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
shadows[VIDEO] = shadow;
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
@@ -438,6 +442,8 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
shadows[VECTORSLASH] = shadow;
icons[ARROWS] = new Mesh("mesh/icon_rightarrow.ply");
shadows[ARROWS] = shadow;
icons[ROTATION] = new Mesh("mesh/border_handles_rotation.ply");
shadows[ROTATION] = shadow;
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
shadows[CIRCLE] = nullptr;
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
@@ -553,54 +559,76 @@ void Disk::accept(Visitor& v)
}
//SelectionBox::SelectionBox()
//{
//// color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
// color = glm::vec4( 1.f, 0.f, 0.f, 1.f);
// square_ = new LineSquare( 3 );
//}
//void SelectionBox::draw (glm::mat4 modelview, glm::mat4 projection)
//FrameBuffer *textToFrameBuffer(const std::string &text, font_style type, uint h)
//{
// if ( !initialized() ) {
// square_->init();
// init();
// FrameBuffer *fb = nullptr;
// uint w = 0;
// for (auto i = 0; i < text.size(); ++i) {
// const ImFontGlyph* glyph = fontmap[type]->FindGlyph( text[i] );
// w += h * (uint) ceil( (glyph->X1 - glyph->X0) / (glyph->Y1 - glyph->Y0) );
// }
// if (w > 0) {
// if (visible_) {
// // use a visitor bounding box to calculate extend of all selected nodes
// BoundingBoxVisitor vbox;
// // visit every child of the selection
// for (NodeSet::iterator node = children_.begin();
// node != children_.end(); node++) {
// // reset the transform before
// vbox.setModelview(glm::identity<glm::mat4>());
// (*node)->accept(vbox);
// // create and fill a FrameBuffer with the ImGui Font texture (once)
// static uint framebufferFont_ = 0;
// static uint textureFont_ = 0;
// static int widthFont_ =0;
// static int heightFont_ =0;
// if (framebufferFont_ == 0) {
// glGenTextures(1, &textureFont_);
// glBindTexture(GL_TEXTURE_2D, textureFont_);
// unsigned char *pixels;
// int bpp = 0;
// ImGuiIO& io = ImGui::GetIO();
// io.Fonts->GetTexDataAsRGBA32(&pixels, &widthFont_, &heightFont_, &bpp);
// glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA, widthFont_, heightFont_);
// glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widthFont_, heightFont_, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
// glBindTexture(GL_TEXTURE_2D, 0);
// glGenFramebuffers(1, &framebufferFont_);
// glBindFramebuffer(GL_FRAMEBUFFER, framebufferFont_);
// glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureFont_, 0);
// }
// // get the bounding box
// bbox_ = vbox.bbox();
// // create an empty FrameBuffer for the target glyphs
// fb = new FrameBuffer(w, h, true);
//// Log::Info(" -------- visitor box (%f, %f)-(%f, %f)", bbox_.min().x, bbox_.min().y, bbox_.max().x, bbox_.max().y);
//// uint textureGlyphs_ = 0;
//// glGenTextures(1, &textureGlyphs_);
//// glBindTexture(GL_TEXTURE_2D, textureGlyphs_);
//// glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA, w, h);
//// glBindTexture(GL_TEXTURE_2D, 0);
//// uint framebufferGlyphs_ = 0;
//// glGenFramebuffers(1, &framebufferGlyphs_);
//// glBindFramebuffer(GL_FRAMEBUFFER, framebufferGlyphs_);
//// glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureGlyphs_, 0);
// // set color
// square_->shader()->color = color;
//// glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferGlyphs_);
// // compute transformation from bounding box
//// glm::mat4 ctm = modelview * GlmToolkit::transform(glm::vec3(0.f), glm::vec3(0.f), glm::vec3(1.f));
// glm::mat4 ctm = modelview * GlmToolkit::transform(bbox_.center(), glm::vec3(0.f), bbox_.scale());
// // draw bbox
//// square_->draw( modelview, projection);
// square_->draw( ctm, projection);
// // blit glyphs from framebufferFont_ to framebuffer target
// glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferFont_);
// glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb->opengl_id() );
// GLint write_x = 0;
// for (auto i = 0; i < text.size(); ++i) {
// const ImFontGlyph* glyph = fontmap[type]->FindGlyph( text[i] );
// w = h * (uint) ceil( (glyph->X1 - glyph->X0) / (glyph->Y1 - glyph->Y0) );
// GLint read_x0 = (GLint) ( glyph->U0 * (float) widthFont_ );
// GLint read_y0 = (GLint) ( glyph->V0 * (float) heightFont_ );
// GLint read_x1 = (GLint) ( glyph->U1 * (float) widthFont_ );
// GLint read_y1 = (GLint) ( glyph->V1 * (float) heightFont_ );
// glBlitFramebuffer(read_x0, read_y0, read_x1, read_y1,
// write_x, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
// write_x += w;
// }
// // DEBUG
//// visible_=false;
// }
// return fb;
//}

View File

@@ -60,9 +60,9 @@ protected:
class Symbol : public Node
{
public:
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE,
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE,
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t, glm::vec3 pos = glm::vec3(0.f));
~Symbol();
void draw (glm::mat4 modelview, glm::mat4 projection) override;
@@ -94,18 +94,19 @@ protected:
static Mesh *disk_;
};
//class SelectionBox : public Group
//class TextSurface : public Node
//{
//public:
// SelectionBox();
// TextSurface();
// void draw (glm::mat4 modelview, glm::mat4 projection) override;
// void setText(const std::string &t);
// glm::vec4 color;
//protected:
// LineSquare *square_;
// GlmToolkit::AxisAlignedBoundingBox bbox_;
// std::string text_;
//};

View File

@@ -167,9 +167,9 @@ void Device::remove(const char *device)
break;
}
nameit++;
descit++;
coit++;
++nameit;
++descit;
++coit;
}
}
@@ -257,7 +257,7 @@ struct hasDevice: public std::unary_function<DeviceSource*, bool>
inline bool operator()(const DeviceSource* elem) const {
return (elem && elem->device() == _d);
}
hasDevice(std::string d) : _d(d) { }
explicit hasDevice(const std::string &d) : _d(d) { }
private:
std::string _d;
};
@@ -325,7 +325,7 @@ int Device::index(const std::string &device) const
return i;
}
DeviceSource::DeviceSource() : StreamSource()
DeviceSource::DeviceSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = new Stream;
@@ -342,7 +342,7 @@ DeviceSource::~DeviceSource()
}
void DeviceSource::setDevice(const std::string &devicename)
{
{
device_ = devicename;
Log::Notify("Creating Source with device '%s'", device_.c_str());
@@ -360,7 +360,7 @@ void DeviceSource::setDevice(const std::string &devicename)
DeviceConfigSet confs = Device::manager().config(index);
#ifdef DEVICE_DEBUG
Log::Info("Device %s supported configs:", devicename.c_str());
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); it++ ){
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
}
@@ -384,8 +384,16 @@ void DeviceSource::setDevice(const std::string &devicename)
pipeline << " ! videoconvert";
// resize render buffer
if (renderbuffer_)
renderbuffer_->resize(best.width, best.height);
// open gstreamer
stream_->open( pipeline.str(), best.width, best.height);
stream_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
else
Log::Warning("No such device '%s'", device_.c_str());
@@ -431,11 +439,11 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
// get the first pad and its content
GstIterator *iter = gst_element_iterate_src_pads(elem);
GValue vPad = G_VALUE_INIT;
GstPad* ret = NULL;
GstPad* pad_ret = NULL;
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
{
ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (ret, NULL);
pad_ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (pad_ret, NULL);
// loop over all caps offered by the pad
int C = gst_caps_get_size(device_caps);
@@ -508,8 +516,8 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
gdouble fps_max = 1.0;
// loop over all fractions
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
const GValue *frac = gst_value_list_get_value(val, n);
for (int i = 0; i < N; ++i ){
const GValue *frac = gst_value_list_get_value(val, i);
// read one fraction in the list
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
int n = gst_value_get_fraction_numerator(frac);

View File

@@ -10,7 +10,7 @@
class DeviceSource : public StreamSource
{
public:
DeviceSource();
DeviceSource(uint64_t id = 0);
~DeviceSource();
// Source interface
@@ -87,8 +87,8 @@ class Device
friend class DeviceSource;
Device();
Device(Device const& copy); // Not Implemented
Device& operator=(Device const& copy); // Not Implemented
Device(Device const& copy) = delete;
Device& operator=(Device const& copy) = delete;
public:

603
DialogToolkit.cpp Normal file
View File

@@ -0,0 +1,603 @@
// multiplatform implementation of system dialogs
//
// 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git
// is the prefered solution, but it is not compatible with linux snap packaging
// because of 'zenity' access rights nightmare :(
// Thus this re-implementation of native GTK+ dialogs for linux
#include "defines.h"
#include "Settings.h"
#include "SystemToolkit.h"
#include "DialogToolkit.h"
#if defined(LINUX)
#define USE_TINYFILEDIALOG 0
#else
#define USE_TINYFILEDIALOG 1
#endif
#if USE_TINYFILEDIALOG
#include "tinyfiledialogs.h"
#else
#include <stdio.h>
#include <gtk/gtk.h>
static bool gtk_init_ok = false;
static int window_x = -1, window_y = -1;
void add_filter_any_file_dialog( GtkWidget *dialog)
{
/* append a wildcard option */
GtkFileFilter *filter;
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, "Any file" );
gtk_file_filter_add_pattern( filter, "*" );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
void add_filter_file_dialog( GtkWidget *dialog, int const countfilterPatterns, char const * const * const filterPatterns, char const * const filterDescription)
{
GtkFileFilter *filter;
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, filterDescription );
for (int i = 0; i < countfilterPatterns; i++ )
gtk_file_filter_add_pattern( filter, filterPatterns[i] );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
void wait_for_event(void)
{
while ( gtk_events_pending() )
gtk_main_iteration();
}
bool gtk_init()
{
if (!gtk_init_ok) {
if ( gtk_init_check( NULL, NULL ) )
gtk_init_ok = true;
}
return gtk_init_ok;
}
#endif
// globals
const std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
bool DialogToolkit::FileDialog::busy_ = false;
//
// FileDialog common functions
//
DialogToolkit::FileDialog::FileDialog(const std::string &name) : id_(name)
{
if ( Settings::application.dialogRecentFolder.count(id_) == 0 )
Settings::application.dialogRecentFolder[id_] = SystemToolkit::home_path();
}
bool DialogToolkit::FileDialog::closed()
{
if ( !promises_.empty() ) {
// check that file dialog thread finished
if (promises_.back().wait_for(timeout) == std::future_status::ready ) {
// get the filename from this file dialog
std::string string = promises_.back().get();
if (!string.empty()) {
// selected a filename
path_ = string;
// save path location
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(string);
}
// done with this file dialog
promises_.pop_back();
busy_ = false;
return true;
}
}
return false;
}
//
// type specific implementations
//
std::string openImageFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenImageDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openImageFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openSessionFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenSessionDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openSessionFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openMediaFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenMediaDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openMediaFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string saveSessionFileDialog(const std::string &label, const std::string &path);
void DialogToolkit::SaveSessionDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, saveSessionFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::string openFolderDialog(const std::string &label, const std::string &path);
void DialogToolkit::OpenFolderDialog::open()
{
if ( !busy_ && promises_.empty() ) {
promises_.emplace_back( std::async(std::launch::async, openFolderDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path);
void DialogToolkit::MultipleImagesDialog::open()
{
if ( !busy_ && promisedlist_.empty() ) {
promisedlist_.emplace_back( std::async(std::launch::async, selectImagesFileDialog, id_,
Settings::application.dialogRecentFolder[id_]) );
busy_ = true;
}
}
bool DialogToolkit::MultipleImagesDialog::closed()
{
if ( !promisedlist_.empty() ) {
// check that file dialog thread finished
if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) {
// get the filename from this file dialog
std::list<std::string> list = promisedlist_.back().get();
if (!list.empty()) {
// selected a filenames
pathlist_ = list;
path_ = list.front();
// save path location
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
}
else {
pathlist_.clear();
path_.clear();
}
// done with this file dialog
promisedlist_.pop_back();
busy_ = false;
return true;
}
}
return false;
}
//
//
// CALLBACKS
//
//
std::string saveSessionFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
char const * save_pattern[2] = { "*.mix", "*.MIX" };
#if USE_TINYFILEDIALOG
char const * save_file_name;
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 2, save_pattern, "vimix session");
if (save_file_name)
filename = std::string(save_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT, NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
// set file filters
add_filter_file_dialog(dialog, 2, save_pattern, "vimix session");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (save_file_name)
filename = std::string(save_file_name);
g_free( save_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
std::string extension = filename.substr(filename.find_last_of(".") + 1);
if (!filename.empty() && extension != "mix")
filename += ".mix";
return filename;
}
std::string openSessionFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[2] = { "*.mix", "*.MIX" };
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 2, open_pattern, "vimix session", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 2, open_pattern, "vimix session");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openMediaFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[52] = { "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",
"*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",
"*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",
"*.tif", "*.tiff", "*.webp", "*.bmp", "*.ppm", "*.svg,"
"*.MIX", "*.MP4", "*.MPG", "*.MPEG","*.M2V", "*.M4V", "*.AVI", "*.MOV",
"*.MKV", "*.WEBM", "*.MOD", "*.WMV", "*.MXF", "*.OGG",
"*.FLV", "*.HEVC", "*.ASF", "*.JPG", "*.PNG", "*.GIF",
"*.TIF", "*.TIFF", "*.WEBP", "*.BMP", "*.PPM", "*.SVG" };
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 18, open_pattern, "All supported formats", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 52, open_pattern, "Supported formats (videos, images, sessions)");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openImageFileDialog(const std::string &label, const std::string &path)
{
std::string filename = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[10] = { "*.jpg", "*.png", "*.bmp", "*.ppm", "*.gif",
"*.JPG", "*.PNG", "*.BMP", "*.PPM", "*.GIF"};
#if USE_TINYFILEDIALOG
char const * open_file_name;
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 4, open_pattern, "Image JPG or PNG", 0);
if (open_file_name)
filename = std::string(open_file_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return filename;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 10, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)");
add_filter_any_file_dialog(dialog);
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_file_name)
filename = std::string(open_file_name);
g_free( open_file_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return filename;
}
std::string openFolderDialog(const std::string &label, const std::string &path)
{
std::string foldername = "";
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
#if USE_TINYFILEDIALOG
char const * open_folder_name;
open_folder_name = tinyfd_selectFolderDialog(label.c_str(), startpath.c_str());
if (open_folder_name)
foldername = std::string(open_folder_name);
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return foldername;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Select", GTK_RESPONSE_ACCEPT, NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
char *open_folder_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
if (open_folder_name)
foldername = std::string(open_folder_name);
g_free( open_folder_name );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return foldername;
}
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path)
{
std::list<std::string> files;
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif",
"*.JPG", "*.PNG", "*.TIF" };
#if USE_TINYFILEDIALOG
char const * open_file_names;
open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 6, open_pattern, "Images", 1);
if (open_file_names) {
const std::string& str (open_file_names);
const std::string& delimiters = "|";
// Skip delimiters at beginning.
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first non-delimiter.
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
while (std::string::npos != pos || std::string::npos != lastPos) {
// Found a token, add it to the vector.
files.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters.
lastPos = str.find_first_not_of(delimiters, pos);
// Find next non-delimiter.
pos = str.find_first_of(delimiters, lastPos);
}
}
#else
if (!gtk_init()) {
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
return files;
}
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT, NULL );
// set file filters
add_filter_file_dialog(dialog, 6, open_pattern, "Images (JPG, PNG, TIF)");
add_filter_any_file_dialog(dialog);
// multiple files
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true );
// Set the default path
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
// ensure front and centered
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
if (window_x > 0 && window_y > 0)
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
// display and get filename
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
while (open_file_names) {
files.push_back( (char *) open_file_names->data );
open_file_names = open_file_names->next;
// g_free( open_file_names->data );
}
g_slist_free( open_file_names );
}
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
// done
gtk_widget_destroy(dialog);
wait_for_event();
#endif
return files;
}
void DialogToolkit::ErrorDialog(const char* message)
{
#if USE_TINYFILEDIALOG
tinyfd_messageBox( APP_TITLE, message, "ok", "error", 0);
#else
if (!gtk_init()) {
return;
}
GtkWidget *dialog = gtk_message_dialog_new( NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"Error: %s", message);
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
static int x = 0, y = 0;
if (x != 0)
gtk_window_move( GTK_WINDOW(dialog), x, y);
// show
gtk_dialog_run( GTK_DIALOG(dialog) );
// remember position
gtk_window_get_position( GTK_WINDOW(dialog), &x, &y);
// done
gtk_widget_destroy( dialog );
wait_for_event();
#endif
}

85
DialogToolkit.h Normal file
View File

@@ -0,0 +1,85 @@
#ifndef DIALOGTOOLKIT_H
#define DIALOGTOOLKIT_H
#include <string>
#include <list>
#include <vector>
#include <thread>
#include <future>
namespace DialogToolkit
{
void ErrorDialog(const char* message);
class FileDialog
{
protected:
std::string id_;
std::string directory_;
std::string path_;
std::vector< std::future<std::string> >promises_;
static bool busy_;
public:
FileDialog(const std::string &name);
virtual void open() = 0;
virtual bool closed();
inline std::string path() const { return path_; }
static bool busy() { return busy_; }
};
class OpenImageDialog : public FileDialog
{
public:
OpenImageDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenSessionDialog : public FileDialog
{
public:
OpenSessionDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenMediaDialog : public FileDialog
{
public:
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class SaveSessionDialog : public FileDialog
{
public:
SaveSessionDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class OpenFolderDialog : public FileDialog
{
public:
OpenFolderDialog(const std::string &name) : FileDialog(name) {}
void open();
};
class MultipleImagesDialog : public FileDialog
{
std::list<std::string> pathlist_;
std::vector< std::future< std::list<std::string> > > promisedlist_;
public:
MultipleImagesDialog(const std::string &name) : FileDialog(name) {}
void open() override;
bool closed() override;
inline std::list<std::string> images() const { return pathlist_; }
};
}
#endif // DIALOGTOOLKIT_H

View File

@@ -16,13 +16,11 @@ DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force): fo
transform_duplicat_ = glm::identity<glm::mat4>();
}
DrawVisitor::DrawVisitor(std::vector<Node *> nodestodraw, glm::mat4 projection, bool force): force_(force)
DrawVisitor::DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force):
force_(force), targets_(nodestodraw), modelview_(glm::identity<glm::mat4>()),
projection_(projection), num_duplicat_(1), transform_duplicat_(glm::identity<glm::mat4>())
{
targets_ = nodestodraw;
modelview_ = glm::identity<glm::mat4>();
projection_ = projection;
num_duplicat_ = 1;
transform_duplicat_ = glm::identity<glm::mat4>();
}
void DrawVisitor::loop(int num, glm::mat4 transform)
@@ -70,7 +68,7 @@ void DrawVisitor::visit(Group &n)
// traverse children
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;

View File

@@ -15,7 +15,7 @@ class DrawVisitor : public Visitor
public:
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
DrawVisitor(std::vector<Node *> nodestodraw, glm::mat4 projection, bool force = false);
DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force = false);
void loop(int num, glm::mat4 transform);

View File

@@ -1,14 +1,8 @@
#include "FileDialog.h"
#include "ImGuiToolkit.h"
#include <fstream>
#include <iostream>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <stb_image.h>
#include <glad/glad.h>
#ifdef WIN32
@@ -29,8 +23,8 @@
#endif
#include "imgui_internal.h"
#include <stb_image.h>
#include "ImGuiToolkit.h"
#include "FileDialog.h"
static std::string s_fs_root(1u, PATH_SEP);
static std::string currentFileDialog;
@@ -184,7 +178,7 @@ inline PathStruct ParsePathFileName(const std::string& vPathFileName)
return res;
}
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, std::string vStr)
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string &vStr)
{
std::string st = vStr;
size_t len = vBufferLen - 1u;
@@ -259,8 +253,6 @@ static bool stringComparator(const FileInfoStruct& a, const FileInfoStruct& b)
void FileDialog::ScanDir(const std::string& vPath)
{
struct dirent **files = NULL;
int i = 0;
int n = 0;
std::string path = vPath;
#if defined(LINUX) or defined(APPLE)
@@ -286,12 +278,12 @@ void FileDialog::ScanDir(const std::string& vPath)
path += PATH_SEP;
}
#endif
n = scandir(path.c_str(), &files, NULL, alphaSort);
int n = scandir(path.c_str(), &files, NULL, alphaSort);
if (n > 0)
{
m_FileList.clear();
for (i = 0; i < n; i++)
for (int i = 0; i < n; ++i)
{
struct dirent *ent = files[i];
@@ -334,7 +326,7 @@ void FileDialog::ScanDir(const std::string& vPath)
}
}
for (i = 0; i < n; i++)
for (int i = 0; i < n; ++i)
{
free(files[i]);
}
@@ -440,7 +432,7 @@ void FileDialog::ComposeNewPath(std::vector<std::string>::iterator vIter)
break;
}
vIter--;
--vIter;
}
}
@@ -709,9 +701,9 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
SetPath("."); // Go Home
}
#ifdef WIN32
bool drivesClick = false;
#ifdef WIN32
ImGui::SameLine();
if (ImGui::Button("Drives"))
@@ -871,10 +863,12 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
SetPath(m_CurrentPath);
}
#ifdef WIN32
if (drivesClick == true)
{
GetDrives();
}
#endif
ImGui::EndChild();
ImGui::PopFont();
@@ -1019,12 +1013,12 @@ std::string FileDialog::GetUserString()
return dlg_userString;
}
void FileDialog::SetFilterColor(std::string vFilter, ImVec4 vColor)
void FileDialog::SetFilterColor(const std::string &vFilter, ImVec4 vColor)
{
m_FilterColor[vFilter] = vColor;
}
bool FileDialog::GetFilterColor(std::string vFilter, ImVec4 *vColor)
bool FileDialog::GetFilterColor(const std::string &vFilter, ImVec4 *vColor)
{
if (vColor)
{
@@ -1060,7 +1054,7 @@ inline void InfosPane(std::string vFilter, bool *vCantContinue)
*vCantContinue = canValidateDialog;
}
inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
inline void TextInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
{
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Text");
@@ -1097,7 +1091,7 @@ inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantC
*vCantContinue = text.size() > 0;
}
inline void ImageInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
inline void ImageInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
{
// opengl texture
static GLuint tex = 0;

View File

@@ -6,15 +6,8 @@
#include <vector>
#include <string>
#include <map>
#include <future>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
#include <string>
#include <vector>
#include <list>
#define MAX_FILE_DIALOG_NAME_BUFFER 1024
@@ -78,8 +71,8 @@ public:
protected:
FileDialog(); // Prevent construction
FileDialog(const FileDialog&) {}; // Prevent construction by copying
FileDialog& operator =(const FileDialog&) { return *this; }; // Prevent assignment
FileDialog(const FileDialog&) = delete; // Prevent construction by copying
FileDialog& operator =(const FileDialog&) = delete; // Prevent assignment
~FileDialog(); // Prevent unwanted destruction
public:
@@ -102,8 +95,8 @@ public:
std::string GetCurrentFilter();
std::string GetUserString();
void SetFilterColor(std::string vFilter, ImVec4 vColor);
bool GetFilterColor(std::string vFilter, ImVec4 *vColor);
void SetFilterColor(const std::string &vFilter, ImVec4 vColor);
bool GetFilterColor(const std::string &vFilter, ImVec4 *vColor);
void ClearFilterColor();
private:

View File

@@ -14,8 +14,8 @@
const char* FrameBuffer::aspect_ratio_name[5] = { "4:3", "3:2", "16:10", "16:9", "21:9" };
glm::vec2 FrameBuffer::aspect_ratio_size[5] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) , glm::vec2(21.f,9.f) };
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "2160" };
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
const char* FrameBuffer::resolution_name[5] = { "720", "1080", "1200", "1440", "2160" };
float FrameBuffer::resolution_height[5] = { 720.f, 1080.f, 1200.f, 1440.f, 2160.f };
glm::vec3 FrameBuffer::getResolutionFromParameters(int ar, int h)
@@ -171,6 +171,31 @@ glm::vec3 FrameBuffer::resolution() const
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
}
void FrameBuffer::resize(int width, int height)
{
if (framebufferid_) {
if (attrib_.viewport.x != width || attrib_.viewport.y != height)
{
// de-init
glDeleteFramebuffers(1, &framebufferid_);
framebufferid_ = 0;
if (intermediate_framebufferid_)
glDeleteFramebuffers(1, &intermediate_framebufferid_);
intermediate_framebufferid_ = 0;
if (textureid_)
glDeleteTextures(1, &textureid_);
textureid_ = 0;
if (intermediate_textureid_)
glDeleteTextures(1, &intermediate_textureid_);
intermediate_textureid_ = 0;
// change resolution
attrib_.viewport = glm::ivec2(width, height);
}
}
}
void FrameBuffer::begin(bool clear)
{
if (!framebufferid_)
@@ -227,13 +252,17 @@ void FrameBuffer::readPixels(uint8_t *target_data)
bool FrameBuffer::blit(FrameBuffer *destination)
{
if (!framebufferid_ || !destination)
if (!framebufferid_ || !destination || (use_alpha_ != destination->use_alpha_) )
return false;
if (!destination->framebufferid_)
destination->init();
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
if (use_multi_sampling_)
glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_framebufferid_);
else
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebufferid_);
// blit to the frame buffer object
glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y,
@@ -313,12 +342,21 @@ FrameBufferImage::FrameBufferImage(jpegBuffer jpgimg) :
rgb = stbi_load_from_memory(jpgimg.buffer, jpgimg.len, &width, &height, &c, 3);
}
FrameBufferImage::~FrameBufferImage() {
FrameBufferImage::FrameBufferImage(const std::string &filename) :
rgb(nullptr), width(0), height(0)
{
int c = 0;
if (!filename.empty())
rgb = stbi_load(filename.c_str(), &width, &height, &c, 3);
}
FrameBufferImage::~FrameBufferImage()
{
if (rgb!=nullptr)
delete rgb;
}
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg()
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg() const
{
jpegBuffer jpgimg;
@@ -348,6 +386,10 @@ FrameBufferImage *FrameBuffer::image(){
if (!framebufferid_)
return img;
// only compatible for RGB FrameBuffers
if (use_alpha_ || use_multi_sampling_)
return img;
// allocate image
img = new FrameBufferImage(attrib_.viewport.x, attrib_.viewport.y);
@@ -363,21 +405,50 @@ bool FrameBuffer::fill(FrameBufferImage *image)
if (!framebufferid_)
init();
// not compatible for RGB
// only compatible for RGB FrameBuffers
if (use_alpha_ || use_multi_sampling_)
return false;
// invalid image
if ( image == nullptr ||
image->rgb==nullptr ||
image->width !=attrib_.viewport.x ||
image->height!=attrib_.viewport.y )
image->width < 1 ||
image->height < 1 )
return false;
// fill texture with image
glBindTexture(GL_TEXTURE_2D, textureid_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
// is it same size ?
if (image->width == attrib_.viewport.x && image->height == attrib_.viewport.y ) {
// directly fill texture with image
glBindTexture(GL_TEXTURE_2D, textureid_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
glBindTexture(GL_TEXTURE_2D, 0);
}
else {
uint textureimage, framebufferimage;
// generate texture
glGenTextures(1, &textureimage);
glBindTexture(GL_TEXTURE_2D, textureimage);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
glBindTexture(GL_TEXTURE_2D, 0);
// create a framebuffer object
glGenFramebuffers(1, &framebufferimage);
glBindFramebuffer(GL_FRAMEBUFFER, framebufferimage);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureimage, 0);
// blit to the frame buffer object with interpolation
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferimage);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferid_);
glBlitFramebuffer(0, 0, image->width, image->height,
0, 0, attrib_.viewport.x, attrib_.viewport.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// cleanup
glDeleteFramebuffers(1, &framebufferimage);
glDeleteTextures(1, &textureimage);
}
return true;
}

View File

@@ -12,7 +12,7 @@
class FrameBufferImage
{
public:
uint8_t *rgb = nullptr;
uint8_t *rgb;
int width;
int height;
@@ -20,10 +20,14 @@ public:
unsigned char *buffer = nullptr;
uint len = 0;
};
jpegBuffer getJpeg();
jpegBuffer getJpeg() const;
FrameBufferImage(int w, int h);
FrameBufferImage(jpegBuffer jpgimg);
FrameBufferImage(const std::string &filename);
// non assignable class
FrameBufferImage(FrameBufferImage const&) = delete;
FrameBufferImage& operator=(FrameBufferImage const&) = delete;
~FrameBufferImage();
};
@@ -36,8 +40,8 @@ public:
// size descriptions
static const char* aspect_ratio_name[5];
static glm::vec2 aspect_ratio_size[5];
static const char* resolution_name[4];
static float resolution_height[4];
static const char* resolution_name[5];
static float resolution_height[5];
static glm::vec3 getResolutionFromParameters(int ar, int h);
static glm::ivec2 getParametersFromResolution(glm::vec3 res);
// unbind any framebuffer object
@@ -45,6 +49,7 @@ public:
FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(FrameBuffer const&) = delete;
~FrameBuffer();
// Bind & push attribs to prepare draw
@@ -65,6 +70,7 @@ public:
inline uint width() const { return attrib_.viewport.x; }
inline uint height() const { return attrib_.viewport.y; }
glm::vec3 resolution() const;
void resize(int width, int height);
float aspectRatio() const;
std::string info() const;
@@ -74,6 +80,7 @@ public:
void setProjectionArea(glm::vec2 c);
// internal pixel format
inline uint opengl_id() const { return framebufferid_; }
inline bool use_alpha() const { return use_alpha_; }
inline bool use_multisampling() const { return use_multi_sampling_; }

View File

@@ -10,13 +10,14 @@
#include "defines.h"
#include "Log.h"
#include "GstToolkit.h"
#include "BaseToolkit.h"
#include "FrameBuffer.h"
#include "FrameGrabber.h"
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(nullptr)
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL)
{
pbo_[0] = 0;
pbo_[1] = 0;
@@ -28,10 +29,10 @@ FrameGrabbing::~FrameGrabbing()
clearAll();
// cleanup
if (caps_!=nullptr)
if (caps_)
gst_caps_unref (caps_);
if (pbo_[0])
glDeleteBuffers(2, pbo_);
// if (pbo_[0] > 0) // automatically deleted at shutdown
// glDeleteBuffers(2, pbo_);
}
void FrameGrabbing::add(FrameGrabber *rec)
@@ -40,12 +41,18 @@ void FrameGrabbing::add(FrameGrabber *rec)
grabbers_.push_back(rec);
}
void FrameGrabbing::verify(FrameGrabber **rec)
{
if ( std::find(grabbers_.begin(), grabbers_.end(), *rec) == grabbers_.end() )
*rec = nullptr;
}
FrameGrabber *FrameGrabbing::front()
{
if (grabbers_.empty())
return nullptr;
else
return grabbers_.front();
return grabbers_.front();
}
struct fgId: public std::unary_function<FrameGrabber*, bool>
@@ -53,7 +60,7 @@ struct fgId: public std::unary_function<FrameGrabber*, bool>
inline bool operator()(const FrameGrabber* elem) const {
return (elem && elem->id() == _id);
}
fgId(uint64_t id) : _id(id) { }
explicit fgId(uint64_t id) : _id(id) { }
private:
uint64_t _id;
};
@@ -73,7 +80,7 @@ FrameGrabber *FrameGrabbing::get(uint64_t id)
void FrameGrabbing::stopAll()
{
std::list<FrameGrabber *>::iterator iter;
for (iter=grabbers_.begin(); iter != grabbers_.end(); iter++ )
for (iter=grabbers_.begin(); iter != grabbers_.end(); ++iter )
(*iter)->stop();
}
@@ -90,7 +97,7 @@ void FrameGrabbing::clearAll()
}
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer)
{
if (frame_buffer == nullptr)
return;
@@ -121,13 +128,12 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
pbo_next_index_ = 0;
// new caps
if (caps_!=nullptr)
if (caps_)
gst_caps_unref (caps_);
caps_ = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
"width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
}
@@ -145,6 +151,7 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
#else
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
// update case ; alternating indices
@@ -179,21 +186,21 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
pbo_index_ = (pbo_index_ + 1) % 2;
// a frame was successfully grabbed
if (buffer != nullptr) {
if ( buffer != nullptr && gst_buffer_get_size(buffer) > 0) {
// give the frame to all recorders
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
while (iter != grabbers_.end())
{
FrameGrabber *rec = *iter;
rec->addFrame(buffer, caps_, dt);
rec->addFrame(buffer, caps_);
if (rec->finished()) {
iter = grabbers_.erase(iter);
delete rec;
}
else
iter++;
++iter;
}
// unref / free the frame
@@ -206,14 +213,14 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
FrameGrabber::FrameGrabber(): finished_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr),
timestamp_(0), duration_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE), timestamp_on_clock_(false)
{
// unique id
id_ = GlmToolkit::uniqueId();
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
timeframe_ = 2 * frame_duration_;
id_ = BaseToolkit::uniqueId();
// configure default parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // 25 FPS by default
}
FrameGrabber::~FrameGrabber()
@@ -241,24 +248,24 @@ bool FrameGrabber::busy() const
return false;
}
double FrameGrabber::duration() const
uint64_t FrameGrabber::duration() const
{
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
return GST_TIME_AS_MSECONDS(duration_);
}
void FrameGrabber::stop ()
{
// send end of stream
gst_app_src_end_of_stream (src_);
// stop recording
active_ = false;
// send end of stream
gst_app_src_end_of_stream (src_);
}
std::string FrameGrabber::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
return GstToolkit::time_to_string(duration_);
else
return "Inactive";
}
@@ -275,83 +282,132 @@ void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
if (grabber) {
grabber->accept_buffer_ = false;
#ifndef NDEBUG
Log::Info("Frame capture : Buffer full");
#endif
}
}
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
GstPadProbeReturn FrameGrabber::callback_event_probe(GstPad *, GstPadProbeInfo * info, gpointer p)
{
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
{
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
if (grabber)
grabber->endofstream_ = true;
}
return GST_PAD_PROBE_OK;
}
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps)
{
// ignore
if (buffer == nullptr)
return;
// first time initialization
if (pipeline_ == nullptr)
if (pipeline_ == nullptr) {
// type specific initialisation
init(caps);
// cancel if finished
if (finished_)
return;
// attach EOS detector
GstPad *pad = gst_element_get_static_pad (gst_bin_get_by_name (GST_BIN (pipeline_), "sink"), "sink");
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, FrameGrabber::callback_event_probe, this, NULL);
gst_object_unref (pad);
}
// stop if an incompatilble frame buffer given
if ( !gst_caps_is_equal( caps_, caps ))
else if ( !gst_caps_is_subset( caps_, caps ))
{
stop();
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
Log::Warning("FrameGrabber interrupted because the resolution changed.");
Log::Warning("Frame capture interrupted because the resolution changed.");
}
// store a frame if recording is active
// store a frame if recording is active and if the encoder accepts data
if (active_)
{
// calculate dt in ns
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
if (accept_buffer_) {
GstClockTime t = 0;
// if time is passed one frame duration (with 10% margin)
// and if the encoder accepts data
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
// initialize timer on first occurence
if (timer_ == nullptr) {
timer_ = gst_pipeline_get_clock ( GST_PIPELINE(pipeline_) );
timer_firstframe_ = gst_clock_get_time(timer_);
}
else
// time since timer starts (first frame registered)
t = gst_clock_get_time(timer_) - timer_firstframe_;
// set timing of buffer
buffer->pts = timestamp_;
buffer->duration = frame_duration_;
// if time is zero (first frame) or if delta time is passed one frame duration (with a margin)
if ( t == 0 || (t - duration_) > (frame_duration_ - 3000) ) {
// increment ref counter to make sure the frame remains available
gst_buffer_ref(buffer);
// count frames
frame_count_++;
// push
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
// set duration to an exact multiples of frame duration
duration_ = ( t / frame_duration_) * frame_duration_;
accept_buffer_ = false;
if (timestamp_on_clock_)
// automatic frame presentation time stamp
// set time to actual time
// & round t to a multiples of frame duration
timestamp_ = duration_;
else {
// monotonic time increment to keep fixed FPS
timestamp_ += frame_duration_;
// force frame presentation time stamp
buffer->pts = timestamp_;
// set frame duration
buffer->duration = frame_duration_;
}
// next timestamp
timestamp_ += frame_duration_;
// when buffering is (almost) full, refuse buffer 1 frame over 2
if (buffering_full_)
accept_buffer_ = frame_count_%2;
else
{
// enter buffering_full_ mode if the space left in buffering is for only few frames
// (this prevents filling the buffer entirely)
if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < MIN_BUFFER_SIZE ) {
#ifndef NDEBUG
Log::Info("Frame capture : Using %s of %s Buffer.",
BaseToolkit::byte_to_string(gst_app_src_get_current_level_bytes(src_)).c_str(),
BaseToolkit::byte_to_string(buffering_size_).c_str());
#endif
buffering_full_ = true;
}
}
// restart frame counter
timeframe_ = 0;
}
}
// did the recording terminate with sink receiving end-of-stream ?
else {
// increment ref counter to make sure the frame remains available
gst_buffer_ref(buffer);
if (!finished_)
{
// Wait for EOS message
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
// received EOS
if (msg) {
// stop the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_FAILURE)
Log::Warning("FrameGrabber Could not stop.");
finished_ = true;
// push frame
gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc
}
}
}
if (finished_)
terminate();
// if we received and end of stream (from callback_event_probe)
if (endofstream_)
{
// try to stop properly when interrupted
if (active_) {
// de-activate and re-send EOS
stop();
// inform
Log::Info("Frame capture : Unnexpected EOF signal (no space left on drive? File deleted?)");
Log::Warning("Frame capture : Failed after %s.", GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str());
}
// terminate properly if finished
else
{
finished_ = true;
terminate();
}
}
}

View File

@@ -8,12 +8,13 @@
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include "GlmToolkit.h"
// use glReadPixel or glGetTextImage
// read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL
#define DEFAULT_GRABBER_FPS 30
#define MIN_BUFFER_SIZE 33177600 // 33177600 bytes = 1 frames 4K, 9 frames 720p
class FrameBuffer;
@@ -40,14 +41,14 @@ public:
virtual void stop();
virtual std::string info() const;
virtual double duration() const;
virtual uint64_t duration() const;
virtual bool finished() const;
virtual bool busy() const;
protected:
// only FrameGrabbing manager can add frame
virtual void addFrame(GstBuffer *buffer, GstCaps *caps, float dt);
virtual void addFrame(GstBuffer *buffer, GstCaps *caps);
// only addFrame method shall call those
virtual void init(GstCaps *caps) = 0;
@@ -56,20 +57,29 @@ protected:
// thread-safe testing termination
std::atomic<bool> finished_;
std::atomic<bool> active_;
std::atomic<bool> endofstream_;
std::atomic<bool> accept_buffer_;
std::atomic<bool> buffering_full_;
// gstreamer pipeline
GstElement *pipeline_;
GstAppSrc *src_;
GstCaps *caps_;
GstClockTime timeframe_;
GstClock *timer_;
GstClockTime timestamp_;
GstClockTime duration_;
GstClockTime frame_duration_;
guint64 frame_count_;
guint64 buffering_size_;
bool timestamp_on_clock_;
GstClockTime timer_firstframe_;
// gstreamer callbacks
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
static void callback_enough_data (GstAppSrc *, gpointer user_data);
static GstPadProbeReturn callback_event_probe(GstPad *, GstPadProbeInfo *info, gpointer user_data);
};
/**
@@ -84,8 +94,8 @@ class FrameGrabbing
// Private Constructor
FrameGrabbing();
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
FrameGrabbing(FrameGrabbing const& copy) = delete;
FrameGrabbing& operator=(FrameGrabbing const& copy) = delete;
public:
@@ -101,6 +111,7 @@ public:
inline uint height() const { return height_; }
void add(FrameGrabber *rec);
void verify(FrameGrabber **rec);
FrameGrabber *front();
FrameGrabber *get(uint64_t id);
void stopAll();
@@ -109,7 +120,7 @@ public:
protected:
// only for friend Session
void grabFrame(FrameBuffer *frame_buffer, float dt);
void grabFrame(FrameBuffer *frame_buffer);
private:
std::list<FrameGrabber *> grabbers_;

View File

@@ -58,7 +58,7 @@ void GarbageVisitor::visit(Group &n)
// loop over members of a group
// and stop when found
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); ++node) {
// visit the child node
(*node)->accept(*this);
// un-stack recursive browsing
@@ -82,78 +82,3 @@ void GarbageVisitor::visit(Primitive &n)
}
void GarbageVisitor::visit(Surface &n)
{
}
void GarbageVisitor::visit(ImageSurface &n)
{
}
void GarbageVisitor::visit(FrameBufferSurface &n)
{
}
void GarbageVisitor::visit(MediaSurface &n)
{
}
void GarbageVisitor::visit(MediaPlayer &n)
{
}
void GarbageVisitor::visit(Shader &n)
{
}
void GarbageVisitor::visit(ImageShader &n)
{
}
void GarbageVisitor::visit(ImageProcessingShader &n)
{
}
void GarbageVisitor::visit(LineStrip &n)
{
}
void GarbageVisitor::visit(LineSquare &)
{
}
void GarbageVisitor::visit(LineCircle &n)
{
}
void GarbageVisitor::visit(Mesh &n)
{
}
void GarbageVisitor::visit(Frame &n)
{
}
void GarbageVisitor::visit (Source& s)
{
}
void GarbageVisitor::visit (MediaSource& s)
{
}

View File

@@ -24,24 +24,6 @@ public:
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(Surface& n) override;
void visit(ImageSurface& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface& n) override;
void visit(LineStrip& n) override;
void visit(LineSquare&) override;
void visit(LineCircle& n) override;
void visit(Mesh& n) override;
void visit(Frame& n) override;
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (Source& s) override;
void visit (MediaSource& s) override;
};
#endif // GARBAGEVISITOR_H

1132
GeometryView.cpp Normal file

File diff suppressed because it is too large Load Diff

49
GeometryView.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef GEOMETRYVIEW_H
#define GEOMETRYVIEW_H
#include "View.h"
class GeometryView : public View
{
public:
GeometryView();
// non assignable class
GeometryView(GeometryView const&) = delete;
GeometryView& operator=(GeometryView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
bool canSelect(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void terminate() override;
void arrow (glm::vec2) override;
private:
Surface *output_surface_;
Node *overlay_position_;
Node *overlay_position_cross_;
Symbol *overlay_rotation_;
Symbol *overlay_rotation_fix_;
Group *overlay_rotation_clock_;
Symbol *overlay_rotation_clock_tic_;
Node *overlay_rotation_clock_hand_;
Symbol *overlay_scaling_;
Symbol *overlay_scaling_cross_;
Node *overlay_scaling_grid_;
Node *overlay_crop_;
void updateSelectionOverlay() override;
bool overlay_selection_active_;
Group *overlay_selection_stored_status_;
Handles *overlay_selection_scale_;
Handles *overlay_selection_rotate_;
void applySelectionTransform(glm::mat4 M);
};
#endif // GEOMETRYVIEW_H

View File

@@ -1,4 +1,3 @@
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
#include "GlmToolkit.h"
@@ -10,14 +9,6 @@
#include <chrono>
#include <ctime>
uint64_t GlmToolkit::uniqueId()
{
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
// 64-bit int 18446744073709551615UL
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
}
glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale)
{
glm::mat4 View = glm::translate(glm::identity<glm::mat4>(), translation);
@@ -47,10 +38,33 @@ void GlmToolkit::inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec
translation = glm::vec3(vec);
}
//float rewrapAngleRestricted(float angle)
//// This function takes an angle in the range [-3*pi, 3*pi] and
//// wraps it to the range [-pi, pi].
//{
// if (angle > glm::pi<float>() )
// return angle - glm::two_pi<float>();
// else if (angle < - glm::pi<float>())
// return angle + glm::two_pi<float>();
// else
// return angle;
//}
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() {
mMin = glm::vec3(1.f);
mMax = glm::vec3(-1.f);
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() :
mMin(glm::vec3(1.f)), mMax(glm::vec3(-1.f))
{
}
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox(const GlmToolkit::AxisAlignedBoundingBox &D) :
mMin(D.mMin), mMax(D.mMax)
{
}
void GlmToolkit::AxisAlignedBoundingBox::operator = (const GlmToolkit::AxisAlignedBoundingBox &D ) {
mMin = D.mMin;
mMax = D.mMax;
}
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
@@ -68,7 +82,7 @@ void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
void GlmToolkit::AxisAlignedBoundingBox::extend(std::vector<glm::vec3> points)
{
for (auto p = points.begin(); p != points.end(); p++)
for (auto p = points.begin(); p != points.end(); ++p)
extend(*p);
}
@@ -195,10 +209,10 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
glm::vec4 vec;
// Apply transform to all four corners (can be rotated) and update bbox accordingly
vec = m * glm::vec4(mMin, 1.f);
vec = m * glm::vec4(mMin.x, mMin.y, 0.f, 1.f);
bb.extend(glm::vec3(vec));
vec = m * glm::vec4(mMax, 1.f);
vec = m * glm::vec4(mMax.x, mMax.y, 0.f, 1.f);
bb.extend(glm::vec3(vec));
vec = m * glm::vec4(mMin.x, mMax.y, 0.f, 1.f);
@@ -210,6 +224,14 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
return bb;
}
bool GlmToolkit::operator< (const GlmToolkit::AxisAlignedBoundingBox& A, const GlmToolkit::AxisAlignedBoundingBox& B )
{
if (A.isNull())
return true;
if (B.isNull())
return false;
return ( glm::length2(A.mMax-A.mMin) < glm::length2(B.mMax-B.mMin) );
}
glm::ivec2 GlmToolkit::resolutionFromDescription(int aspectratio, int height)
{

View File

@@ -1,32 +1,22 @@
#ifndef GLMTOOLKIT_H
#define GLMTOOLKIT_H
#include <vector>
#include <glm/glm.hpp>
namespace GlmToolkit
{
// get integer with unique id
uint64_t uniqueId();
// get Matrix for these transformation components
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
void inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale);
class AxisAlignedBoundingBox
{
glm::vec3 mMin;
glm::vec3 mMax;
public:
AxisAlignedBoundingBox();
void operator = (const AxisAlignedBoundingBox &D ) {
mMin = D.mMin;
mMax = D.mMax;
}
AxisAlignedBoundingBox(const AxisAlignedBoundingBox &D);
void operator = (const AxisAlignedBoundingBox &D );
// test
inline bool isNull() const { return mMin.x > mMax.x || mMin.y > mMax.y || mMin.z > mMax.z;}
@@ -46,8 +36,28 @@ public:
AxisAlignedBoundingBox translated(glm::vec3 t) const;
AxisAlignedBoundingBox scaled(glm::vec3 s) const;
AxisAlignedBoundingBox transformed(glm::mat4 m) const;
friend bool operator<(const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
protected:
glm::vec3 mMin;
glm::vec3 mMax;
};
// a bbox A is < from bbox B if its diagonal is shorter
bool operator< (const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
class OrientedBoundingBox
{
public:
OrientedBoundingBox() : orientation(glm::vec3(0.f,0.f,0.f)) {}
AxisAlignedBoundingBox aabb;
glm::vec3 orientation;
};
static const char* aspect_ratio_names[6] = { "1:1", "4:3", "3:2", "16:10", "16:9", "21:9" };
static const char* height_names[10] = { "16", "64", "200", "320", "480", "576", "720p", "1080p", "1440", "4K" };

View File

@@ -14,6 +14,8 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
return "00:00:00.00";
case TIME_STRING_MINIMAL:
return "0.0";
case TIME_STRING_READABLE:
return "0 second";
default:
return "00.00";
}
@@ -23,8 +25,27 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
guint s = ms / 1000;
ostringstream oss;
// READABLE : long format
if (m == TIME_STRING_READABLE) {
int count = 0;
if (s / 3600) {
oss << s / 3600 << " h ";
count++;
}
if ((s % 3600) / 60) {
oss << (s % 3600) / 60 << " min ";
count++;
}
if (count < 2) {
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
count++;
}
if (count < 2 )
oss << '.'<< setw(2) << setfill('0') << (ms % 1000) / 10;
oss << " sec";
}
// MINIMAL: keep only the 2 higher values (most significant)
if (m == TIME_STRING_MINIMAL) {
else if (m == TIME_STRING_MINIMAL) {
int count = 0;
if (s / 3600) {
oss << s / 3600 << ':';
@@ -39,7 +60,7 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
count++;
}
if (count < 2 )
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 10;
oss << '.'<< setw(2) << setfill('0') << (ms % 1000) / 10;
}
else {
// TIME_STRING_FIXED : fixed length string (11 chars) HH:mm:ss.ii"
@@ -58,11 +79,13 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
std::string GstToolkit::filename_to_uri(std::string path)
{
if (path.empty())
return path;
// set uri to open
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
std::string uri( uritmp );
g_free(uritmp);
return uri;
}
@@ -122,6 +145,23 @@ bool GstToolkit::enable_feature (string name, bool enable) {
return true;
}
bool GstToolkit::has_feature (string name)
{
if (name.empty())
return false;
GstRegistry *registry = NULL;
GstElementFactory *factory = NULL;
registry = gst_registry_get();
if (!registry) return false;
factory = gst_element_factory_find (name.c_str());
if (!factory) return false;
return true;
}
string GstToolkit::gst_version()
{
@@ -134,39 +174,76 @@ string GstToolkit::gst_version()
return oss.str();
}
// see https://developer.ridgerun.com/wiki/index.php?title=GStreamer_modify_the_elements_rank
std::list<std::string> GstToolkit::enable_gpu_decoding_plugins(bool enable)
{
static list<string> pluginslist;
static GstRegistry* plugins_register = nullptr;
if ( plugins_register == nullptr ) {
plugins_register = gst_registry_get();
#if GST_GL_HAVE_PLATFORM_GLX
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
const char *plugins[6] = { "nvh264dec", "nvh265dec", "nvmpeg2videodec",
"nvmpeg4videodec", "nvvp8dec", "nvvp9dec" };
const int N = 6;
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
const char *plugins[10] = { "omxmpeg4videodec", "omxmpeg2dec", "omxh264dec", "vdpaumpegdec",
"nvh264dec", "nvh265dec", "nvmpeg2videodec",
"nvmpeg4videodec", "nvvp8dec", "nvvp9dec"
};
const int N = 10;
#elif GST_GL_HAVE_PLATFORM_CGL
const char *plugins[1] = { "vtdec_hw" };
const int N = 1;
const char *plugins[2] = { "vtdec_hw", "vtdechw" };
const int N = 2;
#else
const char *plugins[0] = { };
const int N = 0;
const char *plugins[0] = { };
const int N = 0;
#endif
// see https://developer.ridgerun.com/wiki/index.php?title=GStreamer_modify_the_elements_rank
std::list<std::string> GstToolkit::enable_gpu_decoding_plugins(bool enable)
{
list<string> plugins_list_;
static GstRegistry* plugins_register = nullptr;
if ( plugins_register == nullptr )
plugins_register = gst_registry_get();
static bool enabled_ = false;
if (enabled_ != enable) {
enabled_ = enable;
for (int i = 0; i < N; i++) {
GstPluginFeature* feature = gst_registry_lookup_feature(plugins_register, plugins[i]);
if(feature != NULL) {
pluginslist.push_front( string( plugins[i] ) );
plugins_list_.push_front( string( plugins[i] ) );
gst_plugin_feature_set_rank(feature, enable ? GST_RANK_PRIMARY + 1 : GST_RANK_MARGINAL);
gst_object_unref(feature);
}
}
}
return pluginslist;
return plugins_list_;
}
std::string GstToolkit::used_gpu_decoding_plugins(GstElement *gstbin)
{
std::string found = "";
GstIterator* it = gst_bin_iterate_recurse(GST_BIN(gstbin));
GValue value = G_VALUE_INIT;
for(GstIteratorResult r = gst_iterator_next(it, &value); r != GST_ITERATOR_DONE; r = gst_iterator_next(it, &value))
{
if ( r == GST_ITERATOR_OK )
{
GstElement *e = static_cast<GstElement*>(g_value_peek_pointer(&value));
if (e) {
gchar *name = gst_element_get_name(e);
// g_print(" - %s", name);
std::string e_name(name);
g_free(name);
for (int i = 0; i < N; i++) {
if (e_name.find(plugins[i]) != std::string::npos) {
found = plugins[i];
break;
}
}
}
}
g_value_unset(&value);
}
gst_iterator_free(it);
return found;
}

View File

@@ -12,7 +12,8 @@ namespace GstToolkit
typedef enum {
TIME_STRING_FIXED = 0,
TIME_STRING_ADJUSTED,
TIME_STRING_MINIMAL
TIME_STRING_MINIMAL,
TIME_STRING_READABLE
} time_string_mode;
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
@@ -22,8 +23,10 @@ std::string gst_version();
std::list<std::string> all_plugins();
std::list<std::string> enable_gpu_decoding_plugins(bool enable = true);
std::string used_gpu_decoding_plugins(GstElement *gstbin);
std::list<std::string> all_plugin_features(std::string pluginname);
bool has_feature (std::string name);
bool enable_feature (std::string name, bool enable);
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
#include <string>
#include <list>
#include <vector>
#include <utility>
#include "rsc/fonts/IconsFontAwesome5.h"
namespace ImGuiToolkit
@@ -13,29 +13,38 @@ namespace ImGuiToolkit
// Icons from resource icon.dds
void Icon (int i, int j, bool enabled = true);
bool IconButton (int i, int j, const char *tooltips = nullptr);
bool IconButton (const char* icon, const char *tooltips = nullptr);
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
void ShowIconsWindow(bool* p_open);
// icon buttons
// buttons and gui items with icon
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state);
bool ComboIcon (std::vector<std::pair<int, int> > icons, int* state);
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state, const char* tooltip = nullptr);
bool MenuItemIcon (int i, int j, const char* label, bool selected = false, bool enabled = true);
bool SelectableIcon(const char* label, int i, int j, bool selected = false);
bool ComboIcon (std::vector<std::pair<int, int> > icons, std::vector<std::string> labels, int* state);
bool ComboIcon (const char* label, std::vector<std::pair<int, int> > icons, std::vector<std::string> items, int* i);
// utility buttons
// buttons
bool ButtonToggle (const char* label, bool* toggle);
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
bool ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0));
void ToolTip (const char* desc, const char* shortcut = "");
// tooltip and mouse over
void ToolTip (const char* desc, const char* shortcut = nullptr);
void HelpMarker (const char* desc, const char* icon = ICON_FA_QUESTION_CIRCLE, const char* shortcut = nullptr);
void HelpIcon (const char* desc, int i = 19, int j = 5, const char* shortcut = "");
void HelpIcon (const char* desc, int i = 19, int j = 5, const char* shortcut = nullptr);
// utility sliders
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
// sliders
bool SliderTiming (const char* label, uint *ms, uint v_min, uint v_max, uint v_step, const char* text_max = nullptr);
bool TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width);
void RenderTimeline (struct ImGuiWindow* window, struct ImRect timeline_bbox, guint64 begin, guint64 end, guint64 step, bool verticalflip = false);
void Timeline (const char* label, guint64 time, guint64 begin, guint64 end, guint64 step, const float width);
bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size);
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, guint64 begin, guint64 end, bool cut, bool *released, const ImVec2 size);
void ShowPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size);
// fonts from ressources 'fonts/'
typedef enum {
@@ -47,13 +56,14 @@ namespace ImGuiToolkit
} font_style;
void SetFont (font_style type, const std::string &ttf_font_name, int pointsize, int oversample = 2);
void PushFont (font_style type);
void ImageGlyph(font_style type, char c, float h = 60);
void Spacing();
void WindowText(const char* window_name, ImVec2 window_pos, const char* text);
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
// text input
bool InputText(const char* label, std::string* str);
bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), int linesize = 0);
// color of gui items
// accent color of UI
typedef enum {
ACCENT_BLUE =0,
ACCENT_ORANGE,
@@ -62,7 +72,10 @@ namespace ImGuiToolkit
void SetAccentColor (accent_color color);
struct ImVec4 HighlightColor (bool active = true);
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
// varia
void WindowText(const char* window_name, ImVec2 window_pos, const char* text);
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
}

View File

@@ -8,6 +8,10 @@
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <tinyxml2.h>
#include "tinyxml2Toolkit.h"
#include "defines.h"
#include "Log.h"
@@ -21,12 +25,16 @@
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "MultiFileSource.h"
#include "SessionCreator.h"
#include "SessionVisitor.h"
#include "Settings.h"
#include "Mixer.h"
#include "ActionManager.h"
#include "imgui.h"
#include "ImGuiToolkit.h"
#include "BaseToolkit.h"
#include "UserInterfaceManager.h"
#include "SystemToolkit.h"
@@ -52,7 +60,7 @@ void ImGuiVisitor::visit(Group &n)
n.rotation_.z = 0.f;
n.scale_.x = 1.f;
n.scale_.y = 1.f;
Action::manager().store("Geometry Reset", n.id());
Action::manager().store("Geometry Reset");
}
ImGui::SameLine(0, 10);
ImGui::Text("Geometry");
@@ -60,7 +68,7 @@ void ImGuiVisitor::visit(Group &n)
if (ImGuiToolkit::ButtonIcon(6, 15)) {
n.translation_.x = 0.f;
n.translation_.y = 0.f;
Action::manager().store("Position 0.0, 0.0", n.id());
Action::manager().store("Position 0.0, 0.0");
}
ImGui::SameLine(0, 10);
float translation[2] = { n.translation_.x, n.translation_.y};
@@ -73,12 +81,12 @@ void ImGuiVisitor::visit(Group &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Position " << std::setprecision(3) << n.translation_.x << ", " << n.translation_.y;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(3, 15)) {
n.scale_.x = 1.f;
n.scale_.y = 1.f;
Action::manager().store("Scale 1.0 x 1.0", n.id());
Action::manager().store("Scale 1.0 x 1.0");
}
ImGui::SameLine(0, 10);
float scale[2] = { n.scale_.x, n.scale_.y} ;
@@ -91,12 +99,12 @@ void ImGuiVisitor::visit(Group &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Scale " << std::setprecision(3) << n.scale_.x << " x " << n.scale_.y;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(18, 9)){
n.rotation_.z = 0.f;
Action::manager().store("Angle 0.0", n.id());
Action::manager().store("Angle 0.0");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -104,7 +112,7 @@ void ImGuiVisitor::visit(Group &n)
if (ImGui::IsItemDeactivatedAfterEdit()) {
std::ostringstream oss;
oss << "Angle " << std::setprecision(3) << n.rotation_.z * 180.f / M_PI;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
@@ -132,7 +140,7 @@ void ImGuiVisitor::visit(Scene &n)
void ImGuiVisitor::visit(Primitive &n)
{
ImGui::PushID(std::to_string(n.id()).c_str());
ImGui::Text("Primitive %d", n.id());
ImGui::Text("Primitive %d");
n.shader()->accept(*this);
@@ -144,14 +152,6 @@ void ImGuiVisitor::visit(FrameBufferSurface &n)
ImGui::Text("Framebuffer");
}
void ImGuiVisitor::visit(MediaSurface &n)
{
ImGui::Text("%s", n.path().c_str());
if (n.mediaPlayer())
n.mediaPlayer()->accept(*this);
}
void ImGuiVisitor::visit(MediaPlayer &n)
{
ImGui::Text("Media Player");
@@ -171,7 +171,8 @@ void ImGuiVisitor::visit(Shader &n)
// ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
int mode = n.blending;
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Subtract\0Multiply\0Soft light\0Soft subtract\0Lighten only\0None\0") ) {
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Subtract\0Multiply\0Soft light"
"\0Hard light\0Soft subtract\0Lighten only\0") ) {
n.blending = Shader::BlendMode(mode);
std::ostringstream oss;
@@ -189,6 +190,9 @@ void ImGuiVisitor::visit(Shader &n)
case Shader::BLEND_MULTIPLY:
oss<<"Multiply";
break;
case Shader::BLEND_HARD_LIGHT:
oss<<"Hard light";
break;
case Shader::BLEND_SOFT_LIGHT:
oss<<"Soft light";
break;
@@ -202,7 +206,7 @@ void ImGuiVisitor::visit(Shader &n)
oss<<"None";
break;
}
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
ImGui::PopID();
@@ -224,7 +228,7 @@ void ImGuiVisitor::visit(Shader &n)
// else {
// // TODO ask for custom mask
// }
// Action::manager().store("Mask "+ std::string(ImageShader::mask_names[n.mask]), n.id());
// Action::manager().store("Mask "+ std::string(ImageShader::mask_names[n.mask]));
// }
// ImGui::PopID();
//}
@@ -233,22 +237,18 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
{
ImGui::PushID(std::to_string(n.id()).c_str());
if (ImGuiToolkit::ButtonIcon(6, 2)) {
ImageProcessingShader defaultvalues;
n = defaultvalues;
Action::manager().store("Reset Filters", n.id());
}
ImGuiToolkit::Icon(6, 2);
ImGui::SameLine(0, 10);
ImGui::Text("Filters");
if (ImGuiToolkit::ButtonIcon(6, 4)) {
n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
Action::manager().store("Gamma & Color", n.id());
Action::manager().store("Gamma & Color");
}
ImGui::SameLine(0, 10);
ImGui::ColorEdit3("Gamma Color", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
if (ImGui::IsItemDeactivatedAfterEdit())
Action::manager().store("Gamma Color changed", n.id());
Action::manager().store("Gamma Color changed");
ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -256,7 +256,7 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Gamma " << std::setprecision(2) << n.gamma.w;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -265,7 +265,7 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGuiToolkit::ButtonIcon(5, 16)) {
n.brightness = 0.f;
n.contrast = 0.f;
Action::manager().store("B & C 0.0 0.0", n.id());
Action::manager().store("B & C 0.0 0.0");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -278,12 +278,12 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "B & C " << std::setprecision(2) << n.brightness << " " << n.contrast;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(9, 16)) {
n.saturation = 0.f;
Action::manager().store("Saturation 0.0", n.id());
Action::manager().store("Saturation 0.0");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -291,12 +291,12 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Saturation " << std::setprecision(2) << n.saturation;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(12, 4)) {
n.hueshift = 0.f;
Action::manager().store("Hue shift 0.0", n.id());
Action::manager().store("Hue shift 0.0");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -304,12 +304,12 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Hue shift " << std::setprecision(2) << n.hueshift;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(18, 1)) {
n.nbColors = 0;
Action::manager().store("Posterize None", n.id());
Action::manager().store("Posterize None");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -318,12 +318,12 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
std::ostringstream oss;
oss << "Posterize ";
if (n.nbColors == 0) oss << "None"; else oss << n.nbColors;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(8, 1)) {
n.threshold = 0.f;
Action::manager().store("Threshold None", n.id());
Action::manager().store("Threshold None");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -332,12 +332,12 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
std::ostringstream oss;
oss << "Threshold ";
if (n.threshold < 0.001) oss << "None"; else oss << std::setprecision(2) << n.threshold;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(3, 1)) {
n.lumakey = 0.f;
Action::manager().store("Lumakey 0.0", n.id());
Action::manager().store("Lumakey 0.0");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
@@ -345,18 +345,18 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << "Lumakey " << std::setprecision(2) << n.lumakey;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(13, 4)) {
n.chromakey = glm::vec4(0.f, 0.8f, 0.f, 1.f);
n.chromadelta = 0.f;
Action::manager().store("Chromakey & Color Reset", n.id());
Action::manager().store("Chromakey & Color Reset");
}
ImGui::SameLine(0, 10);
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
if (ImGui::IsItemDeactivatedAfterEdit())
Action::manager().store("Chroma color changed", n.id());
Action::manager().store("Chroma color changed");
ImGui::SameLine(0, 5);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderFloat("Chromakey", &n.chromadelta, 0.0, 1.0, n.chromadelta < 0.001 ? "None" : "Tolerance %.2f");
@@ -364,33 +364,32 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
std::ostringstream oss;
oss << "Chromakey ";
if (n.chromadelta < 0.001) oss << "None"; else oss << std::setprecision(2) << n.chromadelta;
Action::manager().store(oss.str(), n.id());
Action::manager().store(oss.str());
}
if (ImGuiToolkit::ButtonIcon(6, 16)) {
n.invert = 0;
Action::manager().store("Invert None", n.id());
Action::manager().store("Invert None");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0"))
Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")), n.id());
Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")));
if (ImGuiToolkit::ButtonIcon(1, 7)) {
n.filterid = 0;
Action::manager().store("Filter None", n.id());
Action::manager().store("Filter None");
}
ImGui::SameLine(0, 10);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) ) )
Action::manager().store("Filter " + std::string(ImageProcessingShader::filter_names[n.filterid]), n.id());
Action::manager().store("Filter " + std::string(ImageProcessingShader::filter_names[n.filterid]));
ImGui::PopID();
ImGui::Spacing();
}
void ImGuiVisitor::visit (Source& s)
{
ImGui::PushID(std::to_string(s.id()).c_str());
@@ -427,18 +426,27 @@ void ImGuiVisitor::visit (Source& s)
// Inform on workspace
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) );
if (s.workspace() == Source::BACKGROUND)
ImGuiToolkit::HelpIcon("Background",10, 16);
ImGuiToolkit::HelpIcon("in Background",10, 16);
else if (s.workspace() == Source::FOREGROUND)
ImGuiToolkit::HelpIcon("Foreground",12, 16);
ImGuiToolkit::HelpIcon("in Foreground",12, 16);
else
ImGuiToolkit::HelpIcon("Stage",11, 16);
ImGuiToolkit::HelpIcon("in Workspace",11, 16);
// locking
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.f * ImGui::GetFrameHeightWithSpacing()) );
const char *tooltip[2] = {"Unlocked", "Locked"};
bool l = s.locked();
if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) )
if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) {
s.setLocked(l);
if (l) {
Mixer::selection().clear();
Action::manager().store(s.name() + std::string(": lock."));
}
else {
Mixer::selection().set(&s);
Action::manager().store(s.name() + std::string(": unlock."));
}
}
// toggle enable/disable image processing
bool on = s.imageProcessingEnabled();
@@ -446,19 +454,84 @@ void ImGuiVisitor::visit (Source& s)
if ( ImGuiToolkit::ButtonToggle(ICON_FA_MAGIC, &on) ){
std::ostringstream oss;
oss << s.name() << ": " << ( on ? "Enable Filter" : "Disable Filter");
Action::manager().store(oss.str(), s.id());
Action::manager().store(oss.str());
}
s.setImageProcessingEnabled(on);
ImGui::SetCursorPosY(pos.y + preview_height); // ...come back
// image processing pannel
if (s.imageProcessingEnabled())
s.processingShader()->accept(*this);
if (s.imageProcessingEnabled()) {
// geometry direct control
// s.groupNode(View::GEOMETRY)->accept(*this);
// s.groupNode((View::Mode) Settings::application.current_view)->accept(*this);
// menu icon for image processing
ImGui::SetCursorPos( ImVec2( preview_width - ImGui::GetTextLineHeight(), pos.y + 4.5f * ImGui::GetFrameHeightWithSpacing())); // ...come back
if (ImGuiToolkit::IconButton(5, 8))
ImGui::OpenPopup( "MenuImageProcessing" );
if (ImGui::BeginPopup( "MenuImageProcessing" ))
{
if (s.processingshader_link_.connected()) {
if (ImGui::MenuItem( "Unfollow" )){
s.processingshader_link_.disconnect();
}
}
else {
if (ImGui::MenuItem("Reset" )){
ImageProcessingShader defaultvalues;
s.processingShader()->copy(defaultvalues);
s.processingshader_link_.disconnect();
std::ostringstream oss;
oss << s.name() << ": " << "Reset Filter";
Action::manager().store(oss.str());
}
if (ImGui::MenuItem("Copy" )){
std::string clipboard = SessionVisitor::getClipboard(s.processingShader());
if (!clipboard.empty())
ImGui::SetClipboardText(clipboard.c_str());
}
const char *clipboard = ImGui::GetClipboardText();
const bool can_paste = (clipboard != nullptr && SessionLoader::isClipboard(clipboard));
if (ImGui::MenuItem("Paste", NULL, false, can_paste)) {
SessionLoader::applyImageProcessing(s, clipboard);
std::ostringstream oss;
oss << s.name() << ": " << "Change Filter";
Action::manager().store(oss.str());
}
// ImGui::Separator();
// if (ImGui::BeginMenu("Follow"))
// {
// for (auto mpit = Mixer::manager().session()->begin();
// mpit != Mixer::manager().session()->end(); mpit++ )
// {
// std::string label = (*mpit)->name();
// if ( (*mpit)->id() != s.id() &&
// (*mpit)->imageProcessingEnabled() &&
// !(*mpit)->processingshader_link_.connected()) {
// if (ImGui::MenuItem( label.c_str() )){
// s.processingshader_link_.connect(*mpit);
// s.touch();
// }
// }
// }
// ImGui::EndMenu();
// }
}
ImGui::EndPopup();
}
// full panel for image processing
ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); // ...come back
if (s.processingshader_link_.connected()) {
ImGuiToolkit::Icon(6, 2);
ImGui::SameLine(0, 10);
ImGui::Text("Filters");
Source *target = s.processingshader_link_.source();
ImGui::Text("Following");
if ( target != nullptr && ImGui::Button(target->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().setCurrentSource(target);
}
else
s.processingShader()->accept(*this);
}
ImGui::PopID();
}
@@ -472,9 +545,30 @@ void ImGuiVisitor::visit (MediaSource& s)
else
ImGui::Text("Video File");
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
UserInterface::manager().showMediaPlayer( s.mediaplayer());
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
// Media info
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
// folder
std::string path = SystemToolkit::path_filename(s.path());
std::string label = BaseToolkit::trunc_string(path, 25);
label = BaseToolkit::transliterate(label);
ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
ImGui::SameLine();
ImGui::Text("Folder");
}
void ImGuiVisitor::visit (SessionFileSource& s)
@@ -485,7 +579,27 @@ void ImGuiVisitor::visit (SessionFileSource& s)
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Session File");
// ImGui::Text("%s", SystemToolkit::base_filename(s.path()).c_str());
// info
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().import( &s );
ImGui::SameLine();
ImGui::Text("Sources");
if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFading(0.f);
float f = s.session()->fading();
@@ -496,15 +610,20 @@ void ImGuiVisitor::visit (SessionFileSource& s)
if (ImGui::IsItemDeactivatedAfterEdit()){
std::ostringstream oss;
oss << s.name() << ": Fading " << std::setprecision(2) << f;
Action::manager().store(oss.str(), s.id());
Action::manager().store(oss.str());
}
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open File", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().set( s.detach() );
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import sources", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().import( &s );
ImGui::SameLine();
ImGui::Text("File");
std::string path = SystemToolkit::path_filename(s.path());
std::string label = BaseToolkit::trunc_string(path, 25);
label = BaseToolkit::transliterate(label);
ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
ImGui::SameLine();
ImGui::Text("Folder");
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
}
void ImGuiVisitor::visit (SessionGroupSource& s)
@@ -514,12 +633,27 @@ void ImGuiVisitor::visit (SessionGroupSource& s)
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Flat Session");
ImGui::Text("Flat Sesion group");
if ( ImGui::Button( ICON_FA_UPLOAD " Expand sources", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
Mixer::manager().import( &s );
// info
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
if ( ImGui::Button( ICON_FA_UPLOAD " Expand", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
Mixer::manager().import( &s );
}
}
void ImGuiVisitor::visit (RenderSource& s)
@@ -538,6 +672,8 @@ void ImGuiVisitor::visit (CloneSource& s)
ImGui::Text("Clone");
if ( ImGui::Button(s.origin()->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
Mixer::manager().setCurrentSource(s.origin());
ImGui::SameLine();
ImGui::Text("Source");
}
void ImGuiVisitor::visit (PatternSource& s)
@@ -546,19 +682,38 @@ void ImGuiVisitor::visit (PatternSource& s)
ImGui::SameLine(0, 10);
ImGui::Text("Pattern");
// stream info
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##Patterns", Pattern::pattern_types[s.pattern()->type()].c_str()) )
{
for (uint p = 0; p < Pattern::pattern_types.size(); ++p){
if (ImGui::Selectable( Pattern::pattern_types[p].c_str() )) {
s.setPattern(p, s.pattern()->resolution());
info.reset();
std::ostringstream oss;
oss << s.name() << ": Pattern " << Pattern::pattern_types[p];
Action::manager().store(oss.str(), s.id());
Action::manager().store(oss.str());
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::Text("Generator");
}
void ImGuiVisitor::visit (DeviceSource& s)
@@ -567,6 +722,21 @@ void ImGuiVisitor::visit (DeviceSource& s)
ImGui::SameLine(0, 10);
ImGui::Text("Device");
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##Hardware", s.device().c_str()))
{
@@ -574,19 +744,15 @@ void ImGuiVisitor::visit (DeviceSource& s)
std::string namedev = Device::manager().name(d);
if (ImGui::Selectable( namedev.c_str() )) {
s.setDevice(namedev);
info.reset();
std::ostringstream oss;
oss << s.name() << " Device " << namedev;
Action::manager().store(oss.str(), s.id());
Action::manager().store(oss.str());
}
}
ImGui::EndCombo();
}
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
if ( !confs.empty()) {
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
ImGui::Text("%s %s %dx%d@%.1ffps", best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
}
}
void ImGuiVisitor::visit (NetworkSource& s)
@@ -598,16 +764,93 @@ void ImGuiVisitor::visit (NetworkSource& s)
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
ImGui::Text("%s", s.connection().c_str());
ImGui::PopStyleColor(1);
NetworkStream *ns = s.networkStream();
ImGui::Text(" - %s (%dx%d)\n - Server address %s", NetworkToolkit::protocol_name[ns->protocol()],
ns->resolution().x, ns->resolution().y, ns->serverAddress().c_str());
// network info
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, 10.f + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
if ( ImGui::Button( ICON_FA_REPLY " Reconnect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
{
// TODO : reload ?
s.setConnection(s.connection());
info.reset();
}
}
void ImGuiVisitor::visit (MultiFileSource& s)
{
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
ImGui::SameLine(0, 10);
ImGui::Text("Images sequence");
static int64_t id = 0;
// information text
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
// icon (>) to open player
if ( s.playable() ) {
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SameLine(0, 0);
ImGui::SameLine(0, ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player"))
UserInterface::manager().showSourceEditor(&s);
ImGui::SetCursorPos(pos);
}
// change range
static int _begin = -1;
if (_begin < 0 || id != s.id())
_begin = s.begin();
static int _end = -1;
if (_end < 0 || id != s.id())
_end = s.end();
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::DragIntRange2("Range", &_begin, &_end, 1, s.sequence().min, s.sequence().max);
if (ImGui::IsItemDeactivatedAfterEdit()){
s.setRange( _begin, _end );
std::ostringstream oss;
oss << s.name() << ": Range " << _begin << "-" << _end;
Action::manager().store(oss.str());
_begin = _end = -1;
}
// change framerate
static int _fps = -1;
if (_fps < 0 || id != s.id())
_fps = s.framerate();
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Framerate", &_fps, 1, 30, "%d fps");
if (ImGui::IsItemDeactivatedAfterEdit()){
s.setFramerate(_fps);
std::ostringstream oss;
oss << s.name() << ": Framerate " << _fps << " fps";
Action::manager().store(oss.str());
_fps = -1;
}
// offer to open file browser at location
std::string path = SystemToolkit::path_filename(s.sequence().location);
std::string label = BaseToolkit::trunc_string(path, 25);
label = BaseToolkit::transliterate(label);
ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
ImGui::SameLine();
ImGui::Text("Folder");
if (id != s.id())
id = s.id();
}

View File

@@ -2,9 +2,12 @@
#define IMGUIVISITOR_H
#include "Visitor.h"
#include "InfoVisitor.h"
class ImGuiVisitor: public Visitor
{
InfoVisitor info;
public:
ImGuiVisitor();
@@ -14,7 +17,6 @@ public:
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
void visit (MediaSurface& n) override;
void visit (FrameBufferSurface& n) override;
// Elements with attributes
@@ -30,6 +32,7 @@ public:
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
};
#endif // IMGUIVISITOR_H

View File

@@ -12,26 +12,7 @@ const char* ImageProcessingShader::filter_names[12] = { "None", "Blur", "Sharpen
ImageProcessingShader::ImageProcessingShader(): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
}
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S): Shader()
{
program_ = &imageProcessingShadingProgram;
reset();
brightness = S.brightness;
contrast = S.contrast;
saturation = S.saturation;
hueshift = S.hueshift;
threshold = S.threshold;
lumakey = S.lumakey;
nbColors = S.nbColors;
invert = S.invert;
filterid = S.filterid;
gamma = S.gamma;
levels = S.levels;
chromakey = S.chromakey;
chromadelta = S.chromadelta;
ImageProcessingShader::reset();
}
void ImageProcessingShader::use()
@@ -56,7 +37,6 @@ void ImageProcessingShader::use()
}
void ImageProcessingShader::reset()
{
Shader::reset();
@@ -78,7 +58,7 @@ void ImageProcessingShader::reset()
}
void ImageProcessingShader::operator = (const ImageProcessingShader &S )
void ImageProcessingShader::copy(ImageProcessingShader const& S)
{
brightness = S.brightness;
contrast = S.contrast;

View File

@@ -11,13 +11,12 @@ class ImageProcessingShader : public Shader
public:
ImageProcessingShader();
ImageProcessingShader(const ImageProcessingShader &model);
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const ImageProcessingShader &S);
void copy(ImageProcessingShader const& S);
// color effects
float brightness; // [-1 1]

View File

@@ -8,18 +8,26 @@
#include "ImageShader.h"
ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
ShadingProgram inverseAlphaProgram("shaders/image.vs", "shaders/imageblending.fs");
ShadingProgram imageAlphaProgram ("shaders/image.vs", "shaders/imageblending.fs");
std::vector< ShadingProgram > maskPrograms = {
ShadingProgram("shaders/simple.vs", "shaders/simple.fs"),
ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"),
ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs")
};
const char* MaskShader::mask_names[3] = { ICON_FA_EXPAND, ICON_FA_EDIT, ICON_FA_SHAPES };
const char* MaskShader::mask_shapes[5] = { "Elipse", "Oblong", "Rectangle", "Horizontal", "Vertical" };
std::vector< ShadingProgram* > MaskShader::mask_programs;
ImageShader::ImageShader(): Shader(), stipple(0.0)
ImageShader::ImageShader(): Shader(), stipple(0.f), mask_texture(0)
{
// static program shader
program_ = &imageShadingProgram;
// reset instance
reset();
ImageShader::reset();
}
void ImageShader::use()
@@ -29,6 +37,10 @@ void ImageShader::use()
// set stippling
program_->setUniform("stipple", stipple);
// default mask
if (mask_texture == 0)
mask_texture = Resource::getTextureWhite();
// setup mask texture
glActiveTexture(GL_TEXTURE1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -40,17 +52,14 @@ void ImageShader::use()
void ImageShader::reset()
{
Shader::reset();
mask_texture = 0;
// default mask
mask_texture = Resource::getTextureWhite();
// no stippling
stipple = 0.f;
}
void ImageShader::operator = (const ImageShader &S)
void ImageShader::copy(ImageShader const& S)
{
Shader::operator =(S);
mask_texture = S.mask_texture;
stipple = S.stipple;
}
@@ -62,10 +71,10 @@ void ImageShader::accept(Visitor& v) {
}
DivideAlphaShader::DivideAlphaShader(): ImageShader()
AlphaShader::AlphaShader(): ImageShader()
{
// to inverse alpha mode, use dedicated shading program
program_ = &inverseAlphaProgram;
program_ = &imageAlphaProgram;
// reset instance
reset();
@@ -75,28 +84,18 @@ DivideAlphaShader::DivideAlphaShader(): ImageShader()
MaskShader::MaskShader(): Shader(), mode(0)
{
// first initialization
if ( mask_programs.empty() ) {
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/simple.fs"));
mask_programs.push_back(new ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"));
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs"));
}
// reset instance
reset();
MaskShader::reset();
// static program shader
program_ = mask_programs[0];
program_ = &maskPrograms[0];
}
void MaskShader::use()
{
// select program to use
mode = CLAMP(mode, 0, 2);
shape = CLAMP(shape, 0, 4);
program_ = mode < 2 ? mask_programs[mode] : mask_programs[shape+2] ;
mode = MINI(mode, 2);
shape = MINI(shape, 4);
program_ = mode < 2 ? &maskPrograms[mode] : &maskPrograms[shape+2] ;
// actual use of shader program
Shader::use();
@@ -132,10 +131,8 @@ void MaskShader::reset()
effect = 0;
}
void MaskShader::operator = (const MaskShader &S)
void MaskShader::copy(MaskShader const& S)
{
Shader::operator =(S);
mode = S.mode;
shape = S.shape;
blur = S.blur;

View File

@@ -19,7 +19,7 @@ public:
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const ImageShader &S);
void copy(ImageShader const& S);
uint mask_texture;
@@ -27,11 +27,11 @@ public:
float stipple;
};
class DivideAlphaShader : public ImageShader
class AlphaShader : public ImageShader
{
public:
DivideAlphaShader();
AlphaShader();
};
@@ -45,7 +45,7 @@ public:
void use() override;
void reset() override;
void accept(Visitor& v) override;
void operator = (const MaskShader &S);
void copy(MaskShader const& S);
enum Modes {
NONE = 0,
@@ -74,7 +74,6 @@ public:
static const char* mask_names[3];
static const char* mask_shapes[5];
static std::vector< ShadingProgram* > mask_programs;
};
#endif // IMAGESHADER_H

259
InfoVisitor.cpp Normal file
View File

@@ -0,0 +1,259 @@
#include "InfoVisitor.h"
#include <vector>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <tinyxml2.h>
#include "tinyxml2Toolkit.h"
#include "defines.h"
#include "Log.h"
#include "Scene.h"
#include "Primitives.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
#include "MediaSource.h"
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "MultiFileSource.h"
#include "SessionCreator.h"
#include "SessionVisitor.h"
#include "Settings.h"
#include "Mixer.h"
#include "ActionManager.h"
#include "BaseToolkit.h"
#include "UserInterfaceManager.h"
#include "SystemToolkit.h"
InfoVisitor::InfoVisitor() : brief_(true), current_id_(0)
{
}
void InfoVisitor::visit(Node &n)
{
}
void InfoVisitor::visit(Group &n)
{
}
void InfoVisitor::visit(Switch &n)
{
}
void InfoVisitor::visit(Scene &n)
{
}
void InfoVisitor::visit(Primitive &n)
{
}
void InfoVisitor::visit(MediaPlayer &mp)
{
// do not ask twice
if (current_id_ == mp.id())
return;
std::ostringstream oss;
if (brief_) {
oss << SystemToolkit::filename(mp.filename()) << std::endl;
oss << mp.width() << " x " << mp.height() << ", ";
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,"));
if (!mp.isImage())
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
}
else {
oss << mp.filename() << std::endl;
oss << mp.media().codec_name << std::endl;
oss << mp.width() << " x " << mp.height() ;
if (!mp.isImage())
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
}
information_ = oss.str();
// remember (except if codec was not identified yet)
if ( !mp.media().codec_name.empty() )
current_id_ = mp.id();
}
void InfoVisitor::visit(Stream &n)
{
std::ostringstream oss;
if (brief_) {
}
else {
}
information_ = oss.str();
}
void InfoVisitor::visit (MediaSource& s)
{
s.mediaplayer()->accept(*this);
}
void InfoVisitor::visit (SessionFileSource& s)
{
if (current_id_ == s.id() || s.session() == nullptr)
return;
std::ostringstream oss;
if (brief_) {
oss << SystemToolkit::filename(s.path()) << " (";
oss << s.session()->numSource() << " sources)" << std::endl;
}
else
oss << s.path() << std::endl;
if (s.session()->frame()){
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
oss << "RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (SessionGroupSource& s)
{
if (current_id_ == s.id() || s.session() == nullptr)
return;
std::ostringstream oss;
oss << s.session()->numSource() << " sources in group" << std::endl;
if (s.session()->frame()){
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
oss << "RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (RenderSource& s)
{
if (current_id_ == s.id())
return;
information_ = "Rendering Output";
current_id_ = s.id();
}
void InfoVisitor::visit (CloneSource& s)
{
if (current_id_ == s.id())
return;
information_ = "Clone of " + s.origin()->name();
current_id_ = s.id();
}
void InfoVisitor::visit (PatternSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
oss << Pattern::pattern_types[s.pattern()->type()] << std::endl;
if (s.pattern()) {
oss << s.pattern()->width() << " x " << s.pattern()->height();
oss << ", RGB";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (DeviceSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
if ( !confs.empty()) {
DeviceConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
if (brief_) {
oss << best.width << " x " << best.height << ", ";
oss << best.stream << " " << best.format << ", ";
oss << std::fixed << std::setprecision(1) << fps << " fps";
}
else {
oss << s.device() << std::endl;
oss << best.width << " x " << best.height << ", ";
oss << best.stream << " " << best.format << ", ";
oss << std::fixed << std::setprecision(1) << fps << " fps";
}
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (NetworkSource& s)
{
if (current_id_ == s.id())
return;
NetworkStream *ns = s.networkStream();
std::ostringstream oss;
if (brief_) {
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
oss << NetworkToolkit::protocol_name[ns->protocol()] << std::endl;
oss << "IP " << ns->serverAddress();
}
else {
oss << s.connection() << " (IP " << ns->serverAddress() << ")" << std::endl;
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
oss << NetworkToolkit::protocol_name[ns->protocol()];
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (MultiFileSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
if (brief_) {
oss << s.sequence().width << " x " << s.sequence().height << ", ";
oss << s.sequence().codec << std::endl;
oss << s.sequence().max - s.sequence().min + 1 << " images [";
oss << s.sequence().min << " - " << s.sequence().max << "]";
}
else {
oss << s.sequence().location << " [";
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
oss << s.sequence().width << " x " << s.sequence().height << ", ";
oss << s.sequence().codec << " (";
oss << s.sequence().max - s.sequence().min + 1 << " images)";
}
information_ = oss.str();
current_id_ = s.id();
}

40
InfoVisitor.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef INFOVISITOR_H
#define INFOVISITOR_H
#include "Visitor.h"
class InfoVisitor : public Visitor
{
std::string information_;
bool brief_;
uint64_t current_id_;
public:
InfoVisitor();
inline void setBriefStringMode () { brief_ = true; current_id_ = 0; }
inline void setExtendedStringMode () { brief_ = false; current_id_ = 0; }
inline void reset () { current_id_ = 0; }
inline std::string str () const { return information_; }
// Elements of Scene
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
// Elements with attributes
void visit (Stream& n) override;
void visit (MediaPlayer& n) override;
void visit (MediaSource& s) override;
void visit (SessionFileSource& s) override;
void visit (SessionGroupSource& s) override;
void visit (RenderSource& s) override;
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
};
#endif // INFOVISITOR_H

166
Interpolator.cpp Normal file
View File

@@ -0,0 +1,166 @@
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "defines.h"
#include "Log.h"
#include "Source.h"
#include "ImageProcessingShader.h"
#include "UpdateCallback.h"
#include "Interpolator.h"
SourceInterpolator::SourceInterpolator(Source *subject, const SourceCore &target) :
subject_(subject), from_(static_cast<SourceCore>(*subject)), to_(target), current_cursor_(0.f)
{
}
void SourceInterpolator::interpolateGroup(View::Mode m)
{
current_state_.group(m)->translation_ =
(1.f - current_cursor_) * from_.group(m)->translation_
+ current_cursor_ * to_.group(m)->translation_;
current_state_.group(m)->scale_ =
(1.f - current_cursor_) * from_.group(m)->scale_
+ current_cursor_ * to_.group(m)->scale_;
current_state_.group(m)->rotation_ =
(1.f - current_cursor_) * from_.group(m)->rotation_
+ current_cursor_ * to_.group(m)->rotation_;
current_state_.group(m)->crop_ =
(1.f - current_cursor_) * from_.group(m)->crop_
+ current_cursor_ * to_.group(m)->crop_;
CopyCallback *anim = new CopyCallback( current_state_.group(m) );
subject_->group(m)->update_callbacks_.clear();
subject_->group(m)->update_callbacks_.push_back(anim);
}
void SourceInterpolator::interpolateImageProcessing()
{
current_state_.processingShader()->brightness =
(1.f - current_cursor_) * from_.processingShader()->brightness
+ current_cursor_ * to_.processingShader()->brightness;
current_state_.processingShader()->contrast =
(1.f - current_cursor_) * from_.processingShader()->contrast
+ current_cursor_ * to_.processingShader()->contrast;
current_state_.processingShader()->saturation =
(1.f - current_cursor_) * from_.processingShader()->saturation
+ current_cursor_ * to_.processingShader()->saturation;
current_state_.processingShader()->hueshift =
(1.f - current_cursor_) * from_.processingShader()->hueshift
+ current_cursor_ * to_.processingShader()->hueshift;
current_state_.processingShader()->threshold =
(1.f - current_cursor_) * from_.processingShader()->threshold
+ current_cursor_ * to_.processingShader()->threshold;
current_state_.processingShader()->lumakey =
(1.f - current_cursor_) * from_.processingShader()->lumakey
+ current_cursor_ * to_.processingShader()->lumakey;
current_state_.processingShader()->nbColors =
(1.f - current_cursor_) * from_.processingShader()->nbColors
+ current_cursor_ * to_.processingShader()->nbColors;
current_state_.processingShader()->gamma =
(1.f - current_cursor_) * from_.processingShader()->gamma
+ current_cursor_ * to_.processingShader()->gamma;
current_state_.processingShader()->levels =
(1.f - current_cursor_) * from_.processingShader()->levels
+ current_cursor_ * to_.processingShader()->levels;
current_state_.processingShader()->chromakey =
(1.f - current_cursor_) * from_.processingShader()->chromakey
+ current_cursor_ * to_.processingShader()->chromakey;
current_state_.processingShader()->chromadelta =
(1.f - current_cursor_) * from_.processingShader()->chromadelta
+ current_cursor_ * to_.processingShader()->chromadelta;
subject_->processingShader()->copy( *current_state_.processingShader() );
// not interpolated : invert , filterid
}
float SourceInterpolator::current() const
{
return current_cursor_;
}
void SourceInterpolator::apply(float percent)
{
percent = CLAMP( percent, 0.f, 1.f);
if ( subject_ && ABS_DIFF(current_cursor_, percent) > EPSILON)
{
current_cursor_ = percent;
if (current_cursor_ < EPSILON) {
current_cursor_ = 0.f;
current_state_ = from_;
subject_->copy(current_state_);
}
else if (current_cursor_ > 1.f - EPSILON) {
current_cursor_ = 1.f;
current_state_ = to_;
subject_->copy(current_state_);
}
else {
interpolateGroup(View::MIXING);
interpolateGroup(View::GEOMETRY);
interpolateGroup(View::LAYER);
interpolateGroup(View::TEXTURE);
interpolateImageProcessing();
// Log::Info("SourceInterpolator::update %f", cursor);
}
subject_->touch();
}
}
Interpolator::Interpolator()
{
}
Interpolator::~Interpolator()
{
for (auto i = interpolators_.begin(); i != interpolators_.end(); ) {
delete *i;
i = interpolators_.erase(i);
}
}
void Interpolator::add (Source *s, const SourceCore &target)
{
SourceInterpolator *i = new SourceInterpolator(s, target);
interpolators_.push_back(i);
}
float Interpolator::current() const
{
float ret = 0.f;
if (interpolators_.size() > 0)
ret = interpolators_.front()->current();
return ret;
}
void Interpolator::apply(float percent)
{
for (auto i = interpolators_.begin(); i != interpolators_.end(); ++i)
(*i)->apply( percent );
}

43
Interpolator.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef INTERPOLATOR_H
#define INTERPOLATOR_H
#include "Source.h"
#include "SourceList.h"
class SourceInterpolator
{
public:
SourceInterpolator(Source *subject, const SourceCore &target);
void apply (float percent);
float current() const;
protected:
Source *subject_;
SourceCore from_;
SourceCore to_;
SourceCore current_state_;
float current_cursor_;
void interpolateGroup (View::Mode m);
void interpolateImageProcessing ();
};
class Interpolator
{
public:
Interpolator();
~Interpolator();
void add (Source *s, const SourceCore &target );
void apply (float percent);
float current() const;
protected:
std::list<SourceInterpolator *> interpolators_;
};
#endif // INTERPOLATOR_H

397
LayerView.cpp Normal file
View File

@@ -0,0 +1,397 @@
// Opengl
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "imgui.h"
#include "ImGuiToolkit.h"
#include <string>
#include <sstream>
#include <iomanip>
#include "Mixer.h"
#include "defines.h"
#include "Source.h"
#include "Settings.h"
#include "Decorations.h"
#include "UserInterfaceManager.h"
#include "BoundingBoxVisitor.h"
#include "ActionManager.h"
#include "Log.h"
#include "LayerView.h"
LayerView::LayerView() : View(LAYER), aspect_ratio(1.f)
{
scene.root()->scale_ = glm::vec3(LAYER_DEFAULT_SCALE, LAYER_DEFAULT_SCALE, 1.0f);
scene.root()->translation_ = glm::vec3(2.2f, 1.2f, 0.0f);
// read default settings
if ( Settings::application.views[mode_].name.empty() ) {
// no settings found: store application default
Settings::application.views[mode_].name = "Layer";
saveSettings();
}
else {
restoreSettings();
}
// Geometry Scene background
frame_ = new Group;
Surface *rect = new Surface;
rect->shader()->color.a = 0.3f;
frame_->attach(rect);
Frame *border = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
border->color = glm::vec4( COLOR_FRAME, 0.95f );
frame_->attach(border);
scene.bg()->attach(frame_);
persp_left_ = new Mesh("mesh/perspective_axis_left.ply");
persp_left_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
persp_left_->scale_.x = LAYER_PERSPECTIVE;
persp_left_->translation_.z = -0.1f;
persp_left_->translation_.x = -1.f;
scene.bg()->attach(persp_left_);
persp_right_ = new Mesh("mesh/perspective_axis_right.ply");
persp_right_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
persp_right_->scale_.x = LAYER_PERSPECTIVE;
persp_right_->translation_.z = -0.1f;
persp_right_->translation_.x = 1.f;
scene.bg()->attach(persp_right_);
}
void LayerView::draw()
{
View::draw();
// initialize the verification of the selection
static bool candidate_flatten_group = false;
// display popup menu
if (show_context_menu_ == MENU_SELECTION) {
// initialize the verification of the selection
candidate_flatten_group = true;
// start loop on selection
SourceList::iterator it = Mixer::selection().begin();
float depth_first = (*it)->depth();
for (; it != Mixer::selection().end(); ++it) {
// test if selection is contiguous in layer (i.e. not interrupted)
SourceList::iterator inter = Mixer::manager().session()->find(depth_first, (*it)->depth());
if ( inter != Mixer::manager().session()->end() && !Mixer::selection().contains(*inter)){
// NOT a group: there is a source in the session that
// - is between two selected sources (in depth)
// - is not part of the selection
candidate_flatten_group = false;
break;
}
}
ImGui::OpenPopup( "LayerSelectionContextMenu" );
show_context_menu_ = MENU_NONE;
}
if (ImGui::BeginPopup("LayerSelectionContextMenu")) {
// colored context menu
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
// special action of Mixing view
if (candidate_flatten_group){
if (ImGui::Selectable( ICON_FA_DOWNLOAD " Flatten" )) {
Mixer::manager().groupSelection();
}
}
else {
ImGui::TextDisabled( ICON_FA_DOWNLOAD " Flatten" );
}
ImGui::Separator();
// manipulation of sources in Mixing view
if (ImGui::Selectable( ICON_FA_ALIGN_CENTER " Distribute" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
float depth = (*it)->depth();
float depth_inc = (dsl.back()->depth() - depth) / static_cast<float>(Mixer::selection().size()-1);
for (++it; it != dsl.end(); ++it) {
depth += depth_inc;
(*it)->setDepth(depth);
}
Action::manager().store(std::string("Selection: Layer Distribute"));
++View::need_deep_update_;
}
if (ImGui::Selectable( ICON_FA_RULER_HORIZONTAL " Compress" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
float depth = (*it)->depth();
for (++it; it != dsl.end(); ++it) {
depth += LAYER_STEP;
(*it)->setDepth(depth);
}
Action::manager().store(std::string("Selection: Layer Compress"));
++View::need_deep_update_;
}
if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT " Reverse order" )){
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
SourceList::iterator it = dsl.begin();
SourceList::reverse_iterator rit = dsl.rbegin();
for (; it != dsl.end(); ++it, ++rit) {
(*it)->setDepth((*rit)->depth());
}
Action::manager().store(std::string("Selection: Layer Reverse order"));
++View::need_deep_update_;
}
ImGui::PopStyleColor(2);
ImGui::EndPopup();
}
}
void LayerView::update(float dt)
{
View::update(dt);
// a more complete update is requested
if (View::need_deep_update_ > 0) {
// update rendering of render frame
FrameBuffer *output = Mixer::manager().session()->frame();
if (output){
// correct with aspect ratio
aspect_ratio = output->aspectRatio();
frame_->scale_.x = aspect_ratio;
persp_left_->translation_.x = -aspect_ratio;
persp_right_->translation_.x = aspect_ratio + 0.06;
}
// prevent invalid scaling
float s = CLAMP(scene.root()->scale_.x, LAYER_MIN_SCALE, LAYER_MAX_SCALE);
scene.root()->scale_.x = s;
scene.root()->scale_.y = s;
}
if (Mixer::manager().view() == this )
{
// update the selection overlay
updateSelectionOverlay();
}
}
bool LayerView::canSelect(Source *s) {
return ( View::canSelect(s) && s->active() );
}
void LayerView::resize ( int scale )
{
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
z *= z;
z *= LAYER_MAX_SCALE - LAYER_MIN_SCALE;
z += LAYER_MIN_SCALE;
scene.root()->scale_.x = z;
scene.root()->scale_.y = z;
// Clamp translation to acceptable area
glm::vec3 border_left(scene.root()->scale_.x * -2.f, scene.root()->scale_.y * -1.f, 0.f);
glm::vec3 border_right(scene.root()->scale_.x * 8.f, scene.root()->scale_.y * 8.f, 0.f);
scene.root()->translation_ = glm::clamp(scene.root()->translation_, border_left, border_right);
}
int LayerView::size ()
{
float z = (scene.root()->scale_.x - LAYER_MIN_SCALE) / (LAYER_MAX_SCALE - LAYER_MIN_SCALE);
return (int) ( sqrt(z) * 100.f);
}
std::pair<Node *, glm::vec2> LayerView::pick(glm::vec2 P)
{
// get picking from generic View
std::pair<Node *, glm::vec2> pick = View::pick(P);
// deal with internal interactive objects
if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
openContextMenu(MENU_SELECTION);
}
else {
// get if a source was picked
Source *s = Mixer::manager().findSource(pick.first);
if (s != nullptr) {
// pick on the lock icon; unlock source
if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
lock(s, false);
// pick = { s->locker_, pick.second };
pick = { nullptr, glm::vec2(0.f) };
}
// pick on the open lock icon; lock source and cancel pick
else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) {
lock(s, true);
pick = { nullptr, glm::vec2(0.f) };
}
// pick a locked source; cancel pick
else if ( s->locked() ) {
pick = { nullptr, glm::vec2(0.f) };
}
// pick the symbol: ask to show editor
else if ( pick.first == s->symbol_ ) {
UserInterface::manager().showSourceEditor(s);
}
}
else
pick = { nullptr, glm::vec2(0.f) };
}
return pick;
}
float LayerView::setDepth(Source *s, float d)
{
if (!s)
return -1.f;
// move the layer node of the source
Group *sourceNode = s->group(mode_);
float depth = d < 0.f ? sourceNode->translation_.z : d;
// negative or no depth given; find the front most depth
if ( depth < 0.f ) {
// default to place visible in front of background
depth = LAYER_BACKGROUND + LAYER_STEP;
// find the front-most souce in the workspace (behind FOREGROUND)
for (NodeSet::iterator node = scene.ws()->begin(); node != scene.ws()->end(); ++node) {
// place in front of previous sources
depth = MAX(depth, (*node)->translation_.z + LAYER_STEP);
// in case node is already at max depth
if ((*node)->translation_.z + DELTA_DEPTH > MAX_DEPTH )
(*node)->translation_.z -= DELTA_DEPTH;
}
}
// change depth
sourceNode->translation_.z = CLAMP( depth, MIN_DEPTH, MAX_DEPTH);
// request reordering of scene at next update
View::need_deep_update_++;
// request update of source
s->touch();
return sourceNode->translation_.z;
}
View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
{
if (!s)
return Cursor();
// unproject
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
// compute delta translation
glm::vec3 dest_translation = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
// discretized translation with ALT
if (UserInterface::manager().altModifier())
dest_translation.x = ROUND(dest_translation.x, 5.f);
// apply change
float d = setDepth( s, MAX( -dest_translation.x, 0.f) );
// store action in history
std::ostringstream info;
info << "Depth " << std::fixed << std::setprecision(2) << d << " ";
current_action_ = s->name() + ": " + info.str();
return Cursor(Cursor_ResizeNESW, info.str() );
}
void LayerView::arrow (glm::vec2 movement)
{
static float accumulator = 0.f;
accumulator += dt_;
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
bool first = true;
glm::vec3 delta_translation(0.f);
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
// individual move with SHIFT
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
continue;
Group *sourceNode = (*it)->group(mode_);
glm::vec3 dest_translation(0.f);
if (first) {
// dest starts at current
dest_translation = sourceNode->translation_;
// + ALT : discrete displacement
if (UserInterface::manager().altModifier()) {
if (accumulator > 100.f) {
dest_translation += glm::sign(gl_delta) * 0.21f;
dest_translation.x = ROUND(dest_translation.x, 10.f);
accumulator = 0.f;
}
else
break;
}
else {
// normal case: dest += delta
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
accumulator = 0.f;
}
// store action in history
std::ostringstream info;
info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
current_action_ = (*it)->name() + ": " + info.str();
// delta for others to follow
delta_translation = dest_translation - sourceNode->translation_;
}
else {
// dest = current + delta from first
dest_translation = sourceNode->translation_ + delta_translation;
}
// apply & request update
setDepth( *it, MAX( -dest_translation.x, 0.f) );
first = false;
}
}
void LayerView::updateSelectionOverlay()
{
View::updateSelectionOverlay();
if (overlay_selection_->visible_) {
// calculate bbox on selection
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
overlay_selection_->scale_ = selection_box.scale();
overlay_selection_->translation_ = selection_box.center();
// slightly extend the boundary of the selection
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.07f, 0.07f, 1.f) / overlay_selection_->scale_;
}
}

35
LayerView.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef LAYERVIEW_H
#define LAYERVIEW_H
#include "View.h"
class LayerView : public View
{
public:
LayerView();
// non assignable class
LayerView(LayerView const&) = delete;
LayerView& operator=(LayerView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
bool canSelect(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void arrow (glm::vec2) override;
float setDepth (Source *, float d = -1.f);
private:
void updateSelectionOverlay() override;
float aspect_ratio;
Mesh *persp_left_, *persp_right_;
Group *frame_;
};
#endif // LAYERVIEW_H

150
Log.cpp
View File

@@ -1,4 +1,7 @@
#include "Log.h"
#include <string>
#include <list>
#include <mutex>
using namespace std;
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
@@ -6,16 +9,11 @@
#endif
#include "imgui_internal.h"
#include "ImGuiToolkit.h"
#include "defines.h"
#include "ImGuiToolkit.h"
#include "DialogToolkit.h"
#include "Log.h"
// multiplatform
#include <tinyfiledialogs.h>
#include <string>
#include <list>
#include <mutex>
using namespace std;
static std::mutex mtx;
@@ -59,7 +57,7 @@ struct AppLog
ImGui::SetNextWindowPos(ImVec2(430, 660), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(1150, 220), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(600, 180), ImVec2(FLT_MAX, FLT_MAX));
if (!ImGui::Begin(title, p_open))
if ( !ImGui::Begin(title, p_open))
{
ImGui::End();
return;
@@ -77,74 +75,79 @@ struct AppLog
Filter.Draw("Filter", -60.0f);
ImGui::Separator();
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar);
if (clear)
Clear();
if (copy)
ImGui::LogToClipboard();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
mtx.lock();
const char* buf = Buf.begin();
const char* buf_end = Buf.end();
if (Filter.IsActive())
if ( ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar) )
{
// In this example we don't use the clipper when Filter is enabled.
// This is because we don't have a random access on the result on our filter.
// A real application processing logs with ten of thousands of entries may want to store the result of search/filter.
// especially if the filtering function is not trivial (e.g. reg-exp).
for (int line_no = 0; line_no < LineOffsets.Size; line_no++)
if (clear)
Clear();
if (copy)
ImGui::LogToClipboard();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
mtx.lock();
const char* buf = Buf.begin();
const char* buf_end = Buf.end();
if (Filter.IsActive())
{
const char* line_start = buf + LineOffsets[line_no];
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
if (Filter.PassFilter(line_start, line_end))
ImGui::TextUnformatted(line_start, line_end);
}
}
else
{
// The simplest and easy way to display the entire buffer:
// ImGui::TextUnformatted(buf_begin, buf_end);
// And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines.
// Here we instead demonstrate using the clipper to only process lines that are within the visible area.
// If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended.
// Using ImGuiListClipper requires A) random access into your data, and B) items all being the same height,
// both of which we can handle since we an array pointing to the beginning of each line of text.
// When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper.
// Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries)
ImGuiListClipper clipper;
clipper.Begin(LineOffsets.Size);
while (clipper.Step())
{
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
// In this example we don't use the clipper when Filter is enabled.
// This is because we don't have a random access on the result on our filter.
// A real application processing logs with ten of thousands of entries may want to store the result of search/filter.
// especially if the filtering function is not trivial (e.g. reg-exp).
for (int line_no = 0; line_no < LineOffsets.Size; line_no++)
{
const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6);
const char* line_start = buf + LineOffsets[line_no];
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
ImGui::TextUnformatted(line_start, line_end);
if (Filter.PassFilter(line_start, line_end))
ImGui::TextUnformatted(line_start, line_end);
}
}
clipper.End();
else
{
// The simplest and easy way to display the entire buffer:
// ImGui::TextUnformatted(buf_begin, buf_end);
// And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines.
// Here we instead demonstrate using the clipper to only process lines that are within the visible area.
// If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended.
// Using ImGuiListClipper requires A) random access into your data, and B) items all being the same height,
// both of which we can handle since we an array pointing to the beginning of each line of text.
// When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper.
// Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries)
ImGuiListClipper clipper;
clipper.Begin(LineOffsets.Size);
while (clipper.Step())
{
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
{
const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6);
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
ImGui::TextUnformatted(line_start, line_end);
}
}
clipper.End();
}
mtx.unlock();
ImGui::PopStyleVar();
ImGui::PopFont();
// Auto scroll
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
}
mtx.unlock();
ImGui::PopStyleVar();
ImGui::PopFont();
// Auto scroll
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::End();
}
};
static AppLog logs;
AppLog logs;
list<string> notifications;
list<string> warnings;
float notifications_timeout = 0.f;
void Log::Info(const char* fmt, ...)
{
@@ -160,9 +163,6 @@ void Log::ShowLogWindow(bool* p_open)
logs.Draw( ICON_FA_LIST_UL " Logs", p_open);
}
static list<string> notifications;
static float notifications_timeout = 0.f;
void Log::Notify(const char* fmt, ...)
{
ImGuiTextBuffer buf;
@@ -177,12 +177,9 @@ void Log::Notify(const char* fmt, ...)
notifications_timeout = 0.f;
// always log
Log::Info("%s", buf.c_str());
Log::Info(ICON_FA_INFO_CIRCLE " %s", buf.c_str());
}
static list<string> warnings;
void Log::Warning(const char* fmt, ...)
{
ImGuiTextBuffer buf;
@@ -196,7 +193,7 @@ void Log::Warning(const char* fmt, ...)
warnings.push_back(buf.c_str());
// always log
Log::Info("Warning - %s\n", buf.c_str());
Log::Info(ICON_FA_EXCLAMATION_TRIANGLE " Warning - %s", buf.c_str());
}
void Log::Render(bool *showWarnings)
@@ -207,7 +204,7 @@ void Log::Render(bool *showWarnings)
if (!show_notification && !show_warnings)
return;
ImGuiIO& io = ImGui::GetIO();
const ImGuiIO& io = ImGui::GetIO();
float width = io.DisplaySize.x * 0.4f;
float pos = io.DisplaySize.x * 0.3f;
@@ -296,7 +293,8 @@ void Log::Error(const char* fmt, ...)
buf.appendfv(fmt, args);
va_end(args);
tinyfd_messageBox( APP_TITLE, buf.c_str(), "ok", "error", 0);
DialogToolkit::ErrorDialog(buf.c_str());
Log::Info("Error - %s\n", buf.c_str());
}

View File

@@ -151,8 +151,7 @@ bool Loopback::systemLoopbackInitialized()
Loopback::Loopback() : FrameGrabber()
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // fixed 30 FPS
}
void Loopback::init(GstCaps *caps)
@@ -190,25 +189,35 @@ void Loopback::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ );
// specify streaming framerate in the given caps
GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, 30, 1); // fixed 30 FPS
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;
callbacks.need_data = FrameGrabber::callback_need_data;
callbacks.enough_data = FrameGrabber::callback_enough_data;
callbacks.seek_data = NULL; // stream type is not seekable
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
gst_app_src_set_callbacks( src_, &callbacks, this, NULL);
}
else {

View File

@@ -12,8 +12,9 @@ using namespace std;
#include "Resource.h"
#include "Visitor.h"
#include "SystemToolkit.h"
#include "GlmToolkit.h"
#include "BaseToolkit.h"
#include "GstToolkit.h"
#include "RenderingManager.h"
#include "MediaPlayer.h"
@@ -21,25 +22,26 @@ using namespace std;
#define MEDIA_PLAYER_DEBUG
#endif
#define USE_GST_APPSINK_CALLBACKS
std::list<MediaPlayer*> MediaPlayer::registered_;
MediaPlayer::MediaPlayer()
{
// create unique id
id_ = GlmToolkit::uniqueId();
id_ = BaseToolkit::uniqueId();
uri_ = "undefined";
pipeline_ = nullptr;
opened_ = false;
enabled_ = true;
desired_state_ = GST_STATE_PAUSED;
ready_ = false;
failed_ = false;
seeking_ = false;
enabled_ = true;
rewind_on_disable_ = false;
force_software_decoding_ = false;
decoder_name_ = "";
rate_ = 1.0;
position_ = GST_CLOCK_TIME_NONE;
desired_state_ = GST_STATE_PAUSED;
loop_ = LoopMode::LOOP_REWIND;
// start index in frame_ stack
@@ -59,6 +61,14 @@ MediaPlayer::MediaPlayer()
MediaPlayer::~MediaPlayer()
{
close();
// cleanup opengl texture
if (textureindex_)
glDeleteTextures(1, &textureindex_);
// cleanup picture buffer
if (pbo_[0])
glDeleteBuffers(2, pbo_);
}
void MediaPlayer::accept(Visitor& v) {
@@ -73,23 +83,38 @@ guint MediaPlayer::texture() const
return textureindex_;
}
static MediaInfo UriDiscoverer_(std::string uri)
#define LIMIT_DISCOVERER
MediaInfo MediaPlayer::UriDiscoverer(const std::string &uri)
{
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("Checking file '%s'", uri.c_str());
#endif
#ifdef LIMIT_DISCOVERER
// Limiting the number of discoverer thread to TWO in parallel
// Otherwise, a large number of discoverers are executed (when loading a file)
// leading to a peak of memory and CPU usage : this causes slow down of FPS
// and a hungry consumption of RAM.
static std::mutex mtx_primary;
static std::mutex mtx_secondary;
bool use_primary = true;
if ( !mtx_primary.try_lock() ) { // non-blocking
use_primary = false;
mtx_secondary.lock(); // blocking
}
#endif
MediaInfo video_stream_info;
/* Instantiate the Discoverer */
GError *err = NULL;
GstDiscoverer *discoverer = gst_discoverer_new (15 * GST_SECOND, &err);
/* Instantiate the Discoverer */
if (!discoverer) {
Log::Warning("MediaPlayer Error creating discoverer instance: %s\n", err->message);
g_clear_error (&err);
}
else {
GstDiscovererInfo *info = gst_discoverer_discover_uri (discoverer, uri.c_str(), &err);
GstDiscovererInfo *info = NULL;
info = gst_discoverer_discover_uri (discoverer, uri.c_str(), &err);
GstDiscovererResult result = gst_discoverer_info_get_result (info);
switch (result) {
case GST_DISCOVERER_URI_INVALID:
@@ -137,17 +162,18 @@ static MediaInfo UriDiscoverer_(std::string uri)
video_stream_info.isimage = gst_discoverer_video_info_is_image(vinfo);
// if its a video, set duration, framerate, etc.
if ( !video_stream_info.isimage ) {
video_stream_info.timeline.setEnd( gst_discoverer_info_get_duration (info) );
video_stream_info.end = gst_discoverer_info_get_duration (info) ;
video_stream_info.seekable = gst_discoverer_info_get_seekable (info);
video_stream_info.framerate_n = gst_discoverer_video_info_get_framerate_num(vinfo);
video_stream_info.framerate_d = gst_discoverer_video_info_get_framerate_denom(vinfo);
if (video_stream_info.framerate_n == 0 || video_stream_info.framerate_d == 0) {
video_stream_info.framerate_n = 25;
Log::Info("'%s': No framerate indicated in the file; using default 30fps", uri.c_str());
video_stream_info.framerate_n = 30;
video_stream_info.framerate_d = 1;
}
video_stream_info.timeline.setStep( (GST_SECOND * static_cast<guint64>(video_stream_info.framerate_d)) / (static_cast<guint64>(video_stream_info.framerate_n)) );
video_stream_info.dt = ( (GST_SECOND * static_cast<guint64>(video_stream_info.framerate_d)) / (static_cast<guint64>(video_stream_info.framerate_n)) );
// confirm (or infirm) that its not a single frame
if ( video_stream_info.timeline.numFrames() < 2)
if ( video_stream_info.end < video_stream_info.dt * 2)
video_stream_info.isimage = true;
}
// try to fill-in the codec information
@@ -162,8 +188,9 @@ static MediaInfo UriDiscoverer_(std::string uri)
if ( tags ) {
gchar *container = NULL;
if ( gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container) )
video_stream_info.codec_name += " " + std::string(container);
g_free(container);
video_stream_info.codec_name += ", " + std::string(container);
if (container)
g_free(container);
}
// exit loop
// inform that it succeeded
@@ -177,39 +204,80 @@ static MediaInfo UriDiscoverer_(std::string uri)
}
}
g_object_unref (discoverer);
if (info)
gst_discoverer_info_unref (info);
g_object_unref( discoverer );
}
g_clear_error (&err);
#ifdef LIMIT_DISCOVERER
if (use_primary)
mtx_primary.unlock();
else
mtx_secondary.unlock();
#endif
// return the info
return video_stream_info;
}
void MediaPlayer::open(string path)
void MediaPlayer::open (const std::string & filename, const string &uri)
{
// set path
filename_ = SystemToolkit::transliterate( path );
filename_ = BaseToolkit::transliterate( filename );
// set uri to open
uri_ = GstToolkit::filename_to_uri(path);
if (uri.empty())
uri_ = GstToolkit::filename_to_uri( filename );
else
uri_ = uri;
// reset
ready_ = false;
if (uri_.empty())
failed_ = true;
// close before re-openning
if (isOpen())
close();
// start URI discovering thread:
discoverer_ = std::async( UriDiscoverer_, uri_);
discoverer_ = std::async( MediaPlayer::UriDiscoverer, uri_);
// wait for discoverer to finish in the future (test in update)
// // debug without thread
// media_ = MediaPlayer::UriDiscoverer(uri_);
// if (media_.valid) {
// timeline_.setEnd( media_.end );
// timeline_.setStep( media_.dt );
// execute_open();
// }
}
void MediaPlayer::reopen()
{
// re-openning is meaningfull only if it was already open
if (pipeline_ != nullptr) {
// reload : terminate pipeline and re-create it
close();
execute_open();
}
}
void MediaPlayer::execute_open()
{
// Create gstreamer pipeline :
// " uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink "
// equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink
string description = "uridecodebin uri=" + uri_ + " ! ";
// "uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! appsink "
// equivalent to command line
// "gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink"
string description = "uridecodebin name=decoder uri=" + uri_ + " ! queue max-size-time=0 ! ";
// NB: queue adds some control over the buffer, thereby limiting the frame delay. zero size means no buffering
// video deinterlacing method
// string description = "uridecodebin name=decoder uri=" + uri_ + " decoder. ! ";
// description += "audioconvert ! autoaudiosink decoder. ! ";
// video deinterlacing method (if media is interlaced)
// tomsmocomp (0) Motion Adaptive: Motion Search
// greedyh (1) Motion Adaptive: Advanced Detection
// greedyl (2) Motion Adaptive: Simple Detection
@@ -219,12 +287,19 @@ void MediaPlayer::execute_open()
if (media_.interlaced)
description += "deinterlace method=2 ! ";
// video convertion chroma-resampler
// video convertion algorithm (should only do colorspace conversion, no scaling)
// chroma-resampler:
// Duplicates the samples when upsampling and drops when downsampling 0
// Uses linear interpolation 1 (default)
// Uses cubic interpolation 2
// Uses sinc interpolation 3
description += "videoconvert chroma-resampler=2 n-threads=2 ! ";
// dither:
// no dithering 0
// propagate rounding errors downwards 1
// Dither with floyd-steinberg error diffusion 2
// Dither with Sierra Lite error diffusion 3
// ordered dither using a bayer pattern 4 (default)
description += "videoconvert chroma-resampler=1 dither=0 ! "; // fast
// hack to compensate for lack of PTS in gif animations
if (media_.codec_name.compare("image/gst-libav-gif") == 0){
@@ -245,7 +320,9 @@ void MediaPlayer::execute_open()
failed_ = true;
return;
}
// setup pipeline
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
gst_pipeline_set_auto_flush_bus( GST_PIPELINE(pipeline_), true);
// GstCaps *caps = gst_static_caps_get (&frame_render_caps);
string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(media_.width) +
@@ -257,53 +334,61 @@ void MediaPlayer::execute_open()
return;
}
// setup uridecodebin
if (force_software_decoding_) {
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "decoder")), "force-sw-decoders", true, NULL);
}
// setup appsink
GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline_), "sink");
if (sink) {
// instruct the sink to send samples synched in time
gst_base_sink_set_sync (GST_BASE_SINK(sink), true);
// instruct sink to use the required caps
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 50);
gst_app_sink_set_drop (GST_APP_SINK(sink), true);
#ifdef USE_GST_APPSINK_CALLBACKS
// set the callbacks
GstAppSinkCallbacks callbacks;
callbacks.new_preroll = callback_new_preroll;
if (media_.isimage) {
callbacks.eos = NULL;
callbacks.new_sample = NULL;
}
else {
callbacks.eos = callback_end_of_stream;
callbacks.new_sample = callback_new_sample;
}
gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
#else
// connect signals callbacks
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this);
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this);
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this);
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true);
#endif
// done with ref to sink
gst_object_unref (sink);
}
else {
if (!sink) {
Log::Warning("MediaPlayer %s Could not configure sink", std::to_string(id_).c_str());
failed_ = true;
return;
}
// instruct the sink to send samples synched in time
gst_base_sink_set_sync (GST_BASE_SINK(sink), true);
// instruct sink to use the required caps
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 5);
gst_app_sink_set_drop (GST_APP_SINK(sink), true);
#ifdef USE_GST_APPSINK_CALLBACKS
// set the callbacks
GstAppSinkCallbacks callbacks;
callbacks.new_preroll = callback_new_preroll;
if (media_.isimage) {
callbacks.eos = NULL;
callbacks.new_sample = NULL;
}
else {
callbacks.eos = callback_end_of_stream;
callbacks.new_sample = callback_new_sample;
}
gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
#else
// connect signals callbacks
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this);
if (!media_.isimage) {
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this);
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this);
}
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true);
#endif
// done with ref to sink
gst_object_unref (sink);
gst_caps_unref (caps);
#ifdef USE_GST_OPENGL_SYNC_HANDLER
// capture bus signals to force a unique opengl context for all GST elements
//Rendering::LinkPipeline(GST_PIPELINE (pipeline));
Rendering::LinkPipeline(GST_PIPELINE (pipeline_));
#endif
// set to desired state (PLAY or PAUSE)
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
@@ -314,10 +399,10 @@ void MediaPlayer::execute_open()
}
// in case discoverer failed to get duration
if (media_.timeline.end() == GST_CLOCK_TIME_NONE) {
if (timeline_.end() == GST_CLOCK_TIME_NONE) {
gint64 d = GST_CLOCK_TIME_NONE;
if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) )
media_.timeline.setEnd(d);
timeline_.setEnd(d);
}
// all good
@@ -325,9 +410,9 @@ void MediaPlayer::execute_open()
uri_.c_str(), media_.codec_name.c_str(), media_.width, media_.height);
Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(),
media_.timeline.begin(), media_.timeline.end(), media_.timeline.numFrames(), media_.timeline.numGaps());
timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps());
ready_ = true;
opened_ = true;
// register media player
MediaPlayer::registered_.push_back(this);
@@ -335,7 +420,7 @@ void MediaPlayer::execute_open()
bool MediaPlayer::isOpen() const
{
return ready_;
return opened_;
}
bool MediaPlayer::failed() const
@@ -343,47 +428,53 @@ bool MediaPlayer::failed() const
return failed_;
}
void MediaPlayer::Frame::unmap()
{
if ( full )
gst_video_frame_unmap(&vframe);
full = false;
}
void MediaPlayer::close()
{
// not openned?
if (!ready_ && discoverer_.valid()) {
if (!opened_) {
// wait for loading to finish
discoverer_.wait();
if (discoverer_.valid())
discoverer_.wait();
// nothing else to change
return;
}
// un-ready the media player
ready_ = false;
opened_ = false;
// clean up GST
if (pipeline_ != nullptr) {
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
if (ret == GST_STATE_CHANGE_ASYNC) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
}
// force flush
GstState state;
gst_element_send_event(pipeline_, gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_NONE, 0) );
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
// end pipeline
gst_element_set_state (pipeline_, GST_STATE_NULL);
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
gst_object_unref (pipeline_);
pipeline_ = nullptr;
}
// cleanup eventual remaining frame memory
for(guint i = 0; i < N_VFRAME; i++){
if ( frame_[i].full ) {
gst_video_frame_unmap(&frame_[i].vframe);
frame_[i].status = INVALID;
}
for(guint i = 0; i < N_VFRAME; i++) {
frame_[i].access.lock();
frame_[i].unmap();
frame_[i].access.unlock();
}
write_index_ = 0;
last_index_ = 0;
// cleanup opengl texture
if (textureindex_)
glDeleteTextures(1, &textureindex_);
textureindex_ = 0;
// cleanup picture buffer
if (pbo_[0])
glDeleteBuffers(2, pbo_);
pbo_size_ = 0;
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s closed", std::to_string(id_).c_str());
@@ -422,20 +513,24 @@ GstClockTime MediaPlayer::position()
void MediaPlayer::enable(bool on)
{
if ( !ready_ )
if ( !opened_ || pipeline_ == nullptr)
return;
if ( enabled_ != on ) {
// option to automatically rewind each time the player is disabled
if (!on && rewind_on_disable_ && desired_state_ == GST_STATE_PLAYING)
rewind(true);
// apply change
enabled_ = on;
// default to pause
GstState requested_state = GST_STATE_PAUSED;
// unpause only if enabled
if (enabled_) {
if (enabled_)
requested_state = desired_state_;
}
// apply state change
GstStateChangeReturn ret = gst_element_set_state (pipeline_, requested_state);
@@ -457,6 +552,38 @@ bool MediaPlayer::isImage() const
return media_.isimage;
}
std::string MediaPlayer::decoderName()
{
// decoder_name_ not initialized
if (decoder_name_.empty()) {
// try to know if it is a hardware decoder
decoder_name_ = GstToolkit::used_gpu_decoding_plugins(pipeline_);
// nope, then it is a sofware decoder
if (decoder_name_.empty())
decoder_name_ = "software";
}
return decoder_name_;
}
bool MediaPlayer::softwareDecodingForced()
{
return force_software_decoding_;
}
void MediaPlayer::setSoftwareDecodingForced(bool on)
{
bool need_reload = force_software_decoding_ != on;
// set parameter
force_software_decoding_ = on;
decoder_name_ = "";
// changing state requires reload
if (need_reload)
reopen();
}
void MediaPlayer::play(bool on)
{
// ignore if disabled, and cannot play an image
@@ -479,8 +606,8 @@ void MediaPlayer::play(bool on)
// requesting to play, but stopped at end of stream : rewind first !
if ( desired_state_ == GST_STATE_PLAYING) {
if ( ( rate_ < 0.0 && position_ <= media_.timeline.next(0) )
|| ( rate_ > 0.0 && position_ >= media_.timeline.previous(media_.timeline.last()) ) )
if ( ( rate_ < 0.0 && position_ <= timeline_.next(0) )
|| ( rate_ > 0.0 && position_ >= timeline_.previous(timeline_.last()) ) )
rewind();
}
@@ -497,9 +624,6 @@ void MediaPlayer::play(bool on)
Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position());
#endif
// reset time counter
timecount_.reset();
}
bool MediaPlayer::isPlaying(bool testpipeline) const
@@ -529,7 +653,7 @@ void MediaPlayer::setLoop(MediaPlayer::LoopMode mode)
loop_ = mode;
}
void MediaPlayer::rewind()
void MediaPlayer::rewind(bool force)
{
if (!enabled_ || !media_.seekable)
return;
@@ -538,13 +662,19 @@ void MediaPlayer::rewind()
if (rate_ > 0.0) {
// begin is the end of a gab which includes the first PTS (if exists)
// normal case, begin is zero
execute_seek_command( media_.timeline.next(0) );
execute_seek_command( timeline_.next(0) );
}
// playing backward, loop to endTimeInterval gap;
else {
// end is the start of a gab which includes the last PTS (if exists)
// normal case, end is last frame
execute_seek_command( media_.timeline.previous(media_.timeline.last()) );
execute_seek_command( timeline_.previous(timeline_.last()) );
}
if (force) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
update();
}
}
@@ -555,8 +685,8 @@ void MediaPlayer::step()
if (!enabled_ || isPlaying())
return;
if ( ( rate_ < 0.0 && position_ <= media_.timeline.next(0) )
|| ( rate_ > 0.0 && position_ >= media_.timeline.previous(media_.timeline.last()) ) )
if ( ( rate_ < 0.0 && position_ <= timeline_.next(0) )
|| ( rate_ > 0.0 && position_ >= timeline_.previous(timeline_.last()) ) )
rewind();
// step
@@ -571,7 +701,7 @@ bool MediaPlayer::go_to(GstClockTime pos)
GstClockTime jumpPts = pos;
if (media_.timeline.gapAt(pos, gap)) {
if (timeline_.getGapAt(pos, gap)) {
// if in a gap, find closest seek target
if (gap.is_valid()) {
// jump in one or the other direction
@@ -579,7 +709,7 @@ bool MediaPlayer::go_to(GstClockTime pos)
}
}
if (ABS_DIFF (position_, jumpPts) > 2 * media_.timeline.step() ) {
if (ABS_DIFF (position_, jumpPts) > 2 * timeline_.step() ) {
ret = true;
seek( jumpPts );
}
@@ -593,7 +723,7 @@ void MediaPlayer::seek(GstClockTime pos)
return;
// apply seek
GstClockTime target = CLAMP(pos, media_.timeline.begin(), media_.timeline.end());
GstClockTime target = CLAMP(pos, timeline_.begin(), timeline_.end());
execute_seek_command(target);
}
@@ -657,10 +787,16 @@ void MediaPlayer::init_texture(guint index)
pbo_index_ = 0;
pbo_next_index_ = 1;
// // now that a frame is ready, and once only, browse into the pipeline
// // for possible hadrware decoding plugins used. Empty string means none.
// hardware_decoder_ = GstToolkit::used_gpu_decoding_plugins(pipeline_);
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s Using Pixel Buffer Object texturing.", std::to_string(id_).c_str());
Log::Info("MediaPlayer %s uses OpenGL PBO texturing.", std::to_string(id_).c_str());
#endif
}
glBindTexture(GL_TEXTURE_2D, 0);
}
@@ -671,7 +807,6 @@ void MediaPlayer::fill_texture(guint index)
{
// initialize texture
init_texture(index);
}
else {
glBindTexture(GL_TEXTURE_2D, textureindex_);
@@ -688,19 +823,21 @@ void MediaPlayer::fill_texture(guint index)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// bind the next PBO to write pixels
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
#ifdef USE_GL_BUFFER_SUBDATA
glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, pbo_size_, frame_[index].vframe.data[0]);
#else
// update data directly on the mapped buffer
// NB : equivalent but faster than glBufferSubData (memmove instead of memcpy ?)
// See http://www.songho.ca/opengl/gl_pbo.html#map for more details
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
// map the buffer object into client's memory
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (ptr) {
// update data directly on the mapped buffer
// NB : equivalent but faster (memmove instead of memcpy ?) than
// glNamedBufferSubData(pboIds[nextIndex], 0, imgsize, vp->getBuffer())
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
// release pointer to mapping buffer
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
#endif
// done with PBO
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
@@ -709,6 +846,7 @@ void MediaPlayer::fill_texture(guint index)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height,
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
}
@@ -719,17 +857,22 @@ void MediaPlayer::update()
return;
// not ready yet
if (!ready_ && discoverer_.valid()) {
// try to get info from discoverer
if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
{
media_ = discoverer_.get();
// if its ok, open the media
if (media_.valid)
execute_open();
else {
Log::Warning("MediaPlayer %s Loading cancelled", std::to_string(id_).c_str());
failed_ = true;
if (!opened_) {
if (discoverer_.valid()) {
// try to get info from discoverer
if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
{
media_ = discoverer_.get();
// if its ok, open the media
if (media_.valid) {
timeline_.setEnd( media_.end );
timeline_.setStep( media_.dt );
execute_open();
}
else {
Log::Warning("MediaPlayer %s Loading cancelled", std::to_string(id_).c_str());
failed_ = true;
}
}
}
// wait next frame to display
@@ -764,7 +907,6 @@ void MediaPlayer::update()
// do not fill a frame twice
if (frame_[read_index].status != INVALID ) {
// is this an End-of-Stream frame ?
if (frame_[read_index].status == EOS )
{
@@ -780,6 +922,9 @@ void MediaPlayer::update()
// double update for pre-roll frame and dual PBO (ensure frame is displayed now)
if ( (frame_[read_index].status == PREROLL || seeking_ ) && pbo_size_ > 0)
fill_texture(read_index);
// free frame
frame_[read_index].unmap();
}
// we just displayed a vframe : set position time to frame PTS
@@ -787,11 +932,6 @@ void MediaPlayer::update()
// avoid reading it again
frame_[read_index].status = INVALID;
// // TODO : try to do something when the update is too slow :(
// if ( timecount_.dt() > frame_duration_ * 2) {
// Log::Info("frame late %d", 2 * frame_duration_);
// }
}
// unkock frame after reading it
@@ -810,19 +950,18 @@ void MediaPlayer::update()
else {
// manage timeline: test if position falls into a gap
TimeInterval gap;
if (position_ != GST_CLOCK_TIME_NONE && media_.timeline.gapAt(position_, gap)) {
if (position_ != GST_CLOCK_TIME_NONE && timeline_.getGapAt(position_, gap)) {
// if in a gap, seek to next section
if (gap.is_valid()) {
// jump in one or the other direction
GstClockTime jumpPts = (rate_>0.f) ? gap.end : gap.begin;
// seek to next valid time (if not beginnig or end of timeline)
if (jumpPts > media_.timeline.first() && jumpPts < media_.timeline.last())
if (jumpPts > timeline_.first() && jumpPts < timeline_.last())
seek( jumpPts );
// otherwise, we should loop
else
need_loop = true;
}
}
}
@@ -830,6 +969,7 @@ void MediaPlayer::update()
if (need_loop) {
execute_loop_command();
}
}
void MediaPlayer::execute_loop_command()
@@ -859,7 +999,7 @@ void MediaPlayer::execute_seek_command(GstClockTime target)
// create seek event with current position (rate changed ?)
seek_pos = position_;
// target is given but useless
else if ( ABS_DIFF(target, position_) < media_.timeline.step()) {
else if ( ABS_DIFF(target, position_) < timeline_.step()) {
// ignore request
return;
}
@@ -915,22 +1055,22 @@ double MediaPlayer::playSpeed() const
Timeline *MediaPlayer::timeline()
{
return &media_.timeline;
return &timeline_;
}
float MediaPlayer::currentTimelineFading()
{
return media_.timeline.fadingAt(position_);
return timeline_.fadingAt(position_);
}
void MediaPlayer::setTimeline(Timeline tl)
void MediaPlayer::setTimeline(const Timeline &tl)
{
media_.timeline = tl;
timeline_ = tl;
}
//void MediaPlayer::toggleGapInTimeline(GstClockTime from, GstClockTime to)
//{
// return media_.timeline.toggleGaps(from, to);
// return timeline.toggleGaps(from, to);
//}
MediaInfo MediaPlayer::media() const
@@ -971,10 +1111,7 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
frame_[write_index_].access.lock();
// always empty frame before filling it again
if ( frame_[write_index_].full ) {
gst_video_frame_unmap(&frame_[write_index_].vframe);
frame_[write_index_].full = false;
}
frame_[write_index_].unmap();
// accept status of frame received
frame_[write_index_].status = status;
@@ -985,7 +1122,9 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
// get the frame from buffer
if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) )
{
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s Failed to map the video buffer", std::to_string(id_).c_str());
#endif
// free access to frame & exit
frame_[write_index_].status = INVALID;
frame_[write_index_].access.unlock();
@@ -1002,19 +1141,26 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
frame_[write_index_].position = buf->pts;
// set the start position (i.e. pts of first frame we got)
if (media_.timeline.begin() == GST_CLOCK_TIME_NONE) {
media_.timeline.setFirst(buf->pts);
if (timeline_.first() == GST_CLOCK_TIME_NONE) {
timeline_.setFirst(buf->pts);
}
}
// full but invalid frame : will be deleted next iteration
// (should never happen)
else
else {
#ifdef MEDIA_PLAYER_DEBUG
Log::Info("MediaPlayer %s Received an Invalid frame", std::to_string(id_).c_str());
#endif
// free access to frame & exit
frame_[write_index_].status = INVALID;
frame_[write_index_].access.unlock();
return false;
}
}
// else; null buffer for EOS: give a position
else {
frame_[write_index_].status = EOS;
frame_[write_index_].position = rate_ > 0.0 ? media_.timeline.end() : media_.timeline.begin();
frame_[write_index_].position = rate_ > 0.0 ? timeline_.end() : timeline_.begin();
}
// unlock access to frame
@@ -1038,8 +1184,8 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status)
void MediaPlayer::callback_end_of_stream (GstAppSink *, gpointer p)
{
MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) {
MediaPlayer *m = static_cast<MediaPlayer *>(p);
if (m && m->opened_) {
m->fill_frame(NULL, MediaPlayer::EOS);
}
}
@@ -1054,12 +1200,12 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p)
// if got a valid sample
if (sample != NULL) {
// get buffer from sample
GstBuffer *buf = gst_sample_get_buffer (sample);
// send frames to media player only if ready
MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) {
MediaPlayer *m = static_cast<MediaPlayer *>(p);
if (m && m->opened_) {
// get buffer from sample
GstBuffer *buf = gst_sample_get_buffer (sample);
// fill frame from buffer
if ( !m->fill_frame(buf, MediaPlayer::PREROLL) )
@@ -1089,12 +1235,13 @@ GstFlowReturn MediaPlayer::callback_new_sample (GstAppSink *sink, gpointer p)
// if got a valid sample
if (sample != NULL && !gst_app_sink_is_eos (sink)) {
// get buffer from sample (valid until sample is released)
GstBuffer *buf = gst_sample_get_buffer (sample) ;
// send frames to media player only if ready
MediaPlayer *m = (MediaPlayer *)p;
if (m && m->ready_) {
MediaPlayer *m = static_cast<MediaPlayer *>(p);
if (m && m->opened_) {
// get buffer from sample (valid until sample is released)
GstBuffer *buf = gst_sample_get_buffer (sample) ;
// fill frame with buffer
if ( !m->fill_frame(buf, MediaPlayer::SAMPLE) )
ret = GST_FLOW_ERROR;
@@ -1115,54 +1262,27 @@ GstFlowReturn MediaPlayer::callback_new_sample (GstAppSink *sink, gpointer p)
MediaPlayer::TimeCounter::TimeCounter() {
MediaPlayer::TimeCounter::TimeCounter()
{
timer = g_timer_new ();
}
reset();
MediaPlayer::TimeCounter::~TimeCounter()
{
g_free(timer);
}
void MediaPlayer::TimeCounter::tic ()
{
// how long since last time
GstClockTime t = gst_util_get_timestamp ();
GstClockTime dt = t - last_time;
const double dt = g_timer_elapsed (timer, NULL) * 1000.0;
// one more frame since last time
nbFrames++;
// calculate instantaneous framerate
// Exponential moving averate with previous framerate to filter jitter (50/50)
// The divition of frame/time is done on long integer GstClockTime, counting in microsecond
// NB: factor 100 to get 0.01 precision
fps = 0.5 * fps + 0.005 * static_cast<double>( ( 100 * GST_SECOND * nbFrames ) / dt );
// reset counter every second
if ( dt >= GST_SECOND)
{
last_time = t;
nbFrames = 0;
// ignore refresh after too little time
if (dt > 3.0){
// restart timer
g_timer_start(timer);
// calculate instantaneous framerate
// Exponential moving averate with previous framerate to filter jitter
fps = CLAMP( 0.5 * fps + 500.0 / dt, 0.0, 1000.0);
}
}
GstClockTime MediaPlayer::TimeCounter::dt ()
{
GstClockTime t = gst_util_get_timestamp ();
GstClockTime dt = t - tic_time;
tic_time = t;
// return the instantaneous delta t
return dt;
}
void MediaPlayer::TimeCounter::reset ()
{
last_time = gst_util_get_timestamp ();;
tic_time = last_time;
nbFrames = 0;
fps = 0.0;
}
double MediaPlayer::TimeCounter::frameRate() const
{
return fps;
}

View File

@@ -18,11 +18,10 @@ class Visitor;
#define MAX_PLAY_SPEED 20.0
#define MIN_PLAY_SPEED 0.1
#define N_VFRAME 3
#define N_VFRAME 5
struct MediaInfo {
Timeline timeline;
guint width;
guint par_width; // width to match pixel aspect ratio
guint height;
@@ -34,6 +33,8 @@ struct MediaInfo {
bool interlaced;
bool seekable;
bool valid;
GstClockTime dt;
GstClockTime end;
MediaInfo() {
width = par_width = 640;
@@ -41,19 +42,20 @@ struct MediaInfo {
bitrate = 0;
framerate_n = 1;
framerate_d = 25;
codec_name = "unknown";
codec_name = "";
isimage = false;
interlaced = false;
seekable = false;
valid = false;
dt = GST_CLOCK_TIME_NONE;
end = GST_CLOCK_TIME_NONE;
}
inline MediaInfo& operator = (const MediaInfo& b)
{
if (this != &b) {
this->timeline.setEnd( b.timeline.end() );
this->timeline.setStep( b.timeline.step() );
this->timeline.setFirst( b.timeline.first() );
this->dt = b.dt;
this->end = b.end;
this->width = b.width;
this->par_width = b.par_width;
this->height = b.height;
@@ -89,7 +91,8 @@ public:
/**
* Open a media using gstreamer URI
* */
void open( std::string path);
void open ( const std::string &filename, const std::string &uri = "");
void reopen ();
/**
* Get name of the media
* */
@@ -184,7 +187,11 @@ public:
/**
* Seek to zero
* */
void rewind();
void rewind(bool force = false);
/**
* Get position time
* */
GstClockTime position();
/**
* go to a valid position in media timeline
* pos in nanoseconds.
@@ -204,13 +211,9 @@ public:
* - frame duration : timeline.step()
*/
Timeline *timeline();
void setTimeline(Timeline tl);
void setTimeline(const Timeline &tl);
float currentTimelineFading();
/**
* Get position time
* */
GstClockTime position();
/**
* Get framerate of the media
* */
@@ -229,7 +232,7 @@ public:
* */
guint height() const;
/**
* Get frames displayt aspect ratio
* Get frames display aspect ratio
* NB: can be different than width() / height()
* */
float aspectRatio() const;
@@ -238,9 +241,27 @@ public:
* Must be called in OpenGL context
* */
guint texture() const;
/**
* Get the name of the decoder used,
* return 'software' if no hardware decoder is used
* NB: perform request on pipeline on first call
* */
std::string decoderName();
/**
* Forces open using software decoding
* (i.e. without hadrware decoding)
* NB: this reopens the video and reset decoder name
* */
void setSoftwareDecodingForced(bool on);
bool softwareDecodingForced();
/**
* Option to automatically rewind each time the player is disabled
* (i.e. when enable(false) is called )
* */
inline void setRewindOnDisabled(bool on) { rewind_on_disable_ = on; }
inline bool rewindOnDisabled() const { return rewind_on_disable_; }
/**
* Accept visitors
* Used for saving session file
* */
void accept(Visitor& v);
/**
@@ -251,6 +272,8 @@ public:
static std::list<MediaPlayer*>::const_iterator begin() { return registered_.cbegin(); }
static std::list<MediaPlayer*>::const_iterator end() { return registered_.cend(); }
static MediaInfo UriDiscoverer(const std::string &uri);
private:
// video player description
@@ -261,6 +284,7 @@ private:
// general properties of media
MediaInfo media_;
Timeline timeline_;
std::future<MediaInfo> discoverer_;
// GST & Play status
@@ -270,24 +294,23 @@ private:
GstState desired_state_;
GstElement *pipeline_;
GstVideoInfo v_frame_video_info_;
std::atomic<bool> ready_;
std::atomic<bool> opened_;
std::atomic<bool> failed_;
bool seeking_;
bool enabled_;
bool rewind_on_disable_;
bool force_software_decoding_;
std::string decoder_name_;
// fps counter
struct TimeCounter {
GstClockTime last_time;
GstClockTime tic_time;
int nbFrames;
GTimer *timer;
gdouble fps;
public:
TimeCounter();
GstClockTime dt();
~TimeCounter();
void tic();
void reset();
gdouble frameRate() const;
inline gdouble frameRate() const { return fps; }
};
TimeCounter timecount_;
@@ -311,6 +334,7 @@ private:
status = INVALID;
position = GST_CLOCK_TIME_NONE;
}
void unmap();
};
Frame frame_[N_VFRAME];
guint write_index_;

View File

@@ -11,7 +11,7 @@
#include "Visitor.h"
#include "Log.h"
MediaSource::MediaSource() : Source(), path_("")
MediaSource::MediaSource(uint64_t id) : Source(id), path_("")
{
// create media player
mediaplayer_ = new MediaPlayer;
@@ -25,11 +25,15 @@ MediaSource::~MediaSource()
void MediaSource::setPath(const std::string &p)
{
Log::Notify("Creating Source with media '%s'", p.c_str());
path_ = p;
Log::Notify("Creating Source with media '%s'", path_.c_str());
// open gstreamer
mediaplayer_->open(path_);
mediaplayer_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
std::string MediaSource::path() const
@@ -91,10 +95,9 @@ void MediaSource::init()
active_ = true;
// deep update to reorder
View::need_deep_update_++;
++View::need_deep_update_;
// done init
initialized_ = true;
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
}
}
@@ -105,14 +108,48 @@ void MediaSource::setActive (bool on)
{
bool was_active = active_;
// try to activate (may fail if source is cloned)
Source::setActive(on);
// change status of media player (only if status changed)
if ( active_ != was_active ) {
if ( active_ != was_active )
mediaplayer_->enable(active_);
// change visibility of active surface (show preview of media when inactive)
if (activesurface_) {
if (active_)
activesurface_->setTextureIndex(Resource::getTextureTransparent());
else
activesurface_->setTextureIndex(mediaplayer_->texture());
}
}
bool MediaSource::playing () const
{
return mediaplayer_->isPlaying();
}
void MediaSource::play (bool on)
{
mediaplayer_->play(on);
}
bool MediaSource::playable () const
{
return !mediaplayer_->isImage();
}
void MediaSource::replay ()
{
mediaplayer_->rewind();
}
guint64 MediaSource::playtime () const
{
return mediaplayer_->position();
}
void MediaSource::update(float dt)
{
Source::update(dt);
@@ -123,21 +160,16 @@ void MediaSource::update(float dt)
void MediaSource::render()
{
if (!initialized_)
if ( renderbuffer_ == nullptr )
init();
else {
// blendingshader_->color.r = mediaplayer_->currentTimelineFading();
// blendingshader_->color.g = mediaplayer_->currentTimelineFading();
// blendingshader_->color.b = mediaplayer_->currentTimelineFading();
// render the media player into frame buffer
renderbuffer_->begin();
// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading();
texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading();
// apply fading
texturesurface_->shader()->color = glm::vec4( glm::vec3(mediaplayer_->currentTimelineFading()), 1.f);
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
ready_ = true;
}
}

View File

@@ -8,12 +8,17 @@ class MediaPlayer;
class MediaSource : public Source
{
public:
MediaSource();
MediaSource(uint64_t id = 0);
~MediaSource();
// implementation of source API
void update (float dt) override;
void setActive (bool on) override;
bool playing () const override;
void play (bool) override;
bool playable () const override;
void replay () override;
guint64 playtime () const override;
void render() override;
bool failed() const override;
uint texture() const override;

View File

@@ -31,11 +31,8 @@ typedef struct prop {
std::string name;
bool is_float;
bool is_list;
prop(std::string n, bool t, bool l = false){
name = n;
is_float = t;
is_list = l;
}
prop(const std::string &n, bool t, bool l = false) :
name(n), is_float(t), is_list(l) { }
} plyProperty;
typedef std::map<std::string, std::vector<plyProperty> > plyElementProperties;
@@ -45,9 +42,9 @@ template <typename T>
T parseValue(std::istream& istream) {
T v;
char space = ' ';
istream >> v;
if (!istream.eof()) {
char space = ' ';
istream >> space >> std::ws;
}

1
Mesh.h
View File

@@ -18,6 +18,7 @@ public:
Mesh(const std::string& ply_path, const std::string& tex_path = "");
void setTexture(uint textureindex);
inline uint texture() const { return textureindex_; }
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;

614
Mixer.cpp

File diff suppressed because it is too large Load Diff

35
Mixer.h
View File

@@ -1,18 +1,26 @@
#ifndef MIXER_H
#define MIXER_H
#include "View.h"
#include "GeometryView.h"
#include "MixingView.h"
#include "LayerView.h"
#include "TextureView.h"
#include "TransitionView.h"
#include "Session.h"
#include "Selection.h"
namespace tinyxml2 {
class XMLElement;
}
class SessionSource;
class Mixer
{
// Private Constructor
Mixer();
Mixer(Mixer const& copy); // Not Implemented
Mixer& operator=(Mixer const& copy); // Not Implemented
Mixer(Mixer const& copy) = delete;
Mixer& operator=(Mixer const& copy) = delete;
public:
@@ -32,13 +40,15 @@ public:
// update session and all views
void update();
inline float dt() const { return dt_;}
inline float dt() const { return dt_;} // in miliseconds
inline int fps() const { return int(roundf(1000.f/dt__));}
// draw session and current view
void draw();
// creation of sources
Source * createSourceFile (const std::string &path);
Source * createSourceMultifile(const std::list<std::string> &list_files, uint fps);
Source * createSourceClone (const std::string &namesource = "");
Source * createSourceRender ();
Source * createSourceStream (const std::string &gstreamerpipeline);
@@ -49,8 +59,8 @@ public:
// operations on sources
void addSource (Source *s);
void deleteSource (Source *s, bool withundo=true);
void renameSource (Source *s, const std::string &newname);
void deleteSource (Source *s);
void renameSource (Source *s, const std::string &newname = "");
void attach (Source *s);
void detach (Source *s);
void deselect (Source *s);
@@ -68,12 +78,15 @@ public:
void unsetCurrentSource ();
void setCurrentIndex (int index);
void moveIndex (int current_index, int target_index);
int indexCurrentSource ();
// browsing into sources
Source * findSource (Node *node);
Source * findSource (std::string name);
Source * findSource (uint64_t id);
SourceList findSources (float depth_from, float depth_to);
SourceList validate(const SourceList &list);
// management of view
View *view (View::Mode m = View::INVALID);
@@ -94,13 +107,15 @@ public:
void merge (Session *session);
void merge (SessionSource *source);
void set (Session *session);
void setResolution(glm::vec3 res);
// operations depending on transition mode
void close ();
void open (const std::string& filename);
void close (bool smooth = false);
void open (const std::string& filename, bool smooth = false);
// create sources if clipboard contains well-formed xml text
void paste (const std::string& clipboard);
void restore(tinyxml2::XMLElement *sessionNode);
protected:
@@ -124,11 +139,11 @@ protected:
MixingView mixing_;
GeometryView geometry_;
LayerView layer_;
AppearanceView appearance_;
TextureView appearance_;
TransitionView transition_;
guint64 update_time_;
float dt_;
float dt__;
};
#endif // MIXER_H

373
MixingGroup.cpp Normal file
View File

@@ -0,0 +1,373 @@
#include <algorithm>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include "defines.h"
#include "Source.h"
#include "Decorations.h"
#include "Visitor.h"
#include "BaseToolkit.h"
#include "Log.h"
#include "MixingGroup.h"
MixingGroup::MixingGroup (SourceList sources) : parent_(nullptr), root_(nullptr), lines_(nullptr), center_(nullptr),
center_pos_(glm::vec2(0.f, 0.f)), active_(true), update_action_(ACTION_NONE), updated_source_(nullptr)
{
// create unique id
id_ = BaseToolkit::uniqueId();
// fill the vector of sources with the given list
for (auto it = sources.begin(); it != sources.end(); ++it){
// add only if not linked already
if ((*it)->mixinggroup_ == nullptr) {
(*it)->mixinggroup_ = this;
sources_.push_back(*it);
}
}
// scene elements
root_ = new Group;
root_->visible_ = false;
center_ = new Symbol(Symbol::CIRCLE_POINT);
center_->visible_ = false;
center_->color = glm::vec4(COLOR_MIXING_GROUP, 0.75f);
center_->scale_ = glm::vec3(0.6f, 0.6f, 1.f);
root_->attach(center_);
// create
recenter();
createLineStrip();
}
MixingGroup::~MixingGroup ()
{
for (auto it = sources_.begin(); it != sources_.end(); ++it)
(*it)->clearMixingGroup();
if (parent_)
parent_->detach( root_ );
delete root_;
}
void MixingGroup::accept(Visitor& v)
{
v.visit(*this);
}
void MixingGroup::attachTo( Group *parent )
{
if (parent_ != nullptr)
parent_->detach( root_ );
parent_ = parent;
if (parent_ != nullptr)
parent_->attach(root_);
}
void MixingGroup::recenter()
{
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
for (auto it = sources_.begin(); it != sources_.end(); ++it){
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
// set center
center_->translation_ = glm::vec3(center_pos_, 0.f);
}
void MixingGroup::setAction (Action a)
{
if (a == ACTION_UPDATE) {
// accept UPDATE action only if no other action is ongoing
if (update_action_ == ACTION_NONE)
update_action_ = ACTION_UPDATE;
}
else if (a == ACTION_FINISH) {
// only needs to finish if an action was done
if (update_action_ != ACTION_NONE)
update_action_ = ACTION_FINISH;
}
else
update_action_ = a;
}
void MixingGroup::update (float)
{
// after creation, root is not visible: wait that all sources are initialized to make it visible
if (!root_->visible_) {
auto unintitializedsource = std::find_if_not(sources_.begin(), sources_.end(), Source::isInitialized);
root_->visible_ = (unintitializedsource == sources_.end());
}
// group is active if one source in the group is current
auto currentsource = std::find_if(sources_.begin(), sources_.end(), Source::isCurrent);
setActive(currentsource != sources_.end());
// perform action
if (update_action_ == ACTION_FINISH ) {
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
// update only once
update_action_ = ACTION_NONE;
}
else if (update_action_ == ACTION_UPDATE ) {
std::vector<glm::vec2> p = lines_->path();
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
center_->translation_ = glm::vec3(center_pos_, 0.f);
// update path
lines_->changePath(p);
// update only once
update_action_ = ACTION_NONE;
}
else if (update_action_ != ACTION_NONE && updated_source_ != nullptr) {
if (update_action_ == ACTION_GRAB_ONE ) {
// update path
move(updated_source_);
recenter();
}
else if (update_action_ == ACTION_GRAB_ALL ) {
std::vector<glm::vec2> p = lines_->path();
glm::vec2 displacement = glm::vec2(updated_source_->group(View::MIXING)->translation_);
displacement -= p[ index_points_[updated_source_] ];
// compute barycenter (0)
center_pos_ = glm::vec2(0.f, 0.f);
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// modify all but the already updated source
if ( *it != updated_source_ && !(*it)->locked() ) {
(*it)->group(View::MIXING)->translation_.x += displacement.x;
(*it)->group(View::MIXING)->translation_.y += displacement.y;
(*it)->touch();
}
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
// compute barycenter (1)
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center_pos_ /= sources_.size();
center_->translation_ = glm::vec3(center_pos_, 0.f);
// update path
lines_->changePath(p);
}
else if (update_action_ == ACTION_ROTATE_ALL ) {
std::vector<glm::vec2> p = lines_->path();
// get angle rotation and distance scaling
glm::vec2 pos_first = glm::vec2(updated_source_->group(View::MIXING)->translation_) -center_pos_;
float angle = glm::orientedAngle( glm::normalize(pos_first), glm::vec2(1.f, 0.f) );
float dist = glm::length( pos_first );
glm::vec2 pos_second = glm::vec2(p[ index_points_[updated_source_] ]) -center_pos_;
angle -= glm::orientedAngle( glm::normalize(pos_second), glm::vec2(1.f, 0.f) );
dist /= glm::length( pos_second );
int numactions = 0;
auto it = sources_.begin();
for (; it != sources_.end(); ++it){
// modify all but the already updated source
if ( *it != updated_source_ && !(*it)->locked() ) {
glm::vec2 vec = glm::vec2((*it)->group(View::MIXING)->translation_) -center_pos_;
vec = glm::rotate(vec, -angle) * dist;
vec += center_pos_;
(*it)->group(View::MIXING)->translation_.x = vec.x;
(*it)->group(View::MIXING)->translation_.y = vec.y;
(*it)->touch();
numactions++;
}
// update point
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
}
// update path
lines_->changePath(p);
// no source was rotated? better action is thus to grab
if (numactions<1)
update_action_ = ACTION_GRAB_ALL;
}
// done
updated_source_ = nullptr;
}
}
void MixingGroup::setActive (bool on)
{
active_ = on;
// overlays
lines_->shader()->color.a = active_ ? 0.96f : 0.5f;
center_->visible_ = update_action_ > ACTION_GRAB_ONE;
}
void MixingGroup::detach (Source *s)
{
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
// ok, its in the list !
if (its != sources_.end()) {
// tell the source
(*its)->clearMixingGroup();
// erase the source from the list
sources_.erase(its);
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
}
void MixingGroup::detach (SourceList l)
{
for (auto sit = l.begin(); sit != l.end(); ++sit) {
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), *sit);
// ok, its in the list !
if (its != sources_.end()) {
// tell the source
(*its)->clearMixingGroup();
// erase the source from the list
sources_.erase(its);
}
}
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
void MixingGroup::attach (Source *s)
{
// if source is not already in a group (this or other)
if (s->mixinggroup_ == nullptr) {
// tell the source
s->mixinggroup_ = this;
// add the source
sources_.push_back(s);
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
}
void MixingGroup::attach (SourceList l)
{
for (auto sit = l.begin(); sit != l.end(); ++sit) {
if ( (*sit)->mixinggroup_ == nullptr) {
// tell the source
(*sit)->mixinggroup_ = this;
// add the source
sources_.push_back(*sit);
}
}
// update barycenter
recenter();
// clear index, delete lines_, and recreate path and index with remaining sources
createLineStrip();
}
uint MixingGroup::size()
{
return sources_.size();
}
SourceList MixingGroup::getCopy() const
{
SourceList sl = sources_;
return sl;
}
SourceList::iterator MixingGroup::begin ()
{
return sources_.begin();
}
SourceList::iterator MixingGroup::end ()
{
return sources_.end();
}
bool MixingGroup::contains (Source *s)
{
// find the source
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
// in the list ?
return its != sources_.end();
}
void MixingGroup::move (Source *s)
{
if (contains(s) && lines_) {
// modify one point in the path
lines_->editPath(index_points_[s], glm::vec2(s->group(View::MIXING)->translation_));
}
}
void MixingGroup::createLineStrip()
{
if (sources_.size() > 1) {
if (lines_) {
root_->detach(lines_);
delete lines_;
}
// sort the vector of sources in clockwise order around the center pos_
sources_ = mixing_sorted( sources_, center_pos_);
// start afresh list of indices
index_points_.clear();
// path linking all sources
std::vector<glm::vec2> path;
for (auto it = sources_.begin(); it != sources_.end(); ++it){
index_points_[*it] = path.size();
path.push_back(glm::vec2((*it)->group(View::MIXING)->translation_));
}
// create
lines_ = new LineLoop(path, 1.5f);
lines_->shader()->color = glm::vec4(COLOR_MIXING_GROUP, 0.96f);
root_->attach(lines_);
}
}

86
MixingGroup.h Normal file
View File

@@ -0,0 +1,86 @@
#ifndef MIXINGGROUP_H
#define MIXINGGROUP_H
#include <map>
#include "View.h"
#include "SourceList.h"
class LineLoop;
class Symbol;
class MixingGroup
{
public:
MixingGroup (SourceList sources);
// non assignable class
MixingGroup(MixingGroup const&) = delete;
MixingGroup& operator=(MixingGroup const&) = delete;
~MixingGroup ();
// Get unique id
inline uint64_t id () const { return id_; }
// Source list manipulation
SourceList getCopy() const;
SourceList::iterator begin ();
SourceList::iterator end ();
uint size ();
bool contains (Source *s);
void detach (Source *s);
void detach (SourceList l);
void attach (Source *s);
void attach (SourceList l);
// actions for update
typedef enum {
ACTION_NONE = 0,
ACTION_UPDATE = 1,
ACTION_GRAB_ONE = 2,
ACTION_GRAB_ALL = 3,
ACTION_ROTATE_ALL = 4,
ACTION_FINISH = 5
} Action;
void setAction (Action a) ;
inline Action action () { return update_action_; }
inline void follow (Source *s) { updated_source_ = s; }
// to update in Session
void update (float);
// active if one of its source is current
inline bool active () const { return active_; }
void setActive (bool on);
// attach node to draw in Mixing View
void attachTo( Group *parent );
// accept all kind of visitors
virtual void accept (Visitor& v);
private:
// Drawing elements
Group *parent_;
Group *root_;
LineLoop *lines_;
Symbol *center_;
void createLineStrip();
void recenter();
void move (Source *s);
// properties linked to sources
glm::vec2 center_pos_;
SourceList sources_;
std::map< Source *, uint> index_points_;
// status and actions
uint64_t id_;
bool active_;
Action update_action_;
Source *updated_source_;
};
#endif // MIXINGGROUP_H

687
MixingView.cpp Normal file
View File

@@ -0,0 +1,687 @@
// Opengl
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "imgui.h"
#include "ImGuiToolkit.h"
#include <string>
#include <sstream>
#include <iomanip>
#include "Mixer.h"
#include "defines.h"
#include "Source.h"
#include "Settings.h"
#include "Decorations.h"
#include "UserInterfaceManager.h"
#include "BoundingBoxVisitor.h"
#include "ActionManager.h"
#include "MixingGroup.h"
#include "Log.h"
#include "MixingView.h"
// internal utility
float sin_quad_texture(float x, float y);
uint textureMixingQuadratic();
MixingView::MixingView() : View(MIXING), limbo_scale_(MIXING_LIMBO_SCALE)
{
scene.root()->scale_ = glm::vec3(MIXING_DEFAULT_SCALE, MIXING_DEFAULT_SCALE, 1.0f);
scene.root()->translation_ = glm::vec3(0.0f, 0.0f, 0.0f);
// read default settings
if ( Settings::application.views[mode_].name.empty() ) {
// no settings found: store application default
Settings::application.views[mode_].name = "Mixing";
saveSettings();
}
else
restoreSettings();
// Mixing scene background
Mesh *tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(limbo_scale_, limbo_scale_, 1.f);
tmp->shader()->color = glm::vec4( COLOR_LIMBO_CIRCLE, 0.6f );
scene.bg()->attach(tmp);
mixingCircle_ = new Mesh("mesh/disk.ply");
mixingCircle_->shader()->color = glm::vec4( 1.f, 1.f, 1.f, 1.f );
scene.bg()->attach(mixingCircle_);
circle_ = new Mesh("mesh/circle.ply");
circle_->shader()->color = glm::vec4( COLOR_CIRCLE, 1.0f );
scene.bg()->attach(circle_);
// Mixing scene foreground
// button frame
tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
tmp->translation_ = glm::vec3(0.f, 1.f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
scene.fg()->attach(tmp);
// interactive button
button_white_ = new Disk();
button_white_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
button_white_->translation_ = glm::vec3(0.f, 1.f, 0.f);
button_white_->color = glm::vec4( 0.85f, 0.85f, 0.85f, 1.0f );
scene.fg()->attach(button_white_);
// button frame
tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
tmp->translation_ = glm::vec3(0.f, -1.f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
scene.fg()->attach(tmp);
// interactive button
button_black_ = new Disk();
button_black_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
button_black_->translation_ = glm::vec3(0.f, -1.f, 0.f);
button_black_->color = glm::vec4( 0.1f, 0.1f, 0.1f, 1.0f );
scene.fg()->attach(button_black_);
// moving slider
slider_root_ = new Group;
scene.fg()->attach(slider_root_);
// interactive slider
slider_ = new Disk();
slider_->scale_ = glm::vec3(0.08f, 0.08f, 1.f);
slider_->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
slider_root_->attach(slider_);
// dark mask in front
tmp = new Mesh("mesh/disk.ply");
tmp->scale_ = glm::vec3(0.075f, 0.075f, 1.f);
tmp->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
tmp->shader()->color = glm::vec4( COLOR_SLIDER_CIRCLE, 1.0f );
slider_root_->attach(tmp);
stashCircle_ = new Disk();
stashCircle_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
stashCircle_->translation_ = glm::vec3(2.f, -1.0f, 0.f);
stashCircle_->color = glm::vec4( COLOR_STASH_CIRCLE, 0.6f );
// scene.bg()->attach(stashCircle_);
}
void MixingView::draw()
{
// set texture
if (mixingCircle_->texture() == 0)
mixingCircle_->setTexture(textureMixingQuadratic());
// temporarily force shaders to use opacity blending for rendering icons
Shader::force_blending_opacity = true;
// draw scene of this view
View::draw();
// restore state
Shader::force_blending_opacity = false;
// display popup menu
if (show_context_menu_ == MENU_SELECTION) {
ImGui::OpenPopup( "MixingSelectionContextMenu" );
show_context_menu_ = MENU_NONE;
}
if (ImGui::BeginPopup("MixingSelectionContextMenu")) {
// colored context menu
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
// special action of Mixing view: link or unlink
SourceList selected = Mixer::selection().getCopy();
if ( Mixer::manager().session()->canlink( selected )) {
// the selected sources can be linked
if (ImGui::Selectable( ICON_FA_LINK " Link" )){
// assemble a MixingGroup
Mixer::manager().session()->link(selected, scene.fg() );
Action::manager().store(std::string("Sources linked."));
// clear selection and select one of the sources of the group
Source *cur = Mixer::selection().front();
Mixer::manager().unsetCurrentSource();
Mixer::selection().clear();
Mixer::manager().setCurrentSource( cur );
}
}
else {
// the selected sources cannot be linked: offer to unlink!
if (ImGui::Selectable( ICON_FA_UNLINK " Unlink" )){
// dismantle MixingGroup(s)
Mixer::manager().session()->unlink(selected);
Action::manager().store(std::string("Sources unlinked."));
}
}
ImGui::Separator();
// manipulation of sources in Mixing view
if (ImGui::Selectable( ICON_FA_CROSSHAIRS " Center" )){
glm::vec2 center = glm::vec2(0.f, 0.f);
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// compute barycenter (1)
center += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center /= Mixer::selection().size();
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
(*it)->group(View::MIXING)->translation_ -= glm::vec3(center, 0.f);
(*it)->touch();
}
Action::manager().store(std::string("Selection: Mixing Center"));
}
if (ImGui::Selectable( ICON_FA_HAYKAL " Distribute" )){
SourceList list;
glm::vec2 center = glm::vec2(0.f, 0.f);
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
list.push_back(*it);
// compute barycenter (1)
center += glm::vec2((*it)->group(View::MIXING)->translation_);
}
// compute barycenter (2)
center /= list.size();
// sort the vector of sources in clockwise order around the center pos_
list = mixing_sorted( list, center);
// average distance
float d = 0.f;
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
d += glm::distance(glm::vec2((*it)->group(View::MIXING)->translation_), center);
}
d /= list.size();
// distribute with equal angle
float angle = 0.f;
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, d), angle);
(*it)->group(View::MIXING)->translation_.x = P.x;
(*it)->group(View::MIXING)->translation_.y = P.y;
(*it)->touch();
angle -= glm::two_pi<float>() / float(list.size());
}
Action::manager().store(std::string("Selection: Mixing Distribute"));
}
if (ImGui::Selectable( ICON_FA_CLOUD_SUN " Expand & hide" )){
SourceList::iterator it = Mixer::selection().begin();
for (; it != Mixer::selection().end(); ++it) {
(*it)->setAlpha(0.f);
}
Action::manager().store(std::string("Selection: Mixing Expand & hide"));
}
if (ImGui::Selectable( ICON_FA_SUN " Compress & show" )){
SourceList::iterator it = Mixer::selection().begin();
for (; it != Mixer::selection().end(); ++it) {
(*it)->setAlpha(0.99f);
}
Action::manager().store(std::string("Selection: Mixing Compress & show"));
}
ImGui::PopStyleColor(2);
ImGui::EndPopup();
}
}
void MixingView::resize ( int scale )
{
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
z *= z;
z *= MIXING_MAX_SCALE - MIXING_MIN_SCALE;
z += MIXING_MIN_SCALE;
scene.root()->scale_.x = z;
scene.root()->scale_.y = z;
// Clamp translation to acceptable area
glm::vec3 border(scene.root()->scale_.x * 1.f, scene.root()->scale_.y * 1.f, 0.f);
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
}
int MixingView::size ()
{
float z = (scene.root()->scale_.x - MIXING_MIN_SCALE) / (MIXING_MAX_SCALE - MIXING_MIN_SCALE);
return (int) ( sqrt(z) * 100.f);
}
void MixingView::centerSource(Source *s)
{
// calculate screen area visible in the default view
GlmToolkit::AxisAlignedBoundingBox view_box;
glm::mat4 modelview = GlmToolkit::transform(scene.root()->translation_, scene.root()->rotation_, scene.root()->scale_);
view_box.extend( Rendering::manager().unProject(glm::vec2(0.f, Rendering::manager().mainWindow().height()), modelview) );
view_box.extend( Rendering::manager().unProject(glm::vec2(Rendering::manager().mainWindow().width(), 0.f), modelview) );
// check if upper-left corner of source is in view box
glm::vec3 pos_source = s->group(mode_)->translation_ + glm::vec3( -s->group(mode_)->scale_.x, s->group(mode_)->scale_.y, 0.f);
if ( !view_box.contains(pos_source)) {
// not visible so shift view
glm::vec2 screenpoint = glm::vec2(500.f, 20.f) * Rendering::manager().mainWindow().dpiScale();
glm::vec3 pos_to = Rendering::manager().unProject(screenpoint, scene.root()->transform_);
glm::vec4 pos_delta = glm::vec4(pos_to.x, pos_to.y, 0.f, 0.f) - glm::vec4(pos_source.x, pos_source.y, 0.f, 0.f);
pos_delta = scene.root()->transform_ * pos_delta;
scene.root()->translation_ += glm::vec3(pos_delta);
}
}
void MixingView::update(float dt)
{
View::update(dt);
// // always update the mixinggroups
// for (auto g = groups_.begin(); g != groups_.end(); g++)
// (*g)->update(dt);
// a more complete update is requested
// for mixing, this means restore position of the fading slider
if (View::need_deep_update_ > 0) {
//
// Set slider to match the actual fading of the session
//
float f = Mixer::manager().session()->empty() ? 0.f : Mixer::manager().session()->fading();
// reverse calculate angle from fading & move slider
slider_root_->rotation_.z = SIGN(slider_root_->rotation_.z) * asin(f) * 2.f;
// visual feedback on mixing circle
f = 1.f - f;
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
// prevent invalid scaling
float s = CLAMP(scene.root()->scale_.x, MIXING_MIN_SCALE, MIXING_MAX_SCALE);
scene.root()->scale_.x = s;
scene.root()->scale_.y = s;
}
// the current view is the mixing view
if (Mixer::manager().view() == this )
{
//
// Set session fading to match the slider angle (during animation)
//
// calculate fading from angle
float f = sin( ABS(slider_root_->rotation_.z) * 0.5f);
// apply fading
if ( ABS_DIFF( f, Mixer::manager().session()->fading()) > EPSILON )
{
// apply fading to session
Mixer::manager().session()->setFading(f);
// visual feedback on mixing circle
f = 1.f - f;
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
}
// update the selection overlay
updateSelectionOverlay();
}
}
std::pair<Node *, glm::vec2> MixingView::pick(glm::vec2 P)
{
// get picking from generic View
std::pair<Node *, glm::vec2> pick = View::pick(P);
// deal with internal interactive objects
if ( pick.first == button_white_ || pick.first == button_black_ ) {
RotateToCallback *anim = nullptr;
if (pick.first == button_white_)
anim = new RotateToCallback(0.f, 500.f);
else
anim = new RotateToCallback(SIGN(slider_root_->rotation_.z) * M_PI, 500.f);
// animate clic
pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f));
// reset & start animation
slider_root_->update_callbacks_.clear();
slider_root_->update_callbacks_.push_back(anim);
}
else if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
openContextMenu(MENU_SELECTION);
}
else {
// get if a source was picked
Source *s = Mixer::manager().findSource(pick.first);
if (s != nullptr) {
// pick on the lock icon; unlock source
if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
lock(s, false);
// pick = { s->locker_, pick.second };
pick = { nullptr, glm::vec2(0.f) };
}
// pick on the open lock icon; lock source and cancel pick
else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) {
lock(s, true);
pick = { nullptr, glm::vec2(0.f) };
}
// pick a locked source ; cancel pick
else if ( s->locked() ) {
pick = { nullptr, glm::vec2(0.f) };
}
// pick the symbol: ask to show editor
else if ( pick.first == s->symbol_ ) {
UserInterface::manager().showSourceEditor(s);
}
// pick on the mixing group rotation icon
else if ( pick.first == s->rotation_mixingroup_ ) {
if (UserInterface::manager().shiftModifier())
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
else
s->mixinggroup_->setAction( MixingGroup::ACTION_ROTATE_ALL );
}
// pick source of a mixing group
else if ( s->mixinggroup_ != nullptr ) {
if (UserInterface::manager().ctrlModifier()) {
SourceList linked = s->mixinggroup_->getCopy();
linked.remove(s);
if (Mixer::selection().empty())
Mixer::selection().add(linked);
}
else if (UserInterface::manager().shiftModifier())
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
else
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ALL );
}
}
}
return pick;
}
View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
{
View::Cursor ret = Cursor();
ret.type = Cursor_ResizeAll;
// unproject
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
// No source is given
if (!s) {
// if interaction with slider
if (pick.first == slider_) {
// apply rotation to match angle with mouse cursor
float angle = glm::orientedAngle( glm::normalize(glm::vec2(0.f, 1.0)), glm::normalize(glm::vec2(gl_Position_to)));
// snap on 0 and PI angles
if ( ABS_DIFF(angle, 0.f) < 0.05)
angle = 0.f;
else if ( ABS_DIFF(angle, M_PI) < 0.05)
angle = M_PI;
// animate slider (rotation angle on its parent)
slider_root_->rotation_.z = angle;
// cursor feedback
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
std::ostringstream info;
info << "Global opacity " << 100 - int(Mixer::manager().session()->fading() * 100.0) << " %";
return Cursor(Cursor_Hand, info.str() );
}
// nothing to do
return Cursor();
}
//
// Interaction with source
//
// compute delta translation
s->group(mode_)->translation_ = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
// manage mixing group
if (s->mixinggroup_ != nullptr ) {
// inform mixing groups to follow the current source
if (Source::isCurrent(s) && s->mixinggroup_->action() > MixingGroup::ACTION_UPDATE) {
s->mixinggroup_->follow(s);
// special cursor for rotation
if (s->mixinggroup_->action() == MixingGroup::ACTION_ROTATE_ALL)
ret.type = Cursor_Hand;
}
else
s->mixingGroup()->setAction(MixingGroup::ACTION_NONE);
}
// request update
s->touch();
// // trying to enter stash
// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) {
// // refuse to put an active source in stash
// if (s->active())
// s->group(mode_)->translation_ = s->stored_status_->translation_;
// else {
// Mixer::manager().conceal(s);
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE) - glm::vec3(0.1f, 0.1f, 0.f);
// }
// }
// else if ( Mixer::manager().concealed(s) ) {
// Mixer::manager().uncover(s);
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE);
// }
std::ostringstream info;
if (s->active()) {
info << "Alpha " << std::fixed << std::setprecision(3) << s->blendingShader()->color.a << " ";
info << ( (s->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
}
else
info << "Inactive " << ICON_FA_SNOWFLAKE;
// store action in history
current_action_ = s->name() + ": " + info.str();
// update cursor
ret.info = info.str();
return ret;
}
void MixingView::terminate()
{
View::terminate();
// terminate all mixing group actions
for (auto g = Mixer::manager().session()->beginMixingGroup();
g != Mixer::manager().session()->endMixingGroup(); ++g)
(*g)->setAction( MixingGroup::ACTION_FINISH );
}
View::Cursor MixingView::over (glm::vec2 pos)
{
View::Cursor ret = Cursor();
std::pair<Node *, glm::vec2> pick = View::pick(pos);
// deal with internal interactive objects
if ( pick.first == slider_ ) {
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
ret.type = Cursor_Hand;
}
else
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
return ret;
}
void MixingView::arrow (glm::vec2 movement)
{
static float accumulator = 0.f;
accumulator += dt_;
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
bool first = true;
glm::vec3 delta_translation(0.f);
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
// individual move with SHIFT
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
continue;
Group *sourceNode = (*it)->group(mode_);
glm::vec3 dest_translation(0.f);
if (first) {
// dest starts at current
dest_translation = sourceNode->translation_;
// + ALT : discrete displacement
if (UserInterface::manager().altModifier()) {
if (accumulator > 100.f) {
dest_translation += glm::sign(gl_delta) * 0.1f;
dest_translation.x = ROUND(dest_translation.x, 10.f);
dest_translation.y = ROUND(dest_translation.y, 10.f);
accumulator = 0.f;
}
else
break;
}
else {
// normal case: dest += delta
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
accumulator = 0.f;
}
// store action in history
std::ostringstream info;
if ((*it)->active()) {
info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
}
else
info << "Inactive " << ICON_FA_SNOWFLAKE;
current_action_ = (*it)->name() + ": " + info.str();
// delta for others to follow
delta_translation = dest_translation - sourceNode->translation_;
}
else {
// dest = current + delta from first
dest_translation = sourceNode->translation_ + delta_translation;
}
// apply & request update
sourceNode->translation_ = dest_translation;
(*it)->touch();
first = false;
}
}
void MixingView::setAlpha(Source *s)
{
if (!s)
return;
// move the layer node of the source
Group *sourceNode = s->group(mode_);
glm::vec2 mix_pos = glm::vec2(DEFAULT_MIXING_TRANSLATION);
for(NodeSet::iterator it = scene.ws()->begin(); it != scene.ws()->end(); ++it) {
// avoid superposing icons: distribute equally
if ( glm::distance(glm::vec2((*it)->translation_), mix_pos) < DELTA_ALPHA) {
mix_pos += glm::vec2(-0.03f, 0.03f);
}
}
sourceNode->translation_.x = mix_pos.x;
sourceNode->translation_.y = mix_pos.y;
// request update
s->touch();
}
void MixingView::updateSelectionOverlay()
{
View::updateSelectionOverlay();
if (overlay_selection_->visible_) {
// calculate bbox on selection
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
overlay_selection_->scale_ = selection_box.scale();
overlay_selection_->translation_ = selection_box.center();
// slightly extend the boundary of the selection
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.01f, 0.01f, 1.f) / overlay_selection_->scale_;
}
}
#define CIRCLE_PIXELS 64
#define CIRCLE_PIXEL_RADIUS 1024.f
//#define CIRCLE_PIXELS 256
//#define CIRCLE_PIXEL_RADIUS 16384.0
//#define CIRCLE_PIXELS 1024
//#define CIRCLE_PIXEL_RADIUS 262144.0
float sin_quad_texture(float x, float y) {
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ) / CIRCLE_PIXEL_RADIUS, 0.f, 1.f ) );
float D = sqrt( ( x * x )/ CIRCLE_PIXEL_RADIUS + ( y * y )/ CIRCLE_PIXEL_RADIUS );
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
}
uint textureMixingQuadratic()
{
static GLuint texid = 0;
if (texid == 0) {
// generate the texture with alpha exactly as computed for sources
GLubyte matrix[CIRCLE_PIXELS*CIRCLE_PIXELS * 4];
int l = -CIRCLE_PIXELS / 2 + 1;
for (int i = 0; i < CIRCLE_PIXELS ; ++i) {
int c = -CIRCLE_PIXELS / 2 + 1;
for (int j = 0; j < CIRCLE_PIXELS ; ++j) {
// distance to the center
GLfloat distance = sin_quad_texture( (float) c , (float) l );
// distance = 1.f - (GLfloat) ((c * c) + (l * l)) / CIRCLE_PIXEL_RADIUS; // quadratic
// distance = 1.f - (GLfloat) sqrt( (GLfloat) ((c * c) + (l * l))) / (GLfloat) sqrt(CIRCLE_PIXEL_RADIUS); // linear
// transparency
GLfloat alpha = 255.f * CLAMP( distance , 0.f, 1.f);
GLubyte A = static_cast<GLubyte>(alpha);
// luminance adjustment
GLfloat luminance = 255.f * CLAMP( 0.2f + 0.75f * distance, 0.f, 1.f);
GLubyte L = static_cast<GLubyte>(luminance);
// fill pixel RGBA
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 0 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 1 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 2 ] = L;
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 3 ] = A;
++c;
}
++l;
}
// setup texture
glGenTextures(1, &texid);
glBindTexture(GL_TEXTURE_2D, texid);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, CIRCLE_PIXELS, CIRCLE_PIXELS);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, CIRCLE_PIXELS, CIRCLE_PIXELS, GL_BGRA, GL_UNSIGNED_BYTE, matrix);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
}
return texid;
}

46
MixingView.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef MIXINGVIEW_H
#define MIXINGVIEW_H
#include "View.h"
//class MixingGroup;
class MixingView : public View
{
public:
MixingView();
// non assignable class
MixingView(MixingView const&) = delete;
MixingView& operator=(MixingView const&) = delete;
void draw () override;
void update (float dt) override;
void resize (int) override;
int size () override;
void centerSource(Source *) override;
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>) override;
void terminate() override;
Cursor over (glm::vec2) override;
void arrow (glm::vec2) override;
void setAlpha (Source *s);
inline float limboScale() { return limbo_scale_; }
private:
void updateSelectionOverlay() override;
float limbo_scale_;
Group *slider_root_;
Disk *slider_;
Disk *button_white_;
Disk *button_black_;
Disk *stashCircle_;
Mesh *mixingCircle_;
Mesh *circle_;
};
#endif // MIXINGVIEW_H

242
MultiFileSource.cpp Normal file
View File

@@ -0,0 +1,242 @@
#include <sstream>
#include <algorithm>
#include "defines.h"
#include "ImageShader.h"
#include "Resource.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "GstToolkit.h"
#include "BaseToolkit.h"
#include "SystemToolkit.h"
#include "MediaPlayer.h"
#include "Log.h"
#include "MultiFileSource.h"
// example test gstreamer pipelines
//
// multifile : sequence of numbered images
// gst-launch-1.0 multifilesrc location="/home/bhbn/Images/sequence/frames%03d.png" caps="image/png,framerate=\(fraction\)12/1" loop=1 ! decodebin ! videoconvert ! autovideosink
//
// imagesequencesrc : sequence of numbered images (cannot loop)
// gst-launch-1.0 imagesequencesrc location=frames%03d.png start-index=1 framerate=24/1 ! decodebin ! videoconvert ! autovideosink
//
MultiFileSequence::MultiFileSequence() : width(0), height(0), min(0), max(0)
{
}
MultiFileSequence::MultiFileSequence(const std::list<std::string> &list_files)
{
location = BaseToolkit::common_numbered_pattern(list_files, &min, &max);
// sanity check: the location pattern looks like a filename and seems consecutive numbered
if ( SystemToolkit::extension_filename(location).empty() ||
SystemToolkit::path_filename(location) != SystemToolkit::path_filename(list_files.front()) ||
list_files.size() != max - min + 1 ) {
Log::Info("MultiFileSequence '%s' invalid.", location.c_str());
location.clear();
}
if ( !location.empty() ) {
MediaInfo media = MediaPlayer::UriDiscoverer( GstToolkit::filename_to_uri( list_files.front() ) );
if (media.valid && media.isimage) {
codec.resize(media.codec_name.size());
std::transform(media.codec_name.begin(), media.codec_name.end(), codec.begin(), ::tolower);
width = media.width;
height = media.height;
}
else
Log::Info("MultiFileSequence '%s' does not list images.", location.c_str());
}
}
bool MultiFileSequence::valid() const
{
return !( location.empty() || codec.empty() || width < 1 || height < 1 || max == min);
}
inline MultiFileSequence& MultiFileSequence::operator = (const MultiFileSequence& b)
{
if (this != &b) {
this->width = b.width;
this->height = b.height;
this->min = b.min;
this->max = b.max;
this->location = b.location;
this->codec = b.codec;
}
return *this;
}
bool MultiFileSequence::operator != (const MultiFileSequence& b)
{
return ( location != b.location || codec != b.codec || width != b.width ||
height != b.height || min != b.min || max != b.max );
}
MultiFile::MultiFile() : Stream(), src_(nullptr)
{
}
void MultiFile::open (const MultiFileSequence &sequence, uint framerate )
{
if (sequence.location.empty())
return;
std::ostringstream gstreamer_pipeline;
gstreamer_pipeline << "multifilesrc name=src location=\"";
gstreamer_pipeline << sequence.location;
gstreamer_pipeline << "\" caps=\"image/";
gstreamer_pipeline << sequence.codec;
gstreamer_pipeline << ",framerate=(fraction)";
gstreamer_pipeline << framerate;
gstreamer_pipeline << "/1\" loop=1";
gstreamer_pipeline << " start-index=";
gstreamer_pipeline << sequence.min;
gstreamer_pipeline << " stop-index=";
gstreamer_pipeline << sequence.max;
gstreamer_pipeline << " ! decodebin ! videoconvert";
// (private) open stream
Stream::open(gstreamer_pipeline.str(), sequence.width, sequence.height);
// keep multifile source for dynamic properties change
src_ = gst_bin_get_by_name (GST_BIN (pipeline_), "src");
}
void MultiFile::close ()
{
if (src_ != nullptr) {
gst_object_unref (src_);
src_ = nullptr;
}
Stream::close();
}
void MultiFile::setIndex(int val)
{
if (src_) {
g_object_set (src_, "index", val, NULL);
}
}
int MultiFile::index()
{
int val = 0;
if (src_) {
g_object_get (src_, "index", &val, NULL);
}
return val;
}
void MultiFile::setProperties (int begin, int end, int loop)
{
if (src_) {
g_object_set (src_, "start-index", MAX(begin, 0), NULL);
g_object_set (src_, "stop-index", MAX(end, 0), NULL);
g_object_set (src_, "loop", MIN(loop, 1), NULL);
}
}
MultiFileSource::MultiFileSource (uint64_t id) : StreamSource(id), framerate_(0), begin_(-1), end_(INT_MAX), loop_(1)
{
// create stream
stream_ = static_cast<Stream *>( new MultiFile );
// set symbol
symbol_ = new Symbol(Symbol::SEQUENCE, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
void MultiFileSource::setFiles (const std::list<std::string> &list_files, uint framerate)
{
setSequence(MultiFileSequence(list_files), framerate);
}
void MultiFileSource::setSequence (const MultiFileSequence &sequence, uint framerate)
{
framerate_ = CLAMP( framerate, 1, 30);
sequence_ = sequence;
if (sequence_.valid())
{
// open gstreamer
multifile()->open( sequence_, framerate_ );
stream_->play(true);
// validate range and apply loop_
setRange(begin_, end_);
// will be ready after init and one frame rendered
ready_ = false;
}
}
void MultiFileSource::setFramerate (uint framerate)
{
if (multifile()) {
setSequence(sequence_, framerate);
}
}
void MultiFileSource::setLoop (bool on)
{
if (multifile()) {
loop_ = on ? 1 : 0;
multifile()->setProperties (begin_, end_, loop_);
}
}
void MultiFileSource::setRange (int begin, int end)
{
begin_ = glm::clamp( begin, sequence_.min, sequence_.max );
end_ = glm::clamp( end , sequence_.min, sequence_.max );
begin_ = glm::min( begin_, end_ );
end_ = glm::max( begin_, end_ );
if (multifile())
multifile()->setProperties (begin_, end_, loop_);
}
void MultiFileSource::replay ()
{
if (multifile()) {
multifile()->setIndex (begin_);
stream_->rewind();
}
}
guint64 MultiFileSource::playtime () const
{
guint64 time = 0;
if (multifile())
time += multifile()->index();
time *= GST_SECOND;
time /= framerate_;
return time;
}
void MultiFileSource::accept (Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}
MultiFile *MultiFileSource::multifile () const
{
return dynamic_cast<MultiFile *>(stream_);
}

82
MultiFileSource.h Normal file
View File

@@ -0,0 +1,82 @@
#ifndef MULTIFILESOURCE_H
#define MULTIFILESOURCE_H
#include <string>
#include <list>
#include "StreamSource.h"
struct MultiFileSequence {
std::string location;
std::string codec;
uint width;
uint height;
int min;
int max;
MultiFileSequence ();
MultiFileSequence (const std::list<std::string> &list_files);
bool valid () const;
MultiFileSequence& operator = (const MultiFileSequence& b);
bool operator != (const MultiFileSequence& b);
};
class MultiFile : public Stream
{
public:
MultiFile ();
void open (const MultiFileSequence &sequence, uint framerate = 30);
void close () override;
// dynamic change of gstreamer multifile source properties
void setProperties(int begin, int end, int loop);
// image index
int index();
void setIndex(int val);
protected:
GstElement *src_ ;
};
class MultiFileSource : public StreamSource
{
public:
MultiFileSource (uint64_t id = 0);
// Source interface
void accept (Visitor& v) override;
void replay () override;
guint64 playtime () const override;
// StreamSource interface
Stream *stream () const override { return stream_; }
glm::ivec2 icon () const override { return glm::ivec2(3, 9); }
// specific interface
void setFiles (const std::list<std::string> &list_files, uint framerate);
void setSequence (const MultiFileSequence &sequence, uint framerate);
inline MultiFileSequence sequence () const { return sequence_; }
void setFramerate (uint fps);
inline uint framerate () const { return framerate_; }
void setLoop (bool on);
inline bool loop () const { return loop_ > 0 ? true : false; }
void setRange (int begin, int end);
inline int begin() const { return begin_; }
inline int end () const { return end_; }
MultiFile *multifile () const;
private:
MultiFileSequence sequence_;
uint framerate_;
int begin_, end_, loop_;
};
#endif // MULTIFILESOURCE_H

View File

@@ -67,10 +67,10 @@ void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
}
NetworkStream::NetworkStream(): Stream(), receiver_(nullptr)
NetworkStream::NetworkStream(): Stream(),
receiver_(nullptr), received_config_(false), connected_(false)
{
received_config_ = false;
connected_ = false;
}
glm::ivec2 NetworkStream::resolution() const
@@ -204,7 +204,7 @@ void NetworkStream::update()
{
Stream::update();
if ( !ready_ && !failed_ && received_config_)
if ( !opened_ && !failed_ && received_config_)
{
// only once
received_config_ = false;
@@ -268,10 +268,10 @@ void NetworkStream::update()
}
NetworkSource::NetworkSource() : StreamSource()
NetworkSource::NetworkSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = (Stream *) new NetworkStream;
stream_ = static_cast<Stream *>( new NetworkStream );
// set symbol
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.75f, 0.75f, 0.01f));
@@ -297,6 +297,9 @@ void NetworkSource::setConnection(const std::string &nameconnection)
// open network stream
networkStream()->connect( connection_name_ );
stream_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}

View File

@@ -20,6 +20,7 @@ protected:
const IpEndpointName& remoteEndpoint );
public:
inline void setParent(NetworkStream *s) { parent_ = s; }
StreamerResponseListener() : parent_(nullptr) {}
};
@@ -59,7 +60,7 @@ class NetworkSource : public StreamSource
std::string connection_name_;
public:
NetworkSource();
NetworkSource(uint64_t id = 0);
~NetworkSource();
// Source interface

View File

@@ -73,9 +73,9 @@ const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
const std::vector<std::string> NetworkToolkit::protocol_send_pipeline {
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc ! rtpjpegpay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc idct-method=float ! rtpjpegpay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! udpsink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc idct-method=float ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink"
};
@@ -95,12 +95,11 @@ std::vector<unsigned long> iplongs_;
void add_interface(int fd, const char *name) {
struct ifreq ifreq;
char host[128];
memset(&ifreq, 0, sizeof ifreq);
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
int family;
switch(family=ifreq.ifr_addr.sa_family) {
char host[128];
switch(ifreq.ifr_addr.sa_family) {
case AF_INET:
case AF_INET6:
getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host, 0, 0, NI_NUMERICHOST);
@@ -122,14 +121,14 @@ void add_interface(int fd, const char *name) {
void list_interfaces()
{
struct ifreq *ifreq;
struct ifconf ifconf;
char buf[16384];
int fd=socket(PF_INET, SOCK_DGRAM, 0);
if(fd > -1) {
struct ifconf ifconf;
ifconf.ifc_len=sizeof buf;
ifconf.ifc_buf=buf;
if(ioctl(fd, SIOCGIFCONF, &ifconf)==0) {
struct ifreq *ifreq;
ifreq=ifconf.ifc_req;
for(int i=0;i<ifconf.ifc_len;) {
size_t len;

View File

@@ -12,7 +12,7 @@
#define OSC_STREAM_REJECT "/reject"
#define OSC_STREAM_DISCONNECT "/disconnect"
#define STREAMING_FPS 30
#define MAX_HANDSHAKE 20
#define HANDSHAKE_PORT 71310
#define STREAM_REQUEST_PORT 71510

23
Overlay.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "Overlay.h"
Overlay::Overlay()
{
}
SnapshotOverlay::SnapshotOverlay() : Overlay()
{
}
void SnapshotOverlay::draw()
{
}
void SnapshotOverlay::update (float dt)
{
}

38
Overlay.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef OVERLAY_H
#define OVERLAY_H
#include "View.h"
class Overlay
{
public:
Overlay();
virtual void update (float dt) = 0;
virtual void draw () = 0;
virtual std::pair<Node *, glm::vec2> pick(glm::vec2) {
return { nullptr, glm::vec2(0.f) };
}
virtual View::Cursor grab (Source*, glm::vec2, glm::vec2, std::pair<Node *, glm::vec2>) {
return View::Cursor ();
}
virtual View::Cursor over (glm::vec2) {
return View::Cursor ();
}
};
class SnapshotOverlay: public Overlay
{
public:
SnapshotOverlay();
void draw () override;
void update (float dt) override;
};
#endif // OVERLAY_H

View File

@@ -126,15 +126,16 @@ void Pattern::open( uint pattern, glm::ivec2 res )
// all patterns before 'SMPTE test pattern' are single frames (not animated)
single_frame_ = type_ < 14;
Log::Info("Stream %d SingleFrame", single_frame_);
// (private) open stream
Stream::open(gstreamer_pattern, res.x, res.y);
}
PatternSource::PatternSource() : StreamSource()
PatternSource::PatternSource(uint64_t id) : StreamSource(id)
{
// create stream
stream_ = (Stream *) new Pattern;
stream_ = static_cast<Stream *>( new Pattern );
// set symbol
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.75f, 0.75f, 0.01f));
@@ -145,8 +146,12 @@ void PatternSource::setPattern(uint type, glm::ivec2 resolution)
{
Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str());
// open gstreamer
pattern()->open( (uint) type, resolution );
stream_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
}
void PatternSource::accept(Visitor& v)

View File

@@ -23,7 +23,7 @@ private:
class PatternSource : public StreamSource
{
public:
PatternSource();
PatternSource(uint64_t id = 0);
// Source interface
void accept (Visitor& v) override;

View File

@@ -11,15 +11,15 @@
#include <glm/gtx/vector_angle.hpp>
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(), force_(force)
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(),
force_(force), modelview_(glm::mat4(1.f))
{
modelview_ = glm::mat4(1.f);
points_.push_back( coordinates );
}
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(), force_(force)
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(),
force_(force), modelview_(glm::mat4(1.f))
{
modelview_ = glm::mat4(1.f);
points_.push_back( selectionstart );
points_.push_back( selection_end );
}
@@ -36,7 +36,7 @@ void PickingVisitor::visit(Group &n)
return;
glm::mat4 mv = modelview_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
if ( (*node)->visible_ || force_)
(*node)->accept(*this);
modelview_ = mv;

View File

@@ -19,7 +19,7 @@
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0)
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0), mirror_(true)
{
// geometry for a trianglulated simple rectangle surface with UV
// (0,0) B +---+ D (1,0)
@@ -89,12 +89,8 @@ void Surface::draw(glm::mat4 modelview, glm::mat4 projection)
glActiveTexture(GL_TEXTURE0);
if ( textureindex_ ) {
glBindTexture(GL_TEXTURE_2D, textureindex_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // TODO add user input to select mode
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
}
else
glBindTexture(GL_TEXTURE_2D, Resource::getTextureBlack());
@@ -125,53 +121,6 @@ void ImageSurface::accept(Visitor& v)
v.visit(*this);
}
MediaSurface::MediaSurface(const std::string& p, Shader *s) : Surface(s)
{
path_ = p;
mediaplayer_ = new MediaPlayer;
}
MediaSurface::~MediaSurface()
{
delete mediaplayer_;
}
void MediaSurface::init()
{
Surface::init();
mediaplayer_->open(path_);
mediaplayer_->play(true);
}
void MediaSurface::draw(glm::mat4 modelview, glm::mat4 projection)
{
if ( !initialized() ) {
init();
// set the texture to the media player once openned
if ( mediaplayer_->isOpen() )
textureindex_ = mediaplayer_->texture();
}
Surface::draw(modelview, projection);
}
void MediaSurface::update( float dt )
{
if ( mediaplayer_->isOpen() ) {
mediaplayer_->update();
scale_.x = mediaplayer_->aspectRatio();
}
Primitive::update( dt );
}
void MediaSurface::accept(Visitor& v)
{
Surface::accept(v);
v.visit(*this);
}
FrameBufferSurface::FrameBufferSurface(FrameBuffer *fb, Shader *s) : Surface(s), frame_buffer_(fb)
{
}
@@ -406,6 +355,24 @@ LineSquare::LineSquare(float linewidth) : Group()
}
LineSquare::LineSquare(const LineSquare &square)
{
top_ = new HLine(square.top_->width);
top_->translation_ = glm::vec3(0.f, 1.f, 0.f);
attach(top_);
bottom_ = new HLine(square.bottom_->width);
bottom_->translation_ = glm::vec3(0.f, -1.f, 0.f);
attach(bottom_);
left_ = new VLine(square.left_->width);
left_->translation_ = glm::vec3(-1.f, 0.f, 0.f);
attach(left_);
right_ = new VLine(square.right_->width);
right_->translation_ = glm::vec3(1.f, 0.f, 0.f);
attach(right_);
setColor(square.color());
}
void LineSquare::setLineWidth(float v)
{
top_->width = v;
@@ -422,37 +389,152 @@ void LineSquare::setColor(glm::vec4 c)
right_->color = c;
}
LineStrip::LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth) : Primitive(new Shader), linewidth_(linewidth)
LineStrip::LineStrip(const std::vector<glm::vec2> &path, float linewidth) : Primitive(new Shader),
arrayBuffer_(0), path_(path)
{
for(size_t i = 0; i < points.size(); ++i)
linewidth_ = 0.002f * linewidth;
for(size_t i = 1; i < path_.size(); ++i)
{
points_.push_back( points[i] );
colors_.push_back( colors[i] );
indices_.push_back ( i );
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
glm::vec3 end = glm::vec3(path_[i], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( begin - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
}
drawMode_ = GL_LINE_STRIP;
drawMode_ = GL_TRIANGLE_STRIP;
}
void LineStrip::draw(glm::mat4 modelview, glm::mat4 projection)
LineStrip::~LineStrip()
{
if ( !initialized() )
init();
// delete buffer
if ( arrayBuffer_ )
glDeleteBuffers ( 1, &arrayBuffer_);
}
// glLineWidth(linewidth_ * 2.f * Rendering::manager().mainWindow().dpiScale());
void LineStrip::init()
{
if ( vao_ )
glDeleteVertexArrays ( 1, &vao_);
glm::mat4 mv = modelview;
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.001f, 1.001f, 1.f));
// Vertex Array
glGenVertexArrays( 1, &vao_ );
// TODO FIXME drawing multiple times is not correct to draw lines of different width
// TODO Draw LineStrip using polygons
for (uint i = 0 ; i < linewidth_ ; ++i ) {
Primitive::draw(mv, projection);
mv *= scale;
// Create and initialize buffer objects
if ( arrayBuffer_ )
glDeleteBuffers ( 1, &arrayBuffer_);
glGenBuffers( 1, &arrayBuffer_ );
uint elementBuffer_;
glGenBuffers( 1, &elementBuffer_);
glBindVertexArray( vao_ );
// setup the array buffers for vertices
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors, NULL, GL_DYNAMIC_DRAW);
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
// setup the element array for indices
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(uint) * indices_.size(), &(indices_[0]), GL_STATIC_DRAW);
// explain how to read attributes 0 and 1
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void *)0 );
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void *)(sizeofPoints) );
glEnableVertexAttribArray(1);
// done
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// drawing indications
drawCount_ = indices_.size();
if ( elementBuffer_ )
glDeleteBuffers ( 1, &elementBuffer_);
indices_.clear();
// compute AxisAlignedBoundingBox
bbox_.extend(points_);
Node::init();
}
void LineStrip::updatePath()
{
// redo points_ array
points_.clear();
for(size_t i = 1; i < path_.size(); ++i)
{
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
glm::vec3 end = glm::vec3(path_[i], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
}
// glLineWidth(1);
// bind the vertex array and change the point coordinates
glBindVertexArray( vao_ );
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// reset and compute AxisAlignedBoundingBox
GlmToolkit::AxisAlignedBoundingBox b;
bbox_ = b;
bbox_.extend(points_);
}
void LineStrip::editPath(uint index, glm::vec2 position)
{
if (index < path_.size()) {
path_[index] = position;
updatePath();
}
}
void LineStrip::changePath(std::vector<glm::vec2> path)
{
// invalid if not enough points given
size_t N = path_.size();
if (path.size() < N)
return;
// replace path but keep number of points
path_ = path;
path_.resize(N);
updatePath();
}
void LineStrip::setLineWidth(float linewidth) {
linewidth_ = 0.002f * linewidth;
updatePath();
}
void LineStrip::accept(Visitor& v)
@@ -462,64 +544,88 @@ void LineStrip::accept(Visitor& v)
}
LineCircle::LineCircle(uint linewidth) : LineStrip(std::vector<glm::vec3>(), std::vector<glm::vec4>(), linewidth)
LineLoop::LineLoop(const std::vector<glm::vec2> &path, float linewidth) : LineStrip(path, linewidth)
{
static int N = 72;
static float a = glm::two_pi<float>() / static_cast<float>(N);
static glm::vec4 circle_color_points = glm::vec4(1.f, 1.f, 1.f, 1.f);
// close linestrip loop
glm::vec3 begin = glm::vec3(path_[path_.size()-1], 0.f);
glm::vec3 end = glm::vec3(path_[0], 0.f);
glm::vec3 dir = end - begin;
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( begin - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end + perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
points_.push_back( end - perp * linewidth_ );
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
indices_.push_back ( indices_.size() );
}
void LineLoop::updatePath()
{
glm::vec3 begin;
glm::vec3 end;
glm::vec3 dir;
glm::vec3 perp;
// redo points_ array
points_.clear();
size_t i = 1;
for(; i < path_.size(); ++i)
{
begin = glm::vec3(path_[i-1], 0.f);
end = glm::vec3(path_[i], 0.f);
dir = end - begin;
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
}
// close linestrip loop
begin = glm::vec3(path_[i-1], 0.f);
end = glm::vec3(path_[0], 0.f);
dir = end - begin;
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
points_.push_back( begin + perp * linewidth_ );
points_.push_back( begin - perp * linewidth_ );
points_.push_back( end + perp * linewidth_ );
points_.push_back( end - perp * linewidth_ );
// bind the vertex array and change the point coordinates
glBindVertexArray( vao_ );
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// re-compute AxisAlignedBoundingBox
bbox_.extend(points_);
}
#define LINE_CIRCLE_DENSITY 72
LineCircle::LineCircle(float linewidth) : LineLoop(std::vector<glm::vec2>(LINE_CIRCLE_DENSITY), linewidth)
{
static float a = glm::two_pi<float>() / static_cast<float>(LINE_CIRCLE_DENSITY-1);
// loop to build a circle
glm::vec3 P(1.f, 0.f, 0.f);
for (int i = 0; i < N ; i++ ){
points_.push_back( glm::vec3(P) );
colors_.push_back( circle_color_points );
indices_.push_back ( i );
for (int i = 0; i < LINE_CIRCLE_DENSITY - 1; i++ ){
path_[i] = glm::vec2(P);
P = glm::rotateZ(P, a);
}
// close loop
points_.push_back( glm::vec3(1.f, 0.f, 0.f) );
colors_.push_back( circle_color_points );
indices_.push_back ( N );
}
void LineCircle::init()
{
// use static unique vertex array object
static uint unique_vao_ = 0;
static uint unique_drawCount = 0;
if (unique_vao_) {
// 1. only init Node (not the primitive vao)
Node::init();
// 2. use the global vertex array object
vao_ = unique_vao_;
drawCount_ = unique_drawCount;
// replace AxisAlignedBoundingBox
bbox_.extend(points_);
// arrays of vertices are not needed anymore (STATIC DRAW of vertex object)
points_.clear();
colors_.clear();
texCoords_.clear();
indices_.clear();
}
else {
// 1. init the Primitive (only once)
Primitive::init();
// 2. remember global vertex array object
unique_vao_ = vao_;
unique_drawCount = drawCount_;
// 3. unique_vao_ will NOT be deleted because LineCircle::deleteGLBuffers_() is empty
}
}
void LineCircle::accept(Visitor& v)
{
Primitive::accept(v);
v.visit(*this);
updatePath();
}
LineCircle::~LineCircle()
{
// do NOT delete vao_ (unique)
vao_ = 0;
}

View File

@@ -30,8 +30,12 @@ public:
inline void setTextureIndex(uint t) { textureindex_ = t; }
inline uint textureIndex() const { return textureindex_; }
inline void setMirrorTexture(bool m) { mirror_ = m; }
inline bool mirrorTexture() { return mirror_; }
protected:
uint textureindex_;
bool mirror_;
};
@@ -58,31 +62,6 @@ protected:
};
/**
* @brief The MediaSurface class is a Surface to draw a video
*
* URI is passed to a Media Player to handle the video playback
* Height = 1.0, Width is set by the aspect ratio of the image
*/
class MediaSurface : public Surface {
public:
MediaSurface(const std::string& p, Shader *s = new ImageShader);
~MediaSurface();
void init () override;
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
void update (float dt) override;
inline std::string path() const { return path_; }
inline MediaPlayer *mediaPlayer() const { return mediaplayer_; }
protected:
std::string path_;
MediaPlayer *mediaplayer_;
};
/**
* @brief The FrameBufferSurface class is a Surface to draw a framebuffer
*
@@ -97,7 +76,8 @@ public:
void draw (glm::mat4 modelview, glm::mat4 projection) override;
void accept (Visitor& v) override;
inline FrameBuffer *getFrameBuffer() const { return frame_buffer_; }
inline void setFrameBuffer(FrameBuffer *fb) { frame_buffer_ = fb; }
inline FrameBuffer *frameBuffer() const { return frame_buffer_; }
protected:
FrameBuffer *frame_buffer_;
@@ -162,6 +142,7 @@ class LineSquare : public Group {
public:
LineSquare(float linewidth = 1.f);
LineSquare(const LineSquare &square);
void setLineWidth(float v);
inline float lineWidth() const { return top_->width; }
@@ -175,34 +156,49 @@ public:
*/
class LineStrip : public Primitive {
uint linewidth_;
public:
LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth = 1);
LineStrip(const std::vector<glm::vec2> &path, float linewidth = 1.f);
virtual ~LineStrip();
virtual void draw(glm::mat4 modelview, glm::mat4 projection) override;
virtual void init () override;
virtual void accept(Visitor& v) override;
std::vector<glm::vec3> getPoints() { return points_; }
std::vector<glm::vec4> getColors() { return colors_; }
inline std::vector<glm::vec2> path() { return path_; }
inline float lineWidth() const { return linewidth_ * 500.f; }
inline void setLineWidth(uint v) { linewidth_ = v; }
inline uint getLineWidth() const { return linewidth_; }
void changePath(std::vector<glm::vec2> path);
void editPath(uint index, glm::vec2 position);
void setLineWidth(float linewidth);
protected:
float linewidth_;
uint arrayBuffer_;
std::vector<glm::vec2> path_;
virtual void updatePath();
};
/**
* @brief The LineLoop class is a LineStrip with closed path
*/
class LineLoop : public LineStrip {
public:
LineLoop(const std::vector<glm::vec2> &path, float linewidth = 1.f);
protected:
void updatePath() override;
};
/**
* @brief The LineCircle class is a circular LineStrip (diameter = 1.0)
* @brief The LineCircle class is a circular LineLoop (diameter = 1.0)
*/
class LineCircle : public LineStrip {
class LineCircle : public LineLoop {
public:
LineCircle(uint linewidth = 1);
LineCircle(float linewidth = 1.f);
void init() override;
void accept(Visitor& v) override;
virtual ~LineCircle();
};

View File

@@ -12,13 +12,22 @@ monitor or a projector, but can be recorded live (no audio).
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
# License
GPL-3.0-or-later
See [LICENSE](https://github.com/brunoherbelin/vimix/blob/master/LICENSE)
# Install
Check the [Quick Installation Guide](https://github.com/brunoherbelin/vimix/wiki/Quick-Installation-Guide)
### Linux
Download and install a release package from https://snapcraft.io/vimix
snap install vimix
$ snap install vimix
NB: You'll need to setup the snap permissions.
### Mac OSX
@@ -27,17 +36,26 @@ NB: You'll need to accept the exception in OSX security preference.
## Clone
git clone --recursive https://github.com/brunoherbelin/vimix.git
$ git clone --recursive https://github.com/brunoherbelin/vimix.git
This will create the directory 'vimix', download the latest version of vimix code,
and (recursively) clone all the internal git Dependencies.
and (recursively) clone all the internal git dependencies.
To only update a cloned git copy:
$ git pull
## Compile
mkdir vimix-build
cd vimix-build
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
cmake --build .
First time after git clone:
$ mkdir vimix-build
$ cd vimix-build
$ cmake -DCMAKE_BUILD_TYPE=Release ../vimix
Compile (or re-compile after pull):
$ cmake --build .
### Dependencies
@@ -46,20 +64,42 @@ and (recursively) clone all the internal git Dependencies.
- gcc
- make
- cmake
- git
**Libraries:**
- gstreamer
- gst-plugins : base, good, bad & ugly
- libpng
- libglfw3
- libicu
#### Install Dependencies
**Ubuntu**
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libicu-dev
$ apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libicu-dev libgtk-3-dev
**OSX with Brew**
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
$ brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
#### Generate snap
To generate the snap (from vimix directory):
$ snapcraft
To install the locally created snap:
$ snap install --dangerous vimix_0.5_amd64.snap
### Memcheck
To generate memory usage plots in [massif format](https://valgrind.org/docs/manual/ms-manual.html):
$ G_SLICE=always-malloc valgrind --tool=massif ./vimix
To check for memory leaks:
$ G_SLICE=always-malloc valgrind --leak-check=full --log-file=vimix_mem.txt ./vimix

View File

@@ -60,12 +60,13 @@ void PNGRecorder::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
@@ -107,13 +108,13 @@ void PNGRecorder::terminate()
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
}
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps)
{
FrameGrabber::addFrame(buffer, caps, dt);
FrameGrabber::addFrame(buffer, caps);
// PNG Recorder specific :
// stop after one frame
if (timestamp_ > 0) {
if (frame_count_ > 0) {
stop();
}
}
@@ -123,12 +124,13 @@ const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
"H264 (Realtime)",
"H264 (High 4:4:4)",
"H265 (Realtime)",
"H265 (HQ Animation)",
"H265 (HQ)",
"ProRes (Standard)",
"ProRes (HQ 4444)",
"WebM VP8 (2MB/s)",
"WebM VP8 (Realtime)",
"Multiple JPEG"
};
const std::vector<std::string> VideoRecorder::profile_description {
// Control x264 encoder quality :
// pass
@@ -138,19 +140,17 @@ const std::vector<std::string> VideoRecorder::profile_description {
// The total range is from 0 to 51, where 0 is lossless, 18 can be considered visually lossless,
// and 51 is terrible quality. A sane range is 18-26, and the default is 23.
// speed-preset
// ultrafast (1)
// superfast (2)
// veryfast (3)
// faster (4)
// fast (5)
#ifndef APPLE
// "video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
#else
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
#endif
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=16 speed-preset=4 threads=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" pass=4 quantizer=22 speed-preset=2 ! video/x-h264, profile=baseline ! h264parse ! ",
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=18 speed-preset=3 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
// Control x265 encoder quality :
// NB: apparently x265 only accepts I420 format :(
// speed-preset
// superfast (2)
// veryfast (3)
// faster (4)
// fast (5)
@@ -162,43 +162,112 @@ const std::vector<std::string> VideoRecorder::profile_description {
// fastdecode (5)
// animation (6) optimize the encode quality for animation content without impacting the encode speed
// crf Quality-controlled variable bitrate [0 51]
// default 28
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
"video/x-raw, format=I420 ! x265enc tune=4 speed-preset=3 ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
// default 28
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
"video/x-raw, format=I420 ! x265enc tune=2 speed-preset=2 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=2 option-string=\"crf=12\" ! video/x-h265, profile=(string)main ! h265parse ! ",
// Apple ProRes encoding parameters
// pass
// cbr (0) Constant Bitrate Encoding
// quant (2) Constant Quantizer
// pass1 (512) VBR Encoding - Pass 1
// profile
// 0 proxy
// 1 lt
// 2 standard
// 3 hq
// 4 4444
"avenc_prores_ks pass=2 profile=2 quantizer=26 ! ",
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 profile=4 quantizer=12 ! ",
// 0 proxy 45Mbps YUV 4:2:2
// 1 lt 102Mbps YUV 4:2:2
// 2 standard 147Mbps YUV 4:2:2
// 3 hq 220Mbps YUV 4:2:2
// 4 4444 330Mbps YUVA 4:4:4:4
// quant-mat
// -1 auto
// 0 proxy
// 2 lt
// 3 standard
// 4 hq
// 6 default
"video/x-raw, format=I422_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=2 quant-mat=6 quantizer=8 ! ",
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=4 quant-mat=6 quantizer=4 ! ",
// VP8 WebM encoding
"vp8enc end-usage=vbr cpu-used=8 max-quantizer=35 deadline=100000 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=100 ! ",
"jpegenc ! "
// deadline per frame (usec)
// 0=best,
// 1=realtime
// see https://www.webmproject.org/docs/encoder-parameters/
// "vp8enc end-usage=cbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 undershoot=95 "
// "buffer-size=6000 buffer-initial-size=4000 buffer-optimal-size=5000 "
// "keyframe-max-dist=999999 min-quantizer=4 max-quantizer=50 ! ",
"vp8enc end-usage=vbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 keyframe-max-dist=360 "
"token-partitions=2 static-threshold=1000 min-quantizer=4 max-quantizer=20 ! ",
// JPEG encoding
"jpegenc idct-method=float ! "
};
// Too slow
//// WebM VP9 encoding parameters
//// https://www.webmproject.org/docs/encoder-parameters/
//// https://developers.google.com/media/vp9/settings/vod/
//"vp9enc end-usage=vbr end-usage=vbr cpu-used=3 max-quantizer=35 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=1000 ! "
// FAILED
// x265 encoder quality
// string description = "appsrc name=src ! videoconvert ! "
// "x265enc tune=4 speed-preset=2 option-string='crf=28' ! h265parse ! "
// "qtmux ! filesink name=sink";
#if GST_GL_HAVE_PLATFORM_GLX
// under GLX (Linux), gstreamer might have nvidia encoders
const char* VideoRecorder::hardware_encoder[VideoRecorder::DEFAULT] = {
"nvh264enc",
"nvh264enc",
"nvh265enc",
"nvh265enc",
"", "", "", ""
};
const std::vector<std::string> VideoRecorder::hardware_profile_description {
// qp-const Constant quantizer (-1 = from NVENC preset)
// Range: -1 - 51 Default: -1
// rc-mode Rate Control Mode
// (0): default - Default
// (1): constqp - Constant Quantization
// (2): cbr - Constant Bit Rate
// (3): vbr - Variable Bit Rate
// (4): vbr-minqp - Variable Bit Rate (with minimum quantization parameter, DEPRECATED)
// (5): cbr-ld-hq - Low-Delay CBR, High Quality
// (6): cbr-hq - CBR, High Quality (slower)
// (7): vbr-hq - VBR, High Quality (slower)
// Control nvh264enc encoder
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=(string)main ! h264parse ! ",
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 qp-const=18 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
// Control nvh265enc encoder
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 zerolatency=true ! video/x-h265, profile=(string)main-10 ! h265parse ! ",
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 qp-const=18 ! video/x-h265, profile=(string)main-444 ! h265parse ! ",
"", "", "", ""
};
#elif GST_GL_HAVE_PLATFORM_CGL
// under CGL (Mac), gstreamer might have the VideoToolbox
const char* VideoRecorder::hardware_encoder[VideoRecorder::DEFAULT] = {
"vtenc_h264_hw",
"vtenc_h264_hw",
"", "", "", "", "", ""
};
const std::vector<std::string> VideoRecorder::hardware_profile_description {
// Control vtenc_h264_hw encoder
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
"video/x-raw, format=UYVY ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 quality=0.9 ! h264parse ! ",
"", "", "", "", "", ""
};
#else
const char* VideoRecorder::hardware_encoder[VideoRecorder::DEFAULT] = {
"", "", "", "", "", "", "", ""
};
const std::vector<std::string> VideoRecorder::hardware_profile_description {
"", "", "", "", "", "", "", ""
};
#endif
const char* VideoRecorder::buffering_preset_name[6] = { "Minimum", "100 MB", "200 MB", "500 MB", "1 GB", "2 GB" };
const guint64 VideoRecorder::buffering_preset_value[6] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648 };
const char* VideoRecorder::framerate_preset_name[3] = { "15 FPS", "25 FPS", "30 FPS" };
const gint VideoRecorder::framerate_preset_value[3] = { 15, 25, 30 };
VideoRecorder::VideoRecorder() : FrameGrabber()
{
}
void VideoRecorder::init(GstCaps *caps)
@@ -207,11 +276,30 @@ void VideoRecorder::init(GstCaps *caps)
if (caps == nullptr)
return;
// apply settings
buffering_size_ = MAX( MIN_BUFFER_SIZE, buffering_preset_value[Settings::application.record.buffering_mode]);
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, framerate_preset_value[Settings::application.record.framerate_mode]);
timestamp_on_clock_ = Settings::application.record.priority_mode < 1;
// create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
Settings::application.record.profile = H264_STANDARD;
description += profile_description[Settings::application.record.profile];
// test for a hardware accelerated encoder
if (Settings::application.render.gpu_decoding &&
#if GST_GL_HAVE_PLATFORM_GLX
glGetString(GL_VENDOR)[0] == 'N' && glGetString(GL_VENDOR)[1] == 'V' && // TODO; hack to test for NVIDIA GPU support
#endif
GstToolkit::has_feature(hardware_encoder[Settings::application.record.profile]) ) {
description += hardware_profile_description[Settings::application.record.profile];
Log::Info("Video Recording using hardware accelerated encoder (%s)", hardware_encoder[Settings::application.record.profile]);
}
// revert to software encoder
else
description += profile_description[Settings::application.record.profile];
// verify location path (path is always terminated by the OS dependent separator)
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
@@ -238,7 +326,8 @@ void VideoRecorder::init(GstCaps *caps)
GError *error = NULL;
pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
Log::Info("Video Recording : Could not construct pipeline %s\n%s", description.c_str(), error->message);
Log::Warning("Video Recording : Failed to initiate GStreamer.");
g_clear_error (&error);
finished_ = true;
return;
@@ -255,18 +344,32 @@ void VideoRecorder::init(GstCaps *caps)
if (src_) {
g_object_set (G_OBJECT (src_),
"stream-type", GST_APP_STREAM_TYPE_STREAM,
"is-live", TRUE,
"format", GST_FORMAT_TIME,
// "do-timestamp", TRUE,
NULL);
// Direct encoding (no buffering)
gst_app_src_set_max_bytes( src_, 0 );
if (timestamp_on_clock_)
g_object_set (G_OBJECT (src_),"do-timestamp", TRUE,NULL);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// configure stream
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
gst_app_src_set_latency( src_, -1, 0);
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_);
// specify recorder framerate in the given caps
GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, framerate_preset_value[Settings::application.record.framerate_mode], 1);
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;
@@ -277,7 +380,7 @@ void VideoRecorder::init(GstCaps *caps)
}
else {
Log::Warning("VideoRecorder Could not configure source");
Log::Warning("Video Recording : Failed to configure frame grabber.");
finished_ = true;
return;
}
@@ -285,7 +388,7 @@ void VideoRecorder::init(GstCaps *caps)
// start recording
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
Log::Warning("Video Recording : Failed to start frame grabber.");
finished_ = true;
return;
}
@@ -299,13 +402,34 @@ void VideoRecorder::init(GstCaps *caps)
void VideoRecorder::terminate()
{
// stop the pipeline (again)
gst_element_set_state (pipeline_, GST_STATE_NULL);
// statistics on expected number of frames
guint64 N = MAX( (guint64) duration_ / (guint64) frame_duration_, frame_count_);
float loss = 100.f * ((float) (N - frame_count_) ) / (float) N;
Log::Info("Video Recording : %ld frames captured in %s (aming for %ld, %.0f%% lost)",
frame_count_, GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str(), N, loss);
// warn user if more than 10% lost
if (loss > 10.f) {
if (timestamp_on_clock_)
Log::Warning("Video Recording lost %.0f%% of frames: framerate could not be maintained at %ld FPS.", loss, GST_SECOND / frame_duration_);
else
Log::Warning("Video Recording lost %.0f%% of frames: video is only %s long.",
loss, GstToolkit::time_to_string(timestamp_, GstToolkit::TIME_STRING_READABLE).c_str());
Log::Info("Video Recording : try a lower resolution / a lower framerate / a larger buffer size / a faster codec.");
}
Log::Notify("Video Recording %s is ready.", filename_.c_str());
}
std::string VideoRecorder::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
else
return FrameGrabber::info();
else if (!endofstream_)
return "Saving file...";
else
return "...";
}

View File

@@ -20,11 +20,10 @@ protected:
void init(GstCaps *caps) override;
void terminate() override;
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
void addFrame(GstBuffer *buffer, GstCaps *caps) override;
};
class VideoRecorder : public FrameGrabber
{
std::string filename_;
@@ -45,8 +44,15 @@ public:
JPEG_MULTI,
DEFAULT
} Profile;
static const char* profile_name[DEFAULT];
static const char* profile_name[DEFAULT];
static const char* hardware_encoder[DEFAULT];
static const std::vector<std::string> profile_description;
static const std::vector<std::string> hardware_profile_description;
static const char* buffering_preset_name[6];
static const guint64 buffering_preset_value[6];
static const char* framerate_preset_name[3];
static const int framerate_preset_value[3];
VideoRecorder();
std::string info() const override;

147
RenderView.cpp Normal file
View File

@@ -0,0 +1,147 @@
#include <thread>
// Opengl
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtx/vector_angle.hpp>
#include "defines.h"
#include "Settings.h"
#include "Decorations.h"
#include "RenderView.h"
RenderView::RenderView() : View(RENDERING), frame_buffer_(nullptr), fading_overlay_(nullptr)
{
}
RenderView::~RenderView()
{
if (frame_buffer_)
delete frame_buffer_;
if (fading_overlay_)
delete fading_overlay_;
}
void RenderView::setFading(float f)
{
if (fading_overlay_ == nullptr)
fading_overlay_ = new Surface;
fading_overlay_->shader()->color.a = CLAMP( f < EPSILON ? 0.f : f, 0.f, 1.f);
}
float RenderView::fading() const
{
if (fading_overlay_)
return fading_overlay_->shader()->color.a;
else
return 0.f;
}
void RenderView::setResolution(glm::vec3 resolution, bool useAlpha)
{
// use default resolution if invalid resolution is given (default behavior)
if (resolution.x < 2.f || resolution.y < 2.f)
resolution = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
// do we need to change resolution ?
if (frame_buffer_ && frame_buffer_->resolution() != resolution) {
// new frame buffer
delete frame_buffer_;
frame_buffer_ = nullptr;
}
if (!frame_buffer_)
// output frame is an RBG Multisamples FrameBuffer
frame_buffer_ = new FrameBuffer(resolution, useAlpha, true);
// reset fading
setFading();
}
void RenderView::draw()
{
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -SCENE_DEPTH, 1.f);
if (frame_buffer_) {
// draw in frame buffer
glm::mat4 P = glm::scale( projection, glm::vec3(1.f / frame_buffer_->aspectRatio(), 1.f, 1.f));
// render the scene normally (pre-multiplied alpha in RGB)
frame_buffer_->begin();
scene.root()->draw(glm::identity<glm::mat4>(), P);
fading_overlay_->draw(glm::identity<glm::mat4>(), projection);
frame_buffer_->end();
}
}
void RenderView::drawThumbnail()
{
if (frame_buffer_) {
// if a thumbnailer is pending
if (thumbnailer_.size() > 0) {
try {
// new thumbnailing framebuffer
FrameBuffer *frame_thumbnail = new FrameBuffer( glm::vec3(SESSION_THUMBNAIL_HEIGHT * frame_buffer_->aspectRatio(), SESSION_THUMBNAIL_HEIGHT, 1.f) );
// render
if (Settings::application.render.blit) {
if ( !frame_buffer_->blit(frame_thumbnail) )
throw std::runtime_error("no blit");
}
else {
FrameBufferSurface *thumb = new FrameBufferSurface(frame_buffer_);
frame_thumbnail->begin();
thumb->draw(glm::identity<glm::mat4>(), frame_thumbnail->projection());
frame_thumbnail->end();
delete thumb;
}
// return valid thumbnail promise
thumbnailer_.back().set_value( frame_thumbnail->image() );
// done with thumbnailing framebuffer
delete frame_thumbnail;
}
catch(...) {
// return failed thumbnail promise
thumbnailer_.back().set_exception(std::current_exception());
}
// done with this promise
thumbnailer_.pop_back();
}
}
}
FrameBufferImage *RenderView::thumbnail ()
{
// by default null image
FrameBufferImage *img = nullptr;
// this function is always called from a parallel thread
// So we wait for a few frames of rendering before trying to capture a thumbnail
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// create and store a promise for a FrameBufferImage
thumbnailer_.emplace_back( std::promise<FrameBufferImage *>() );
// future will return the promised FrameBufferImage
std::future<FrameBufferImage *> ft = thumbnailer_.back().get_future();
try {
// wait for a valid return value from promise
img = ft.get();
}
// catch any failed promise
catch (const std::exception&){
}
return img;
}

40
RenderView.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef RENDERVIEW_H
#define RENDERVIEW_H
#include <vector>
#include <future>
#include "View.h"
class RenderView : public View
{
// rendering FBO
FrameBuffer *frame_buffer_;
Surface *fading_overlay_;
// promises of returning thumbnails after an update
std::vector< std::promise<FrameBufferImage *> > thumbnailer_;
public:
RenderView ();
~RenderView ();
// render frame (in opengl context)
void draw () override;
bool canSelect(Source *) override { return false; }
void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false);
glm::vec3 resolution() const { return frame_buffer_->resolution(); }
void setFading(float f = 0.f);
float fading() const;
// current frame
inline FrameBuffer *frame () const { return frame_buffer_; }
// get a thumbnail outside of opengl context; wait for a promise to be fullfiled after draw
void drawThumbnail();
FrameBufferImage *thumbnail ();
};
#endif // RENDERVIEW_H

View File

@@ -50,11 +50,57 @@
#include "UserInterfaceManager.h"
#include "RenderingManager.h"
// local statics
#ifdef USE_GST_OPENGL_SYNC_HANDLER
//
// Discarded because not working under OSX - kept in case it would become useful
//
// Linking pipeline to the rendering instance ensures the opengl contexts
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
//
static GstGLContext *global_gl_context = NULL;
static GstGLDisplay *global_display = NULL;
static std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
static GstBusSyncReply bus_sync_handler( GstBus *, GstMessage * msg, gpointer )
{
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
const gchar* contextType;
gst_message_parse_context_type(msg, &contextType);
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
gst_context_set_gl_display(displayContext, global_display);
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
gst_context_unref (displayContext);
g_info ("Managed %s\n", contextType);
}
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
GstStructure* structure = gst_context_writable_structure(appContext);
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
gst_context_unref (appContext);
g_info ("Managed %s\n", contextType);
}
}
gst_message_unref (msg);
return GST_BUS_DROP;
}
void Rendering::LinkPipeline( GstPipeline *pipeline )
{
// capture bus signals to force a unique opengl context for all GST elements
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
gst_object_unref (m_bus);
}
#endif
std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
static void glfw_error_callback(int error, const char* description)
{
@@ -95,10 +141,9 @@ static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
{
static double seconds = 0.f;
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
{
static double seconds = 0.f;
// detect double clic
if ( glfwGetTime() - seconds < 0.2f ) {
// toggle fullscreen
@@ -143,6 +188,7 @@ bool Rendering::init()
// additional window callbacks for main window
glfwSetWindowRefreshCallback( main_.window(), WindowRefreshCallback );
glfwSetDropCallback( main_.window(), Rendering::FileDropped);
glfwSetWindowSizeLimits( main_.window(), 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
//
// Gstreamer setup
@@ -161,29 +207,32 @@ bool Rendering::init()
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins(Settings::application.render.gpu_decoding);
if (Settings::application.render.gpu_decoding) {
if (gpuplugins.size() > 0) {
Log::Info("Video decoding favoring the following GPU decoding plugin(s):");
Log::Info("Fond the following GPU decoding plugin(s):");
for(auto it = gpuplugins.begin(); it != gpuplugins.end(); it++)
Log::Info(" - %s", (*it).c_str());
}
else {
Log::Info("No GPU decoding plugin found.");
}
}
#ifdef SYNC_GSTREAMER_OPENGL_CONTEXT
#if GST_GL_HAVE_PLATFORM_WGL
global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
#elif GST_GL_HAVE_PLATFORM_CGL
// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
//#if GST_GL_HAVE_PLATFORM_WGL
// global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
// GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
//#elif GST_GL_HAVE_PLATFORM_CGL
//// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
// global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
// global_gl_context = gst_gl_context_new_wrapped (global_display,
// (guintptr) 0,
// GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
//#elif GST_GL_HAVE_PLATFORM_GLX
// global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
// global_gl_context = gst_gl_context_new_wrapped (global_display,
// (guintptr) glfwGetGLXContext(main_.window()),
// GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
//#endif
global_gl_context = gst_gl_context_new_wrapped (global_display,
(guintptr) 0,
GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
#elif GST_GL_HAVE_PLATFORM_GLX
global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
global_gl_context = gst_gl_context_new_wrapped (global_display,
(guintptr) glfwGetGLXContext(main_.window()),
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
#endif
#endif
//
// output window
@@ -229,8 +278,6 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
void Rendering::draw()
{
// guint64 _time = gst_util_get_timestamp ();
// operate on main window context
main_.makeCurrent();
@@ -239,7 +286,7 @@ void Rendering::draw()
// Custom draw
std::list<Rendering::RenderingCallback>::iterator iter;
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); iter++)
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); ++iter)
{
(*iter)();
}
@@ -272,18 +319,25 @@ void Rendering::draw()
main_.toggleFullscreen_();
output_.toggleFullscreen_();
#ifndef USE_GST_APPSINK_CALLBACKS
// no g_main_loop_run(loop) : update global GMainContext
g_main_context_iteration(NULL, FALSE);
#endif
// software framerate limiter 60FPS if not v-sync
if ( Settings::application.render.vsync < 1 ) {
static GTimer *timer = g_timer_new ();
double elapsed = g_timer_elapsed (timer, NULL) * 1000000.0;
if (elapsed < 16000)
if ( (elapsed < 16000.0) && (elapsed > 0.0) )
g_usleep( 16000 - (gulong)elapsed );
g_timer_start(timer);
}
// change main window title if requested
if (!main_new_title_.empty()) {
main_.setTitle(main_new_title_);
main_new_title_.clear();
}
}
@@ -292,7 +346,7 @@ void Rendering::terminate()
// close window
glfwDestroyWindow(output_.window());
glfwDestroyWindow(main_.window());
glfwTerminate();
// glfwTerminate();
}
@@ -373,14 +427,15 @@ glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bo
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
{
for (int i = 0; i < path_count; ++i) {
int i = 0;
for (; i < path_count; ++i) {
std::string filename(paths[i]);
if (filename.empty())
break;
// try to create a source
Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) );
}
if (path_count>0) {
if (i>0) {
UserInterface::manager().showPannel();
Rendering::manager().mainWindow().show();
}
@@ -433,9 +488,11 @@ RenderingWindow::~RenderingWindow()
void RenderingWindow::setTitle(const std::string &title)
{
std::string fulltitle = Settings::application.windows[index_].name;
if ( !title.empty() )
fulltitle += " -- " + title;
std::string fulltitle;
if ( title.empty() )
fulltitle = Settings::application.windows[index_].name;
else
fulltitle = title + std::string(" - " APP_NAME);
glfwSetWindowTitle(window_, fulltitle.c_str());
}
@@ -676,6 +733,8 @@ bool RenderingWindow::init(int index, GLFWwindow *share)
// DPI scaling (retina)
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
// We decide for byte aligned textures all over
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
// This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
// fast mipmaps (we are not really using mipmaps anyway)
@@ -772,39 +831,45 @@ void RenderingWindow::draw(FrameBuffer *fb)
// attach the 2D texture to local FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
#ifndef NDEBUG
Log::Info("Blit to output window enabled.");
#endif
}
// calculate scaling factor of frame buffer inside window
int rx, ry, rw, rh;
float renderingAspectRatio = fb->aspectRatio();
if (aspectRatio() < renderingAspectRatio) {
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
rx = 0;
ry = (window_attributes_.viewport.y - nh) / 2;
rw = window_attributes_.viewport.x;
rh = (window_attributes_.viewport.y + nh) / 2;
} else {
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
rx = (window_attributes_.viewport.x - nw) / 2;
ry = 0;
rw = (window_attributes_.viewport.x + nw) / 2;
rh = window_attributes_.viewport.y;
// if not disabled
if (!Settings::application.render.disabled) {
// calculate scaling factor of frame buffer inside window
int rx, ry, rw, rh;
float renderingAspectRatio = fb->aspectRatio();
if (aspectRatio() < renderingAspectRatio) {
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
rx = 0;
ry = (window_attributes_.viewport.y - nh) / 2;
rw = window_attributes_.viewport.x;
rh = (window_attributes_.viewport.y + nh) / 2;
} else {
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
rx = (window_attributes_.viewport.x - nw) / 2;
ry = 0;
rw = (window_attributes_.viewport.x + nw) / 2;
rh = window_attributes_.viewport.y;
}
// select fbo texture read target
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
// select screen target
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit operation from fbo (containing texture) to screen
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
// select fbo texture read target
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
// select screen target
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit operation from fbo (containing texture) to screen
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
// draw geometry
else
else if (!Settings::application.render.disabled)
{
// VAO is not shared between multiple contexts of different windows
// so we have to create a new VAO for rendering the surface in this window
@@ -834,64 +899,12 @@ void RenderingWindow::draw(FrameBuffer *fb)
glBindTexture(GL_TEXTURE_2D, 0);
}
// restore attribs
Rendering::manager().popAttrib();
}
// restore attribs
Rendering::manager().popAttrib();
// give back context ownership
glfwMakeContextCurrent(master_);
}
//
// Discarded because not working under OSX - kept in case it would become useful
//
// Linking pipeline to the rendering instance ensures the opengl contexts
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
//
static GstBusSyncReply
bus_sync_handler (GstBus *, GstMessage * msg, gpointer )
{
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
const gchar* contextType;
gst_message_parse_context_type(msg, &contextType);
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
gst_context_set_gl_display(displayContext, global_display);
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
gst_context_unref (displayContext);
g_info ("Managed %s\n", contextType);
}
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
GstStructure* structure = gst_context_writable_structure(appContext);
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
gst_context_unref (appContext);
g_info ("Managed %s\n", contextType);
}
}
gst_message_unref (msg);
return GST_BUS_DROP;
}
void Rendering::LinkPipeline( GstPipeline *pipeline )
{
// capture bus signals to force a unique opengl context for all GST elements
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
gst_object_unref (m_bus);
// GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
// gst_bus_enable_sync_message_emission (m_bus);
// g_signal_connect (m_bus, "sync-message", G_CALLBACK (bus_sync_handler), pipeline);
// gst_object_unref (m_bus);
}

View File

@@ -10,6 +10,7 @@
#include "Screenshot.h"
//#define USE_GST_OPENGL_SYNC_HANDLER
typedef struct GLFWmonitor GLFWmonitor;
typedef struct GLFWwindow GLFWwindow;
@@ -28,6 +29,7 @@ class RenderingWindow
GLFWwindow *window_, *master_;
RenderingAttrib window_attributes_;
std::string title_changed_;
int index_;
float dpi_scale_;
@@ -91,8 +93,8 @@ class Rendering
// Private Constructor
Rendering();
Rendering(Rendering const& copy); // Not Implemented
Rendering& operator=(Rendering const& copy); // Not Implemented
Rendering(Rendering const& copy) = delete;
Rendering& operator=(Rendering const& copy) = delete;
public:
@@ -105,9 +107,8 @@ public:
// Initialization OpenGL and GLFW window creation
bool init();
// show windows and reset views
void show();
// true if active rendering window
bool isActive();
// draw one frame
@@ -130,6 +131,7 @@ public:
// get hold on the windows
inline RenderingWindow& mainWindow() { return main_; }
inline RenderingWindow& outputWindow() { return output_; }
inline void setMainWindowTitle(const std::string t) { main_new_title_ = t; }
// request screenshot
void requestScreenshot();
@@ -143,6 +145,11 @@ public:
// project from scene coordinate to window
glm::vec2 project(glm::vec3 scene_coordinate, glm::mat4 modelview = glm::mat4(1.f), bool to_framebuffer = true);
#ifdef USE_GST_OPENGL_SYNC_HANDLER
// for opengl pipeline in gstreamer
static void LinkPipeline( GstPipeline *pipeline );
#endif
private:
std::string glsl_version;
@@ -154,6 +161,7 @@ private:
std::list<RenderingCallback> draw_callbacks_;
RenderingWindow main_;
std::string main_new_title_;
RenderingWindow output_;
// file drop callback
@@ -162,8 +170,6 @@ private:
Screenshot screenshot_;
bool request_screenshot_;
// for opengl pipeline in gstreamer
void LinkPipeline( GstPipeline *pipeline );
};

View File

@@ -10,11 +10,8 @@
// Desktop OpenGL function loader
#include <glad/glad.h>
// multiplatform message box
#include <tinyfiledialogs.h>
// standalone image loader
#include "stb_image.h"
#include <stb_image.h>
// CMake Ressource Compiler
#include <cmrc/cmrc.hpp>
@@ -41,6 +38,7 @@ uint Resource::getTextureBlack()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_black;
@@ -62,6 +60,7 @@ uint Resource::getTextureWhite()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_white;
@@ -83,6 +82,7 @@ uint Resource::getTextureTransparent()
// texture with one black pixel
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
return tex_index_transparent;
@@ -102,7 +102,7 @@ const char *Resource::getData(const std::string& path, size_t* out_file_size){
cmrc::file::iterator it = file.begin();
data = static_cast<const char *>(it);
}
catch (std::system_error e) {
catch (const std::system_error &e) {
Log::Error("Could not access ressource %s", std::string(path).c_str());
}
@@ -118,7 +118,7 @@ std::string Resource::getText(const std::string& path){
file = fs.open(path.c_str());
file_stream << std::string(file.begin(), file.end()) << std::endl;
}
catch (std::system_error e) {
catch (const std::system_error &e) {
Log::Error("Could not access ressource %s", std::string(path).c_str());
}
@@ -162,9 +162,8 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
uint mipMapCount = *(uint*)&(header[24]);
uint fourCC = *(uint*)&(header[80]);
// how big is it going to be including all mipmaps?
uint bufsize;
bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
// how big is it going to be including all mipmaps?
uint bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
// get the buffer = bytes [128 - ]
const char *buffer = fp + 128;
@@ -188,7 +187,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
}
}
if (height == 0){
if (height == 0 || bufsize == 0){
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
return 0;
}
@@ -209,11 +208,11 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
// load the mipmaps
for (uint level = 0; level < mipMapCount && (width || height); ++level)
{
uint size = ((width+3)/4)*((height+3)/4)*blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset);
uint s = ((width+3)/4)*((height+3)/4)*blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, s, buffer + offset);
glGenerateMipmap(GL_TEXTURE_2D);
offset += size;
offset += s;
width /= 2;
height /= 2;
@@ -222,6 +221,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
if(height < 1) height = 1;
}
glBindTexture(GL_TEXTURE_2D, 0);
// remember to avoid openning the same resource twice
textureIndex[path] = textureID;
@@ -266,19 +266,20 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
}
if (h == 0){
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
stbi_image_free(img);
return 0;
}
ar = static_cast<float>(w) / static_cast<float>(h);
glGenTextures(1, &textureID);
glBindTexture( GL_TEXTURE_2D, textureID);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glBindTexture( GL_TEXTURE_2D, textureID);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
// free memory
stbi_image_free(img);

View File

@@ -1,38 +1,49 @@
#include "defines.h"
#include "Scene.h"
#include "Shader.h"
#include "Primitives.h"
#include "Visitor.h"
#include "GarbageVisitor.h"
#include "Log.h"
#include "GlmToolkit.h"
#include "SessionVisitor.h"
#include <glad/glad.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/random.hpp>
#include <glad/glad.h>
#include <algorithm>
#include "defines.h"
#include "Shader.h"
#include "Primitives.h"
#include "Visitor.h"
#include "GarbageVisitor.h"
#include "Log.h"
#include "BaseToolkit.h"
#include "GlmToolkit.h"
#include "SessionVisitor.h"
#include "Scene.h"
#define DEBUG_SCENE 0
static int num_nodes_ = 0;
// Node
Node::Node() : initialized_(false), visible_(true), refcount_(0)
{
// create unique id
id_ = GlmToolkit::uniqueId();
id_ = BaseToolkit::uniqueId();
transform_ = glm::identity<glm::mat4>();
scale_ = glm::vec3(1.f);
rotation_ = glm::vec3(0.f);
translation_ = glm::vec3(0.f);
crop_ = glm::vec3(1.f);
#if DEBUG_SCENE
num_nodes_++;
#endif
}
Node::~Node ()
{
clearCallbacks();
#if DEBUG_SCENE
num_nodes_--;
#endif
}
void Node::clearCallbacks()
@@ -46,7 +57,7 @@ void Node::clearCallbacks()
}
}
void Node::copyTransform(Node *other)
void Node::copyTransform(const Node *other)
{
if (!other)
return;
@@ -72,7 +83,7 @@ void Node::update( float dt)
delete callback;
}
else {
iter++;
++iter;
}
}
@@ -111,14 +122,14 @@ void Primitive::init()
glGenBuffers( 1, &elementBuffer_);
glBindVertexArray( vao_ );
// compute the memory needs for points normals and indicies
// compute the memory needs for points
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
std::size_t sizeofTexCoords = sizeof(glm::vec2) * texCoords_.size();
// setup the array buffers for vertices
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
glBufferData(GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
if ( sizeofTexCoords )
@@ -126,7 +137,7 @@ void Primitive::init()
// setup the element array for the triangle indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
int sizeofIndices = indices_.size()*sizeof(uint);
std::size_t sizeofIndices = indices_.size() * sizeof(uint);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeofIndices, &(indices_[0]), GL_STATIC_DRAW);
// explain how to read attributes 0, 1 and 2 (for point, color and textcoord respectively)
@@ -273,7 +284,7 @@ void Group::update( float dt )
// update every child node
for (NodeSet::iterator node = children_.begin();
node != children_.end(); node++) {
node != children_.end(); ++node) {
(*node)->update ( dt );
}
}
@@ -290,7 +301,7 @@ void Group::draw(glm::mat4 modelview, glm::mat4 projection)
// draw every child node
for (NodeSet::iterator node = children_.begin();
node != children_.end(); node++) {
node != children_.end(); ++node) {
(*node)->draw ( ctm, projection );
}
}
@@ -384,13 +395,13 @@ void Switch::accept(Visitor& v)
void Switch::setActive (uint index)
{
active_ = CLAMP(index, 0, children_.size() - 1);
active_ = MINI(index, children_.size() - 1);
}
Node *Switch::child(uint index) const
{
if (!children_.empty()) {
uint i = CLAMP(index, 0, children_.size() - 1);
uint i = MINI(index, children_.size() - 1);
return children_.at(i);
}
return nullptr;
@@ -447,6 +458,9 @@ Scene::~Scene()
clear();
// bg and fg are deleted as children of root
delete root_;
#if DEBUG_SCENE
Log::Info("Total scene nodes %d", num_nodes_);
#endif
}

View File

@@ -13,6 +13,7 @@
#include <map>
#include "UpdateCallback.h"
#include "GlmToolkit.h"
// Forward declare classes referenced
class Shader;
@@ -64,7 +65,7 @@ public:
// accept all kind of visitors
virtual void accept (Visitor& v);
void copyTransform (Node *other);
void copyTransform (const Node *other);
// public members, to manipulate with care
bool visible_;
@@ -239,6 +240,9 @@ class Scene {
public:
Scene();
// non assignable class
Scene(Scene const&) = delete;
Scene& operator=(Scene const&) = delete;
~Scene();
void accept (Visitor& v);

View File

@@ -25,7 +25,7 @@ void SearchVisitor::visit(Group &n)
if (found_)
return;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
if (found_)
break;
@@ -59,7 +59,7 @@ void SearchFileVisitor::visit(Node &n)
void SearchFileVisitor::visit(Group &n)
{
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
}
}

View File

@@ -2,7 +2,7 @@
#include "defines.h"
#include "SessionVisitor.h"
#include <tinyxml2.h>
#include "Source.h"
#include "Selection.h"
@@ -61,7 +61,7 @@ void Selection::set(SourceList l)
{
clear();
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::SELECTED);
l.sort();
@@ -71,7 +71,7 @@ void Selection::set(SourceList l)
void Selection::add(SourceList l)
{
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::SELECTED);
// generate new set as union of current selection and give list
@@ -86,7 +86,7 @@ void Selection::add(SourceList l)
void Selection::remove(SourceList l)
{
for(auto it = l.begin(); it != l.end(); it++)
for(auto it = l.begin(); it != l.end(); ++it)
(*it)->setMode(Source::VISIBLE);
// generate new set as difference of current selection and give list
@@ -98,13 +98,13 @@ void Selection::remove(SourceList l)
void Selection::clear()
{
for(auto it = selection_.begin(); it != selection_.end(); it++)
for(auto it = selection_.begin(); it != selection_.end(); ++it)
(*it)->setMode(Source::VISIBLE);
selection_.clear();
}
uint Selection::size()
uint Selection::size() const
{
return selection_.size();
}
@@ -117,13 +117,21 @@ Source *Selection::front()
return selection_.front();
}
Source *Selection::back()
{
if (selection_.empty())
return nullptr;
return selection_.back();
}
void Selection::pop_front()
{
if (!selection_.empty()) // TODO set mode ?
selection_.pop_front();
}
bool Selection::empty()
bool Selection::empty() const
{
return selection_.empty();
}
@@ -149,41 +157,14 @@ SourceList::iterator Selection::end()
return selection_.end();
}
std::string Selection::xml()
std::string Selection::clipboard() const
{
std::string x = "";
if (!selection_.empty()) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
selectionNode->SetAttribute("size", (int) selection_.size());
xmlDoc.InsertEndChild(selectionNode);
// fill doc
SourceList selection_clones_;
SessionVisitor sv(&xmlDoc, selectionNode);
for (auto iter = selection_.begin(); iter != selection_.end(); iter++, sv.setRoot(selectionNode) ){
// keep the clones for later
CloneSource *clone = dynamic_cast<CloneSource *>(*iter);
if (clone)
(*iter)->accept(sv);
else
selection_clones_.push_back(*iter);
}
// add the clones at the end
for (auto iter = selection_clones_.begin(); iter != selection_clones_.end(); iter++, sv.setRoot(selectionNode) ){
(*iter)->accept(sv);
}
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
return SessionVisitor::getClipboard(selection_);
}
SourceList Selection::getCopy() const
{
SourceList dsl = selection_;
return dsl;
}

View File

@@ -1,7 +1,7 @@
#ifndef SELECTION_H
#define SELECTION_H
#include "Source.h"
#include "SourceList.h"
class Selection
{
@@ -9,25 +9,32 @@ class Selection
public:
Selection();
// construct list
void add (Source *s);
void add (SourceList l);
void remove (Source *s);
void remove (SourceList l);
void set (Source *s);
void set (SourceList l);
void toggle (Source *s);
void add (SourceList l);
void remove (SourceList l);
void set (SourceList l);
void clear ();
void pop_front();
// access elements
SourceList::iterator begin ();
SourceList::iterator end ();
Source *front();
void pop_front();
bool contains (Source *s);
bool empty();
uint size ();
Source *back();
std::string xml();
// properties
bool contains (Source *s);
bool empty() const;
uint size () const;
// extract
std::string clipboard() const;
SourceList getCopy() const;
protected:
SourceList::iterator find (Source *s);

View File

@@ -1,19 +1,25 @@
#include <algorithm>
#include "defines.h"
#include "BaseToolkit.h"
#include "Source.h"
#include "Settings.h"
#include "FrameBuffer.h"
#include "Session.h"
#include "FrameGrabber.h"
#include "SessionCreator.h"
#include "SessionSource.h"
#include "MixingGroup.h"
#include "Log.h"
Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_string(BaseToolkit::uniqueId())),
text(t), large(l), stick(s), pos(glm::vec2(520.f, 30.f)), size(glm::vec2(220.f, 220.f))
{
filename_ = "";
}
Session::Session() : active_(true), filename_(""), failedSource_(nullptr), fading_target_(0.f), thumbnail_(nullptr)
{
config_[View::RENDERING] = new Group;
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
@@ -29,14 +35,23 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
config_[View::APPEARANCE] = new Group;
config_[View::APPEARANCE]->scale_ = Settings::application.views[View::APPEARANCE].default_scale;
config_[View::APPEARANCE]->translation_ = Settings::application.views[View::APPEARANCE].default_translation;
config_[View::TEXTURE] = new Group;
config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale;
config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation;
snapshots_.xmlDoc_ = new tinyxml2::XMLDocument;
}
Session::~Session()
{
// TODO delete all mixing groups?
auto group_iter = mixing_groups_.begin();
while ( group_iter != mixing_groups_.end() ){
delete (*group_iter);
group_iter = mixing_groups_.erase(group_iter);
}
// delete all sources
for(auto it = sources_.begin(); it != sources_.end(); ) {
// erase this source from the list
@@ -47,14 +62,17 @@ Session::~Session()
delete config_[View::GEOMETRY];
delete config_[View::LAYER];
delete config_[View::MIXING];
delete config_[View::APPEARANCE];
delete config_[View::TEXTURE];
snapshots_.keys_.clear();
delete snapshots_.xmlDoc_;
}
void Session::setActive (bool on)
{
if (active_ != on) {
active_ = on;
for(auto it = sources_.begin(); it != sources_.end(); it++) {
for(auto it = sources_.begin(); it != sources_.end(); ++it) {
(*it)->setActive(active_);
}
}
@@ -67,19 +85,24 @@ void Session::update(float dt)
if ( render_.frame() == nullptr )
return;
// pre-render of all sources
// pre-render all sources
failedSource_ = nullptr;
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); it++){
bool ready = true;
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){
// ensure the RenderSource is rendering this session
RenderSource *s = dynamic_cast<RenderSource *>( *it );
if ( s!= nullptr && s->session() != this )
s->setSession(this);
// ensure the RenderSource is rendering *this* session
RenderSource *rs = dynamic_cast<RenderSource *>( *it );
if ( rs!= nullptr && rs->session() != this )
rs->setSession(this);
// discard failed source
if ( (*it)->failed() ) {
failedSource_ = (*it);
}
// render normally
else {
if ( !(*it)->ready() )
ready = false;
// render the source
(*it)->render();
// update the source
@@ -87,6 +110,19 @@ void Session::update(float dt)
}
}
// update session's mixing groups
auto group_iter = mixing_groups_.begin();
while ( group_iter != mixing_groups_.end() ){
// update all valid groups
if ((*group_iter)->size() > 1) {
(*group_iter)->update(dt);
group_iter++;
}
else
// delete invalid groups (singletons)
group_iter = deleteMixingGroup(group_iter);
}
// apply fading (smooth dicotomic reaching)
float f = render_.fading();
if ( ABS_DIFF(f, fading_target_) > EPSILON) {
@@ -99,18 +135,18 @@ void Session::update(float dt)
// draw render view in Frame Buffer
render_.draw();
// draw the thumbnail only after all sources are ready
if (ready)
render_.drawThumbnail();
}
SourceList::iterator Session::addSource(Source *s)
{
SourceList::iterator its = sources_.end();
// lock before change
access_.lock();
// find the source
its = find(s);
SourceList::iterator its = find(s);
// ok, its NOT in the list !
if (its == sources_.end()) {
@@ -139,6 +175,9 @@ SourceList::iterator Session::deleteSource(Source *s)
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// inform group
if (s->mixingGroup() != nullptr)
s->mixingGroup()->detach(s);
// erase the source from the update list & get next element
its = sources_.erase(its);
// delete the source : safe now
@@ -152,7 +191,6 @@ SourceList::iterator Session::deleteSource(Source *s)
return its;
}
void Session::removeSource(Source *s)
{
// lock before change
@@ -164,6 +202,9 @@ void Session::removeSource(Source *s)
if (its != sources_.end()) {
// remove Node from the rendering scene
render_.scene.ws()->detach( s->group(View::RENDERING) );
// inform group
if (s->mixingGroup() != nullptr)
s->mixingGroup()->detach(s);
// erase the source from the update list & get next element
sources_.erase(its);
}
@@ -172,7 +213,6 @@ void Session::removeSource(Source *s)
access_.unlock();
}
Source *Session::popSource()
{
Source *s = nullptr;
@@ -190,6 +230,33 @@ Source *Session::popSource()
return s;
}
static void replaceThumbnail(Session *s)
{
if (s != nullptr) {
FrameBufferImage *t = s->renderThumbnail();
if (t != nullptr) // avoid recursive infinite loop
s->setThumbnail(t);
}
}
void Session::setThumbnail(FrameBufferImage *t)
{
resetThumbnail();
// replace with given image
if (t != nullptr)
thumbnail_ = t;
// no thumbnail image given: capture from rendering in a parallel thread
else
std::thread( replaceThumbnail, this ).detach();
}
void Session::resetThumbnail()
{
if (thumbnail_ != nullptr)
delete thumbnail_;
thumbnail_ = nullptr;
}
void Session::setResolution(glm::vec3 resolution, bool useAlpha)
{
// setup the render view: if not specified the default config resulution will be used
@@ -241,24 +308,34 @@ SourceList::iterator Session::find(float depth_from, float depth_to)
return std::find_if(sources_.begin(), sources_.end(), Source::hasDepth(depth_from, depth_to));
}
SourceList Session::getDepthSortedList() const
{
return depth_sorted(sources_);
}
uint Session::numSource() const
{
return sources_.size();
}
std::list<uint64_t> Session::getIdList() const
SourceIdList Session::getIdList() const
{
std::list<uint64_t> idlist;
for( auto sit = sources_.begin(); sit != sources_.end(); sit++)
idlist.push_back( (*sit)->id() );
// make sure no duplicate
idlist.unique();
return idlist;
return ids(sources_);
}
std::list<std::string> Session::getNameList(uint64_t exceptid) const
{
std::list<std::string> namelist;
for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) {
if ( (*it)->id() != exceptid )
namelist.push_back( (*it)->name() );
}
return namelist;
}
bool Session::empty() const
{
return sources_.empty();
@@ -273,7 +350,7 @@ SourceList::iterator Session::at(int index)
SourceList::iterator it = sources_.begin();
while ( i < index && it != sources_.end() ){
i++;
it++;
++it;
}
return it;
}
@@ -282,7 +359,7 @@ int Session::index(SourceList::iterator it) const
{
int index = -1;
int count = 0;
for(auto i = sources_.begin(); i != sources_.end(); i++, count++) {
for(auto i = sources_.begin(); i != sources_.end(); ++i, ++count) {
if ( i == it ) {
index = count;
break;
@@ -301,13 +378,169 @@ void Session::move(int current_index, int target_index)
SourceList::iterator from = at(current_index);
SourceList::iterator to = at(target_index);
if ( target_index > current_index )
to++;
++to;
Source *s = (*from);
sources_.erase(from);
sources_.insert(to, s);
}
bool Session::canlink (SourceList sources)
{
bool canlink = true;
// verify that all sources given are valid in the sesion
validate(sources);
for (auto it = sources.begin(); it != sources.end(); ++it) {
// this source is linked
if ( (*it)->mixingGroup() != nullptr ) {
// askt its group to detach it
canlink = false;
}
}
return canlink;
}
void Session::link(SourceList sources, Group *parent)
{
// we need at least 2 sources to make a group
if (sources.size() > 1) {
unlink(sources);
// create and add a new mixing group
MixingGroup *g = new MixingGroup(sources);
mixing_groups_.push_back(g);
// if provided, attach the group to the parent
if (g && parent != nullptr)
g->attachTo( parent );
}
}
void Session::unlink (SourceList sources)
{
// verify that all sources given are valid in the sesion
validate(sources);
// brute force : detach all given sources
for (auto it = sources.begin(); it != sources.end(); ++it) {
// this source is linked
if ( (*it)->mixingGroup() != nullptr ) {
// askt its group to detach it
(*it)->mixingGroup()->detach(*it);
}
}
}
void Session::addNote(SessionNote note)
{
notes_.push_back( note );
}
std::list<SessionNote>::iterator Session::beginNotes ()
{
return notes_.begin();
}
std::list<SessionNote>::iterator Session::endNotes ()
{
return notes_.end();
}
std::list<SessionNote>::iterator Session::deleteNote (std::list<SessionNote>::iterator n)
{
if (n != notes_.end())
return notes_.erase(n);
return notes_.end();
}
std::list<SourceList> Session::getMixingGroups () const
{
std::list<SourceList> lmg;
for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); ++group_it)
lmg.push_back( (*group_it)->getCopy() );
return lmg;
}
std::list<MixingGroup *>::iterator Session::deleteMixingGroup (std::list<MixingGroup *>::iterator g)
{
if (g != mixing_groups_.end()) {
delete (*g);
return mixing_groups_.erase(g);
}
return mixing_groups_.end();
}
std::list<MixingGroup *>::iterator Session::beginMixingGroup()
{
return mixing_groups_.begin();
}
std::list<MixingGroup *>::iterator Session::endMixingGroup()
{
return mixing_groups_.end();
}
size_t Session::numPlayGroups() const
{
return play_groups_.size();
}
void Session::addPlayGroup(const SourceIdList &ids)
{
play_groups_.push_back( ids );
}
void Session::addToPlayGroup(size_t i, Source *s)
{
if (i < play_groups_.size() )
{
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() )
play_groups_[i].push_back(s->id());
}
}
void Session::removeFromPlayGroup(size_t i, Source *s)
{
if (i < play_groups_.size() )
{
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() )
play_groups_[i].remove( s->id() );
}
}
void Session::deletePlayGroup(size_t i)
{
if (i < play_groups_.size() )
play_groups_.erase( play_groups_.begin() + i);
}
SourceList Session::playGroup(size_t i) const
{
SourceList list;
if (i < play_groups_.size() )
{
for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){
SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));;
if ( it != sources_.end())
list.push_back( *it);
}
}
return list;
}
void Session::lock()
{
access_.lock();
@@ -318,12 +551,26 @@ void Session::unlock()
access_.unlock();
}
void Session::validate (SourceList &sources)
{
// verify that all sources given are valid in the sesion
// and remove the invalid sources
for (auto _it = sources.begin(); _it != sources.end(); ) {
SourceList::iterator found = std::find(sources_.begin(), sources_.end(), *_it);
if ( found == sources_.end() )
_it = sources.erase(_it);
else
_it++;
}
}
Session *Session::load(const std::string& filename, uint recursion)
{
// create session
SessionCreator creator(recursion);
creator.load(filename);
// return created session
return creator.session();
}

View File

@@ -3,10 +3,32 @@
#include <mutex>
#include "View.h"
#include "Source.h"
#include "SourceList.h"
#include "RenderView.h"
namespace tinyxml2 {
class XMLDocument;
}
class FrameGrabber;
class MixingGroup;
struct SessionNote
{
std::string label;
std::string text;
bool large;
int stick;
glm::vec2 pos;
glm::vec2 size;
SessionNote(const std::string &t = "", bool l = false, int s = 0);
};
struct SessionSnapshots {
tinyxml2::XMLDocument *xmlDoc_;
std::list<uint64_t> keys_;
};
class Session
{
@@ -39,9 +61,12 @@ public:
SourceList::iterator find (std::string name);
SourceList::iterator find (Node *node);
SourceList::iterator find (float depth_from, float depth_to);
SourceList getDepthSortedList () const;
SourceList::iterator find (uint64_t id);
std::list<uint64_t> getIdList() const;
SourceIdList getIdList() const;
std::list<std::string> getNameList(uint64_t exceptid=0) const;
SourceList::iterator at (int index);
int index (SourceList::iterator it) const;
@@ -55,39 +80,85 @@ public:
inline bool active () { return active_; }
// return the last source which failed
Source *failedSource() { return failedSource_; }
Source *failedSource () { return failedSource_; }
// get frame result of render
inline FrameBuffer *frame () const { return render_.frame(); }
// get an newly rendered thumbnail
inline FrameBufferImage *renderThumbnail () { return render_.thumbnail(); }
// get / set thumbnail image
inline FrameBufferImage *thumbnail () const { return thumbnail_; }
void setThumbnail(FrameBufferImage *t = nullptr);
void resetThumbnail();
// configure rendering resolution
void setResolution(glm::vec3 resolution, bool useAlpha = false);
void setResolution (glm::vec3 resolution, bool useAlpha = false);
// manipulate fading of output
void setFading(float f, bool forcenow = false);
inline float fading() const { return fading_target_; }
void setFading (float f, bool forcenow = false);
inline float fading () const { return fading_target_; }
// configuration for group nodes of views
inline Group *config (View::Mode m) const { return config_.at(m); }
// name of file containing this session (for transfer)
void setFilename(const std::string &filename) { filename_ = filename; }
std::string filename() const { return filename_; }
void setFilename (const std::string &filename) { filename_ = filename; }
std::string filename () const { return filename_; }
// get the list of notes
void addNote(SessionNote note = SessionNote());
std::list<SessionNote>::iterator beginNotes ();
std::list<SessionNote>::iterator endNotes ();
std::list<SessionNote>::iterator deleteNote (std::list<SessionNote>::iterator n);
// get the list of sources in mixing groups
std::list<SourceList> getMixingGroups () const;
// returns true if something can be done to create a mixing group
bool canlink (SourceList sources);
// try to link sources of the given list:
// can either create a new mixing group or extend an existing group
void link (SourceList sources, Group *parent = nullptr);
// try to unlink sources of the given list:
// can either delete an entire mixing group, or remove the given sources from existing groups
void unlink (SourceList sources);
// iterators for looping over mixing groups
std::list<MixingGroup *>::iterator beginMixingGroup ();
std::list<MixingGroup *>::iterator endMixingGroup ();
std::list<MixingGroup *>::iterator deleteMixingGroup (std::list<MixingGroup *>::iterator g);
// snapshots
SessionSnapshots * snapshots () { return &snapshots_; }
// playlists
void addPlayGroup(const SourceIdList &ids);
void deletePlayGroup(size_t i);
size_t numPlayGroups() const;
SourceList playGroup(size_t i) const;
void addToPlayGroup(size_t i, Source *s);
void removeFromPlayGroup(size_t i, Source *s);
std::vector<SourceIdList> getPlayGroups() { return play_groups_; }
// lock and unlock access (e.g. while saving)
void lock();
void unlock();
void lock ();
void unlock ();
protected:
bool active_;
RenderView render_;
std::string filename_;
Source *failedSource_;
SourceList sources_;
void validate(SourceList &sources);
std::list<SessionNote> notes_;
std::list<MixingGroup *> mixing_groups_;
std::map<View::Mode, Group*> config_;
bool active_;
std::list<FrameGrabber *> grabbers_;
SessionSnapshots snapshots_;
std::vector<SourceIdList> play_groups_;
float fading_target_;
std::mutex access_;
FrameBufferImage *thumbnail_;
};

View File

@@ -1,4 +1,4 @@
#include "SessionCreator.h"
#include <sstream>
#include "Log.h"
#include "defines.h"
@@ -12,41 +12,59 @@
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "MultiFileSource.h"
#include "Session.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
#include "SystemToolkit.h"
#include <tinyxml2.h>
#include "tinyxml2Toolkit.h"
using namespace tinyxml2;
#include "SessionCreator.h"
std::string SessionCreator::info(const std::string& filename)
SessionInformation SessionCreator::info(const std::string& filename)
{
std::string ret = "";
SessionInformation ret;
XMLDocument doc;
XMLError eResult = doc.LoadFile(filename.c_str());
if ( XMLResultError(eResult)) {
Log::Warning("%s could not be openned.", filename.c_str());
return ret;
}
// if the file exists
if (SystemToolkit::file_exists(filename)) {
// impose C locale
setlocale(LC_ALL, "C");
// try to load the file
XMLDocument doc;
XMLError eResult = doc.LoadFile(filename.c_str());
// silently ignore on error
if ( !XMLResultError(eResult, false)) {
XMLElement *header = doc.FirstChildElement(APP_NAME);
if (header != nullptr && header->Attribute("date") != 0) {
int s = header->IntAttribute("size");
ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
const char *att_string = header->Attribute("resolution");
if (att_string)
ret += std::string( att_string ) + "\n";
att_string = header->Attribute("date");
if (att_string) {
std::string date( att_string );
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ ";
ret += date.substr(8,2) + ":" + date.substr(10,2);
const XMLElement *header = doc.FirstChildElement(APP_NAME);
if (header != nullptr) {
int s = header->IntAttribute("size");
ret.description = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
const char *att_string = header->Attribute("resolution");
if (att_string)
ret.description += std::string( att_string ) + "\n";
att_string = header->Attribute("date");
if (att_string) {
std::string date( att_string );
ret.description += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ ";
ret.description += date.substr(8,2) + ":" + date.substr(10,2);
}
}
const XMLElement *session = doc.FirstChildElement("Session");
if (session != nullptr ) {
const XMLElement *thumbnailelement = session->FirstChildElement("Thumbnail");
// if there is a user defined thumbnail, get it
if (thumbnailelement) {
ret.thumbnail = XMLToImage(thumbnailelement);
ret.user_thumbnail_ = true;
}
// otherwise get the default saved thumbnail in session
else
ret.thumbnail = XMLToImage(session);
}
}
}
return ret;
@@ -89,7 +107,32 @@ void SessionCreator::load(const std::string& filename)
loadConfig( xmlDoc_.FirstChildElement("Views") );
// ready to read sources
SessionLoader::load( xmlDoc_.FirstChildElement("Session") );
sessionFilePath_ = SystemToolkit::path_filename(filename);
XMLElement *sessionNode = xmlDoc_.FirstChildElement("Session");
SessionLoader::load( sessionNode );
// create groups
std::list< SourceList > groups = getMixingGroups();
for (auto group_it = groups.begin(); group_it != groups.end(); ++group_it)
session_->link( *group_it );
// load snapshots
loadSnapshots( xmlDoc_.FirstChildElement("Snapshots") );
// load notes
loadNotes( xmlDoc_.FirstChildElement("Notes") );
// load playlists
loadPlayGroups( xmlDoc_.FirstChildElement("PlayGroups") );
// thumbnail
const XMLElement *thumbnailelement = sessionNode->FirstChildElement("Thumbnail");
// if there is a user-defined thumbnail, get it
if (thumbnailelement) {
FrameBufferImage *thumbnail = XMLToImage(thumbnailelement);
if (thumbnail != nullptr)
session_->setThumbnail( thumbnail );
}
// all good
session_->setFilename(filename);
@@ -98,19 +141,126 @@ void SessionCreator::load(const std::string& filename)
void SessionCreator::loadConfig(XMLElement *viewsNode)
{
if (viewsNode != nullptr) {
if (viewsNode != nullptr && session_ != nullptr) {
// ok, ready to read views
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Appearance"), *session_->config(View::APPEARANCE));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Texture"), *session_->config(View::TEXTURE));
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
}
}
SessionLoader::SessionLoader(Session *session, int recursion): Visitor(), session_(session), recursion_(recursion)
void SessionCreator::loadSnapshots(XMLElement *snapshotsNode)
{
if (snapshotsNode != nullptr && session_ != nullptr) {
const XMLElement* N = snapshotsNode->FirstChildElement();
for( ; N ; N = N->NextSiblingElement()) {
char c;
u_int64_t id = 0;
std::istringstream nodename( N->Name() );
nodename >> c >> id;
session_->snapshots()->keys_.push_back(id);
session_->snapshots()->xmlDoc_->InsertEndChild( N->DeepClone(session_->snapshots()->xmlDoc_) );
}
}
}
void SessionCreator::loadNotes(XMLElement *notesNode)
{
if (notesNode != nullptr && session_ != nullptr) {
XMLElement* note = notesNode->FirstChildElement("Note");
for( ; note ; note = note->NextSiblingElement())
{
SessionNote N;
note->QueryBoolAttribute("large", &N.large);
note->QueryIntAttribute("stick", &N.stick);
XMLElement *posNode = note->FirstChildElement("pos");
if (posNode) tinyxml2::XMLElementToGLM( posNode->FirstChildElement("vec2"), N.pos);
XMLElement *sizeNode = note->FirstChildElement("size");
if (sizeNode) tinyxml2::XMLElementToGLM( sizeNode->FirstChildElement("vec2"), N.size);
XMLElement* contentNode = note->FirstChildElement("text");
if (contentNode && contentNode->GetText())
N.text = std::string ( contentNode->GetText() );
session_->addNote(N);
}
}
}
void SessionCreator::loadPlayGroups(tinyxml2::XMLElement *playgroupNode)
{
if (playgroupNode != nullptr && session_ != nullptr) {
XMLElement* playgroup = playgroupNode->FirstChildElement("PlayGroup");
for( ; playgroup ; playgroup = playgroup->NextSiblingElement())
{
SourceIdList playgroup_sources;
XMLElement* playgroupSourceNode = playgroup->FirstChildElement("source");
for ( ; playgroupSourceNode ; playgroupSourceNode = playgroupSourceNode->NextSiblingElement()) {
uint64_t id__ = 0;
playgroupSourceNode->QueryUnsigned64Attribute("id", &id__);
if (sources_id_.count(id__) > 0)
playgroup_sources.push_back( id__ );
}
session_->addPlayGroup( playgroup_sources );
}
}
}
SessionLoader::SessionLoader(): Visitor(),
session_(nullptr), xmlCurrent_(nullptr), recursion_(0)
{
// impose C locale
setlocale(LC_ALL, "C");
}
SessionLoader::SessionLoader(Session *session, int recursion): Visitor(),
session_(session), xmlCurrent_(nullptr), recursion_(recursion)
{
// impose C locale
setlocale(LC_ALL, "C");
}
std::map< uint64_t, Source* > SessionLoader::getSources() const
{
return sources_id_;
}
// groups_sources_id_ is parsed in XML and contains list of groups of ids
// Here we return the list of groups of newly created sources
// based on correspondance map sources_id_
// NB: importantly the list is cleared from duplicates
std::list< SourceList > SessionLoader::getMixingGroups() const
{
std::list< SourceList > groups_new_sources_id;
// perform conversion from xml id to new id
for (auto git = groups_sources_id_.begin(); git != groups_sources_id_.end(); ++git)
{
SourceList new_sources;
for (auto sit = (*git).begin(); sit != (*git).end(); ++sit ) {
if (sources_id_.count(*sit) > 0)
new_sources.push_back( sources_id_.at(*sit) );
}
new_sources.sort();
groups_new_sources_id.push_back( new_sources );
}
// remove duplicates
groups_new_sources_id.unique();
return groups_new_sources_id;
}
void SessionLoader::load(XMLElement *sessionNode)
@@ -133,9 +283,9 @@ void SessionLoader::load(XMLElement *sessionNode)
Source *load_source = nullptr;
// check if a source with the given id exists in the session
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
uint64_t id_xml_ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
SourceList::iterator sit = session_->find(id_xml_);
// no source with this id exists
if ( sit == session_->end() ) {
@@ -144,25 +294,28 @@ void SessionLoader::load(XMLElement *sessionNode)
if (!pType)
continue;
if ( std::string(pType) == "MediaSource") {
load_source = new MediaSource;
load_source = new MediaSource(id_xml_);
}
else if ( std::string(pType) == "SessionSource") {
load_source = new SessionFileSource;
load_source = new SessionFileSource(id_xml_);
}
else if ( std::string(pType) == "GroupSource") {
load_source = new SessionGroupSource;
load_source = new SessionGroupSource(id_xml_);
}
else if ( std::string(pType) == "RenderSource") {
load_source = new RenderSource;
load_source = new RenderSource(id_xml_);
}
else if ( std::string(pType) == "PatternSource") {
load_source = new PatternSource;
load_source = new PatternSource(id_xml_);
}
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource;
load_source = new DeviceSource(id_xml_);
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource;
load_source = new NetworkSource(id_xml_);
}
else if ( std::string(pType) == "MultiFileSource") {
load_source = new MultiFileSource(id_xml_);
}
// skip failed (including clones)
@@ -179,8 +332,9 @@ void SessionLoader::load(XMLElement *sessionNode)
// apply config to source
load_source->accept(*this);
load_source->touch();
// remember
sources_id_.push_back( load_source->id() );
sources_id_[id_xml_] = load_source;
}
// create clones after all sources, to be able to clone a source created above
@@ -194,9 +348,9 @@ void SessionLoader::load(XMLElement *sessionNode)
if ( pType && std::string(pType) == "CloneSource") {
// check if a source with same id exists
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->find(id__);
uint64_t id_xml_ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
SourceList::iterator sit = session_->find(id_xml_);
// no source clone with this id exists
if ( sit == session_->end() ) {
@@ -204,31 +358,45 @@ void SessionLoader::load(XMLElement *sessionNode)
// clone from given origin
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
uint64_t id_origin_ = 0;
originNode->QueryUnsigned64Attribute("id", &id_origin_);
SourceList::iterator origin;
if (id_origin_ > 0)
origin = session_->find(id_origin_);
else {
const char *text = originNode->GetText();
if (text)
origin = session_->find( std::string(text) );
else
origin = session_->end();
}
// found the orign source
if (origin != session_->end()) {
// create a new source of type Clone
Source *clone_source = (*origin)->clone();
Source *clone_source = (*origin)->clone(id_xml_);
// add source to session
session_->addSource(clone_source);
// apply config to source
clone_source->accept(*this);
clone_source->touch();
// remember
sources_id_.push_back( clone_source->id() );
sources_id_[id_xml_] = clone_source;
}
}
}
}
}
// make sure no duplicate
sources_id_.unique();
}
// loop over SourceLinks and resolve them
// NB: this could become the mechanism for clone sources too
}
}
Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, bool clone_duplicates)
Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode)
{
xmlCurrent_ = sourceNode;
@@ -236,49 +404,58 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, bool clone
Source *load_source = nullptr;
bool is_clone = false;
SourceList::iterator sit = session_->end();
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
// check if a source with the given id exists in the session
if (clone_duplicates) {
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
SourceList::iterator sit = session_->end();
if (mode == CLONE) {
sit = session_->find(id__);
}
// no source with this id exists
// no source with this id exists or Mode DUPLICATE
if ( sit == session_->end() ) {
// create a new source depending on type
const char *pType = xmlCurrent_->Attribute("type");
if (pType) {
if ( std::string(pType) == "MediaSource") {
load_source = new MediaSource;
load_source = new MediaSource(id__);
}
else if ( std::string(pType) == "SessionSource") {
load_source = new SessionFileSource;
load_source = new SessionFileSource(id__);
}
else if ( std::string(pType) == "GroupSource") {
load_source = new SessionGroupSource;
load_source = new SessionGroupSource(id__);
}
else if ( std::string(pType) == "RenderSource") {
load_source = new RenderSource;
load_source = new RenderSource(id__);
}
else if ( std::string(pType) == "PatternSource") {
load_source = new PatternSource;
load_source = new PatternSource(id__);
}
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource;
load_source = new DeviceSource(id__);
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource;
load_source = new NetworkSource(id__);
}
else if ( std::string(pType) == "MultiFileSource") {
load_source = new MultiFileSource(id__);
}
else if ( std::string(pType) == "CloneSource") {
// clone from given origin
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
if (originNode) {
std::string sourcename = std::string ( originNode->GetText() );
SourceList::iterator origin = session_->find(sourcename);
uint64_t id_origin_ = 0;
originNode->QueryUnsigned64Attribute("id", &id_origin_);
SourceList::iterator origin;
if (id_origin_ > 0)
origin = session_->find(id_origin_);
else
origin = session_->find( std::string ( originNode->GetText() ) );
// found the orign source
if (origin != session_->end())
load_source = (*origin)->clone();
load_source = (*origin)->clone(id__);
}
}
}
@@ -301,28 +478,178 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, bool clone
}
void SessionLoader::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
bool SessionLoader::isClipboard(const std::string &clipboard)
{
if (clipboard.size() > 6 && clipboard.substr(0, 6) == "<" APP_NAME )
return true;
return false;
}
tinyxml2::XMLElement* SessionLoader::firstSourceElement(const std::string &clipboard, XMLDocument &xmlDoc)
{
tinyxml2::XMLElement* sourceNode = nullptr;
if ( !isClipboard(clipboard) )
return sourceNode;
// header
tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
if ( XMLResultError(eResult))
return sourceNode;
tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
if ( root == nullptr )
return sourceNode;
// find node
sourceNode = root->FirstChildElement("Source");
return sourceNode;
}
void SessionLoader::applyImageProcessing(const Source &s, const std::string &clipboard)
{
if ( !isClipboard(clipboard) )
return;
// header
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
if ( XMLResultError(eResult))
return;
tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
if ( root == nullptr )
return;
// find node
tinyxml2::XMLElement* imgprocNode = nullptr;
tinyxml2::XMLElement* sourceNode = root->FirstChildElement("Source");
if (sourceNode == nullptr)
imgprocNode = root->FirstChildElement("ImageProcessing");
else
imgprocNode = sourceNode->FirstChildElement("ImageProcessing");
if (imgprocNode == nullptr)
return;
// create session visitor and browse
SessionLoader loader;
loader.xmlCurrent_ = imgprocNode;
s.processingShader()->accept(loader);
}
//void SessionLoader::applyMask(const Source &s, std::string clipboard)
//{
// if ( !isClipboard(clipboard) )
// return;
// // header
// tinyxml2::XMLDocument xmlDoc;
// tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
// if ( XMLResultError(eResult))
// return;
// tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
// if ( root == nullptr )
// return;
// // find node
// tinyxml2::XMLElement* naskNode = nullptr;
// tinyxml2::XMLElement* sourceNode = root->FirstChildElement("Source");
// if (sourceNode == nullptr)
// naskNode = root->FirstChildElement("Mask");
// else
// naskNode = sourceNode->FirstChildElement("ImageProcessing");
// if (naskNode == nullptr)
// return;
// // create session visitor and browse
// SessionLoader loader;
// loader.xmlCurrent_ = naskNode;
//// s.processingShader()->accept(loader);
//}
void SessionLoader::XMLToNode(const tinyxml2::XMLElement *xml, Node &n)
{
if (xml != nullptr){
XMLElement *node = xml->FirstChildElement("Node");
const XMLElement *node = xml->FirstChildElement("Node");
if ( !node || std::string(node->Name()).find("Node") == std::string::npos )
return;
XMLElement *scaleNode = node->FirstChildElement("scale");
const XMLElement *scaleNode = node->FirstChildElement("scale");
if (scaleNode)
tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"), n.scale_);
XMLElement *translationNode = node->FirstChildElement("translation");
const XMLElement *translationNode = node->FirstChildElement("translation");
if (translationNode)
tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"), n.translation_);
XMLElement *rotationNode = node->FirstChildElement("rotation");
const XMLElement *rotationNode = node->FirstChildElement("rotation");
if (rotationNode)
tinyxml2::XMLElementToGLM( rotationNode->FirstChildElement("vec3"), n.rotation_);
XMLElement *cropNode = node->FirstChildElement("crop");
const XMLElement *cropNode = node->FirstChildElement("crop");
if (cropNode)
tinyxml2::XMLElementToGLM( cropNode->FirstChildElement("vec3"), n.crop_);
}
}
void SessionLoader::XMLToSourcecore(tinyxml2::XMLElement *xml, SourceCore &s)
{
SessionLoader::XMLToNode(xml->FirstChildElement("Mixing"), *s.group(View::MIXING) );
SessionLoader::XMLToNode(xml->FirstChildElement("Geometry"),*s.group(View::GEOMETRY) );
SessionLoader::XMLToNode(xml->FirstChildElement("Layer"), *s.group(View::LAYER) );
SessionLoader::XMLToNode(xml->FirstChildElement("Texture"), *s.group(View::TEXTURE) );
SessionLoader v(nullptr);
v.xmlCurrent_ = xml->FirstChildElement("ImageProcessing");
if (v.xmlCurrent_)
s.processingShader()->accept(v);
}
FrameBufferImage *SessionLoader::XMLToImage(const XMLElement *xml)
{
FrameBufferImage *i = nullptr;
if (xml != nullptr){
// if there is an Image mask stored
const XMLElement* imageNode = xml->FirstChildElement("Image");
if (imageNode) {
// get theoretical image size
int w = 0, h = 0;
imageNode->QueryIntAttribute("width", &w);
imageNode->QueryIntAttribute("height", &h);
// if there is an internal array of data
const XMLElement* array = imageNode->FirstChildElement("array");
if (array) {
// create a temporary jpeg with size of the array
FrameBufferImage::jpegBuffer jpgimg;
array->QueryUnsignedAttribute("len", &jpgimg.len);
// ok, we got a size of data to load
if (jpgimg.len>0) {
// allocate jpeg buffer
jpgimg.buffer = (unsigned char*) malloc(jpgimg.len);
// actual decoding of array
if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) ) {
// create and set the image from jpeg
i = new FrameBufferImage(jpgimg);
// failed if wrong size
if ( (w>0 && h>0) && (i->width != w || i->height != h) ) {
delete i;
i = nullptr;
}
}
// free temporary buffer
if (jpgimg.buffer)
free(jpgimg.buffer);
}
}
}
}
return i;
}
void SessionLoader::visit(Node &n)
{
XMLToNode(xmlCurrent_, n);
@@ -331,10 +658,11 @@ void SessionLoader::visit(Node &n)
void SessionLoader::visit(MediaPlayer &n)
{
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
uint64_t id__ = -1;
mediaplayerNode->QueryUnsigned64Attribute("id", &id__);
if (mediaplayerNode) {
uint64_t id__ = -1;
mediaplayerNode->QueryUnsigned64Attribute("id", &id__);
// timeline
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
if (timelineelement) {
@@ -371,6 +699,14 @@ void SessionLoader::visit(MediaPlayer &n)
mediaplayerNode->QueryIntAttribute("loop", &loop);
n.setLoop( (MediaPlayer::LoopMode) loop);
bool gpudisable = false;
mediaplayerNode->QueryBoolAttribute("software_decoding", &gpudisable);
n.setSoftwareDecodingForced(gpudisable);
bool rewind_on_disabled = false;
mediaplayerNode->QueryBoolAttribute("rewind_on_disabled", &rewind_on_disabled);
n.setRewindOnDisabled(rewind_on_disabled);
bool play = true;
mediaplayerNode->QueryBoolAttribute("play", &play);
n.play(play);
@@ -472,46 +808,46 @@ void SessionLoader::visit (Source& s)
xmlCurrent_ = sourceNode->FirstChildElement("Layer");
if (xmlCurrent_) s.groupNode(View::LAYER)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Appearance");
if (xmlCurrent_) s.groupNode(View::APPEARANCE)->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Texture");
if (xmlCurrent_) {
s.groupNode(View::TEXTURE)->accept(*this);
bool m = true;
xmlCurrent_->QueryBoolAttribute("mirrored", &m);
s.setTextureMirrored(m);
}
xmlCurrent_ = sourceNode->FirstChildElement("Blending");
if (xmlCurrent_) s.blendingShader()->accept(*this);
if (xmlCurrent_)
s.blendingShader()->accept(*this);
xmlCurrent_ = sourceNode->FirstChildElement("Mask");
if (xmlCurrent_) {
// read the mask shader attributes
s.maskShader()->accept(*this);
// if there is an Image mask stored
XMLElement* imageNode = xmlCurrent_->FirstChildElement("Image");
if (imageNode) {
// if there is an internal array of data
XMLElement* array = imageNode->FirstChildElement("array");
if (array) {
// create a temporary jpeg with size of the array
FrameBufferImage::jpegBuffer jpgimg;
array->QueryUnsignedAttribute("len", &jpgimg.len);
// ok, we got a size of data to load
if (jpgimg.len>0) {
// allocate jpeg buffer
jpgimg.buffer = (unsigned char*) malloc(jpgimg.len);
// actual decoding of array
if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) )
// create and set the image from jpeg
s.setMask(new FrameBufferImage(jpgimg));
// free temporary buffer
if (jpgimg.buffer)
free(jpgimg.buffer);
}
}
}
// set the mask from jpeg
s.setMask( SessionLoader::XMLToImage(xmlCurrent_) );
}
xmlCurrent_ = sourceNode->FirstChildElement("ImageProcessing");
if (xmlCurrent_) {
bool on = xmlCurrent_->BoolAttribute("enabled", true);
uint64_t id__ = 0;
xmlCurrent_->QueryUnsigned64Attribute("follow", &id__);
s.processingShader()->accept(*this);
s.setImageProcessingEnabled(on);
s.processingshader_link_.connect(id__, session_);
}
xmlCurrent_ = sourceNode->FirstChildElement("MixingGroup");
if (xmlCurrent_) {
SourceIdList idlist;
XMLElement* mixingSourceNode = xmlCurrent_->FirstChildElement("source");
for ( ; mixingSourceNode ; mixingSourceNode = mixingSourceNode->NextSiblingElement()) {
uint64_t id__ = 0;
mixingSourceNode->QueryUnsigned64Attribute("id", &id__);
idlist.push_back(id__);
}
groups_sources_id_.push_back(idlist);
}
// restore current
@@ -521,12 +857,27 @@ void SessionLoader::visit (Source& s)
void SessionLoader::visit (MediaSource& s)
{
// set uri
XMLElement* uriNode = xmlCurrent_->FirstChildElement("uri");
if (uriNode) {
std::string uri = std::string ( uriNode->GetText() );
// load only new files
if ( uri != s.path() )
s.setPath(uri);
XMLElement* pathNode = xmlCurrent_->FirstChildElement("uri"); // TODO change to "path" but keep backward compatibility
if (pathNode) {
const char * text = pathNode->GetText();
if (text) {
std::string path(text);
// load only new files
if ( path.compare(s.path()) != 0 ) {
if ( !SystemToolkit::file_exists(path)){
const char * relative;
if ( pathNode->QueryStringAttribute("relative", &relative) == XML_SUCCESS) {
std::string rel = SystemToolkit::path_absolute_from_path(std::string( relative ), sessionFilePath_);
Log::Info("File %s not found; Trying %s instead.", path.c_str(), rel.c_str());
path = rel;
}
}
s.setPath(path);
}
}
// ensures the source is initialized even if no valid path is given
else
s.setPath("");
}
// set config media player
@@ -535,15 +886,31 @@ void SessionLoader::visit (MediaSource& s)
void SessionLoader::visit (SessionFileSource& s)
{
// set fading
float f = 0.f;
xmlCurrent_->QueryFloatAttribute("fading", &f);
s.session()->setFading(f);
// set uri
XMLElement* pathNode = xmlCurrent_->FirstChildElement("path");
if (pathNode) {
std::string path = std::string ( pathNode->GetText() );
// load only new files
if ( path != s.path() )
s.load(path, recursion_ + 1);
const char * text = pathNode->GetText();
if (text) {
std::string path(text);
// load only new files
if ( path != s.path() ) {
if ( !SystemToolkit::file_exists(path)){
const char * relative;
if ( pathNode->QueryStringAttribute("relative", &relative) == XML_SUCCESS) {
std::string rel = SystemToolkit::path_absolute_from_path(std::string( relative ), sessionFilePath_);
Log::Info("File %s not found; Trying %s instead.", path.c_str(), rel.c_str());
path = rel;
}
}
s.load(path, recursion_ + 1);
}
}
}
}
void SessionLoader::visit (SessionGroupSource& s)
@@ -554,9 +921,12 @@ void SessionLoader::visit (SessionGroupSource& s)
// get the inside session
XMLElement* sessionGroupNode = xmlCurrent_->FirstChildElement("Session");
if (sessionGroupNode) {
// load session inside group
SessionLoader grouploader( s.session(), recursion_ + 1 );
grouploader.load( sessionGroupNode );
// only parse if newly created
if (s.session()->empty()) {
// load session inside group
SessionLoader grouploader( s.session(), recursion_ + 1 );
grouploader.load( sessionGroupNode );
}
}
}
@@ -599,4 +969,65 @@ void SessionLoader::visit (NetworkSource& s)
}
void SessionLoader::visit (MultiFileSource& s)
{
XMLElement* seq = xmlCurrent_->FirstChildElement("Sequence");
if (seq) {
MultiFileSequence sequence;
const char *text = seq->GetText();
if (text) {
sequence.location = std::string (text);
// fix path if absolute path is not found
std::string folder = SystemToolkit::path_filename(sequence.location);
std::string dir = SystemToolkit::path_directory(folder);
if ( dir.empty() ){
const char * relative;
if ( seq->QueryStringAttribute("relative", &relative) == XML_SUCCESS) {
std::string rel = SystemToolkit::path_absolute_from_path(std::string(relative), sessionFilePath_);
Log::Info("Folder %s not found; Trying %s instead.", folder.c_str(), rel.c_str());
sequence.location = rel;
}
}
// set sequence parameters
seq->QueryIntAttribute("min", &sequence.min);
seq->QueryIntAttribute("max", &sequence.max);
seq->QueryUnsignedAttribute("width", &sequence.width);
seq->QueryUnsignedAttribute("height", &sequence.height);
const char *codec = seq->Attribute("codec");
if (codec)
sequence.codec = std::string(codec);
uint fps = 0;
seq->QueryUnsignedAttribute("fps", &fps);
// different sequence
if ( sequence != s.sequence() ) {
s.setSequence( sequence, fps);
}
// same sequence, different framerate
else if ( fps != s.framerate() ) {
s.setFramerate( fps );
}
int begin = -1;
seq->QueryIntAttribute("begin", &begin);
int end = INT_MAX;
seq->QueryIntAttribute("end", &end);
if ( begin != s.begin() || end != s.end() )
s.setRange(begin, end);
bool loop = true;
seq->QueryBoolAttribute("loop", &loop);
if ( loop != s.loop() )
s.setLoop(loop);
}
}
}

View File

@@ -1,41 +1,46 @@
#ifndef SESSIONCREATOR_H
#define SESSIONCREATOR_H
#include <list>
#include "Visitor.h"
#include <map>
#include <tinyxml2.h>
#include "Visitor.h"
#include "SourceList.h"
class Session;
class FrameBufferImage;
class SessionLoader : public Visitor {
SessionLoader();
public:
SessionLoader(Session *session, int recursion = 0);
inline Session *session() const { return session_; }
void load(tinyxml2::XMLElement *sessionNode);
inline std::list<uint64_t> getIdList() const { return sources_id_; }
std::map< uint64_t, Source* > getSources() const;
std::list< SourceList > getMixingGroups() const;
Source *createSource(tinyxml2::XMLElement *sourceNode, bool clone_duplicates = true);
typedef enum {
CLONE,
DUPLICATE
} Mode;
Source *createSource(tinyxml2::XMLElement *sourceNode, Mode mode = CLONE);
static bool isClipboard(const std::string &clipboard);
static tinyxml2::XMLElement* firstSourceElement(const std::string &clipboard, tinyxml2::XMLDocument &xmlDoc);
static void applyImageProcessing(const Source &s, const std::string &clipboard);
//TODO static void applyMask(const Source &s, const std::string &clipboard);
// Elements of Scene
void visit (Node& n) override;
void visit (Scene&) override {}
void visit (Group&) override {}
void visit (Switch&) override {}
void visit (Primitive&) override {}
void visit (Surface&) override {}
void visit (ImageSurface&) override {}
void visit (MediaSurface&) override {}
void visit (FrameBufferSurface&) override {}
void visit (LineStrip&) override {}
void visit (LineSquare&) override {}
void visit (LineCircle&) override {}
void visit (Mesh&) override {}
// Elements with attributes
void visit (MediaPlayer& n) override;
@@ -53,14 +58,36 @@ public:
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
static void XMLToNode(const tinyxml2::XMLElement *xml, Node &n);
static void XMLToSourcecore(tinyxml2::XMLElement *xml, SourceCore &s);
static FrameBufferImage *XMLToImage(const tinyxml2::XMLElement *xml);
protected:
tinyxml2::XMLElement *xmlCurrent_;
// result created session
Session *session_;
std::list<uint64_t> sources_id_;
std::string sessionFilePath_;
// parsing current xml
tinyxml2::XMLElement *xmlCurrent_;
// level of loading recursion
int recursion_;
// map of correspondance from xml source id (key) to new source pointer (value)
std::map< uint64_t, Source* > sources_id_;
// list of groups (lists of xml source id)
std::list< SourceIdList > groups_sources_id_;
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
};
struct SessionInformation {
std::string description;
FrameBufferImage *thumbnail;
bool user_thumbnail_;
SessionInformation() {
description = "";
thumbnail = nullptr;
user_thumbnail_ = false;
}
};
class SessionCreator : public SessionLoader {
@@ -68,13 +95,16 @@ class SessionCreator : public SessionLoader {
tinyxml2::XMLDocument xmlDoc_;
void loadConfig(tinyxml2::XMLElement *viewsNode);
void loadNotes(tinyxml2::XMLElement *notesNode);
void loadPlayGroups(tinyxml2::XMLElement *playlistsNode);
void loadSnapshots(tinyxml2::XMLElement *snapshotNode);
public:
SessionCreator(int recursion = 0);
void load(const std::string& filename);
static std::string info(const std::string& filename);
static SessionInformation info(const std::string& filename);
};
#endif // SESSIONCREATOR_H

111
SessionParser.cpp Normal file
View File

@@ -0,0 +1,111 @@
#include "SessionParser.h"
using namespace tinyxml2;
#include "SystemToolkit.h"
#include "tinyxml2Toolkit.h"
SessionParser::SessionParser()
{
}
bool SessionParser::open(const std::string &filename)
{
// if the file exists
if (filename.empty() || !SystemToolkit::file_exists(filename))
return false;
// try to load the file
xmlDoc_.Clear();
XMLError eResult = xmlDoc_.LoadFile(filename.c_str());
// error
if ( XMLResultError(eResult, false) )
return false;
filename_ = filename;
return true;
}
bool SessionParser::save()
{
if (filename_.empty())
return false;
// save file to disk
return ( XMLSaveDoc(&xmlDoc_, filename_) );
}
std::map< uint64_t, std::pair<std::string, bool> > SessionParser::pathList() const
{
std::map< uint64_t, std::pair<std::string, bool> > paths;
// fill path list
const XMLElement *session = xmlDoc_.FirstChildElement("Session");
if (session != nullptr ) {
const XMLElement *sourceNode = session->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
// get id
uint64_t sid = 0;
sourceNode->QueryUnsigned64Attribute("id", &sid);
// get path
const XMLElement* pathNode = nullptr;
pathNode = sourceNode->FirstChildElement("uri");
if (!pathNode)
pathNode = sourceNode->FirstChildElement("path");
if (!pathNode)
pathNode = sourceNode->FirstChildElement("Sequence");
if (pathNode) {
const char *text = pathNode->GetText();
if (text) {
bool exists = SystemToolkit::file_exists(text);
paths[sid] = std::pair<std::string, bool>(std::string(text), exists);
}
}
}
}
// return path list
return paths;
}
void SessionParser::replacePath(uint64_t id, const std::string &path)
{
XMLElement *session = xmlDoc_.FirstChildElement("Session");
if (session != nullptr ) {
XMLElement *sourceNode = session->FirstChildElement("Source");
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
{
// get id
uint64_t sid = 0;
sourceNode->QueryUnsigned64Attribute("id", &sid);
if (sid == id) {
// get path
XMLElement* pathNode = nullptr;
pathNode = sourceNode->FirstChildElement("uri");
if (!pathNode)
pathNode = sourceNode->FirstChildElement("path");
if (!pathNode)
pathNode = sourceNode->FirstChildElement("Sequence");
if (pathNode) {
XMLText *text = xmlDoc_.NewText( path.c_str() );
pathNode->InsertEndChild( text );
}
break;
}
}
}
}

40
SessionParser.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef SESSIONPARSER_H
#define SESSIONPARSER_H
#include <map>
#include <string>
#include <tinyxml2.h>
class Session;
//struct SessionInformation {
// std::string description;
// FrameBufferImage *thumbnail;
// bool user_thumbnail_;
// SessionInformation() {
// description = "";
// thumbnail = nullptr;
// user_thumbnail_ = false;
// }
//};
class SessionParser
{
public:
SessionParser();
bool open(const std::string& filename);
bool save();
std::map<uint64_t, std::pair<std::string, bool> > pathList() const;
void replacePath(uint64_t id, const std::string &path);
// static SessionInformation info(const std::string& filename);
private:
tinyxml2::XMLDocument xmlDoc_;
std::string filename_;
};
#endif // SESSIONPARSER_H

View File

@@ -1,6 +1,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <thread>
#include <chrono>
#include <algorithm>
#include "SessionSource.h"
@@ -17,9 +18,8 @@
#include "Mixer.h"
SessionSource::SessionSource() : Source()
SessionSource::SessionSource(uint64_t id) : Source(id), failed_(false), timer_(0), paused_(false)
{
failed_ = false;
session_ = new Session;
}
@@ -38,8 +38,8 @@ Session *SessionSource::detach()
// work on a new session
session_ = new Session;
// make disabled
initialized_ = false;
// un-ready
ready_ = false;
// ask to delete me
failed_ = true;
@@ -62,12 +62,21 @@ uint SessionSource::texture() const
}
void SessionSource::setActive (bool on)
{
{
Source::setActive(on);
// change status of session (recursive change of internal sources)
if (session_ != nullptr)
if (session_) {
session_->setActive(active_);
// change visibility of active surface (show preview of session when inactive)
if (activesurface_) {
if (active_)
activesurface_->setTextureIndex(Resource::getTextureTransparent());
else
activesurface_->setTextureIndex(session_->frame()->texture());
}
}
}
void SessionSource::update(float dt)
@@ -76,8 +85,10 @@ void SessionSource::update(float dt)
return;
// update content
if (active_)
if (active_ && !paused_) {
session_->update(dt);
timer_ += guint64(dt * 1000.f) * GST_USECOND;
}
// delete a source which failed
if (session_->failedSource() != nullptr) {
@@ -90,8 +101,16 @@ void SessionSource::update(float dt)
Source::update(dt);
}
void SessionSource::replay ()
{
if (session_) {
for( SourceList::iterator it = session_->begin(); it != session_->end(); ++it)
(*it)->replay();
timer_ = 0;
}
}
SessionFileSource::SessionFileSource() : SessionSource(), path_("")
SessionFileSource::SessionFileSource(uint64_t id) : SessionSource(id), path_(""), initialized_(false), wait_for_sources_(false)
{
// specific node for transition view
groups_[View::TRANSITION]->visible_ = false;
@@ -125,7 +144,6 @@ SessionFileSource::SessionFileSource() : SessionSource(), path_("")
symbol_ = new Symbol(Symbol::SESSION, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
wait_for_sources_ = false;
}
void SessionFileSource::load(const std::string &p, uint recursion)
@@ -149,6 +167,10 @@ void SessionFileSource::load(const std::string &p, uint recursion)
sessionLoader_ = std::async(std::launch::async, Session::load, path_, recursion);
Log::Notify("Opening %s", p.c_str());
}
// will be ready after init and one frame rendered
initialized_ = false;
ready_ = false;
}
void SessionFileSource::init()
@@ -164,26 +186,18 @@ void SessionFileSource::init()
}
else {
session_->update(dt_);
if (wait_for_sources_) {
// force update of of all sources
active_ = true;
touch();
// check that every source is ready..
bool ready = true;
for (SourceList::iterator iter = session_->begin(); iter != session_->end(); iter++)
{
// interrupt if any source is NOT ready
if ( !(*iter)->ready() ){
ready = false;
break;
}
}
// update to draw framebuffer
session_->update(dt_);
// if all sources are ready, done with initialization!
if (ready) {
auto unintitializedsource = std::find_if_not(session_->begin(), session_->end(), Source::isInitialized);
if (unintitializedsource == session_->end()) {
// done init
wait_for_sources_ = false;
initialized_ = true;
@@ -224,7 +238,20 @@ void SessionFileSource::init()
overlays_[View::TRANSITION]->detach(loader);
delete loader;
// deep update to reorder
View::need_deep_update_++;
++View::need_deep_update_;
}
}
void SessionFileSource::render()
{
if ( !initialized_ )
init();
else {
// render the media player into frame buffer
renderbuffer_->begin();
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
ready_ = true;
}
}
@@ -236,7 +263,7 @@ void SessionFileSource::accept(Visitor& v)
}
SessionGroupSource::SessionGroupSource() : SessionSource(), resolution_(glm::vec3(0.f))
SessionGroupSource::SessionGroupSource(uint64_t id) : SessionSource(id), resolution_(glm::vec3(0.f))
{
// // redo frame for layers view
// frames_[View::LAYER]->clear();
@@ -288,10 +315,9 @@ void SessionGroupSource::init()
attach(renderbuffer);
// deep update to reorder
View::need_deep_update_++;
++View::need_deep_update_;
// done init
initialized_ = true;
Log::Info("Source Group (%d x %d).", int(renderbuffer->resolution().x), int(renderbuffer->resolution().y) );
}
}
@@ -317,19 +343,16 @@ void SessionGroupSource::accept(Visitor& v)
v.visit(*this);
}
RenderSource::RenderSource() : Source(), session_(nullptr)
RenderSource::RenderSource(uint64_t id) : Source(id), session_(nullptr)
{
// set symbol
symbol_ = new Symbol(Symbol::RENDER, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
bool RenderSource::failed() const
{
if (initialized_ && session_!=nullptr)
if ( renderbuffer_ != nullptr && session_ != nullptr )
return renderbuffer_->resolution() != session_->frame()->resolution();
return false;
@@ -359,10 +382,9 @@ void RenderSource::init()
attach(renderbuffer);
// deep update to reorder
View::need_deep_update_++;
++View::need_deep_update_;
// done init
initialized_ = true;
Log::Info("Source Render linked to session (%d x %d).", int(fb->resolution().x), int(fb->resolution().y) );
}
}
@@ -370,7 +392,7 @@ void RenderSource::init()
glm::vec3 RenderSource::resolution() const
{
if (initialized_)
if (renderbuffer_ != nullptr)
return renderbuffer_->resolution();
else if (session_ && session_->frame())
return session_->frame()->resolution();

View File

@@ -8,12 +8,17 @@
class SessionSource : public Source
{
public:
SessionSource();
SessionSource(uint64_t id = 0);
virtual ~SessionSource();
// implementation of source API
void update (float dt) override;
void setActive (bool on) override;
bool playing () const override { return !paused_; }
void play (bool on) override { paused_ = !on; }
bool playable () const override { return true; }
guint64 playtime () const override { return timer_; }
void replay () override;
bool failed () const override;
uint texture () const override;
@@ -24,15 +29,18 @@ protected:
Session *session_;
std::atomic<bool> failed_;
guint64 timer_;
bool paused_;
};
class SessionFileSource : public SessionSource
{
public:
SessionFileSource();
SessionFileSource(uint64_t id = 0);
// implementation of source API
void accept (Visitor& v) override;
void render() override;
// SessionFile Source specific interface
void load(const std::string &p = "", uint recursion = 0);
@@ -45,14 +53,15 @@ protected:
void init() override;
std::string path_;
std::atomic<bool> wait_for_sources_;
bool initialized_;
bool wait_for_sources_;
std::future<Session *> sessionLoader_;
};
class SessionGroupSource : public SessionSource
{
public:
SessionGroupSource();
SessionGroupSource(uint64_t id = 0);
// implementation of source API
void accept (Visitor& v) override;
@@ -63,25 +72,24 @@ public:
// import a source
bool import(Source *source);
// TODO import session entirely : bool import();
glm::ivec2 icon() const override { return glm::ivec2(10, 6); }
protected:
void init() override;
glm::vec3 resolution_;
};
class RenderSource : public Source
{
public:
RenderSource();
RenderSource(uint64_t id = 0);
// implementation of source API
bool playing () const override { return true; }
void play (bool) override {}
bool playable () const override { return false; }
bool failed () const override;
uint texture() const override;
void accept (Visitor& v) override;

View File

@@ -1,6 +1,7 @@
#include "SessionVisitor.h"
#include "Log.h"
#include "defines.h"
#include "Scene.h"
#include "Decorations.h"
#include "Source.h"
@@ -10,27 +11,185 @@
#include "PatternSource.h"
#include "DeviceSource.h"
#include "NetworkSource.h"
#include "MultiFileSource.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "MediaPlayer.h"
#include "MixingGroup.h"
#include "SystemToolkit.h"
#include "ActionManager.h"
#include <iostream>
#include <locale>
#include <tinyxml2.h>
using namespace tinyxml2;
bool SessionVisitor::saveSession(const std::string& filename, Session *session)
{
// impose C locale
setlocale(LC_ALL, "C");
// creation of XML doc
XMLDocument xmlDoc;
XMLElement *rootnode = xmlDoc.NewElement(APP_NAME);
rootnode->SetAttribute("major", XML_VERSION_MAJOR);
rootnode->SetAttribute("minor", XML_VERSION_MINOR);
rootnode->SetAttribute("size", session->numSource());
rootnode->SetAttribute("date", SystemToolkit::date_time_string().c_str());
rootnode->SetAttribute("resolution", session->frame()->info().c_str());
xmlDoc.InsertEndChild(rootnode);
// 1. list of sources
XMLElement *sessionNode = xmlDoc.NewElement("Session");
xmlDoc.InsertEndChild(sessionNode);
SessionVisitor sv(&xmlDoc, sessionNode);
sv.sessionFilePath_ = SystemToolkit::path_filename(filename);
for (auto iter = session->begin(); iter != session->end(); ++iter, sv.setRoot(sessionNode) )
// source visitor
(*iter)->accept(sv);
// save the thumbnail
FrameBufferImage *thumbnail = session->thumbnail();
if (thumbnail != nullptr && thumbnail->width > 0 && thumbnail->height > 0) {
XMLElement *thumbnailelement = xmlDoc.NewElement("Thumbnail");
XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc);
if (imageelement) {
sessionNode->InsertEndChild(thumbnailelement);
thumbnailelement->InsertEndChild(imageelement);
}
}
// if no thumbnail is set by user, capture thumbnail now
else {
thumbnail = session->renderThumbnail();
if (thumbnail) {
XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc);
if (imageelement)
sessionNode->InsertEndChild(imageelement);
delete thumbnail;
}
}
// 2. config of views
saveConfig( &xmlDoc, session );
// 3. snapshots
saveSnapshots( &xmlDoc, session );
// 4. optional notes
saveNotes( &xmlDoc, session );
// 5. optional playlists
savePlayGroups( &xmlDoc, session );
// save file to disk
return ( XMLSaveDoc(&xmlDoc, filename) );
}
void SessionVisitor::saveConfig(tinyxml2::XMLDocument *doc, Session *session)
{
if (doc != nullptr && session != nullptr)
{
XMLElement *views = doc->NewElement("Views");
XMLElement *mixing = doc->NewElement( "Mixing" );
mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), doc));
views->InsertEndChild(mixing);
XMLElement *geometry = doc->NewElement( "Geometry" );
geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), doc));
views->InsertEndChild(geometry);
XMLElement *layer = doc->NewElement( "Layer" );
layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), doc));
views->InsertEndChild(layer);
XMLElement *appearance = doc->NewElement( "Texture" );
appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::TEXTURE), doc));
views->InsertEndChild(appearance);
XMLElement *render = doc->NewElement( "Rendering" );
render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), doc));
views->InsertEndChild(render);
doc->InsertEndChild(views);
}
}
void SessionVisitor::saveSnapshots(tinyxml2::XMLDocument *doc, Session *session)
{
if (doc != nullptr && session != nullptr)
{
XMLElement *snapshots = doc->NewElement("Snapshots");
const XMLElement* N = session->snapshots()->xmlDoc_->FirstChildElement();
for( ; N ; N=N->NextSiblingElement())
snapshots->InsertEndChild( N->DeepClone( doc ));
doc->InsertEndChild(snapshots);
}
}
void SessionVisitor::saveNotes(tinyxml2::XMLDocument *doc, Session *session)
{
if (doc != nullptr && session != nullptr)
{
XMLElement *notes = doc->NewElement("Notes");
for (auto nit = session->beginNotes(); nit != session->endNotes(); ++nit) {
XMLElement *note = doc->NewElement( "Note" );
note->SetAttribute("large", (*nit).large );
note->SetAttribute("stick", (*nit).stick );
XMLElement *pos = doc->NewElement("pos");
pos->InsertEndChild( XMLElementFromGLM(doc, (*nit).pos) );
note->InsertEndChild(pos);
XMLElement *size = doc->NewElement("size");
size->InsertEndChild( XMLElementFromGLM(doc, (*nit).size) );
note->InsertEndChild(size);
XMLElement *content = doc->NewElement("text");
XMLText *text = doc->NewText( (*nit).text.c_str() );
content->InsertEndChild( text );
note->InsertEndChild(content);
notes->InsertEndChild(note);
}
doc->InsertEndChild(notes);
}
}
void SessionVisitor::savePlayGroups(tinyxml2::XMLDocument *doc, Session *session)
{
if (doc != nullptr && session != nullptr)
{
XMLElement *playlistNode = doc->NewElement("PlayGroups");
std::vector<SourceIdList> pl = session->getPlayGroups();
for (auto plit = pl.begin(); plit != pl.end(); ++plit) {
XMLElement *list = doc->NewElement("PlayGroup");
playlistNode->InsertEndChild(list);
for (auto id = plit->begin(); id != plit->end(); ++id) {
XMLElement *sour = doc->NewElement("source");
sour->SetAttribute("id", *id);
list->InsertEndChild(sour);
}
}
doc->InsertEndChild(playlistNode);
}
}
SessionVisitor::SessionVisitor(tinyxml2::XMLDocument *doc,
tinyxml2::XMLElement *root,
bool recursive) : Visitor(), recursive_(recursive), xmlCurrent_(root)
{
// impose C locale
setlocale(LC_ALL, "C");
if (doc == nullptr)
xmlDoc_ = new XMLDocument;
else
xmlDoc_ = doc;
}
tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *doc)
XMLElement *SessionVisitor::NodeToXML(const Node &n, XMLDocument *doc)
{
XMLElement *newelement = doc->NewElement("Node");
newelement->SetAttribute("visible", n.visible_);
@@ -55,6 +214,31 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *
return newelement;
}
XMLElement *SessionVisitor::ImageToXML(const FrameBufferImage *img, XMLDocument *doc)
{
XMLElement *imageelement = nullptr;
if (img != nullptr) {
// get the jpeg encoded buffer
FrameBufferImage::jpegBuffer jpgimg = img->getJpeg();
if (jpgimg.buffer != nullptr) {
// fill the xml array with jpeg buffer
XMLElement *array = XMLElementEncodeArray(doc, jpgimg.buffer, jpgimg.len);
// free the buffer
free(jpgimg.buffer);
// if we could create the array
if (array) {
// create an Image node to store the mask image
imageelement = doc->NewElement("Image");
imageelement->SetAttribute("width", img->width);
imageelement->SetAttribute("height", img->height);
imageelement->InsertEndChild(array);
}
}
}
return imageelement;
}
void SessionVisitor::visit(Node &n)
{
XMLElement *newelement = NodeToXML(n, xmlDoc_);
@@ -74,7 +258,7 @@ void SessionVisitor::visit(Group &n)
if (recursive_) {
// loop over members of a group
XMLElement *group = xmlCurrent_;
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
(*node)->accept(*this);
// revert to group as current
xmlCurrent_ = group;
@@ -91,7 +275,7 @@ void SessionVisitor::visit(Switch &n)
if (recursive_) {
// loop over members of the group
XMLElement *group = xmlCurrent_;
for(uint i = 0; i < n.numChildren(); i++) {
for(uint i = 0; i < n.numChildren(); ++i) {
n.child(i)->accept(*this);
// revert to group as current
xmlCurrent_ = group;
@@ -140,14 +324,6 @@ void SessionVisitor::visit(FrameBufferSurface &)
xmlCurrent_->SetAttribute("type", "FrameBufferSurface");
}
void SessionVisitor::visit(MediaSurface &n)
{
// Node of a different type
xmlCurrent_->SetAttribute("type", "MediaSurface");
n.mediaPlayer()->accept(*this);
}
void SessionVisitor::visit(MediaPlayer &n)
{
XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer");
@@ -157,6 +333,8 @@ void SessionVisitor::visit(MediaPlayer &n)
newelement->SetAttribute("play", n.isPlaying());
newelement->SetAttribute("loop", (int) n.loop());
newelement->SetAttribute("speed", n.playSpeed());
newelement->SetAttribute("software_decoding", n.softwareDecodingForced());
newelement->SetAttribute("rewind_on_disabled", n.rewindOnDisabled());
// timeline
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
@@ -164,7 +342,7 @@ void SessionVisitor::visit(MediaPlayer &n)
// gaps in timeline
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");
TimeIntervalSet gaps = n.timeline()->gaps();
for( auto it = gaps.begin(); it!= gaps.end(); it++) {
for( auto it = gaps.begin(); it!= gaps.end(); ++it) {
XMLElement *g = xmlDoc_->NewElement("Interval");
g->SetAttribute("begin", (*it).begin);
g->SetAttribute("end", (*it).end);
@@ -266,24 +444,14 @@ void SessionVisitor::visit(LineStrip &n)
xmlCurrent_->SetAttribute("type", "LineStrip");
XMLElement *points_node = xmlDoc_->NewElement("points");
std::vector<glm::vec3> points = n.getPoints();
for(size_t i = 0; i < points.size(); ++i)
std::vector<glm::vec2> path = n.path();
for(size_t i = 0; i < path.size(); ++i)
{
XMLElement *p = XMLElementFromGLM(xmlDoc_, points[i]);
XMLElement *p = XMLElementFromGLM(xmlDoc_, path[i]);
p->SetAttribute("index", (int) i);
points_node->InsertEndChild(p);
}
xmlCurrent_->InsertEndChild(points_node);
XMLElement *colors_node = xmlDoc_->NewElement("colors");
std::vector<glm::vec4> colors = n.getColors();
for(size_t i = 0; i < colors.size(); ++i)
{
XMLElement *p = XMLElementFromGLM(xmlDoc_, colors[i]);
p->SetAttribute("index", (int) i);
colors_node->InsertEndChild(p);
}
xmlCurrent_->InsertEndChild(colors_node);
}
void SessionVisitor::visit(LineSquare &)
@@ -293,16 +461,6 @@ void SessionVisitor::visit(LineSquare &)
}
void SessionVisitor::visit(LineCircle &)
{
// Node of a different type
xmlCurrent_->SetAttribute("type", "LineCircle");
// XMLElement *color = xmlDoc_->NewElement("color");
// color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.getColor()) );
// xmlCurrent_->InsertEndChild(color);
}
void SessionVisitor::visit(Mesh &n)
{
// Node of a different type
@@ -363,9 +521,10 @@ void SessionVisitor::visit (Source& s)
sourceNode->InsertEndChild(xmlCurrent_);
s.groupNode(View::LAYER)->accept(*this);
xmlCurrent_ = xmlDoc_->NewElement( "Appearance" );
xmlCurrent_ = xmlDoc_->NewElement( "Texture" );
xmlCurrent_->SetAttribute("mirrored", s.textureMirrored() );
sourceNode->InsertEndChild(xmlCurrent_);
s.groupNode(View::APPEARANCE)->accept(*this);
s.groupNode(View::TEXTURE)->accept(*this);
xmlCurrent_ = xmlDoc_->NewElement( "Blending" );
sourceNode->InsertEndChild(xmlCurrent_);
@@ -377,31 +536,23 @@ void SessionVisitor::visit (Source& s)
// if we are saving a pain mask
if (s.maskShader()->mode == MaskShader::PAINT) {
// get the mask previously stored
FrameBufferImage *img = s.getMask();
if (img != nullptr) {
// get the jpeg encoded buffer
FrameBufferImage::jpegBuffer jpgimg = img->getJpeg();
if (jpgimg.buffer != nullptr) {
// fill the xml array with jpeg buffer
XMLElement *array = XMLElementEncodeArray(xmlDoc_, jpgimg.buffer, jpgimg.len);
// free the buffer
free(jpgimg.buffer);
// if we could create the array
if (array) {
// create an Image node to store the mask image
XMLElement *imageelement = xmlDoc_->NewElement("Image");
imageelement->InsertEndChild(array);
xmlCurrent_->InsertEndChild(imageelement);
}
}
}
XMLElement *imageelement = SessionVisitor::ImageToXML(s.getMask(), xmlDoc_);
if (imageelement)
xmlCurrent_->InsertEndChild(imageelement);
}
xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" );
xmlCurrent_->SetAttribute("enabled", s.imageProcessingEnabled());
xmlCurrent_->SetAttribute("follow", s.processingshader_link_.id());
sourceNode->InsertEndChild(xmlCurrent_);
s.processingShader()->accept(*this);
if (s.mixingGroup()) {
xmlCurrent_ = xmlDoc_->NewElement( "MixingGroup" );
sourceNode->InsertEndChild(xmlCurrent_);
s.mixingGroup()->accept(*this);
}
xmlCurrent_ = sourceNode; // parent for next visits (other subtypes of Source)
}
@@ -414,17 +565,25 @@ void SessionVisitor::visit (MediaSource& s)
XMLText *text = xmlDoc_->NewText( s.path().c_str() );
uri->InsertEndChild( text );
if (!sessionFilePath_.empty())
uri->SetAttribute("relative", SystemToolkit::path_relative_to_path(s.path(), sessionFilePath_).c_str());
s.mediaplayer()->accept(*this);
}
void SessionVisitor::visit (SessionFileSource& s)
{
xmlCurrent_->SetAttribute("type", "SessionSource");
if (s.session() != nullptr)
xmlCurrent_->SetAttribute("fading", s.session()->fading());
XMLElement *path = xmlDoc_->NewElement("path");
xmlCurrent_->InsertEndChild(path);
XMLText *text = xmlDoc_->NewText( s.path().c_str() );
path->InsertEndChild( text );
if (!sessionFilePath_.empty())
path->SetAttribute("relative", SystemToolkit::path_relative_to_path(s.path(), sessionFilePath_).c_str());
}
void SessionVisitor::visit (SessionGroupSource& s)
@@ -433,11 +592,11 @@ void SessionVisitor::visit (SessionGroupSource& s)
Session *se = s.session();
XMLElement *rootgroup = xmlDoc_->NewElement("Session");
xmlCurrent_->InsertEndChild(rootgroup);
XMLElement *sessionNode = xmlDoc_->NewElement("Session");
xmlCurrent_->InsertEndChild(sessionNode);
for (auto iter = se->begin(); iter != se->end(); iter++){
setRoot(rootgroup);
for (auto iter = se->begin(); iter != se->end(); ++iter){
setRoot(sessionNode);
(*iter)->accept(*this);
}
@@ -453,6 +612,7 @@ void SessionVisitor::visit (CloneSource& s)
xmlCurrent_->SetAttribute("type", "CloneSource");
XMLElement *origin = xmlDoc_->NewElement("origin");
origin->SetAttribute("id", s.origin()->id());
xmlCurrent_->InsertEndChild(origin);
XMLText *text = xmlDoc_->NewText( s.origin()->name().c_str() );
origin->InsertEndChild( text );
@@ -461,11 +621,14 @@ void SessionVisitor::visit (CloneSource& s)
void SessionVisitor::visit (PatternSource& s)
{
xmlCurrent_->SetAttribute("type", "PatternSource");
xmlCurrent_->SetAttribute("pattern", s.pattern()->type() );
XMLElement *resolution = xmlDoc_->NewElement("resolution");
resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) );
xmlCurrent_->InsertEndChild(resolution);
if (s.pattern()) {
xmlCurrent_->SetAttribute("pattern", s.pattern()->type() );
XMLElement *resolution = xmlDoc_->NewElement("resolution");
resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) );
xmlCurrent_->InsertEndChild(resolution);
}
}
void SessionVisitor::visit (DeviceSource& s)
@@ -479,3 +642,127 @@ void SessionVisitor::visit (NetworkSource& s)
xmlCurrent_->SetAttribute("type", "NetworkSource");
xmlCurrent_->SetAttribute("connection", s.connection().c_str() );
}
void SessionVisitor::visit (MixingGroup& g)
{
xmlCurrent_->SetAttribute("size", g.size());
for (auto it = g.begin(); it != g.end(); ++it) {
XMLElement *sour = xmlDoc_->NewElement("source");
sour->SetAttribute("id", (*it)->id());
xmlCurrent_->InsertEndChild(sour);
}
}
void SessionVisitor::visit (MultiFileSource& s)
{
xmlCurrent_->SetAttribute("type", "MultiFileSource");
XMLElement *sequence = xmlDoc_->NewElement("Sequence");
// play properties
sequence->SetAttribute("fps", s.framerate());
sequence->SetAttribute("begin", s.begin());
sequence->SetAttribute("end", s.end());
sequence->SetAttribute("loop", s.loop());
// file sequence description
sequence->SetAttribute("min", s.sequence().min);
sequence->SetAttribute("max", s.sequence().max);
sequence->SetAttribute("width", s.sequence().width);
sequence->SetAttribute("height", s.sequence().height);
sequence->SetAttribute("codec", s.sequence().codec.c_str());
if (!sessionFilePath_.empty())
sequence->SetAttribute("relative", SystemToolkit::path_relative_to_path(s.sequence().location, sessionFilePath_).c_str());
XMLText *location = xmlDoc_->NewText( s.sequence().location.c_str() );
sequence->InsertEndChild( location );
xmlCurrent_->InsertEndChild(sequence);
}
std::string SessionVisitor::getClipboard(const SourceList &list)
{
std::string x = "";
if (!list.empty()) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
selectionNode->SetAttribute("size", (int) list.size());
xmlDoc.InsertEndChild(selectionNode);
// fill doc by visiting sources
SourceList selection_clones_;
SessionVisitor sv(&xmlDoc, selectionNode);
for (auto iter = list.begin(); iter != list.end(); ++iter, sv.setRoot(selectionNode) ){
// start with clones
CloneSource *clone = dynamic_cast<CloneSource *>(*iter);
if (clone)
(*iter)->accept(sv);
else
selection_clones_.push_back(*iter);
}
// add others in front
for (auto iter = selection_clones_.begin(); iter != selection_clones_.end(); ++iter, sv.setRoot(selectionNode) ){
(*iter)->accept(sv);
}
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
}
std::string SessionVisitor::getClipboard(Source * const s)
{
std::string x = "";
if (s != nullptr) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
selectionNode->SetAttribute("size", 1);
xmlDoc.InsertEndChild(selectionNode);
// visit source
SessionVisitor sv(&xmlDoc, selectionNode);
s->accept(sv);
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
}
std::string SessionVisitor::getClipboard(ImageProcessingShader * const s)
{
std::string x = "";
if (s != nullptr) {
// create xml doc and root node
tinyxml2::XMLDocument xmlDoc;
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
xmlDoc.InsertEndChild(selectionNode);
tinyxml2::XMLElement *imgprocNode = xmlDoc.NewElement( "ImageProcessing" );
selectionNode->InsertEndChild(imgprocNode);
// visit source
SessionVisitor sv(&xmlDoc, imgprocNode);
s->accept(sv);
// get compact string
tinyxml2::XMLPrinter xmlPrint(0, true);
xmlDoc.Print( &xmlPrint );
x = xmlPrint.CStr();
}
return x;
}

View File

@@ -3,43 +3,56 @@
#include "Visitor.h"
#include "tinyxml2Toolkit.h"
#include "SourceList.h"
class Session;
class FrameBufferImage;
class SessionVisitor : public Visitor {
bool recursive_;
tinyxml2::XMLDocument *xmlDoc_;
tinyxml2::XMLElement *xmlCurrent_;
std::string sessionFilePath_;
static void saveConfig(tinyxml2::XMLDocument *doc, Session *session);
static void saveSnapshots(tinyxml2::XMLDocument *doc, Session *session);
static void saveNotes(tinyxml2::XMLDocument *doc, Session *session);
static void savePlayGroups(tinyxml2::XMLDocument *doc, Session *session);
public:
SessionVisitor(tinyxml2::XMLDocument *doc = nullptr,
tinyxml2::XMLElement *root = nullptr,
bool recursive = false);
inline tinyxml2::XMLDocument *doc() const { return xmlDoc_; }
inline void setRoot(tinyxml2::XMLElement *root) { xmlCurrent_ = root; }
static bool saveSession(const std::string& filename, Session *session);
static std::string getClipboard(const SourceList &list);
static std::string getClipboard(Source * const s);
static std::string getClipboard(ImageProcessingShader * const s);
// Elements of Scene
void visit(Scene& n) override;
void visit(Node& n) override;
void visit(Group& n) override;
void visit(Switch& n) override;
void visit(Primitive& n) override;
void visit(Surface&) override;
void visit(ImageSurface& n) override;
void visit(MediaSurface& n) override;
void visit(FrameBufferSurface&) override;
void visit(LineStrip& n) override;
void visit(LineSquare&) override;
void visit(LineCircle&) override;
void visit(Mesh& n) override;
void visit(Frame& n) override;
void visit (Scene& n) override;
void visit (Node& n) override;
void visit (Group& n) override;
void visit (Switch& n) override;
void visit (Primitive& n) override;
void visit (Surface&) override;
void visit (ImageSurface& n) override;
void visit (FrameBufferSurface&) override;
void visit (LineStrip& n) override;
void visit (LineSquare&) override;
void visit (Mesh& n) override;
void visit (Frame& n) override;
// Elements with attributes
void visit(MediaPlayer& n) override;
void visit(Shader& n) override;
void visit(ImageShader& n) override;
void visit(MaskShader& n) override;
void visit(ImageProcessingShader& n) override;
void visit (MediaPlayer& n) override;
void visit (Shader& n) override;
void visit (ImageShader& n) override;
void visit (MaskShader& n) override;
void visit (ImageProcessingShader& n) override;
// Sources
void visit (Source& s) override;
@@ -51,8 +64,11 @@ public:
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (NetworkSource& s) override;
void visit (MixingGroup& s) override;
void visit (MultiFileSource& s) override;
static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc);
static tinyxml2::XMLElement *NodeToXML(const Node &n, tinyxml2::XMLDocument *doc);
static tinyxml2::XMLElement *ImageToXML(const FrameBufferImage *img, tinyxml2::XMLDocument *doc);
};
#endif // XMLVISITOR_H

View File

@@ -1,5 +1,6 @@
#include <algorithm>
#include <iostream>
#include <locale>
using namespace std;
#include <tinyxml2.h>
@@ -12,19 +13,23 @@ using namespace tinyxml2;
Settings::Application Settings::application;
static string settingsFilename = "";
string settingsFilename = "";
void Settings::Save()
{
// impose C locale for all app
setlocale(LC_ALL, "C");
XMLDocument xmlDoc;
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
xmlDoc.InsertFirstChild(pDec);
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
pRoot->SetAttribute("major", APP_VERSION_MAJOR);
pRoot->SetAttribute("minor", APP_VERSION_MINOR);
#ifdef VIMIX_VERSION_MAJOR
pRoot->SetAttribute("major", VIMIX_VERSION_MAJOR);
pRoot->SetAttribute("minor", VIMIX_VERSION_MINOR);
xmlDoc.InsertEndChild(pRoot);
#endif
string comment = "Settings for " + application.name;
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
@@ -34,13 +39,13 @@ void Settings::Save()
{
XMLElement *windowsNode = xmlDoc.NewElement( "Windows" );
for (int i = 0; i < application.windows.size(); i++)
for (int i = 0; i < application.windows.size(); ++i)
{
const Settings::WindowConfig& w = application.windows[i];
XMLElement *window = xmlDoc.NewElement( "Window" );
window->SetAttribute("id", i);
window->SetAttribute("name", w.name.c_str());
window->SetAttribute("id", i);
window->SetAttribute("x", w.x);
window->SetAttribute("y", w.y);
window->SetAttribute("w", w.w);
@@ -57,21 +62,25 @@ void Settings::Save()
XMLElement *applicationNode = xmlDoc.NewElement( "Application" );
applicationNode->SetAttribute("scale", application.scale);
applicationNode->SetAttribute("accent_color", application.accent_color);
applicationNode->SetAttribute("pannel_stick", application.pannel_stick);
applicationNode->SetAttribute("smooth_transition", application.smooth_transition);
applicationNode->SetAttribute("smooth_snapshot", application.smooth_snapshot);
applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor);
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
applicationNode->SetAttribute("accept_connections", application.accept_connections);
applicationNode->SetAttribute("pannel_history_mode", application.pannel_history_mode);
pRoot->InsertEndChild(applicationNode);
// Widgets
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
widgetsNode->SetAttribute("preview", application.widget.preview);
widgetsNode->SetAttribute("preview_view", application.widget.preview_view);
widgetsNode->SetAttribute("history", application.widget.history);
widgetsNode->SetAttribute("media_player", application.widget.media_player);
widgetsNode->SetAttribute("media_player_view", application.widget.media_player_view);
widgetsNode->SetAttribute("timeline_editmode", application.widget.timeline_editmode);
widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor);
widgetsNode->SetAttribute("stats", application.widget.stats);
widgetsNode->SetAttribute("stats_timer", application.widget.stats_timer);
widgetsNode->SetAttribute("stats_mode", application.widget.stats_mode);
widgetsNode->SetAttribute("stats_corner", application.widget.stats_corner);
widgetsNode->SetAttribute("logs", application.widget.logs);
widgetsNode->SetAttribute("toolbox", application.widget.toolbox);
@@ -92,11 +101,15 @@ void Settings::Save()
RecordNode->SetAttribute("path", application.record.path.c_str());
RecordNode->SetAttribute("profile", application.record.profile);
RecordNode->SetAttribute("timeout", application.record.timeout);
RecordNode->SetAttribute("delay", application.record.delay);
RecordNode->SetAttribute("resolution_mode", application.record.resolution_mode);
RecordNode->SetAttribute("framerate_mode", application.record.framerate_mode);
RecordNode->SetAttribute("buffering_mode", application.record.buffering_mode);
RecordNode->SetAttribute("priority_mode", application.record.priority_mode);
pRoot->InsertEndChild(RecordNode);
// Transition
XMLElement *TransitionNode = xmlDoc.NewElement( "Transition" );
TransitionNode->SetAttribute("auto_open", application.transition.auto_open);
TransitionNode->SetAttribute("hide_windows", application.transition.hide_windows);
TransitionNode->SetAttribute("cross_fade", application.transition.cross_fade);
TransitionNode->SetAttribute("duration", application.transition.duration);
@@ -139,19 +152,19 @@ void Settings::Save()
viewsNode->SetAttribute("workspace", application.current_workspace);
map<int, Settings::ViewConfig>::iterator iter;
for (iter=application.views.begin(); iter != application.views.end(); iter++)
for (iter=application.views.begin(); iter != application.views.end(); ++iter)
{
const Settings::ViewConfig& v = iter->second;
const Settings::ViewConfig& view_config = iter->second;
XMLElement *view = xmlDoc.NewElement( "View" );
view->SetAttribute("name", v.name.c_str());
view->SetAttribute("name", view_config.name.c_str());
view->SetAttribute("id", iter->first);
XMLElement *scale = xmlDoc.NewElement("default_scale");
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_scale) );
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_scale) );
view->InsertEndChild(scale);
XMLElement *translation = xmlDoc.NewElement("default_translation");
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_translation) );
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_translation) );
view->InsertEndChild(translation);
viewsNode->InsertEndChild(view);
@@ -164,13 +177,14 @@ void Settings::Save()
{
XMLElement *recent = xmlDoc.NewElement( "Recent" );
// recent session filenames
XMLElement *recentsession = xmlDoc.NewElement( "Session" );
recentsession->SetAttribute("path", application.recentSessions.path.c_str());
recentsession->SetAttribute("autoload", application.recentSessions.load_at_start);
recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit);
recentsession->SetAttribute("valid", application.recentSessions.front_is_valid);
for(auto it = application.recentSessions.filenames.begin();
it != application.recentSessions.filenames.end(); it++) {
for(auto it = application.recentSessions.filenames.cbegin();
it != application.recentSessions.filenames.cend(); ++it) {
XMLElement *fileNode = xmlDoc.NewElement("path");
XMLText *text = xmlDoc.NewText( (*it).c_str() );
fileNode->InsertEndChild( text );
@@ -178,9 +192,10 @@ void Settings::Save()
};
recent->InsertEndChild(recentsession);
// recent session folders
XMLElement *recentfolder = xmlDoc.NewElement( "Folder" );
for(auto it = application.recentFolders.filenames.begin();
it != application.recentFolders.filenames.end(); it++) {
for(auto it = application.recentFolders.filenames.cbegin();
it != application.recentFolders.filenames.cend(); ++it) {
XMLElement *fileNode = xmlDoc.NewElement("path");
XMLText *text = xmlDoc.NewText( (*it).c_str() );
fileNode->InsertEndChild( text );
@@ -188,10 +203,11 @@ void Settings::Save()
};
recent->InsertEndChild(recentfolder);
// recent media uri
XMLElement *recentmedia = xmlDoc.NewElement( "Import" );
recentmedia->SetAttribute("path", application.recentImport.path.c_str());
for(auto it = application.recentImport.filenames.begin();
it != application.recentImport.filenames.end(); it++) {
for(auto it = application.recentImport.filenames.cbegin();
it != application.recentImport.filenames.cend(); ++it) {
XMLElement *fileNode = xmlDoc.NewElement("path");
XMLText *text = xmlDoc.NewText( (*it).c_str() );
fileNode->InsertEndChild( text );
@@ -199,10 +215,21 @@ void Settings::Save()
}
recent->InsertEndChild(recentmedia);
// recent dialog path
XMLElement *recentdialogpath = xmlDoc.NewElement( "Dialog" );
for(auto it = application.dialogRecentFolder.cbegin();
it != application.dialogRecentFolder.cend(); ++it) {
XMLElement *pathNode = xmlDoc.NewElement("path");
pathNode->SetAttribute("label", (*it).first.c_str() );
XMLText *text = xmlDoc.NewText( (*it).second.c_str() );
pathNode->InsertEndChild( text );
recentdialogpath->InsertFirstChild(pathNode);
}
recent->InsertEndChild(recentdialogpath);
pRoot->InsertEndChild(recent);
}
// First save : create filename
if (settingsFilename.empty())
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
@@ -214,6 +241,9 @@ void Settings::Save()
void Settings::Load()
{
// impose C locale for all app
setlocale(LC_ALL, "C");
XMLDocument xmlDoc;
if (settingsFilename.empty())
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
@@ -233,33 +263,39 @@ void Settings::Load()
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
return;
#ifdef VIMIX_VERSION_MAJOR
// cancel on different version
int version_major = -1, version_minor = -1;
pRoot->QueryIntAttribute("major", &version_major);
pRoot->QueryIntAttribute("minor", &version_minor);
if (version_major != APP_VERSION_MAJOR || version_minor != APP_VERSION_MINOR)
if (version_major != VIMIX_VERSION_MAJOR || version_minor != VIMIX_VERSION_MINOR)
return;
#endif
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
if (applicationNode != nullptr) {
applicationNode->QueryFloatAttribute("scale", &application.scale);
applicationNode->QueryIntAttribute("accent_color", &application.accent_color);
applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick);
applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition);
applicationNode->QueryBoolAttribute("smooth_snapshot", &application.smooth_snapshot);
applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor);
applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view);
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_history_mode);
}
// Widgets
XMLElement * widgetsNode = pRoot->FirstChildElement("Widgets");
if (widgetsNode != nullptr) {
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
widgetsNode->QueryIntAttribute("preview_view", &application.widget.preview_view);
widgetsNode->QueryBoolAttribute("history", &application.widget.history);
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
widgetsNode->QueryIntAttribute("media_player_view", &application.widget.media_player_view);
widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.timeline_editmode);
widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor);
widgetsNode->QueryBoolAttribute("stats", &application.widget.stats);
widgetsNode->QueryBoolAttribute("stats_timer", &application.widget.stats_timer);
widgetsNode->QueryIntAttribute("stats_mode", &application.widget.stats_mode);
widgetsNode->QueryIntAttribute("stats_corner", &application.widget.stats_corner);
widgetsNode->QueryBoolAttribute("logs", &application.widget.logs);
widgetsNode->QueryBoolAttribute("toolbox", &application.widget.toolbox);
@@ -280,7 +316,12 @@ void Settings::Load()
XMLElement * recordnode = pRoot->FirstChildElement("Record");
if (recordnode != nullptr) {
recordnode->QueryIntAttribute("profile", &application.record.profile);
recordnode->QueryFloatAttribute("timeout", &application.record.timeout);
recordnode->QueryUnsignedAttribute("timeout", &application.record.timeout);
recordnode->QueryIntAttribute("delay", &application.record.delay);
recordnode->QueryIntAttribute("resolution_mode", &application.record.resolution_mode);
recordnode->QueryIntAttribute("framerate_mode", &application.record.framerate_mode);
recordnode->QueryIntAttribute("buffering_mode", &application.record.buffering_mode);
recordnode->QueryIntAttribute("priority_mode", &application.record.priority_mode);
const char *path_ = recordnode->Attribute("path");
if (path_)
@@ -301,7 +342,6 @@ void Settings::Load()
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
if (transitionnode != nullptr) {
transitionnode->QueryBoolAttribute("hide_windows", &application.transition.hide_windows);
transitionnode->QueryBoolAttribute("auto_open", &application.transition.auto_open);
transitionnode->QueryBoolAttribute("cross_fade", &application.transition.cross_fade);
transitionnode->QueryFloatAttribute("duration", &application.transition.duration);
transitionnode->QueryIntAttribute("profile", &application.transition.profile);
@@ -316,16 +356,18 @@ void Settings::Load()
for( ; windowNode ; windowNode=windowNode->NextSiblingElement())
{
Settings::WindowConfig w;
w.name = std::string(windowNode->Attribute("name"));
windowNode->QueryIntAttribute("x", &w.x); // If this fails, original value is left as-is
windowNode->QueryIntAttribute("y", &w.y);
windowNode->QueryIntAttribute("w", &w.w);
windowNode->QueryIntAttribute("h", &w.h);
windowNode->QueryBoolAttribute("f", &w.fullscreen);
w.monitor = std::string(windowNode->Attribute("m"));
const char *text = windowNode->Attribute("m");
if (text)
w.monitor = std::string(text);
int i = 0;
windowNode->QueryIntAttribute("id", &i);
w.name = application.windows[i].name; // keep only original name
application.windows[i] = w;
}
}
@@ -339,10 +381,10 @@ void Settings::Load()
// bloc views
{
application.views.clear(); // trash existing list
XMLElement * pElement = pRoot->FirstChildElement("Views");
if (pElement)
{
application.views.clear(); // trash existing list
pElement->QueryIntAttribute("current", &application.current_view);
pElement->QueryIntAttribute("workspace", &application.current_workspace);
@@ -439,11 +481,59 @@ void Settings::Load()
application.recentImport.push( std::string (p) );
}
}
// recent dialog path
XMLElement * pDialog = pElement->FirstChildElement("Dialog");
if (pDialog)
{
application.dialogRecentFolder.clear();
XMLElement* path = pDialog->FirstChildElement("path");
for( ; path ; path = path->NextSiblingElement())
{
const char *l = path->Attribute("label");
const char *p = path->GetText();
if (l && p)
application.dialogRecentFolder[ std::string(l)] = std::string (p);
}
}
}
}
}
void Settings::History::push(const string &filename)
{
if (filename.empty()) {
front_is_valid = false;
return;
}
filenames.remove(filename);
filenames.push_front(filename);
if (filenames.size() > MAX_RECENT_HISTORY)
filenames.pop_back();
front_is_valid = true;
changed = true;
}
void Settings::History::remove(const std::string &filename)
{
if (filename.empty())
return;
if (filenames.front() == filename)
front_is_valid = false;
filenames.remove(filename);
changed = true;
}
void Settings::History::validate()
{
for (auto fit = filenames.begin(); fit != filenames.end();) {
if ( SystemToolkit::file_exists( *fit ))
++fit;
else
fit = filenames.erase(fit);
}
}
void Settings::Lock()
{

View File

@@ -1,7 +1,9 @@
#ifndef __SETTINGS_H_
#define __SETTINGS_H_
#include "defines.h"
#ifdef __APPLE__
#include <sys/types.h>
#endif
#include <string>
#include <map>
@@ -9,32 +11,40 @@
#include <list>
#include <glm/glm.hpp>
#include "defines.h"
namespace Settings {
struct WidgetsConfig
{
bool stats;
int stats_corner;
bool stats_timer;
int stats_mode;
bool logs;
bool preview;
bool history;
int preview_view;
bool media_player;
bool media_player_view;
int media_player_view;
bool timeline_editmode;
bool shader_editor;
bool toolbox;
bool history;
bool help;
WidgetsConfig() {
stats = false;
stats_timer = false;
stats_mode = 0;
stats_corner = 1;
logs = false;
preview = false;
preview_view = -1;
history = false;
media_player = false;
media_player_view = true;
media_player_view = -1;
timeline_editmode = false;
shader_editor = false;
toolbox = false;
help = false;
}
};
@@ -62,17 +72,26 @@ struct ViewConfig
};
#define RECORD_MAX_TIMEOUT 1800.f
struct RecordConfig
{
std::string path;
int profile;
float timeout;
uint timeout;
int delay;
int resolution_mode;
int framerate_mode;
int buffering_mode;
int priority_mode;
RecordConfig() : path("") {
profile = 0;
timeout = RECORD_MAX_TIMEOUT;
delay = 0;
resolution_mode = 1;
framerate_mode = 1;
buffering_mode = 2;
priority_mode = 1;
}
};
@@ -84,44 +103,29 @@ struct History
bool front_is_valid;
bool load_at_start;
bool save_on_exit;
bool changed;
History() {
path = IMGUI_LABEL_RECENT_FILES;
front_is_valid = false;
load_at_start = false;
save_on_exit = false;
changed = false;
}
void push(const std::string &filename) {
if (filename.empty()) {
front_is_valid = false;
return;
}
filenames.remove(filename);
filenames.push_front(filename);
if (filenames.size() > MAX_RECENT_HISTORY)
filenames.pop_back();
front_is_valid = true;
}
void remove(const std::string &filename) {
if (filename.empty())
return;
if (filenames.front() == filename)
front_is_valid = false;
filenames.remove(filename);
}
void push(const std::string &filename);
void remove(const std::string &filename);
void validate();
};
struct TransitionConfig
{
bool cross_fade;
bool auto_open;
bool hide_windows;
float duration;
int profile;
TransitionConfig() {
cross_fade = true;
auto_open = true;
hide_windows = true;
duration = 1.f;
profile = 0;
@@ -130,6 +134,7 @@ struct TransitionConfig
struct RenderConfig
{
bool disabled;
bool blit;
int vsync;
int multisampling;
@@ -139,6 +144,7 @@ struct RenderConfig
bool gpu_decoding;
RenderConfig() {
disabled = false;
blit = false;
vsync = 1;
multisampling = 2;
@@ -176,14 +182,15 @@ struct Application
// Global settings Application interface
float scale;
int accent_color;
bool pannel_stick;
bool smooth_snapshot;
bool smooth_transition;
bool smooth_cursor;
bool action_history_follow_view;
int pannel_history_mode;
// connection settings
bool accept_connections;
// std::map<int, std::string> instance_names;
// Settings of widgets
WidgetsConfig widget;
@@ -215,23 +222,25 @@ struct Application
History recentSessions;
History recentFolders;
History recentImport;
std::map< std::string, std::string > dialogRecentFolder;
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
scale = 1.f;
accent_color = 0;
pannel_stick = false;
smooth_transition = true;
smooth_transition = false;
smooth_snapshot = false;
smooth_cursor = false;
action_history_follow_view = false;
accept_connections = false;
pannel_history_mode = 0;
current_view = 1;
current_workspace= 1;
brush = glm::vec3(0.5f, 0.1f, 0.f);
windows = std::vector<WindowConfig>(3);
windows[0].name = APP_NAME APP_TITLE;
windows[0].name = APP_TITLE;
windows[0].w = 1600;
windows[0].h = 900;
windows[1].name = APP_NAME " -- Output";
windows[1].name = "Output " APP_TITLE;
}
};

Some files were not shown because too many files have changed in this diff Show More