Save thumbnails next to infinipaint savefile
This commit is contained in:
parent
962665ffd4
commit
c9c61a8b68
10 changed files with 281 additions and 215 deletions
|
|
@ -46,6 +46,7 @@ set(sources "src/main.cpp"
|
|||
"src/ResourceManager.cpp"
|
||||
"src/Toolbar.cpp"
|
||||
"src/World.cpp"
|
||||
"src/WorldScreenshot.cpp"
|
||||
"src/FileHelpers.cpp"
|
||||
"src/VersionConstants.cpp"
|
||||
"src/ScaleUpCanvas.cpp"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ namespace CustomEvents {
|
|||
struct OpenInfiniPaintFileEvent {
|
||||
static uint32_t EVENT_NUM;
|
||||
bool isClient;
|
||||
bool saveThumbnail = false;
|
||||
std::optional<std::filesystem::path> filePathEmptyAutoSaveDir;
|
||||
std::optional<std::filesystem::path> filePathSource;
|
||||
std::string netSource;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
#include "../SharedTypes.hpp"
|
||||
#include "Tools/ScreenshotTool.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "../GUIStuff/GUIManager.hpp"
|
||||
#include "../WorldScreenshot.hpp"
|
||||
#include "Tools/DrawingProgramToolBase.hpp"
|
||||
|
||||
class ToolConfiguration {
|
||||
public:
|
||||
|
|
@ -44,7 +45,7 @@ class ToolConfiguration {
|
|||
struct ScreenshotToolConfig {
|
||||
int setDimensionSize = 1000;
|
||||
bool setDimensionIsX = true;
|
||||
ScreenshotTool::ScreenshotType selectedType = ScreenshotTool::ScreenshotType::SCREENSHOT_JPG;
|
||||
WorldScreenshotInfo::ScreenshotType selectedType = WorldScreenshotInfo::ScreenshotType::JPG;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ScreenshotToolConfig, setDimensionSize, setDimensionIsX, selectedType)
|
||||
} screenshot;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,35 +4,14 @@
|
|||
#include "../../DrawData.hpp"
|
||||
#include "DrawingProgramToolBase.hpp"
|
||||
#include "Helpers/SCollision.hpp"
|
||||
#include <include/core/SkAlphaType.h>
|
||||
#include <include/core/SkColorType.h>
|
||||
#include <include/core/SkPaint.h>
|
||||
#include <include/core/SkPathTypes.h>
|
||||
#include <include/core/SkStream.h>
|
||||
#include <include/core/SkSurface.h>
|
||||
#include <include/core/SkImage.h>
|
||||
#include <include/core/SkData.h>
|
||||
#include <include/core/SkPathBuilder.h>
|
||||
#include <include/svg/SkSVGCanvas.h>
|
||||
#include <include/encode/SkPngEncoder.h>
|
||||
#include <include/encode/SkWebpEncoder.h>
|
||||
#include <include/encode/SkJpegEncoder.h>
|
||||
#include <include/gpu/GpuTypes.h>
|
||||
#include <filesystem>
|
||||
#ifdef USE_SKIA_BACKEND_GRAPHITE
|
||||
#include <include/gpu/graphite/Surface.h>
|
||||
#elif USE_SKIA_BACKEND_GANESH
|
||||
#include <include/gpu/ganesh/GrDirectContext.h>
|
||||
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
|
||||
#endif
|
||||
#include <include/core/SkPathBuilder.h>
|
||||
|
||||
#include "../../GridManager.hpp"
|
||||
#include <Helpers/Logger.hpp>
|
||||
|
||||
#include "../../MainProgram.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
||||
#endif
|
||||
|
||||
#include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
|
||||
#include "../../GUIStuff/ElementHelpers/CheckBoxHelpers.hpp"
|
||||
|
|
@ -60,7 +39,7 @@ void ScreenshotTool::gui_toolbox(Toolbar& t) {
|
|||
text_label_centered(gui, "Screenshot");
|
||||
if(controls.selectionMode == ScreenshotControls::SelectionMode::NO_SELECTION)
|
||||
text_label(gui, "Select an area on the canvas...");
|
||||
if(controls.selectionMode != ScreenshotControls::SelectionMode::NO_SELECTION && screenshotConfig.selectedType != SCREENSHOT_SVG) {
|
||||
if(controls.selectionMode != ScreenshotControls::SelectionMode::NO_SELECTION && screenshotConfig.selectedType != WorldScreenshotInfo::ScreenshotType::SVG) {
|
||||
input_scalars_field(gui, "Image Size", "Image Size", &controls.imageSize, 2, 0, 999999999, {
|
||||
.onEdit = [&] (size_t i) {
|
||||
if(i == 0) {
|
||||
|
|
@ -82,11 +61,11 @@ void ScreenshotTool::gui_toolbox(Toolbar& t) {
|
|||
text_label(gui, "Image Type");
|
||||
gui.element<DropDown<size_t>>("image type select", (size_t*)(&screenshotConfig.selectedType), controls.typeSelections);
|
||||
});
|
||||
if(screenshotConfig.selectedType != SCREENSHOT_SVG)
|
||||
if(screenshotConfig.selectedType != WorldScreenshotInfo::ScreenshotType::SVG)
|
||||
checkbox_boolean_field(gui, "Display Grid", "Display Grid", &controls.displayGrid);
|
||||
else
|
||||
text_label(gui, "Note: Screenshot will ignore blend\nmodes and layer alpha");
|
||||
if(screenshotConfig.selectedType != 0)
|
||||
if(screenshotConfig.selectedType != WorldScreenshotInfo::ScreenshotType::JPG)
|
||||
checkbox_boolean_field(gui, "Transparent Background", "Transparent Background", &controls.transparentBackground);
|
||||
text_button(gui, "Take Screenshot", "Take Screenshot", {
|
||||
.wide = true,
|
||||
|
|
@ -98,16 +77,16 @@ void ScreenshotTool::gui_toolbox(Toolbar& t) {
|
|||
// We can't actually use the extension from the callback, so we have to set the extension of choice beforehand
|
||||
Toolbar::ExtensionFilter setExtensionFilter;
|
||||
switch(screenshotConfig.selectedType) {
|
||||
case SCREENSHOT_JPG:
|
||||
case WorldScreenshotInfo::ScreenshotType::JPG:
|
||||
setExtensionFilter = {"JPEG", "jpg;jpeg"};
|
||||
break;
|
||||
case SCREENSHOT_PNG:
|
||||
case WorldScreenshotInfo::ScreenshotType::PNG:
|
||||
setExtensionFilter = {"PNG", "png"};
|
||||
break;
|
||||
case SCREENSHOT_WEBP:
|
||||
case WorldScreenshotInfo::ScreenshotType::WEBP:
|
||||
setExtensionFilter = {"WEBP", "webp"};
|
||||
break;
|
||||
case SCREENSHOT_SVG:
|
||||
case WorldScreenshotInfo::ScreenshotType::SVG:
|
||||
setExtensionFilter = {"SVG", "svg"};
|
||||
break;
|
||||
}
|
||||
|
|
@ -298,170 +277,21 @@ bool ScreenshotTool::dragging_area_update(const Vector2f& camCursorPos) {
|
|||
void ScreenshotTool::erase_component(CanvasComponentContainer::ObjInfo* erasedComp) {
|
||||
}
|
||||
|
||||
void ScreenshotTool::take_screenshot(const std::filesystem::path& filePath, ScreenshotType screenshotType) {
|
||||
void ScreenshotTool::take_screenshot(const std::filesystem::path& filePath, WorldScreenshotInfo::ScreenshotType screenshotType) {
|
||||
if(controls.imageSize.x() <= 0 || controls.imageSize.y() <= 0) {
|
||||
std::cout << "[ScreenshotTool::take_screenshot] Image size is 0 or negative" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if(screenshotType != SCREENSHOT_SVG) {
|
||||
size_t imageByteSize = (size_t)controls.imageSize.x() * (size_t)controls.imageSize.y() * 4;
|
||||
size_t imageRowSize = 4 * controls.imageSize.x();
|
||||
|
||||
SkImageInfo finalImgInfo = SkImageInfo::Make(controls.imageSize.x(), controls.imageSize.y(), kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
|
||||
std::vector<uint8_t> finalImgRawData(imageByteSize);
|
||||
SkPixmap finalImgData(finalImgInfo, finalImgRawData.data(), imageRowSize);
|
||||
|
||||
sk_sp<SkSurface> surface = drawP.world.main.create_native_surface(drawP.world.main.window.size, true);
|
||||
SkCanvas* screenshotCanvas = surface->getCanvas();
|
||||
if(!screenshotCanvas) {
|
||||
Logger::get().log("INFO", "Screenshot Tool could not make canvas");
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < controls.imageSize.x(); i += drawP.world.main.window.size.x())
|
||||
for(int j = 0; j < controls.imageSize.y(); j += drawP.world.main.window.size.y())
|
||||
take_screenshot_area_hw(surface, screenshotCanvas, finalImgRawData.data(), controls.imageSize, Vector2i{i, j}, Vector2i{std::min(drawP.world.main.window.size.x(), controls.imageSize.x() - i), std::min(drawP.world.main.window.size.y(), controls.imageSize.y() - j)}, drawP.world.main.window.size, screenshotType != SCREENSHOT_JPG && controls.transparentBackground);
|
||||
|
||||
bool success = false;
|
||||
SkDynamicMemoryWStream out;
|
||||
|
||||
switch(screenshotType) {
|
||||
case SCREENSHOT_JPG:
|
||||
success = SkJpegEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
case SCREENSHOT_PNG:
|
||||
success = SkPngEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
case SCREENSHOT_WEBP:
|
||||
success = SkWebpEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(!success) {
|
||||
Logger::get().log("WORLDFATAL", "[ScreenshotTool::take_screenshot] Could not encode and write screenshot");
|
||||
return;
|
||||
}
|
||||
out.flush();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
try {
|
||||
auto skData = out.detachAsData();
|
||||
if(!SDL_SaveFile(filePath.string().c_str(), skData->bytes(), skData->size()))
|
||||
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("WORLDFATAL", std::string("[ScreenshotTool::take_screenshot] Save screenshot error: ") + e.what());
|
||||
}
|
||||
#else
|
||||
if(success) {
|
||||
std::string mimeTypeArray[] = {"image/jpeg", "image/png", "image/webp"};
|
||||
auto skData = out.detachAsData();
|
||||
emscripten_browser_file::download(
|
||||
"screenshot" + controls.typeSelections[screenshotType],
|
||||
mimeTypeArray[screenshotType],
|
||||
std::string_view((const char*)skData->bytes(), skData->size())
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
SkDynamicMemoryWStream out;
|
||||
Vector2f canvasSize{controls.rectX2 - controls.rectX1, controls.rectY2 - controls.rectY1};
|
||||
SkRect canvasBounds = SkRect::MakeLTRB(0.0f, 0.0f, canvasSize.x(), canvasSize.y());
|
||||
std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(canvasBounds, &out, SkSVGCanvas::kConvertTextToPaths_Flag | SkSVGCanvas::kNoPrettyXML_Flag);
|
||||
take_screenshot_svg(canvas.get(), controls.transparentBackground);
|
||||
canvas = nullptr; // Ensure that SVG is completely written into the stream
|
||||
out.flush();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
try {
|
||||
auto skData = out.detachAsData();
|
||||
if(!SDL_SaveFile(filePath.string().c_str(), skData->bytes(), skData->size()))
|
||||
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("WORLDFATAL", std::string("[ScreenshotTool::take_screenshot] Save screenshot error: ") + e.what());
|
||||
}
|
||||
#else
|
||||
auto skData = out.detachAsData();
|
||||
emscripten_browser_file::download(
|
||||
"screenshot.svg",
|
||||
"image/svg+xml",
|
||||
std::string_view((const char*)skData->bytes(), skData->size())
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshotTool::take_screenshot_svg(SkCanvas* canvas, bool transparentBackground) {
|
||||
float secRectX1 = controls.rectX1;
|
||||
float secRectX2 = controls.rectX2;
|
||||
float secRectY1 = controls.rectY1;
|
||||
float secRectY2 = controls.rectY2;
|
||||
|
||||
Vector2f canvasSize{controls.rectX2 - controls.rectX1, controls.rectY2 - controls.rectY1};
|
||||
|
||||
WorldVec topLeft = controls.coords.from_space({secRectX1, secRectY1});
|
||||
WorldVec topRight = controls.coords.from_space({secRectX2, secRectY1});
|
||||
WorldVec bottomLeft = controls.coords.from_space({secRectX1, secRectY2});
|
||||
WorldVec bottomRight = controls.coords.from_space({secRectX2, secRectY2});
|
||||
WorldVec camCenter = (topLeft + bottomRight) / WorldScalar(2);
|
||||
|
||||
WorldVec vectorZoom;
|
||||
WorldScalar distX = (camCenter - (topLeft + bottomLeft) * WorldScalar(0.5)).norm();
|
||||
WorldScalar distY = (camCenter - (topLeft + topRight) * WorldScalar(0.5)).norm();
|
||||
vectorZoom.x() = distX / WorldScalar(canvasSize.x() * 0.5);
|
||||
vectorZoom.y() = distY / WorldScalar(canvasSize.y() * 0.5);
|
||||
WorldScalar newInverseScale = (vectorZoom.x() + vectorZoom.y()) * WorldScalar(0.5);
|
||||
|
||||
DrawData screenshotDrawData = drawP.world.drawData;
|
||||
screenshotDrawData.cam.set_based_on_properties(drawP.world, topLeft, newInverseScale, controls.coords.rotation);
|
||||
screenshotDrawData.cam.set_viewing_area(canvasSize);
|
||||
screenshotDrawData.takingScreenshot = true;
|
||||
screenshotDrawData.transparentBackground = transparentBackground;
|
||||
screenshotDrawData.drawGrids = false;
|
||||
screenshotDrawData.isSVGRender = true;
|
||||
screenshotDrawData.refresh_draw_optimizing_values();
|
||||
drawP.world.main.draw_world(canvas, drawP.world.main.world, screenshotDrawData);
|
||||
}
|
||||
|
||||
void ScreenshotTool::take_screenshot_area_hw(const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground) {
|
||||
//std::cout << "[ScreenshotTool::take_screenshot_area_hw] Screenshot Pos: " << "[" << sectionImagePos.x() << " " << sectionImagePos.y() << "]" << std::endl;
|
||||
|
||||
float secRectX1 = controls.rectX1 + (controls.rectX2 - controls.rectX1) * (sectionImagePos.x() / (double)fullImageSize.x());
|
||||
float secRectX2 = controls.rectX1 + (controls.rectX2 - controls.rectX1) * ((sectionImagePos.x() + canvasSize.x()) / (double)fullImageSize.x());
|
||||
float secRectY1 = controls.rectY1 + (controls.rectY2 - controls.rectY1) * (sectionImagePos.y() / (double)fullImageSize.y());
|
||||
float secRectY2 = controls.rectY1 + (controls.rectY2 - controls.rectY1) * ((sectionImagePos.y() + canvasSize.y()) / (double)fullImageSize.y());
|
||||
|
||||
WorldVec topLeft = controls.coords.from_space({secRectX1, secRectY1});
|
||||
WorldVec topRight = controls.coords.from_space({secRectX2, secRectY1});
|
||||
WorldVec bottomLeft = controls.coords.from_space({secRectX1, secRectY2});
|
||||
WorldVec bottomRight = controls.coords.from_space({secRectX2, secRectY2});
|
||||
WorldVec camCenter = (topLeft + bottomRight) / WorldScalar(2);
|
||||
|
||||
WorldVec vectorZoom;
|
||||
WorldScalar distX = (camCenter - (topLeft + bottomLeft) * WorldScalar(0.5)).norm();
|
||||
WorldScalar distY = (camCenter - (topLeft + topRight) * WorldScalar(0.5)).norm();
|
||||
vectorZoom.x() = distX / WorldScalar(canvasSize.x() * 0.5);
|
||||
vectorZoom.y() = distY / WorldScalar(canvasSize.y() * 0.5);
|
||||
WorldScalar newInverseScale = (vectorZoom.x() + vectorZoom.y()) * WorldScalar(0.5);
|
||||
|
||||
DrawData screenshotDrawData = drawP.world.drawData;
|
||||
screenshotDrawData.cam.set_based_on_properties(drawP.world, topLeft, newInverseScale, controls.coords.rotation);
|
||||
screenshotDrawData.cam.set_viewing_area(sectionImageSize.cast<float>());
|
||||
screenshotDrawData.takingScreenshot = true;
|
||||
screenshotDrawData.transparentBackground = transparentBackground;
|
||||
screenshotDrawData.drawGrids = controls.displayGrid;
|
||||
screenshotDrawData.refresh_draw_optimizing_values();
|
||||
drawP.world.main.draw_world(canvas, drawP.world.main.world, screenshotDrawData);
|
||||
|
||||
SkImageInfo aaImgInfo = SkImageInfo::Make(sectionImageSize.x(), sectionImageSize.y(), kRGBA_8888_SkColorType, kPremul_SkAlphaType);
|
||||
void* fullImgRawDataStartPt = (uint8_t*)fullImgRawData + 4 * (size_t)sectionImagePos.x() + 4 * (size_t)fullImageSize.x() * (size_t)sectionImagePos.y();
|
||||
SkPixmap aaImgData(aaImgInfo, fullImgRawDataStartPt, fullImageSize.x() * 4);
|
||||
if(!surface->readPixels(aaImgData, 0, 0))
|
||||
throw std::runtime_error("[ScreenshotTool::take_screenshot_area_hw] Error copy pixmap");
|
||||
world_take_screenshot(drawP.world.main.world, {
|
||||
.filePath = filePath,
|
||||
.type = screenshotType,
|
||||
.imageSizePixels = controls.imageSize,
|
||||
.cameraCoords = controls.coords,
|
||||
.imageBounds = {{controls.rectX1, controls.rectY1}, {controls.rectX2, controls.rectY2}},
|
||||
.transparentBackground = controls.transparentBackground,
|
||||
.displayGrid = controls.displayGrid
|
||||
});
|
||||
}
|
||||
|
||||
void ScreenshotTool::switch_tool(DrawingProgramToolType newTool) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <filesystem>
|
||||
#include "../../InputManager.hpp"
|
||||
#include "../../CoordSpaceHelper.hpp"
|
||||
#include "../../World.hpp"
|
||||
|
||||
class DrawingProgram;
|
||||
|
||||
|
|
@ -20,18 +21,9 @@ class ScreenshotTool : public DrawingProgramToolBase {
|
|||
virtual bool prevent_undo_or_redo() override;
|
||||
virtual void input_mouse_button_on_canvas_callback(const InputManager::MouseButtonCallbackArgs& button) override;
|
||||
virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override;
|
||||
|
||||
enum ScreenshotType : size_t {
|
||||
SCREENSHOT_JPG,
|
||||
SCREENSHOT_PNG,
|
||||
SCREENSHOT_WEBP,
|
||||
SCREENSHOT_SVG
|
||||
};
|
||||
private:
|
||||
void commit_rect();
|
||||
void take_screenshot(const std::filesystem::path& filePath, ScreenshotType screenshotType);
|
||||
void take_screenshot_area_hw(const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground);
|
||||
void take_screenshot_svg(SkCanvas* canvas, bool transparentBackground);
|
||||
void take_screenshot(const std::filesystem::path& filePath, WorldScreenshotInfo::ScreenshotType screenshotType);
|
||||
|
||||
struct ScreenshotControls {
|
||||
CoordSpaceHelper translateBeginCoords;
|
||||
|
|
@ -59,16 +51,9 @@ class ScreenshotTool : public DrawingProgramToolBase {
|
|||
|
||||
std::atomic<bool> setToTakeScreenshot = false;
|
||||
std::filesystem::path screenshotSavePath;
|
||||
ScreenshotType screenshotSaveType;
|
||||
WorldScreenshotInfo::ScreenshotType screenshotSaveType;
|
||||
} controls;
|
||||
bool dragging_border_update(const Vector2f& camCursorPos);
|
||||
bool selection_exists_update();
|
||||
bool dragging_area_update(const Vector2f& camCursorPos);
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ScreenshotTool::ScreenshotType, {
|
||||
{ScreenshotTool::ScreenshotType::SCREENSHOT_JPG, "jpg"},
|
||||
{ScreenshotTool::ScreenshotType::SCREENSHOT_PNG, "png"},
|
||||
{ScreenshotTool::ScreenshotType::SCREENSHOT_WEBP, "webp"},
|
||||
{ScreenshotTool::ScreenshotType::SCREENSHOT_SVG, "svg"}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -203,11 +203,11 @@ void FileSelectScreen::create_file_button() {
|
|||
}) {
|
||||
svg_icon_button(gui, "add button", "data/icons/plus.svg", {
|
||||
.onClick = [&] {
|
||||
main.create_new_tab({
|
||||
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
||||
.isClient = false,
|
||||
.saveThumbnail = true,
|
||||
.filePathEmptyAutoSaveDir = savePath
|
||||
});
|
||||
main.set_screen([&] (std::unique_ptr<Screen>) { return std::make_unique<PhoneDrawingProgramScreen>(main); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -577,6 +577,7 @@ void FileSelectScreen::file_view() {
|
|||
else {
|
||||
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
||||
.isClient = false,
|
||||
.saveThumbnail = true,
|
||||
.filePathSource = filePath
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "CanvasComponents/CanvasComponent.hpp"
|
||||
#include "CanvasComponents/CanvasComponentContainer.hpp"
|
||||
#include "CanvasComponents/CanvasComponentAllocator.hpp"
|
||||
#include "WorldScreenshot.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
||||
|
|
@ -45,6 +46,8 @@ World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEvent
|
|||
gridMan(*this),
|
||||
canvasTheme(*this)
|
||||
{
|
||||
saveThumbnail = worldInfo.saveThumbnail;
|
||||
|
||||
init_net_obj_type_list();
|
||||
netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) {
|
||||
undo.remove_by_netid(idToDestroy);
|
||||
|
|
@ -438,10 +441,10 @@ void World::autosave_to_directory(const std::filesystem::path& directoryToSaveAt
|
|||
catch(...) {
|
||||
}
|
||||
std::string nameToSaveUnder = ensure_string_unique(strList, name);
|
||||
save_to_file(directoryToSaveAt / std::filesystem::path(nameToSaveUnder + "." + FILE_EXTENSION));
|
||||
save_to_file(directoryToSaveAt / std::filesystem::path(nameToSaveUnder + "." + FILE_EXTENSION), true);
|
||||
}
|
||||
|
||||
void World::save_to_file(const std::filesystem::path& filePathToSaveAt) {
|
||||
void World::save_to_file(const std::filesystem::path& filePathToSaveAt, bool disableThumbnailSaving) {
|
||||
try {
|
||||
filePath = filePathToSaveAt;
|
||||
|
||||
|
|
@ -473,6 +476,21 @@ void World::save_to_file(const std::filesystem::path& filePathToSaveAt) {
|
|||
#else
|
||||
if(!SDL_SaveFile(filePath.string().c_str(), f.view().data(), f.view().size()))
|
||||
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
|
||||
else if(saveThumbnail && !disableThumbnailSaving) {
|
||||
Vector2f imageCenter{main.window.size.x() * 0.5f, main.window.size.y() * 0.5f};
|
||||
float imageDim = std::max(main.window.size.x(), main.window.size.y());
|
||||
Vector2f imageDimVec{imageDim * 0.5f, imageDim * 0.5f};
|
||||
SCollision::AABB<float> imageBounds{imageCenter - imageDimVec, imageCenter + imageDimVec};
|
||||
world_take_screenshot(main.world, {
|
||||
.filePath = filePath.parent_path() / (filePath.stem().string() + ".jpg"),
|
||||
.type = WorldScreenshotInfo::ScreenshotType::JPG,
|
||||
.imageSizePixels = {512, 512},
|
||||
.cameraCoords = drawData.cam.c,
|
||||
.imageBounds = imageBounds,
|
||||
.transparentBackground = false,
|
||||
.displayGrid = false
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
Logger::get().log("USERINFO", "File saved");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
|
||||
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
|
||||
#include "CoordSpaceHelper.hpp"
|
||||
#include "CustomEvents.hpp"
|
||||
#include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp"
|
||||
#include "WorldUndoManager.hpp"
|
||||
|
|
@ -20,6 +21,8 @@ class MainProgram;
|
|||
|
||||
//#define ENABLE_ORDERED_LIST_TEST
|
||||
|
||||
struct WorldScreenshotInfo;
|
||||
|
||||
class World {
|
||||
public:
|
||||
static constexpr std::string DOT_FILE_EXTENSION = ".infpnt";
|
||||
|
|
@ -63,7 +66,7 @@ class World {
|
|||
bool clientStillConnecting = false;
|
||||
|
||||
void autosave_to_directory(const std::filesystem::path& directoryToSaveAt);
|
||||
void save_to_file(const std::filesystem::path& filePathToSaveAt);
|
||||
void save_to_file(const std::filesystem::path& filePathToSaveAt, bool disableThumbnailSaving = false);
|
||||
void load_from_file(const std::filesystem::path& filePathToLoadFrom, std::string_view buffer);
|
||||
|
||||
void undo_with_checks();
|
||||
|
|
@ -107,7 +110,7 @@ class World {
|
|||
void input_multi_finger_touch_callback(const InputManager::MultiFingerTouchCallbackArgs& touch);
|
||||
void input_multi_finger_motion_callback(const InputManager::MultiFingerMotionCallbackArgs& motion);
|
||||
private:
|
||||
|
||||
bool saveThumbnail = false;
|
||||
bool hasUnsavedLocalChanges = false;
|
||||
|
||||
void load_empty_canvas(const std::optional<std::filesystem::path>& filePathEmptyAutoSaveDir = std::nullopt);
|
||||
|
|
|
|||
197
src/WorldScreenshot.cpp
Normal file
197
src/WorldScreenshot.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#include "CoordSpaceHelper.hpp"
|
||||
#include "World.hpp"
|
||||
#include "MainProgram.hpp"
|
||||
#include <Helpers/Logger.hpp>
|
||||
#include "WorldScreenshot.hpp"
|
||||
|
||||
#include <include/core/SkAlphaType.h>
|
||||
#include <include/core/SkColorType.h>
|
||||
#include <include/core/SkPaint.h>
|
||||
#include <include/core/SkPathTypes.h>
|
||||
#include <include/core/SkStream.h>
|
||||
#include <include/core/SkSurface.h>
|
||||
#include <include/core/SkImage.h>
|
||||
#include <include/core/SkData.h>
|
||||
#include <include/svg/SkSVGCanvas.h>
|
||||
#include <include/encode/SkPngEncoder.h>
|
||||
#include <include/encode/SkWebpEncoder.h>
|
||||
#include <include/encode/SkJpegEncoder.h>
|
||||
#include <include/gpu/GpuTypes.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SKIA_BACKEND_GRAPHITE
|
||||
#include <include/gpu/graphite/Surface.h>
|
||||
#elif USE_SKIA_BACKEND_GANESH
|
||||
#include <include/gpu/ganesh/GrDirectContext.h>
|
||||
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
|
||||
#endif
|
||||
|
||||
void take_screenshot_svg(const std::shared_ptr<World>& w, SkCanvas* canvas, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB<float>& imageBounds);
|
||||
void take_screenshot_area_hw(const std::shared_ptr<World>& w, const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB<float>& imageBounds, bool displayGrid);
|
||||
|
||||
void world_take_screenshot(const std::shared_ptr<World>& w, const WorldScreenshotInfo& info) {
|
||||
if(info.imageSizePixels.x() <= 0 || info.imageSizePixels.y() <= 0) {
|
||||
std::cout << "[ScreenshotTool::take_screenshot] Image size is 0 or negative" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if(info.type != WorldScreenshotInfo::ScreenshotType::SVG) {
|
||||
size_t imageByteSize = (size_t)info.imageSizePixels.x() * (size_t)info.imageSizePixels.y() * 4;
|
||||
size_t imageRowSize = 4 * info.imageSizePixels.x();
|
||||
|
||||
SkImageInfo finalImgInfo = SkImageInfo::Make(info.imageSizePixels.x(), info.imageSizePixels.y(), kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
|
||||
std::vector<uint8_t> finalImgRawData(imageByteSize);
|
||||
SkPixmap finalImgData(finalImgInfo, finalImgRawData.data(), imageRowSize);
|
||||
|
||||
sk_sp<SkSurface> surface = w->main.create_native_surface(w->main.window.size, true);
|
||||
SkCanvas* screenshotCanvas = surface->getCanvas();
|
||||
if(!screenshotCanvas) {
|
||||
Logger::get().log("INFO", "Screenshot Tool could not make canvas");
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < info.imageSizePixels.x(); i += w->main.window.size.x())
|
||||
for(int j = 0; j < info.imageSizePixels.y(); j += w->main.window.size.y())
|
||||
take_screenshot_area_hw(w, surface, screenshotCanvas, finalImgRawData.data(), info.imageSizePixels, Vector2i{i, j}, Vector2i{std::min(w->main.window.size.x(), info.imageSizePixels.x() - i), std::min(w->main.window.size.y(), info.imageSizePixels.y() - j)}, w->main.window.size, info.type != WorldScreenshotInfo::ScreenshotType::JPG && info.transparentBackground, info.cameraCoords, info.imageBounds, info.displayGrid);
|
||||
|
||||
bool success = false;
|
||||
SkDynamicMemoryWStream out;
|
||||
|
||||
switch(info.type) {
|
||||
case WorldScreenshotInfo::ScreenshotType::JPG:
|
||||
success = SkJpegEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
case WorldScreenshotInfo::ScreenshotType::PNG:
|
||||
success = SkPngEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
case WorldScreenshotInfo::ScreenshotType::WEBP:
|
||||
success = SkWebpEncoder::Encode(&out, finalImgData, {});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(!success) {
|
||||
Logger::get().log("WORLDFATAL", "[ScreenshotTool::take_screenshot] Could not encode and write screenshot");
|
||||
return;
|
||||
}
|
||||
out.flush();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
try {
|
||||
auto skData = out.detachAsData();
|
||||
if(!SDL_SaveFile(info.filePath.string().c_str(), skData->bytes(), skData->size()))
|
||||
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("WORLDFATAL", std::string("[ScreenshotTool::take_screenshot] Save screenshot error: ") + e.what());
|
||||
}
|
||||
#else
|
||||
if(success) {
|
||||
std::string mimeTypeArray[] = {"image/jpeg", "image/png", "image/webp"};
|
||||
auto skData = out.detachAsData();
|
||||
emscripten_browser_file::download(
|
||||
"screenshot" + controls.typeSelections[screenshotType],
|
||||
mimeTypeArray[screenshotType],
|
||||
std::string_view((const char*)skData->bytes(), skData->size())
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
SkDynamicMemoryWStream out;
|
||||
Vector2f canvasSize{info.imageBounds.max.x() - info.imageBounds.min.x(), info.imageBounds.max.y() - info.imageBounds.min.y()};
|
||||
SkRect canvasBounds = SkRect::MakeLTRB(0.0f, 0.0f, canvasSize.x(), canvasSize.y());
|
||||
std::unique_ptr<SkCanvas> canvas = SkSVGCanvas::Make(canvasBounds, &out, SkSVGCanvas::kConvertTextToPaths_Flag | SkSVGCanvas::kNoPrettyXML_Flag);
|
||||
take_screenshot_svg(w, canvas.get(), info.transparentBackground, info.cameraCoords, info.imageBounds);
|
||||
canvas = nullptr; // Ensure that SVG is completely written into the stream
|
||||
out.flush();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
try {
|
||||
auto skData = out.detachAsData();
|
||||
if(!SDL_SaveFile(info.filePath.string().c_str(), skData->bytes(), skData->size()))
|
||||
throw std::runtime_error("SDL_SaveFile failed with error: " + std::string(SDL_GetError()));
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("WORLDFATAL", std::string("[ScreenshotTool::take_screenshot] Save screenshot error: ") + e.what());
|
||||
}
|
||||
#else
|
||||
auto skData = out.detachAsData();
|
||||
emscripten_browser_file::download(
|
||||
"screenshot.svg",
|
||||
"image/svg+xml",
|
||||
std::string_view((const char*)skData->bytes(), skData->size())
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void take_screenshot_svg(const std::shared_ptr<World>& w, SkCanvas* canvas, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB<float>& imageBounds) {
|
||||
float secRectX1 = imageBounds.min.x();
|
||||
float secRectX2 = imageBounds.max.x();
|
||||
float secRectY1 = imageBounds.min.y();
|
||||
float secRectY2 = imageBounds.max.y();
|
||||
|
||||
Vector2f canvasSize{imageBounds.max.x() - imageBounds.min.x(), imageBounds.max.y() - imageBounds.min.y()};
|
||||
|
||||
WorldVec topLeft = cameraCoords.from_space({secRectX1, secRectY1});
|
||||
WorldVec topRight = cameraCoords.from_space({secRectX2, secRectY1});
|
||||
WorldVec bottomLeft = cameraCoords.from_space({secRectX1, secRectY2});
|
||||
WorldVec bottomRight = cameraCoords.from_space({secRectX2, secRectY2});
|
||||
WorldVec camCenter = (topLeft + bottomRight) / WorldScalar(2);
|
||||
|
||||
WorldVec vectorZoom;
|
||||
WorldScalar distX = (camCenter - (topLeft + bottomLeft) * WorldScalar(0.5)).norm();
|
||||
WorldScalar distY = (camCenter - (topLeft + topRight) * WorldScalar(0.5)).norm();
|
||||
vectorZoom.x() = distX / WorldScalar(canvasSize.x() * 0.5);
|
||||
vectorZoom.y() = distY / WorldScalar(canvasSize.y() * 0.5);
|
||||
WorldScalar newInverseScale = (vectorZoom.x() + vectorZoom.y()) * WorldScalar(0.5);
|
||||
|
||||
DrawData screenshotDrawData = w->drawData;
|
||||
screenshotDrawData.cam.set_based_on_properties(*w, topLeft, newInverseScale, cameraCoords.rotation);
|
||||
screenshotDrawData.cam.set_viewing_area(canvasSize);
|
||||
screenshotDrawData.takingScreenshot = true;
|
||||
screenshotDrawData.transparentBackground = transparentBackground;
|
||||
screenshotDrawData.drawGrids = false;
|
||||
screenshotDrawData.isSVGRender = true;
|
||||
screenshotDrawData.refresh_draw_optimizing_values();
|
||||
w->main.draw_world(canvas, w->main.world, screenshotDrawData);
|
||||
}
|
||||
|
||||
void take_screenshot_area_hw(const std::shared_ptr<World>& w, const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB<float>& imageBounds, bool displayGrid) {
|
||||
float secRectX1 = imageBounds.min.x() + (imageBounds.max.x() - imageBounds.min.x()) * (sectionImagePos.x() / (double)fullImageSize.x());
|
||||
float secRectX2 = imageBounds.min.x() + (imageBounds.max.x() - imageBounds.min.x()) * ((sectionImagePos.x() + canvasSize.x()) / (double)fullImageSize.x());
|
||||
float secRectY1 = imageBounds.min.y() + (imageBounds.max.y() - imageBounds.min.y()) * (sectionImagePos.y() / (double)fullImageSize.y());
|
||||
float secRectY2 = imageBounds.min.y() + (imageBounds.max.y() - imageBounds.min.y()) * ((sectionImagePos.y() + canvasSize.y()) / (double)fullImageSize.y());
|
||||
|
||||
WorldVec topLeft = cameraCoords.from_space({secRectX1, secRectY1});
|
||||
WorldVec topRight = cameraCoords.from_space({secRectX2, secRectY1});
|
||||
WorldVec bottomLeft = cameraCoords.from_space({secRectX1, secRectY2});
|
||||
WorldVec bottomRight = cameraCoords.from_space({secRectX2, secRectY2});
|
||||
WorldVec camCenter = (topLeft + bottomRight) / WorldScalar(2);
|
||||
|
||||
WorldVec vectorZoom;
|
||||
WorldScalar distX = (camCenter - (topLeft + bottomLeft) * WorldScalar(0.5)).norm();
|
||||
WorldScalar distY = (camCenter - (topLeft + topRight) * WorldScalar(0.5)).norm();
|
||||
vectorZoom.x() = distX / WorldScalar(canvasSize.x() * 0.5);
|
||||
vectorZoom.y() = distY / WorldScalar(canvasSize.y() * 0.5);
|
||||
WorldScalar newInverseScale = (vectorZoom.x() + vectorZoom.y()) * WorldScalar(0.5);
|
||||
|
||||
DrawData screenshotDrawData = w->drawData;
|
||||
screenshotDrawData.cam.set_based_on_properties(*w, topLeft, newInverseScale, cameraCoords.rotation);
|
||||
screenshotDrawData.cam.set_viewing_area(sectionImageSize.cast<float>());
|
||||
screenshotDrawData.takingScreenshot = true;
|
||||
screenshotDrawData.transparentBackground = transparentBackground;
|
||||
screenshotDrawData.drawGrids = displayGrid;
|
||||
screenshotDrawData.refresh_draw_optimizing_values();
|
||||
w->main.draw_world(canvas, w, screenshotDrawData);
|
||||
|
||||
SkImageInfo aaImgInfo = SkImageInfo::Make(sectionImageSize.x(), sectionImageSize.y(), kRGBA_8888_SkColorType, kPremul_SkAlphaType);
|
||||
void* fullImgRawDataStartPt = (uint8_t*)fullImgRawData + 4 * (size_t)sectionImagePos.x() + 4 * (size_t)fullImageSize.x() * (size_t)sectionImagePos.y();
|
||||
SkPixmap aaImgData(aaImgInfo, fullImgRawDataStartPt, fullImageSize.x() * 4);
|
||||
if(!surface->readPixels(aaImgData, 0, 0))
|
||||
throw std::runtime_error("[ScreenshotTool::take_screenshot_area_hw] Error copy pixmap");
|
||||
}
|
||||
29
src/WorldScreenshot.hpp
Normal file
29
src/WorldScreenshot.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <filesystem>
|
||||
#include "CoordSpaceHelper.hpp"
|
||||
#include <Helpers/SCollision.hpp>
|
||||
|
||||
struct WorldScreenshotInfo {
|
||||
enum class ScreenshotType : size_t {
|
||||
JPG,
|
||||
PNG,
|
||||
WEBP,
|
||||
SVG
|
||||
};
|
||||
std::filesystem::path filePath;
|
||||
ScreenshotType type;
|
||||
Vector2i imageSizePixels;
|
||||
CoordSpaceHelper cameraCoords;
|
||||
SCollision::AABB<float> imageBounds;
|
||||
bool transparentBackground;
|
||||
bool displayGrid;
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(WorldScreenshotInfo::ScreenshotType, {
|
||||
{WorldScreenshotInfo::ScreenshotType::JPG, "jpg"},
|
||||
{WorldScreenshotInfo::ScreenshotType::PNG, "png"},
|
||||
{WorldScreenshotInfo::ScreenshotType::WEBP, "webp"},
|
||||
{WorldScreenshotInfo::ScreenshotType::SVG, "svg"}
|
||||
})
|
||||
|
||||
void world_take_screenshot(const std::shared_ptr<World>& w, const WorldScreenshotInfo& info);
|
||||
Loading…
Add table
Add a link
Reference in a new issue