diff --git a/FrameBuffer.cpp b/FrameBuffer.cpp index 65e5eb7..deb5113 100644 --- a/FrameBuffer.cpp +++ b/FrameBuffer.cpp @@ -557,3 +557,30 @@ bool FrameBuffer::fill(FrameBufferImage *image) } +//void FrameBuffer::writePNG(const std::string &filename) +//{ +// // not ready +// if (!framebufferid_) +// return; + +// // create a temporary RGBA frame buffer at the resolution of cropped area +// int w = attrib_.viewport.x * projection_area_.x; +// int h = attrib_.viewport.y * projection_area_.y; +// FrameBuffer copy(w, h, FrameBuffer_alpha); + +// // create temporary RAM buffer to store the cropped RGBA +// uint8_t *buffer = new uint8_t[w * h * 4]; + +// // blit the frame buffer into the copy +// blit(©); + +// // get pixels of the copy into buffer +// glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // set buffer target readpixel +// copy.readPixels(buffer); + +// // save to file +// stbi_write_png(filename.c_str(), w, h, 4, buffer, w * 4); + +// // delete (copy is also deleted) +// delete[] buffer; +//} diff --git a/RenderingManager.cpp b/RenderingManager.cpp index 8349e50..a778c62 100644 --- a/RenderingManager.cpp +++ b/RenderingManager.cpp @@ -340,8 +340,7 @@ void Rendering::draw() // perform screenshot if requested if (request_screenshot_) { - // glfwMakeContextCurrent(main_window_); - screenshot_.captureGL(0, 0, main_.width(), main_.height()); + screenshot_.captureGL(main_.width(), main_.height()); request_screenshot_ = false; } diff --git a/Screenshot.cpp b/Screenshot.cpp index 02ae6f5..bb18f47 100644 --- a/Screenshot.cpp +++ b/Screenshot.cpp @@ -28,12 +28,14 @@ #include #include +#include "FrameBuffer.h" #include "Screenshot.h" Screenshot::Screenshot() { Width = Height = 0; + bpp = 3; Data = nullptr; Pbo = 0; Pbo_size = 0; @@ -53,12 +55,8 @@ bool Screenshot::isFull() return Pbo_full; } -void Screenshot::captureGL(int x, int y, int w, int h) +void Screenshot::capture() { - Width = w - x; - Height = h - y; - unsigned int size = Width * Height * 3; - // create BPO if (Pbo == 0) glGenBuffers(1, &Pbo); @@ -67,22 +65,57 @@ void Screenshot::captureGL(int x, int y, int w, int h) glBindBuffer(GL_PIXEL_PACK_BUFFER, Pbo); // init + unsigned int size = Width * Height * bpp; if (Pbo_size != size) { Pbo_size = size; - if (Data) free(Data); + if (Data) + free(Data); Data = (unsigned char*) malloc(Pbo_size); glBufferData(GL_PIXEL_PACK_BUFFER, Pbo_size, NULL, GL_STREAM_READ); } // screenshot to PBO (fast) glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, 0); - Pbo_full = true; + glReadPixels(0, 0, Width, Height, bpp > 3 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); // done + Pbo_full = true; glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } +void Screenshot::captureGL(int w, int h) +{ + bpp = 3; // screen capture of GL in RGB + Width = w; + Height = h; + + // do capture + capture(); +} + +void Screenshot::captureFramebuffer(FrameBuffer *fb) +{ + if (!fb) + return; + + bpp = 4; // capture of FBO in RGBA + Width = fb->width() * fb->projectionArea().x; + Height = fb->height() * fb->projectionArea().y; + + // blit the frame buffer into an RBBA copy of cropped size + FrameBuffer copy(Width, Height, FrameBuffer::FrameBuffer_alpha); + fb->blit(©); + + // get pixels from copy + glBindFramebuffer(GL_READ_FRAMEBUFFER, copy.opengl_id()); + + // do capture + capture(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + void Screenshot::save(std::string filename) { // is there something to save? @@ -108,35 +141,6 @@ void Screenshot::save(std::string filename) } -void Screenshot::RemoveAlpha() -{ - unsigned int* p = (unsigned int*)Data; - int n = Width * Height; - while (n-- > 0) - { - *p |= 0xFF000000; - p++; - } -} - -void Screenshot::FlipVertical() -{ - int comp = 4; - int stride = Width * comp; - unsigned char* line_tmp = new unsigned char[stride]; - unsigned char* line_a = (unsigned char*)Data; - unsigned char* line_b = (unsigned char*)Data + (stride * (Height - 1)); - while (line_a < line_b) - { - memcpy(line_tmp, line_a, stride); - memcpy(line_a, line_b, stride); - memcpy(line_b, line_tmp, stride); - line_a += stride; - line_b -= stride; - } - delete[] line_tmp; -} - // Thread to perform slow operation of saving to file void Screenshot::storeToFile(Screenshot *s, std::string filename) { @@ -148,8 +152,9 @@ void Screenshot::storeToFile(Screenshot *s, std::string filename) // got data to save ? if (s && s->Data) { // save file - stbi_flip_vertically_on_write(true); - stbi_write_png(filename.c_str(), s->Width, s->Height, 3, s->Data, s->Width * 3); + if (s->bpp < 4) // hack : inverse screen capture GL + stbi_flip_vertically_on_write(true); + stbi_write_png(filename.c_str(), s->Width, s->Height, s->bpp, s->Data, s->Width * s->bpp); } ScreenshotSavePending_ = false; } diff --git a/Screenshot.h b/Screenshot.h index 59375d5..2598681 100644 --- a/Screenshot.h +++ b/Screenshot.h @@ -5,15 +5,14 @@ class Screenshot { - int Width, Height; + int Width, Height, bpp; unsigned char * Data; unsigned int Pbo; unsigned int Pbo_size; bool Pbo_full; - void RemoveAlpha(); - void FlipVertical(); static void storeToFile(Screenshot *s, std::string filename); + void capture(); public: Screenshot(); @@ -21,7 +20,8 @@ public: // Quick usage : // 1) Capture screenshot - void captureGL(int x, int y, int w, int h); + void captureGL(int w, int h); + void captureFramebuffer(class FrameBuffer *fb); // 2) if it is full after capture bool isFull(); // 3) then you can save to file diff --git a/Settings.cpp b/Settings.cpp index 5c6ed90..6a28f5c 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -170,6 +170,7 @@ void Settings::Save(uint64_t runtime) SourceConfNode->SetAttribute("new_type", application.source.new_type); SourceConfNode->SetAttribute("ratio", application.source.ratio); SourceConfNode->SetAttribute("res", application.source.res); + SourceConfNode->SetAttribute("capture_path", application.source.capture_path.c_str()); pRoot->InsertEndChild(SourceConfNode); // Brush @@ -411,6 +412,12 @@ void Settings::Load() sourceconfnode->QueryIntAttribute("new_type", &application.source.new_type); sourceconfnode->QueryIntAttribute("ratio", &application.source.ratio); sourceconfnode->QueryIntAttribute("res", &application.source.res); + + const char *path_ = recordnode->Attribute("capture_path"); + if (path_) + application.source.capture_path = std::string(path_); + else + application.source.capture_path = SystemToolkit::home_path(); } // Transition diff --git a/Settings.h b/Settings.h index dea8963..9a52046 100644 --- a/Settings.h +++ b/Settings.h @@ -165,6 +165,7 @@ struct SourceConfig int new_type; int ratio; int res; + std::string capture_path; SourceConfig() { new_type = 0; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index db44bbd..309081b 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -319,13 +319,8 @@ void UserInterface::handleKeyboard() Mixer::manager().view()->selectAll(); } else if (ImGui::IsKeyPressed( GLFW_KEY_R, false )) { - if (shift_modifier_active) { - FrameGrabbing::manager().add(new PNGRecorder); - } - else { - // toggle recording stop / start (or save and continue if + ALT modifier) - outputcontrol.ToggleRecord(alt_modifier_active); - } + // toggle recording stop / start (or save and continue if + ALT modifier) + outputcontrol.ToggleRecord(alt_modifier_active); } else if (ImGui::IsKeyPressed( GLFW_KEY_Z )) { if (shift_modifier_active) @@ -379,8 +374,15 @@ void UserInterface::handleKeyboard() Mixer::manager().setView(View::LAYER); else if (ImGui::IsKeyPressed( GLFW_KEY_F4, false )) Mixer::manager().setView(View::TEXTURE); - else if (ImGui::IsKeyPressed( GLFW_KEY_F11, false )) + else if (ImGui::IsKeyPressed( GLFW_KEY_F9, false )) StartScreenshot(); + else if (ImGui::IsKeyPressed( GLFW_KEY_F10, false )) + sourcecontrol.Capture(); + else if (ImGui::IsKeyPressed( GLFW_KEY_F11, false )) + FrameGrabbing::manager().add(new PNGRecorder); + else if (ImGui::IsKeyPressed( GLFW_KEY_F12, false )) { + Settings::application.render.disabled = !Settings::application.render.disabled; + } // button home to toggle menu else if (ImGui::IsKeyPressed( GLFW_KEY_HOME, false )) navigator.togglePannelMenu(); @@ -405,9 +407,6 @@ void UserInterface::handleKeyboard() WorkspaceWindow::restoreWorkspace(); esc_repeat_ = false; } - else if (ImGui::IsKeyPressed( GLFW_KEY_F12, false )) { - Settings::application.render.disabled = !Settings::application.render.disabled; - } // Space bar else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE, false )) // Space bar to toggle play / pause @@ -1445,7 +1444,7 @@ void ToolBox::Render() { if (ImGui::BeginMenu("Render")) { - if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Screenshot", "F12") ) + if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " View screenshot", "F9") ) UserInterface::manager().StartScreenshot(); ImGui::EndMenu(); @@ -2142,6 +2141,8 @@ SourceController::SourceController() : WorkspaceWindow("SourceController"), mediaplayer_active_(nullptr), mediaplayer_edit_fading_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f) { info_.setExtendedStringMode(); + + captureFolderDialog = new DialogToolkit::OpenFolderDialog("Capture frame Location"); } @@ -2150,10 +2151,11 @@ void SourceController::resetActiveSelection() info_.reset(); active_selection_ = -1; active_label_ = LABEL_AUTO_MEDIA_PLAYER; + play_toggle_request_ = false; + replay_request_ = false; + capture_request_ = false; } - - void SourceController::setVisible(bool on) { // restore workspace to show the window @@ -2202,7 +2204,9 @@ void SourceController::Update() n_play++; } - // Play button or keyboard [space] was pressed + // + // Play button or keyboard [Space] was pressed + // if ( play_toggle_request_ ) { for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) @@ -2211,7 +2215,9 @@ void SourceController::Update() play_toggle_request_ = false; } - // Replay / rewind button or keyboard [B] was pressed + // + // Replay / rewind button or keyboard [CTRL+Space] was pressed + // if ( replay_request_ ) { for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) @@ -2220,6 +2226,31 @@ void SourceController::Update() replay_request_ = false; } + // + // return from thread for selecting capture folder + // + if (captureFolderDialog->closed() && !captureFolderDialog->path().empty()) + // get the folder from this file dialog + Settings::application.source.capture_path = captureFolderDialog->path(); + + // + // Capture frame on current source + // + Source *source = Mixer::manager().currentSource(); + if (source != nullptr) { + // back from capture of FBO: can save file + if ( capture.isFull() ){ + std::string filename = SystemToolkit::full_filename( Settings::application.source.capture_path, source->name() + SystemToolkit::date_time_string() + ".png" ); + capture.save( filename ); + Log::Notify("Frame saved %s", filename.c_str() ); + } + // request capture : initiate capture of FBO + if ( capture_request_ ) { + capture.captureFramebuffer(source->frame()); + capture_request_ = false; + } + } + // reset on session change static Session *__session = nullptr; if ( Mixer::manager().session() != __session ) { @@ -2262,26 +2293,56 @@ void SourceController::Render() } if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER)) { + // // Menu section for play control - if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD"Space")) + // + if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD "Space", nullptr, !selection_.empty())) replay_request_ = true; - if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space")) + if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space", nullptr, !selection_.empty())) play_toggle_request_ = true; + ImGui::Separator(); + + // + // Menu for capture frame + // + if (ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame", "F10", nullptr, !selection_.empty())) + capture_request_ = true; + // path + static char* name_path[4] = { nullptr }; + if ( name_path[0] == nullptr ) { + for (int i = 0; i < 4; ++i) + name_path[i] = (char *) malloc( 1024 * sizeof(char)); + sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); + sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location"); + sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); + } + if (Settings::application.source.capture_path.empty()) + Settings::application.source.capture_path = SystemToolkit::home_path(); + sprintf( name_path[0], "%s", Settings::application.source.capture_path.c_str()); + int selected_path = 0; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("Path", &selected_path, name_path, 4); + if (selected_path > 2) + captureFolderDialog->open(); + else if (selected_path > 1) + Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); + else if (selected_path > 0) + Settings::application.source.capture_path = SystemToolkit::home_path(); ImGui::Separator(); - // Menu section for displayed image + // + // Menu section for display + // if (ImGui::BeginMenu( ICON_FA_IMAGE " Displayed image")) { - if (ImGuiToolkit::MenuItemIcon(8, 9, " Post-processed")) + if (ImGuiToolkit::MenuItemIcon(8, 9, " Render")) Settings::application.widget.media_player_slider = 0.0; - if (ImGuiToolkit::MenuItemIcon(6, 9, " Split view")) + if (ImGuiToolkit::MenuItemIcon(6, 9, " Split")) Settings::application.widget.media_player_slider = 0.5; - if (ImGuiToolkit::MenuItemIcon(7, 9, " Pre-processed")) + if (ImGuiToolkit::MenuItemIcon(7, 9, " Input")) Settings::application.widget.media_player_slider = 1.0; ImGui::EndMenu(); } - - // Menu section for list if (ImGui::MenuItem( ICON_FA_TH " List all")) { selection_.clear(); resetActiveSelection(); @@ -2289,13 +2350,15 @@ void SourceController::Render() Mixer::selection().clear(); selection_ = playable_only( Mixer::manager().session()->getDepthSortedList() ); } - if (ImGui::MenuItem( ICON_FA_ELLIPSIS_H " List none")) { + if (ImGui::MenuItem( ICON_FA_MINUS " Clear")) { selection_.clear(); resetActiveSelection(); Mixer::manager().unsetCurrentSource(); Mixer::selection().clear(); } + // // Menu section for window management + // ImGui::Separator(); bool pinned = Settings::application.widget.media_player_view == Settings::application.current_view; std::string menutext = std::string( ICON_FA_MAP_PIN " Stick to ") + Settings::application.views[Settings::application.current_view].name + " view"; @@ -2389,18 +2452,6 @@ void SourceController::Render() mediaplayer_active_->setRewindOnDisabled(true); ImGui::EndMenu(); } - -// if (ImGui::BeginMenu(ICON_FA_CUT " Auto cut" )) -// { -// if (ImGuiToolkit::MenuItemIcon(14, 12, "Cut faded areas" )) -// if (mediaplayer_active_->timeline()->autoCut()){ -// std::ostringstream oss; -// oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); -// oss << ": Cut faded areas"; -// Action::manager().store(oss.str()); -// } -// ImGui::EndMenu(); -// } if (Settings::application.render.gpu_decoding) { ImGui::Separator(); @@ -3877,6 +3928,7 @@ void OutputPreview::Render() UserInterface::manager().navigator.showConfig(); ImGui::SameLine(0); ImGui::Text("Settings"); + // BASIC OPTIONS static char* name_path[4] = { nullptr }; if ( name_path[0] == nullptr ) { @@ -3889,7 +3941,6 @@ void OutputPreview::Render() if (Settings::application.record.path.empty()) Settings::application.record.path = SystemToolkit::home_path(); sprintf( name_path[0], "%s", Settings::application.record.path.c_str()); - int selected_path = 0; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::Combo("Path", &selected_path, name_path, 4); @@ -3902,6 +3953,7 @@ void OutputPreview::Render() ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGuiToolkit::SliderTiming ("Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped"); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::SliderInt("Trigger", &Settings::application.record.delay, 0, 5, Settings::application.record.delay < 1 ? "Immediate" : "After %d s"); diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 0c2fde8..b9b2e05 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -65,7 +65,7 @@ #define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue" #define SHORTCUT_RECORDCONT CTRL_MOD "Alt+R" #define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame" -#define SHORTCUT_CAPTUREFRAME CTRL_MOD "Shitf+R" +#define SHORTCUT_CAPTUREFRAME "F11" #define MENU_OUTPUTDISABLE ICON_FA_EYE_SLASH " Disable" #define SHORTCUT_OUTPUTDISABLE "F12" #define MENU_OUTPUTFULLSCREEN ICON_FA_EXPAND_ALT " Fullscreen window" @@ -101,6 +101,7 @@ #include "DialogToolkit.h" #include "SessionParser.h" #include "ImageFilter.h" +#include "Screenshot.h" struct ImVec2; @@ -269,7 +270,7 @@ class SourceController : public WorkspaceWindow float buttons_width_; float buttons_height_; - bool play_toggle_request_, replay_request_; + bool play_toggle_request_, replay_request_, capture_request_; bool pending_; std::string active_label_; int active_selection_; @@ -303,11 +304,16 @@ class SourceController : public WorkspaceWindow float mediaplayer_timeline_zoom_; void RenderMediaPlayer(MediaSource *ms); + // dialog to select frame capture location + DialogToolkit::OpenFolderDialog *captureFolderDialog; + Screenshot capture; + public: SourceController(); inline void Play() { play_toggle_request_ = true; } inline void Replay() { replay_request_= true; } + inline void Capture(){ capture_request_= true; } void resetActiveSelection(); void setVisible(bool on);