diff --git a/dep/Makefile b/dep/Makefile index b0180fad..5f0cd941 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -160,7 +160,7 @@ $(rtaudio): $(MAKE) -C rtaudio/build $(MAKE) -C rtaudio/build install -$(nanovg): $(wildcard nanovg/src/*.h) +$(nanovg): $(wildcard nanovg/src/*.h) nanovg/example/stb_image_write.h cp $^ include/ $(nanosvg): $(wildcard nanosvg/src/*.h) diff --git a/helper.py b/helper.py index e7d029b6..c03b4c7d 100755 --- a/helper.py +++ b/helper.py @@ -134,6 +134,9 @@ Plugin *pluginInstance; void init(Plugin *p) { pluginInstance = p; + // Add modules here, e.g. + // p->addModel(modelMyModule); + // Any other plugin initialization may go here. // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. } diff --git a/include/window.hpp b/include/window.hpp index 814af647..d6d20a35 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -98,6 +98,8 @@ struct Window { Window(); ~Window(); void run(); + /** Takes a screenshot of each module */ + void screenshot(); void close(); void cursorLock(); void cursorUnlock(); diff --git a/src/dep.cpp b/src/dep.cpp index 4dbe668b..3a677cdd 100644 --- a/src/dep.cpp +++ b/src/dep.cpp @@ -21,3 +21,6 @@ #define NANOSVG_IMPLEMENTATION #define NANOSVG_ALL_COLOR_KEYWORDS #include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include diff --git a/src/main.cpp b/src/main.cpp index 95322123..44acd81a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,11 +57,12 @@ int main(int argc, char *argv[]) { #endif std::string patchPath; + bool screenshot = false; // Parse command line arguments int c; opterr = 0; - while ((c = getopt(argc, argv, "ds:u:")) != -1) { + while ((c = getopt(argc, argv, "dhps:u:")) != -1) { switch (c) { case 'd': { settings::devMode = true; @@ -69,6 +70,9 @@ int main(int argc, char *argv[]) { case 'h': { settings::headless = true; } break; + case 'p': { + screenshot = true; + } break; case 's': { asset::systemDir = optarg; } break; @@ -154,15 +158,18 @@ int main(int argc, char *argv[]) { INFO("Starting engine"); APP->engine->start(); - if (!settings::headless) { + if (settings::headless) { + // TEMP Prove that the app doesn't crash + std::this_thread::sleep_for(std::chrono::seconds(2)); + } + else if (screenshot) { + APP->window->screenshot(); + } + else { INFO("Running window"); APP->window->run(); INFO("Stopped window"); } - else { - // TEMP Prove that the app doesn't crash - std::this_thread::sleep_for(std::chrono::seconds(2)); - } INFO("Stopping engine"); APP->engine->stop(); diff --git a/src/window.cpp b/src/window.cpp index f7749448..a1262550 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -7,17 +7,20 @@ #include "app.hpp" #include "patch.hpp" #include "settings.hpp" +#include "plugin.hpp" // used in Window::screenshot +#include "system.hpp" // used in Window::screenshot #include #include #include #if defined ARCH_MAC - // For CGAssociateMouseAndMouseCursorPosition - #include +// For CGAssociateMouseAndMouseCursorPosition +#include #endif #include +#include namespace rack { @@ -271,6 +274,9 @@ Window::Window() { osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed."); exit(1); } + + // Load default Blendish font + uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf")); } Window::~Window() { @@ -300,10 +306,8 @@ Window::~Window() { } void Window::run() { - uiFont = APP->window->loadFont(asset::system("res/fonts/DejaVuSans.ttf")); - frame = 0; - while(!glfwWindowShouldClose(win)) { + while (!glfwWindowShouldClose(win)) { frameTimeStart = glfwGetTime(); // Make event handlers and step() have a clean nanovg context @@ -399,6 +403,57 @@ void Window::run() { } } +void Window::screenshot() { + // Iterate plugins and create directories + std::string screenshotsDir = asset::user("screenshots"); + system::createDirectory(screenshotsDir); + for (plugin::Plugin *p : plugin::plugins) { + std::string dir = screenshotsDir + "/" + p->slug; + system::createDirectory(dir); + for (plugin::Model *model : p->models) { + std::string filename = dir + "/" + model->slug + ".png"; + // Skip model if screenshot already exists + if (system::isFile(filename)) + continue; + INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str()); + + // Create widgets + app::ModuleWidget *mw = model->createModuleWidgetNull(); + widget::FramebufferWidget *fb = new widget::FramebufferWidget; + fb->addChild(mw); + float zoom = 2.f; + fb->scale = math::Vec(zoom, zoom); + + // Draw to framebuffer + frameTimeStart = glfwGetTime(); + fb->step(); + nvgluBindFramebuffer(fb->fb); + + // Read pixels + int width, height; + nvgImageSize(vg, fb->getImageHandle(), &width, &height); + uint8_t data[height * width * 4]; + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); + + // Flip image vertically + for (int y = 0; y < height / 2; y++) { + int flipY = height - y - 1; + uint8_t tmp[width * 4]; + memcpy(tmp, &data[y * width * 4], width * 4); + memcpy(&data[y * width * 4], &data[flipY * width * 4], width * 4); + memcpy(&data[flipY * width * 4], tmp, width * 4); + } + + // Write pixels to PNG + stbi_write_png(filename.c_str(), width, height, 4, data, width * 4); + + // Cleanup + nvgluBindFramebuffer(NULL); + delete fb; + } + } +} + void Window::close() { glfwSetWindowShouldClose(win, GLFW_TRUE); }