From 1dbb2a2e1d512fb0bb7e3e897a9ac464af1b7672 Mon Sep 17 00:00:00 2001 From: Frank DeMarco Date: Tue, 7 May 2019 03:33:54 -0400 Subject: [PATCH] save indexed screenshot to directory --- demo/Demo.cpp | 47 +++++++++++++-------------- demo/Makefile | 8 +++-- demo/config.json | 8 +++-- src/Configuration.cpp | 17 +++++++++- src/Delegate.cpp | 8 ++++- src/Delegate.hpp | 10 +++++- src/Display.cpp | 11 +++++++ src/Display.hpp | 19 +++++++++++ src/Game.cpp | 2 +- src/Game.hpp | 12 ++++--- src/Input.cpp | 26 ++++----------- src/Input.hpp | 4 ++- src/Node.cpp | 6 ++++ src/Node.hpp | 2 ++ src/Recorder.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++ src/Recorder.hpp | 39 ++++++++++++++++++++++ src/extension.cpp | 22 +++++++++++++ src/extension.hpp | 28 ++++++++++++++++ 18 files changed, 284 insertions(+), 60 deletions(-) create mode 100644 src/Display.cpp create mode 100644 src/Display.hpp create mode 100644 src/Recorder.cpp create mode 100644 src/Recorder.hpp create mode 100644 src/extension.cpp create mode 100644 src/extension.hpp diff --git a/demo/Demo.cpp b/demo/Demo.cpp index 5368e60..ecae7f1 100644 --- a/demo/Demo.cpp +++ b/demo/Demo.cpp @@ -1,6 +1,10 @@ -// reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit game +/*** -// sweaty gamer hands oily snacks and bad hygiene + reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit + game, screen wipes + :) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :) + +***/ #include "Demo.hpp" @@ -65,7 +69,7 @@ SDL_Surface* get_screen_surface(SDL_Window *window) { int w, h; SDL_GetWindowSize(window, &w, &h); - unsigned char *pixels = (unsigned char *) malloc(24 * w * h); + unsigned char* pixels = new unsigned char[24 * w * h]; GLenum format; #if SDL_BYTEORDER == SDL_BIG_ENDIAN format = GL_RGB; @@ -76,7 +80,7 @@ SDL_Surface* get_screen_surface(SDL_Window *window) SDL_Surface *surface = zoomSurface( SDL_CreateRGBSurfaceFrom(pixels, w, h, 24, 3 * w, 0, 0, 0, 0), 1, -1, SMOOTHING_OFF); - free(pixels); + delete[] pixels; return surface; } @@ -176,6 +180,7 @@ struct Demo : Game Mix_Music *music = Mix_LoadMUS("resource/Field.mp3"); Mix_PlayMusic(music, -1); load_gl_context(); + delegate->subscribe(&Demo::respond, this); Input *input = new Input(this); input->print_branch(); mushroom->print_branch(); @@ -343,23 +348,26 @@ struct Demo : Game void respond(SDL_Event& event) { + if (delegate->compare(event, "context")) + { + if (is_gl_context) + { + load_sdl_context(); + } + else + { + load_gl_context(); + } + } } void update() { // while (SDL_PollEvent(&event)) // { - // if (event.type == SDL_QUIT) + // if (event.type == SDL_KEYDOWN) // { - // flag_to_end(); - // } - // else if (event.type == SDL_KEYDOWN) - // { - // if (event.key.keysym.sym == SDLK_F9) - // { - // capture_screen(window); - // } - // else if (event.key.keysym.sym == SDLK_F10) + // if (event.key.keysym.sym == SDLK_F10) // { // if (not is_recording) // { @@ -412,17 +420,6 @@ struct Demo : Game // { // left_active = true; // } - // else if (event.key.keysym.sym == SDLK_SPACE) - // { - // if (is_gl_context) - // { - // load_sdl_context(); - // } - // else - // { - // load_gl_context(); - // } - // } // } // else if (event.type == SDL_KEYUP) // { diff --git a/demo/Makefile b/demo/Makefile index 7f1220c..f2f8b05 100644 --- a/demo/Makefile +++ b/demo/Makefile @@ -37,15 +37,17 @@ $(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h $(CC_LINUX) $(CFLAGS) $< -o $@ $(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp) -$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp) +$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp Recorder.*pp) $(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp) +$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp) $(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp Node.*pp) $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ -Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp) +Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp Recorder.*pp) $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ -linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Location.o Configuration.o Input.o Delegate.o) \ +linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Location.o Configuration.o Input.o Delegate.o \ + Display.o Recorder.o extension.o) \ $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o demo diff --git a/demo/config.json b/demo/config.json index 6812142..a0cc862 100644 --- a/demo/config.json +++ b/demo/config.json @@ -3,13 +3,15 @@ { "dimensions": [640, 480] }, - + "path": + { + "screenshots": "local/screenshots" + }, "gamepad": { }, - "keys": { - "screenshot": ["CTRL", "s"] + "context": " " } } diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 177caa9..bd4f909 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -21,6 +21,15 @@ void Configuration::set_defaults() {"down", "down"}, {"left", "left"} }; + sys_config["path"] = { + {"screenshots", "."} + }; + sys_config["display"] = { + {"dimensions", {640, 480}}, + {"screenshot-prefix", "screenshot-"}, + {"screenshot-extension", ".png"}, + {"screenshot-zfill", 5} + }; } void Configuration::load() @@ -42,7 +51,13 @@ void Configuration::merge() config = sys_config; if (not game_config.empty()) { - config["keys"].update(game_config["keys"]); + for (auto& section: config.items()) + { + if (game_config.contains(section.key())) + { + config[section.key()].update(game_config[section.key()]); + } + } } std::cout << std::setw(4) << config << std::endl; } diff --git a/src/Delegate.cpp b/src/Delegate.cpp index c4d10ae..e3a8bbc 100644 --- a/src/Delegate.cpp +++ b/src/Delegate.cpp @@ -2,7 +2,7 @@ int Delegate::command_event_type = SDL_RegisterEvents(1); -Delegate::Delegate(Node *parent) : Node(parent) {} +Delegate::Delegate(Node* parent) : Node(parent) {} void Delegate::add_subscriber(subscriber s, int type) { @@ -30,3 +30,9 @@ void Delegate::dispatch() } } } + +bool Delegate::compare(SDL_Event& event, std::string command) +{ + return event.type == command_event_type and + *static_cast(event.user.data1) == command; +} diff --git a/src/Delegate.hpp b/src/Delegate.hpp index 00aeca7..a4947cb 100644 --- a/src/Delegate.hpp +++ b/src/Delegate.hpp @@ -1,6 +1,7 @@ #ifndef Delegate_h_ #define Delegate_h_ +#include #include #include #include @@ -14,12 +15,19 @@ typedef std::function subscriber; struct Delegate : Node { - std::map> subscribers; + std::map> subscribers; static int command_event_type; Delegate(Node*); void add_subscriber(subscriber, int); void dispatch(); + bool compare(SDL_Event&, std::string); + + template + void subscribe(void(T::*f)(SDL_Event&), T* o, int type = command_event_type) + { + add_subscriber(std::bind(f, o, std::placeholders::_1), type); + } }; diff --git a/src/Display.cpp b/src/Display.cpp new file mode 100644 index 0000000..6296324 --- /dev/null +++ b/src/Display.cpp @@ -0,0 +1,11 @@ +#include "Display.hpp" +#include "Game.hpp" + +Display::Display(Node* parent) : Node(parent) {} + +glm::ivec2 Display::get_window_size() +{ + glm::ivec2 size; + SDL_GetWindowSize(get_root()->window, &size.x, &size.y); + return size; +} diff --git a/src/Display.hpp b/src/Display.hpp new file mode 100644 index 0000000..42b1012 --- /dev/null +++ b/src/Display.hpp @@ -0,0 +1,19 @@ +#ifndef Display_h_ +#define Display_h_ + +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/vec2.hpp" + +#include "SDL.h" + +#include "Node.hpp" + +struct Display : Node +{ + + Display(Node*); + glm::ivec2 get_window_size(); + +}; + +#endif diff --git a/src/Game.cpp b/src/Game.cpp index cabcde4..e576e9c 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -2,7 +2,7 @@ Game::Game() { - delegate->add_subscriber(std::bind(&Game::handle_quit_event, this, std::placeholders::_1), SDL_QUIT); + delegate->subscribe(&Game::handle_quit_event, this, SDL_QUIT); std::cout << "GLEW " << glewGetString(GLEW_VERSION) << std::endl; putenv("SDL_VIDEO_X11_LEGACY_FULLSCREEN=0"); putenv("SDL_VIDEO_CENTERED=1"); diff --git a/src/Game.hpp b/src/Game.hpp index c77a595..3a79666 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -18,6 +18,8 @@ #include "Node.hpp" #include "Configuration.hpp" #include "Delegate.hpp" +#include "Display.hpp" +#include "Recorder.hpp" struct Game : Node { @@ -27,16 +29,18 @@ struct Game : Node Game(Game&&) = delete; Game& operator=(Game&&) = delete; - SDL_Window *window; - SDL_Renderer *renderer = NULL; + SDL_Window* window; + SDL_Renderer* renderer = NULL; SDL_GLContext glcontext = NULL; int sw = 640, sh = 480, framerate = 60, frame_time_overflow = 0, last_frame_timestamp, frame_count_timestamp, ticks, last_frame_length; float frame_length = 1000.0 / framerate; bool done = false, show_framerate = false, is_gl_context = true; - Configuration *configuration = new Configuration(this); - Delegate *delegate = new Delegate(this); + Configuration* configuration = new Configuration(this); + Delegate* delegate = new Delegate(this); + Display* display = new Display(this); + Recorder* recorder = new Recorder(this); Game(); void print_error(std::string); diff --git a/src/Input.cpp b/src/Input.cpp index 1b3f1d6..61945c8 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -3,8 +3,8 @@ Input::Input(Node *parent) : Node(parent) { load_key_map(); - get_delegate()->add_subscriber(std::bind(&Input::respond, this, std::placeholders::_1), SDL_KEYDOWN); - get_delegate()->add_subscriber(std::bind(&Input::respond, this, std::placeholders::_1), Delegate::command_event_type); + get_delegate()->subscribe(&Input::respond, this, SDL_KEYDOWN); + get_delegate()->subscribe(&Input::respond, this); for (KeyCombination& combination : key_map) { print_key_combination(combination); @@ -29,19 +29,10 @@ void Input::load_key_map() { for (std::string part : entry.value()) { - if (part == "CTRL") - { - ctrl = true; - } - else if (part == "SHIFT") - { - shift = true; - } - else if (part == "ALT") - { - alt = true; - } - else + ctrl = part == "CTRL"; + shift = part == "SHIFT"; + alt = part == "ALT"; + if (not ctrl and not shift and not alt) { key = get_key_code(part); } @@ -77,7 +68,6 @@ void Input::respond(SDL_Event &event) { if (event.type == SDL_KEYDOWN) { - std::cout << event.key.keysym.sym << std::endl; SDL_Keymod mod = SDL_GetModState(); for (KeyCombination &combination : key_map) { @@ -93,8 +83,4 @@ void Input::respond(SDL_Event &event) } } } - else if (event.type == Delegate::command_event_type) - { - std::cout << event.type << " " << *((std::string*) event.user.data1) << std::endl; - } } diff --git a/src/Input.hpp b/src/Input.hpp index 3101b09..7fa417a 100644 --- a/src/Input.hpp +++ b/src/Input.hpp @@ -1,7 +1,9 @@ #ifndef Input_h_ #define Input_h_ +#include #include +#include #include #include "SDL.h" @@ -40,7 +42,7 @@ struct Input : Node {"f11", SDLK_F11}, {"f12", SDLK_F11} }; - std::list key_map; + std::vector key_map; Input(Node*); void respond(SDL_Event&); diff --git a/src/Node.cpp b/src/Node.cpp index d857479..e39c197 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -1,5 +1,6 @@ #include "Node.hpp" #include "Game.hpp" +#include "Display.hpp" Node::Node() : Node(NULL) {} @@ -19,6 +20,11 @@ Delegate* Node::get_delegate() return get_root()->delegate; } +Display* Node::get_display() +{ + return get_root()->display; +} + Game* Node::get_root() { Node *current = this; diff --git a/src/Node.hpp b/src/Node.hpp index d2967ff..7b4fade 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -10,6 +10,7 @@ struct Game; struct Delegate; +struct Display; struct Node { @@ -21,6 +22,7 @@ struct Node Game* get_root(); nlohmann::json& get_configuration(); Delegate* get_delegate(); + Display* get_display(); void print_branch(); virtual std::string get_class_name() { return "Node"; }; diff --git a/src/Recorder.cpp b/src/Recorder.cpp new file mode 100644 index 0000000..fb6af7a --- /dev/null +++ b/src/Recorder.cpp @@ -0,0 +1,75 @@ +#include "Recorder.hpp" + +Recorder::Recorder(Node* parent) : Node(parent) +{ + get_delegate()->subscribe(&Recorder::respond, this); +} + +void Recorder::respond(SDL_Event& event) +{ + if (get_delegate()->compare(event, "screenshot")) + { + capture_screen(); + } +} + +void Recorder::capture_screen() +{ + nlohmann::json config = get_configuration(); + SDL_Surface* surface = get_screen_surface(); + fs::path directory = config["path"]["screenshots"]; + fs::create_directories(directory); + std::string prefix = config["display"]["screenshot-prefix"]. + get(); + std::string extension = config["display"]["screenshot-extension"]. + get(); + std::stringstream file_pattern; + file_pattern << prefix << "(.*)" << extension; + fs::path query = directory / file_pattern.str(); + std::vector files = sfw::glob(query); + int zfill = config["display"]["screenshot-zfill"].get(); + int index = 1; + if (files.size()) + { + const std::string last = files.back().string(); + std::smatch matches; + std::regex_match(last, matches, std::regex(query.string())); + index = std::stoi(matches[1]) + 1; + } + std::stringstream filename; + fs::path path; + do + { + filename << prefix << sfw::pad(index++, zfill) << extension; + path = directory / filename.str(); + filename.str(""); + filename.clear(); + } + while (fs::exists(path)); + IMG_SavePNG(surface, path.c_str()); + std::cout << "screenshot saved to " << path.string() << std::endl; +} + +SDL_Surface* Recorder::get_screen_surface() +{ + glm::ivec2 size = get_display()->get_window_size(); + unsigned char* pixels = new unsigned char[24 * size.x * size.y]; + get_screen_pixels(pixels, size.x, size.y); + SDL_Surface* surface = zoomSurface( + SDL_CreateRGBSurfaceFrom( + pixels, size.x, size.y, 24, 3 * size.x, 0, 0, 0, 0), + 1, -1, SMOOTHING_OFF); + delete[] pixels; + return surface; +} + +void Recorder::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) +{ + GLenum format; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + format = GL_RGB; +#else + format = GL_BGR; +#endif + glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); +} diff --git a/src/Recorder.hpp b/src/Recorder.hpp new file mode 100644 index 0000000..6a27616 --- /dev/null +++ b/src/Recorder.hpp @@ -0,0 +1,39 @@ +#ifndef Recorder_h_ +#define Recorder_h_ + +#include +#include + +#include "SDL.h" + +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/ext.hpp" + +#define GL_GLEXT_PROTOTYPES +#define GLEW_STATIC +#include "glew/glew.h" + +#include +#include "sdl2-gfx/SDL2_gfxPrimitives.h" +#include "sdl2-gfx/SDL2_rotozoom.h" + +#include "json/json.hpp" + +#include "filesystem.hpp" +#include "Node.hpp" +#include "Delegate.hpp" +#include "Display.hpp" +#include "extension.hpp" + +struct Recorder : Node +{ + + Recorder(Node*); + void respond(SDL_Event&); + void capture_screen(); + SDL_Surface* get_screen_surface(); + void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0); + +}; + +#endif diff --git a/src/extension.cpp b/src/extension.cpp new file mode 100644 index 0000000..8a27f7f --- /dev/null +++ b/src/extension.cpp @@ -0,0 +1,22 @@ +#include "extension.hpp" + +std::vector sfw::glob(fs::path query) +{ + fs::path basename = query.parent_path(); + if (basename == "") + { + basename = "."; + } + std::regex expression(query.string()); + std::vector files; + std::cout << basename << " " << query << std::endl; + for (auto& entry: fs::directory_iterator(basename)) + { + if (std::regex_match(entry.path().string(), expression)) + { + files.push_back(entry.path()); + } + } + std::sort(files.begin(), files.end()); + return files; +} diff --git a/src/extension.hpp b/src/extension.hpp new file mode 100644 index 0000000..781e29a --- /dev/null +++ b/src/extension.hpp @@ -0,0 +1,28 @@ +#ifndef extension_h_ +#define extension_h_ + +#include +#include +#include +#include +#include +#include + +#include "filesystem.hpp" + +namespace sfw +{ + std::vector glob(fs::path); + + template + std::string pad(T end, int width, char fill = '0') + { + std::stringstream padded; + padded.fill(fill); + padded.width(width); + padded << end; + return padded.str(); + } +} + +#endif