[Plugin] Add API for taking captures and giant captures of the park

This commit is contained in:
Ted John 2020-05-09 12:55:24 +01:00 committed by GitHub
parent 3d3dbde642
commit 45d32bec68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 202 additions and 2 deletions

View File

@ -138,6 +138,13 @@ declare global {
*/
sharedStorage: Configuration;
/**
* Render the current state of the map and save to disc.
* Useful for server administration and timelapse creation.
* @param options Options that control the capture and output file.
*/
captureImage(options: CaptureOptions): void;
/**
* Gets the loaded object at the given index.
* @param type The object type.
@ -214,6 +221,42 @@ declare global {
has(key: string): boolean;
}
interface CaptureOptions {
/**
* A relative filename from the screenshot directory to save the capture as.
* By default, the filename will be automatically generated using the system date and time.
*/
filename?: string;
/**
* Width of the capture in pixels.
* Do not set if you would like a giant screenshot.
*/
width?: number;
/**
* Height of the capture in pixels.
* Do not set if you would like a giant screenshot.
*/
height?: number;
/**
* Map position to centre the view on in map units.
* Do not set if you would like a giant screenshot.
*/
position?: CoordsXY;
/**
* The zoom level, 0 is 1:1, 1 is 2:1, 2 is 4:1 etc.
*/
zoom: number;
/**
* Rotation of the camera from 0 to 3.
*/
rotation: number;
}
type ObjectType =
"ride" |
"small_scenery" |

View File

@ -35,7 +35,22 @@
# include <filesystem>
namespace fs = std::filesystem;
#else
# ifdef _WIN32
# ifndef NOMINMAX
# define NOMINMAX
# endif
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# define BITMAP WIN32_BITMAP
# endif
# include <filesystem.hpp>
# ifdef _WIN32
# undef CreateDirectory
# undef CreateWindow
# undef GetMessage
# undef BITMAP
# endif
namespace fs = ghc::filesystem;
#endif

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
@ -758,3 +758,89 @@ int32_t cmdline_for_screenshot(const char** argv, int32_t argc, ScreenshotOption
return exitCode;
}
static bool IsPathChildOf(fs::path x, const fs::path& parent)
{
auto xp = x.parent_path();
while (xp != x)
{
if (xp == parent)
{
return true;
}
x = xp;
xp = x.parent_path();
}
return false;
}
static std::string ResolveFilenameForCapture(const fs::path& filename)
{
if (filename.empty())
{
// Automatic filename
auto path = screenshot_get_next_path();
if (!path)
{
throw std::runtime_error("Unable to generate a filename for capture.");
}
return *path;
}
else
{
auto screenshotDirectory = fs::u8path(screenshot_get_directory());
auto screenshotPath = fs::absolute(screenshotDirectory / filename);
// Check the filename isn't attempting to leave the screenshot directory for security
if (!IsPathChildOf(screenshotPath, screenshotDirectory))
{
throw std::runtime_error("Filename is not a child of the screenshot directory.");
}
auto directory = screenshotPath.parent_path();
if (!fs::is_directory(directory))
{
if (!fs::create_directory(directory, screenshotDirectory))
{
throw std::runtime_error("Unable to create directory.");
}
}
return screenshotPath.string();
}
}
void CaptureImage(const CaptureOptions& options)
{
rct_viewport viewport{};
if (options.View)
{
viewport.width = options.View->Width;
viewport.height = options.View->Height;
viewport.view_width = viewport.width;
viewport.view_height = viewport.height;
auto z = tile_element_height(options.View->Position);
CoordsXYZ coords3d(options.View->Position, z);
auto coords2d = translate_3d_to_2d_with_z(options.Rotation, coords3d);
viewport.viewPos = { coords2d.x - ((viewport.view_width * options.Zoom) / 2),
coords2d.y - ((viewport.view_height * options.Zoom) / 2) };
viewport.zoom = options.Zoom;
}
else
{
viewport = GetGiantViewport(gMapSize, options.Rotation, options.Zoom);
}
auto backupRotation = gCurrentRotation;
gCurrentRotation = options.Rotation;
auto outputPath = ResolveFilenameForCapture(options.Filename);
auto dpi = CreateDPI(viewport);
RenderViewport(nullptr, viewport, dpi);
auto renderedPalette = screenshot_get_rendered_palette();
WriteDpiToFile(outputPath, &dpi, renderedPalette);
ReleaseDPI(dpi);
gCurrentRotation = backupRotation;
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
@ -10,7 +10,11 @@
#pragma once
#include "../common.h"
#include "../core/FileSystem.hpp"
#include "../world/Location.hpp"
#include "ZoomLevel.hpp"
#include <optional>
#include <string>
struct rct_drawpixelinfo;
@ -31,6 +35,21 @@ struct ScreenshotOptions
bool transparent = false;
};
struct CaptureView
{
int32_t Width{};
int32_t Height{};
CoordsXY Position;
};
struct CaptureOptions
{
fs::path Filename;
std::optional<CaptureView> View;
ZoomLevel Zoom;
uint8_t Rotation{};
};
void screenshot_check();
std::string screenshot_dump();
std::string screenshot_dump_png(rct_drawpixelinfo* dpi);
@ -39,3 +58,5 @@ std::string screenshot_dump_png_32bpp(int32_t width, int32_t height, const void*
void screenshot_giant();
int32_t cmdline_for_screenshot(const char** argv, int32_t argc, ScreenshotOptions* options);
int32_t cmdline_for_gfxbench(const char** argv, int32_t argc);
void CaptureImage(const CaptureOptions& options);

View File

@ -12,6 +12,7 @@
#ifdef ENABLE_SCRIPTING
# include "../actions/GameAction.h"
# include "../interface/Screenshot.h"
# include "../object/ObjectManager.h"
# include "../scenario/Scenario.h"
# include "Duktape.hpp"
@ -51,6 +52,39 @@ namespace OpenRCT2::Scripting
return std::make_shared<ScConfiguration>(scriptEngine.GetSharedStorage());
}
void captureImage(const DukValue& options)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
try
{
CaptureOptions captureOptions;
captureOptions.Filename = fs::u8path(AsOrDefault(options["filename"], ""));
captureOptions.Rotation = options["rotation"].as_int() & 3;
captureOptions.Zoom = ZoomLevel(options["zoom"].as_int());
auto dukPosition = options["position"];
if (dukPosition.type() == DukValue::Type::OBJECT)
{
CaptureView view;
view.Width = options["width"].as_int();
view.Height = options["height"].as_int();
view.Position.x = dukPosition["x"].as_int();
view.Position.y = dukPosition["y"].as_int();
captureOptions.View = view;
}
CaptureImage(captureOptions);
}
catch (const DukException&)
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid options.");
}
catch (const std::exception& ex)
{
duk_error(ctx, DUK_ERR_ERROR, ex.what());
}
}
static DukValue CreateScObject(duk_context* ctx, uint8_t type, int32_t index)
{
switch (type)
@ -247,6 +281,7 @@ namespace OpenRCT2::Scripting
{
dukglue_register_property(ctx, &ScContext::configuration_get, nullptr, "configuration");
dukglue_register_property(ctx, &ScContext::sharedStorage_get, nullptr, "sharedStorage");
dukglue_register_method(ctx, &ScContext::captureImage, "captureImage");
dukglue_register_method(ctx, &ScContext::getObject, "getObject");
dukglue_register_method(ctx, &ScContext::getAllObjects, "getAllObjects");
dukglue_register_method(ctx, &ScContext::getRandom, "getRandom");