diff --git a/CMakeLists.txt b/CMakeLists.txt index 714926f..a68fad2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/src/CustomEvents.hpp b/src/CustomEvents.hpp index 1a24d2a..191af57 100644 --- a/src/CustomEvents.hpp +++ b/src/CustomEvents.hpp @@ -48,6 +48,7 @@ namespace CustomEvents { struct OpenInfiniPaintFileEvent { static uint32_t EVENT_NUM; bool isClient; + bool saveThumbnail = false; std::optional filePathEmptyAutoSaveDir; std::optional filePathSource; std::string netSource; diff --git a/src/DrawingProgram/ToolConfiguration.hpp b/src/DrawingProgram/ToolConfiguration.hpp index 188d242..6d20488 100644 --- a/src/DrawingProgram/ToolConfiguration.hpp +++ b/src/DrawingProgram/ToolConfiguration.hpp @@ -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; diff --git a/src/DrawingProgram/Tools/ScreenshotTool.cpp b/src/DrawingProgram/Tools/ScreenshotTool.cpp index 0591846..1a580f6 100644 --- a/src/DrawingProgram/Tools/ScreenshotTool.cpp +++ b/src/DrawingProgram/Tools/ScreenshotTool.cpp @@ -4,35 +4,14 @@ #include "../../DrawData.hpp" #include "DrawingProgramToolBase.hpp" #include "Helpers/SCollision.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#ifdef USE_SKIA_BACKEND_GRAPHITE - #include -#elif USE_SKIA_BACKEND_GANESH - #include - #include -#endif +#include + #include "../../GridManager.hpp" #include #include "../../MainProgram.hpp" -#ifdef __EMSCRIPTEN__ - #include -#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>("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 finalImgRawData(imageByteSize); - SkPixmap finalImgData(finalImgInfo, finalImgRawData.data(), imageRowSize); - - sk_sp 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 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& 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()); - 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) { diff --git a/src/DrawingProgram/Tools/ScreenshotTool.hpp b/src/DrawingProgram/Tools/ScreenshotTool.hpp index a493b22..d6a8366 100644 --- a/src/DrawingProgram/Tools/ScreenshotTool.hpp +++ b/src/DrawingProgram/Tools/ScreenshotTool.hpp @@ -4,6 +4,7 @@ #include #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& 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 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"} -}) diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index 391bf97..a3bf90f 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -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({ .isClient = false, + .saveThumbnail = true, .filePathEmptyAutoSaveDir = savePath }); - main.set_screen([&] (std::unique_ptr) { return std::make_unique(main); }); } }); } @@ -577,6 +577,7 @@ void FileSelectScreen::file_view() { else { CustomEvents::emit_event({ .isClient = false, + .saveThumbnail = true, .filePathSource = filePath }); } diff --git a/src/World.cpp b/src/World.cpp index 3dcfb10..270dbad 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -30,6 +30,7 @@ #include "CanvasComponents/CanvasComponent.hpp" #include "CanvasComponents/CanvasComponentContainer.hpp" #include "CanvasComponents/CanvasComponentAllocator.hpp" +#include "WorldScreenshot.hpp" #ifdef __EMSCRIPTEN__ #include @@ -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 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"); diff --git a/src/World.hpp b/src/World.hpp index 732d5a2..0385850 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#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& filePathEmptyAutoSaveDir = std::nullopt); diff --git a/src/WorldScreenshot.cpp b/src/WorldScreenshot.cpp new file mode 100644 index 0000000..715d9f7 --- /dev/null +++ b/src/WorldScreenshot.cpp @@ -0,0 +1,197 @@ +#include "CoordSpaceHelper.hpp" +#include "World.hpp" +#include "MainProgram.hpp" +#include +#include "WorldScreenshot.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ + #include +#endif + +#ifdef USE_SKIA_BACKEND_GRAPHITE + #include +#elif USE_SKIA_BACKEND_GANESH + #include + #include +#endif + +void take_screenshot_svg(const std::shared_ptr& w, SkCanvas* canvas, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB& imageBounds); +void take_screenshot_area_hw(const std::shared_ptr& w, const sk_sp& 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& imageBounds, bool displayGrid); + +void world_take_screenshot(const std::shared_ptr& 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 finalImgRawData(imageByteSize); + SkPixmap finalImgData(finalImgInfo, finalImgRawData.data(), imageRowSize); + + sk_sp 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 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& w, SkCanvas* canvas, bool transparentBackground, const CoordSpaceHelper& cameraCoords, const SCollision::AABB& 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& w, const sk_sp& 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& 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()); + 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"); +} diff --git a/src/WorldScreenshot.hpp b/src/WorldScreenshot.hpp new file mode 100644 index 0000000..ccfec49 --- /dev/null +++ b/src/WorldScreenshot.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include "CoordSpaceHelper.hpp" +#include + +struct WorldScreenshotInfo { + enum class ScreenshotType : size_t { + JPG, + PNG, + WEBP, + SVG + }; + std::filesystem::path filePath; + ScreenshotType type; + Vector2i imageSizePixels; + CoordSpaceHelper cameraCoords; + SCollision::AABB 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& w, const WorldScreenshotInfo& info);