Player Frame capture F10

New feature of Player: capture frame (F10 shortcut). Extending the Screenshot class for reading pixels and saving to PNG. Cleaup of screenshot (now associated to F9).
This commit is contained in:
Bruno Herbelin
2022-06-22 01:40:47 +02:00
parent f2405e02f6
commit 7858033628
8 changed files with 182 additions and 85 deletions

View File

@@ -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(&copy);
// // 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;
//}

View File

@@ -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;
}

View File

@@ -28,12 +28,14 @@
#include <stb_image.h>
#include <stb_image_write.h>
#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(&copy);
// 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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -165,6 +165,7 @@ struct SourceConfig
int new_type;
int ratio;
int res;
std::string capture_path;
SourceConfig() {
new_type = 0;

View File

@@ -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");

View File

@@ -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);