Browse Source

Initial experimental work for screenshots

Signed-off-by: falkTX <falktx@falktx.com>
tags/22.02
falkTX 3 years ago
parent
commit
fff4d6f61d
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
5 changed files with 132 additions and 10 deletions
  1. +4
    -0
      src/CardinalCommon.hpp
  2. +9
    -5
      src/CardinalPlugin.cpp
  3. +2
    -0
      src/override/MenuBar.cpp
  4. +14
    -1
      src/override/Scene.cpp
  5. +103
    -4
      src/override/Window.cpp

+ 4
- 0
src/CardinalCommon.hpp View File

@@ -37,6 +37,10 @@ namespace ui {
struct Menu; struct Menu;
} }


namespace window {
void generateScreenshot();
}

} // namespace rack } // namespace rack


namespace patchUtils { namespace patchUtils {


+ 9
- 5
src/CardinalPlugin.cpp View File

@@ -49,10 +49,10 @@
#ifndef HEADLESS #ifndef HEADLESS
# include "WindowParameters.hpp" # include "WindowParameters.hpp"
static const constexpr uint kCardinalStateCount = 3; // patch, text, windowSize
static const constexpr uint kCardinalStateCount = 4; // patch, screenshot, comment, windowSize
#else #else
# define kWindowParameterCount 0 # define kWindowParameterCount 0
static const constexpr uint kCardinalStateCount = 2; // patch, text
static const constexpr uint kCardinalStateCount = 3; // patch, screenshot, comment
#endif #endif
namespace rack { namespace rack {
@@ -698,10 +698,13 @@ protected:
stateKey = "patch"; stateKey = "patch";
break; break;
case 1: case 1:
stateKey = "text";
stateKey = "screenshot";
break; break;
#ifndef HEADLESS
case 2: case 2:
stateKey = "comment";
break;
#ifndef HEADLESS
case 3:
stateKey = "windowSize"; stateKey = "windowSize";
break; break;
#endif #endif
@@ -712,7 +715,8 @@ protected:
{ {
switch (index) switch (index)
{ {
case 1:
case 1: // screenshot
case 2: // comment
return kStateIsHostVisible; return kStateIsHostVisible;
default: default:
return 0x0; return 0x0;


+ 2
- 0
src/override/MenuBar.cpp View File

@@ -479,6 +479,8 @@ struct ViewButton : MenuButton {


menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules)); menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules));


menu->addChild(new ui::MenuSeparator);

static const std::vector<std::string> rateLimitLabels = { static const std::vector<std::string> rateLimitLabels = {
"None", "None",
"2x", "2x",


+ 14
- 1
src/override/Scene.cpp View File

@@ -185,11 +185,20 @@ math::Vec Scene::getMousePos() {




void Scene::step() { void Scene::step() {
if (APP->window->isFullScreen()) {
// Expand RackScrollWidget to cover entire screen if fullscreen
rackScroll->box.pos.y = 0;
}
else {
// Always show MenuBar if not fullscreen
menuBar->show();
rackScroll->box.pos.y = menuBar->box.size.y;
}

internal->resizeHandle->box.pos = box.size.minus(internal->resizeHandle->box.size); internal->resizeHandle->box.pos = box.size.minus(internal->resizeHandle->box.size);


// Resize owned descendants // Resize owned descendants
menuBar->box.size.x = box.size.x; menuBar->box.size.x = box.size.x;
rackScroll->box.pos.y = menuBar->box.size.y;
rackScroll->box.size = box.size.minus(rackScroll->box.pos); rackScroll->box.size = box.size.minus(rackScroll->box.pos);


// Scroll RackScrollWidget with arrow keys // Scroll RackScrollWidget with arrow keys
@@ -334,6 +343,10 @@ void Scene::onHoverKey(const HoverKeyEvent& e) {
patchUtils::deployToRemote(); patchUtils::deployToRemote();
e.consume(this); e.consume(this);
} }
if (e.key == GLFW_KEY_F9 && (e.mods & RACK_MOD_MASK) == 0) {
window::generateScreenshot();
e.consume(this);
}


// Module selections // Module selections
if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {


+ 103
- 4
src/override/Window.cpp View File

@@ -36,15 +36,25 @@
#include <context.hpp> #include <context.hpp>
#include <patch.hpp> #include <patch.hpp>
#include <settings.hpp> #include <settings.hpp>
#include <plugin.hpp> // used in Window::screenshot
#include <system.hpp> // used in Window::screenshot
#include <system.hpp>


#ifdef NDEBUG #ifdef NDEBUG
# undef DEBUG # undef DEBUG
#endif #endif


// comment out if wanting to generate a local screenshot.png
#define STBI_WRITE_NO_STDIO

// uncomment to generate screenshots without the rack rail background (ie, transparent)
// #define CARDINAL_TRANSPARENT_SCREENSHOTS

// used in Window::screenshot
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "../src/Rack/dep/glfw/deps/stb_image_write.h"

#include "DistrhoUI.hpp" #include "DistrhoUI.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "extra/String.hpp"
#include "../CardinalCommon.hpp" #include "../CardinalCommon.hpp"
#include "../WindowParameters.hpp" #include "../WindowParameters.hpp"


@@ -119,6 +129,15 @@ std::shared_ptr<Image> Image::load(const std::string& filename) {
} }




enum ScreenshotStep {
kScreenshotStepNone,
kScreenshotStepStarted,
kScreenshotStepFirstPass,
kScreenshotStepSecondPass,
kScreenshotStepSaving
};


struct Window::Internal { struct Window::Internal {
std::string lastWindowTitle; std::string lastWindowTitle;


@@ -139,6 +158,7 @@ struct Window::Internal {


int frame = 0; int frame = 0;
int frameSwapInterval = 1; int frameSwapInterval = 1;
int generateScreenshotStep = kScreenshotStepNone;
double monitorRefreshRate = 60.0; double monitorRefreshRate = 60.0;
double frameTime = 0.0; double frameTime = 0.0;
double lastFrameDuration = 0.0; double lastFrameDuration = 0.0;
@@ -342,6 +362,26 @@ void Window::run() {
} }




static void Window__flipBitmap(uint8_t* pixels, int width, int height, int depth) {
for (int y = 0; y < height / 2; y++) {
const int flipY = height - y - 1;
uint8_t tmp[width * depth];
std::memcpy(tmp, &pixels[y * width * depth], width * depth);
std::memcpy(&pixels[y * width * depth], &pixels[flipY * width * depth], width * depth);
std::memcpy(&pixels[flipY * width * depth], tmp, width * depth);
}
}


static void Window__writeImagePNG(void* context, void* data, int size) {
USE_NAMESPACE_DISTRHO
UI* const ui = static_cast<UI*>(context);
String encodedPNG("data:image/png;base64,");
encodedPNG += String::asBase64(data, size);
ui->setState("screenshot", encodedPNG.buffer());
}


void Window::step() { void Window::step() {
DISTRHO_SAFE_ASSERT_RETURN(internal->ui != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(internal->ui != nullptr,);


@@ -378,6 +418,16 @@ void Window::step() {
APP->event->handleDirty(); APP->event->handleDirty();
} }


// Hide menu and background if generating screenshot
if (internal->generateScreenshotStep == kScreenshotStepStarted) {
#ifdef CARDINAL_TRANSPARENT_SCREENSHOTS
APP->scene->menuBar->hide();
APP->scene->rack->children.front()->hide();
#else
internal->generateScreenshotStep = kScreenshotStepSecondPass;
#endif
}

// Get framebuffer/window ratio // Get framebuffer/window ratio
int winWidth = internal->ui->getWidth(); int winWidth = internal->ui->getWidth();
int winHeight = internal->ui->getHeight(); int winHeight = internal->ui->getHeight();
@@ -388,7 +438,7 @@ void Window::step() {
if (APP->scene) { if (APP->scene) {
// DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth); // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
// Resize scene // Resize scene
APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(newPixelRatio);


// Step scene // Step scene
APP->scene->step(); APP->scene->step();
@@ -396,7 +446,7 @@ void Window::step() {
// Render scene // Render scene
{ {
// Update and render // Update and render
nvgScale(vg, pixelRatio, pixelRatio);
nvgScale(vg, newPixelRatio, newPixelRatio);


// Draw scene // Draw scene
widget::Widget::DrawArgs args; widget::Widget::DrawArgs args;
@@ -405,12 +455,52 @@ void Window::step() {
APP->scene->draw(args); APP->scene->draw(args);


glViewport(0, 0, fbWidth, fbHeight); glViewport(0, 0, fbWidth, fbHeight);
#ifdef CARDINAL_TRANSPARENT_SCREENSHOTS
glClearColor(0.0, 0.0, 0.0, 0.0);
#else
glClearColor(0.0, 0.0, 0.0, 1.0); glClearColor(0.0, 0.0, 0.0, 1.0);
#endif
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
} }
} }


++internal->frame; ++internal->frame;

if (internal->generateScreenshotStep != kScreenshotStepNone) {
++internal->generateScreenshotStep;

int y = 0;
#ifndef CARDINAL_TRANSPARENT_SCREENSHOTS
y = APP->scene->menuBar->box.size.y * newPixelRatio;
#endif

// Allocate pixel color buffer
uint8_t* const pixels = new uint8_t[winHeight * winWidth * 4];

// glReadPixels defaults to GL_BACK, but the back-buffer is unstable, so use the front buffer (what the user sees)
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

if (internal->generateScreenshotStep == kScreenshotStepSaving)
{
// Write pixels to PNG
const int stride = winWidth * 4;
const uint8_t* const pixelsWithOffset = pixels + (stride * y);
Window__flipBitmap(pixels, winWidth, winHeight, 4);
#ifdef STBI_WRITE_NO_STDIO
stbi_write_png_to_func(Window__writeImagePNG, internal->ui,
winWidth, winHeight - y, 4, pixelsWithOffset, stride);
#else
stbi_write_png("screenshot.png", winWidth, winHeight - y, 4, pixelsWithOffset, stride);
#endif

internal->generateScreenshotStep = kScreenshotStepNone;
APP->scene->menuBar->show();
APP->scene->rack->children.front()->show();
}

delete[] pixels;
}
} }




@@ -456,7 +546,11 @@ void Window::setFullScreen(bool) {




bool Window::isFullScreen() { bool Window::isFullScreen() {
#ifdef CARDINAL_TRANSPARENT_SCREENSHOTS
return internal->generateScreenshotStep != kScreenshotStepNone;
#else
return false; return false;
#endif
} }




@@ -533,6 +627,11 @@ int& Window::fbCount() {
} }




void generateScreenshot() {
APP->window->internal->generateScreenshotStep = kScreenshotStepStarted;
}


void init() { void init() {
} }




Loading…
Cancel
Save