From 77f6e65b153bb7950c2afe5b67242c2d334a45f9 Mon Sep 17 00:00:00 2001 From: shweet Date: Thu, 5 Mar 2026 00:10:59 -0500 Subject: [PATCH] some bug fixes, added spritesheet filepath setting, etc. --- src/anm2/spritesheet.cpp | 16 +- src/dialog.h | 5 +- src/imgui/window/animation_preview.cpp | 149 ++++++++++++++---- src/imgui/window/animation_preview.h | 5 +- src/imgui/window/spritesheet_editor.cpp | 13 +- src/imgui/window/spritesheets.cpp | 25 +++ .../wizard/change_all_frame_properties.cpp | 4 +- src/imgui/wizard/render_animation.cpp | 3 +- src/playback.cpp | 29 +++- src/playback.h | 6 +- src/render.cpp | 72 ++++++--- src/render.h | 6 +- src/resource/strings.h | 2 + src/resource/texture.cpp | 14 ++ 14 files changed, 272 insertions(+), 77 deletions(-) diff --git a/src/anm2/spritesheet.cpp b/src/anm2/spritesheet.cpp index 6637be6..bd9a0c5 100644 --- a/src/anm2/spritesheet.cpp +++ b/src/anm2/spritesheet.cpp @@ -226,6 +226,8 @@ namespace anm2ed::anm2 { WorkingDirectory workingDirectory(directory); this->path = !path.empty() ? path::make_relative(path) : this->path; + if (this->path.empty()) return false; + path::ensure_directory(this->path.parent_path()); return texture.write_png(this->path); } @@ -250,6 +252,7 @@ namespace anm2ed::anm2 hash_combine(seed, std::hash{}(texture.size.y)); hash_combine(seed, std::hash{}(texture.channels)); hash_combine(seed, std::hash{}(texture.filter)); + hash_combine(seed, std::hash{}(path::to_utf8(path))); if (!texture.pixels.empty()) { @@ -261,19 +264,6 @@ namespace anm2ed::anm2 hash_combine(seed, 0); } - for (const auto& [id, region] : regions) - { - hash_combine(seed, std::hash{}(id)); - hash_combine(seed, std::hash{}(region.name)); - hash_combine(seed, std::hash{}(region.crop.x)); - hash_combine(seed, std::hash{}(region.crop.y)); - hash_combine(seed, std::hash{}(region.size.x)); - hash_combine(seed, std::hash{}(region.size.y)); - hash_combine(seed, std::hash{}(region.pivot.x)); - hash_combine(seed, std::hash{}(region.pivot.y)); - hash_combine(seed, std::hash{}(static_cast(region.origin))); - } - return static_cast(seed); } diff --git a/src/dialog.h b/src/dialog.h index d48a7bf..58725c1 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -18,7 +18,7 @@ namespace anm2ed #define FILTER_LIST \ X(NO_FILTER, {}) \ X(ANM2, {"Anm2 file", "anm2;xml"}) \ - X(PNG, {"PNG image", "png"}) \ + X(PNG, {"PNG image", "png;PNG"}) \ X(SOUND, {"WAV file;OGG file", "wav;ogg"}) \ X(GIF, {"GIF image", "gif"}) \ X(WEBM, {"WebM video", "webm"}) \ @@ -49,6 +49,7 @@ namespace anm2ed X(SOUND_REPLACE, SOUND) \ X(SPRITESHEET_OPEN, PNG) \ X(SPRITESHEET_REPLACE, PNG) \ + X(SPRITESHEET_PATH_SET, PNG) \ X(FFMPEG_PATH_SET, EXECUTABLE) \ X(PNG_DIRECTORY_SET, NO_FILTER) \ X(PNG_PATH_SET, PNG) \ @@ -85,4 +86,4 @@ namespace anm2ed void reset(); void file_explorer_open(const std::filesystem::path&); }; -} \ No newline at end of file +} diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index 4e98ff2..e821ba3 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -1,10 +1,12 @@ #include "animation_preview.h" #include +#include #include #include #include #include +#include #include @@ -30,6 +32,57 @@ namespace anm2ed::imgui constexpr auto POINT_SIZE = vec2(4, 4); constexpr auto TRIGGER_TEXT_COLOR_DARK = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); constexpr auto TRIGGER_TEXT_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 0.5f); + constexpr auto PLAYBACK_TICK_RATE = 30.0f; + + namespace + { + std::filesystem::path render_destination_directory(const std::filesystem::path& path, int type) + { + if (type == render::PNGS) return path; + auto directory = path.parent_path(); + if (directory.empty()) directory = std::filesystem::current_path(); + return directory; + } + + std::filesystem::path render_frame_filename(const std::filesystem::path& format, int index) + { + auto formatString = path::to_utf8(format); + try + { + auto name = std::vformat(formatString, std::make_format_args(index)); + auto filename = path::from_utf8(name).filename(); + if (filename.empty()) return path::from_utf8(std::format("frame_{:06}.png", index)); + if (filename.extension().empty()) filename.replace_extension(render::EXTENSIONS[render::SPRITESHEET]); + return filename; + } + catch (...) + { + return path::from_utf8(std::format("frame_{:06}.png", index)); + } + } + + std::filesystem::path render_temp_directory_create(const std::filesystem::path& directory) + { + auto timestamp = (uint64_t)std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + for (int suffix = 0; suffix < 1000; ++suffix) + { + auto tempDirectory = directory / path::from_utf8(std::format(".anm2ed_render_tmp_{}_{}", timestamp, suffix)); + std::error_code ec; + if (std::filesystem::create_directories(tempDirectory, ec)) return tempDirectory; + } + return {}; + } + + void render_temp_cleanup(std::filesystem::path& directory, std::vector& frames) + { + std::error_code ec; + if (!directory.empty()) std::filesystem::remove_all(directory, ec); + directory.clear(); + frames.clear(); + } + } AnimationPreview::AnimationPreview() : Canvas(vec2()) {} @@ -55,22 +108,7 @@ namespace anm2ed::imgui { if (type == render::PNGS) { - auto& format = settings.renderFormat; - auto formatString = path::to_utf8(format); - bool isSuccess{true}; - for (auto [i, frame] : std::views::enumerate(renderFrames)) - { - auto outputPath = path / std::vformat(formatString, std::make_format_args(i)); - - if (!frame.write_png(outputPath)) - { - isSuccess = false; - break; - } - logger.info(std::format("Saved frame to: {}", outputPath.string())); - } - - if (isSuccess) + if (!renderTempFrames.empty()) { toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES), std::make_format_args(pathString))); logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES, anm2ed::ENGLISH), @@ -89,14 +127,14 @@ namespace anm2ed::imgui auto& rows = settings.renderRows; auto& columns = settings.renderColumns; - if (renderFrames.empty()) + if (renderTempFrames.empty()) { toasts.push(localize.get(TOAST_SPRITESHEET_NO_FRAMES)); logger.warning(localize.get(TOAST_SPRITESHEET_NO_FRAMES, anm2ed::ENGLISH)); } else { - auto& firstFrame = renderFrames.front(); + auto firstFrame = Texture(renderTempFrames.front()); if (firstFrame.size.x <= 0 || firstFrame.size.y <= 0 || firstFrame.pixels.empty()) { toasts.push(localize.get(TOAST_SPRITESHEET_EMPTY)); @@ -110,9 +148,9 @@ namespace anm2ed::imgui std::vector spritesheet((size_t)(spritesheetSize.x) * spritesheetSize.y * CHANNELS); - for (std::size_t index = 0; index < renderFrames.size(); ++index) + for (std::size_t index = 0; index < renderTempFrames.size(); ++index) { - const auto& frame = renderFrames[index]; + auto frame = Texture(renderTempFrames[index]); auto row = (int)(index / columns); auto column = (int)(index % columns); if (row >= rows || column >= columns) break; @@ -147,7 +185,7 @@ namespace anm2ed::imgui } else { - if (animation_render(ffmpegPath, path, renderFrames, audioStream, (render::Type)type, size)) + if (animation_render(ffmpegPath, path, renderTempFrames, audioStream, (render::Type)type, anm2.info.fps)) { toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION), std::make_format_args(pathString))); logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION, anm2ed::ENGLISH), @@ -162,7 +200,13 @@ namespace anm2ed::imgui } } - renderFrames.clear(); + if (type == render::PNGS) + { + renderTempDirectory.clear(); + renderTempFrames.clear(); + } + else + render_temp_cleanup(renderTempDirectory, renderTempFrames); if (settings.renderIsRawAnimation) { @@ -188,7 +232,23 @@ namespace anm2ed::imgui bind(); auto pixels = pixels_get(); - renderFrames.push_back(Texture(pixels.data(), size)); + auto frameIndex = (int)renderTempFrames.size(); + auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex); + if (Texture::write_pixels_png(framePath, size, pixels.data())) + { + renderTempFrames.push_back(framePath); + } + else + { + toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString))); + logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH), + std::make_format_args(pathString))); + if (type != render::PNGS) render_temp_cleanup(renderTempDirectory, renderTempFrames); + playback.isPlaying = false; + playback.isFinished = false; + manager.isRecording = false; + manager.progressPopup.close(); + } } } @@ -214,8 +274,10 @@ namespace anm2ed::imgui } } - playback.tick(anm2.info.fps, animation->frameNum, - (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording); + auto fps = std::max(anm2.info.fps, 1); + auto deltaSeconds = manager.isRecording ? (1.0f / (float)fps) : (1.0f / PLAYBACK_TICK_RATE); + playback.tick(fps, animation->frameNum, (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording, + deltaSeconds); frameTime = playback.time; } @@ -366,7 +428,7 @@ namespace anm2ed::imgui combo_negative_one_indexed(localize.get(LABEL_OVERLAY), &overlayIndex, document.animation.labels); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OVERLAY)); - ImGui::InputFloat(localize.get(BASIC_ALPHA), &overlayTransparency, 0, 0, "%.0f"); + ImGui::DragFloat(localize.get(BASIC_ALPHA), &overlayTransparency, DRAG_SPEED, 0, 255, "%.0f"); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OVERLAY_ALPHA)); } ImGui::EndChild(); @@ -434,7 +496,34 @@ namespace anm2ed::imgui manager.isRecordingStart = false; manager.isRecording = true; + renderTempFrames.clear(); + if (settings.renderType == render::PNGS) + { + renderTempDirectory = settings.renderPath; + std::error_code ec; + std::filesystem::create_directories(renderTempDirectory, ec); + } + else + { + auto destinationDirectory = render_destination_directory(settings.renderPath, settings.renderType); + std::error_code ec; + std::filesystem::create_directories(destinationDirectory, ec); + renderTempDirectory = render_temp_directory_create(destinationDirectory); + } + if (renderTempDirectory.empty()) + { + auto pathString = path::to_utf8(settings.renderPath); + toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString))); + logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH), + std::make_format_args(pathString))); + manager.isRecording = false; + manager.progressPopup.close(); + playback.isPlaying = false; + playback.isFinished = false; + return; + } playback.isPlaying = true; + playback.timing_reset(); playback.time = manager.recordingStart; } @@ -935,7 +1024,13 @@ namespace anm2ed::imgui shortcut(manager.chords[SHORTCUT_CANCEL]); if (ImGui::Button(localize.get(BASIC_CANCEL), ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - renderFrames.clear(); + if (settings.renderType == render::PNGS) + { + renderTempDirectory.clear(); + renderTempFrames.clear(); + } + else + render_temp_cleanup(renderTempDirectory, renderTempFrames); pan = savedPan; zoom = savedZoom; diff --git a/src/imgui/window/animation_preview.h b/src/imgui/window/animation_preview.h index 61395c6..7c0773b 100644 --- a/src/imgui/window/animation_preview.h +++ b/src/imgui/window/animation_preview.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "audio_stream.h" #include "canvas.h" #include "manager.h" @@ -26,7 +28,8 @@ namespace anm2ed::imgui bool hasPendingZoomPanAdjust{}; bool isMoveDragging{}; glm::vec2 moveOffset{}; - std::vector renderFrames{}; + std::filesystem::path renderTempDirectory{}; + std::vector renderTempFrames{}; public: AnimationPreview(); diff --git a/src/imgui/window/spritesheet_editor.cpp b/src/imgui/window/spritesheet_editor.cpp index 31d45d2..0c062d6 100644 --- a/src/imgui/window/spritesheet_editor.cpp +++ b/src/imgui/window/spritesheet_editor.cpp @@ -445,17 +445,22 @@ namespace anm2ed::imgui { DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_REGION), Document::FRAMES, { - frame->regionID = hoveredRegionId; + anm2::FrameChange change{}; + change.regionID = hoveredRegionId; if (spritesheet) { auto regionIt = spritesheet->regions.find(hoveredRegionId); if (regionIt != spritesheet->regions.end()) { - frame->crop = regionIt->second.crop; - frame->size = regionIt->second.size; - frame->pivot = regionIt->second.pivot; + change.cropX = regionIt->second.crop.x; + change.cropY = regionIt->second.crop.y; + change.sizeX = regionIt->second.size.x; + change.sizeY = regionIt->second.size.y; + change.pivotX = regionIt->second.pivot.x; + change.pivotY = regionIt->second.pivot.y; } } + frame_change_apply(change); }); } } diff --git a/src/imgui/window/spritesheets.cpp b/src/imgui/window/spritesheets.cpp index af9d1ab..f8b62f0 100644 --- a/src/imgui/window/spritesheets.cpp +++ b/src/imgui/window/spritesheets.cpp @@ -13,6 +13,7 @@ #include "path_.h" #include "strings.h" #include "toast.h" +#include "working_directory.h" using namespace anm2ed::types; using namespace anm2ed::resource; @@ -36,6 +37,7 @@ namespace anm2ed::imgui auto add_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_OPEN); }; auto replace_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_REPLACE); }; + auto set_file_path_open = [&]() { dialog.file_save(Dialog::SPRITESHEET_PATH_SET); }; auto merge_open = [&]() { if (selection.size() <= 1) return; @@ -120,6 +122,21 @@ namespace anm2ed::imgui DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SPRITESHEET), Document::SPRITESHEETS, behavior()); }; + auto set_file_path = [&](const std::filesystem::path& path) + { + if (selection.size() != 1 || path.empty()) return; + + auto behavior = [&]() + { + auto id = *selection.begin(); + if (!anm2.content.spritesheets.contains(id)) return; + WorkingDirectory workingDirectory(document.directory_get()); + anm2.content.spritesheets[id].path = path::make_relative(path); + }; + + DOCUMENT_EDIT(document, localize.get(EDIT_SET_SPRITESHEET_FILE_PATH), Document::SPRITESHEETS, behavior()); + }; + auto save = [&](const std::set& ids) { if (ids.empty()) return; @@ -294,6 +311,8 @@ namespace anm2ed::imgui if (ImGui::MenuItem(localize.get(BASIC_OPEN_DIRECTORY), nullptr, false, selection.size() == 1)) open_directory(anm2.content.spritesheets[*selection.begin()]); + if (ImGui::MenuItem(localize.get(BASIC_SET_FILE_PATH), nullptr, false, selection.size() == 1)) + set_file_path_open(); if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open(); if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused(); @@ -536,6 +555,12 @@ namespace anm2ed::imgui dialog.reset(); } + if (dialog.is_selected(Dialog::SPRITESHEET_PATH_SET)) + { + set_file_path(dialog.path); + dialog.reset(); + } + auto rowTwoWidgetSize = widget_size_with_row_get(2); shortcut(manager.chords[SHORTCUT_REMOVE]); diff --git a/src/imgui/wizard/change_all_frame_properties.cpp b/src/imgui/wizard/change_all_frame_properties.cpp index 61dc404..4ce3e51 100644 --- a/src/imgui/wizard/change_all_frame_properties.cpp +++ b/src/imgui/wizard/change_all_frame_properties.cpp @@ -210,6 +210,7 @@ namespace anm2ed::imgui::wizard "##Color Offset B", "##Color Offset G", localize.get(BASIC_COLOR_OFFSET), isColorOffsetR, isColorOffsetG, isColorOffsetB, colorOffset); + ImGui::BeginDisabled(itemType != anm2::LAYER); std::vector fallbackIds{-1}; std::vector fallbackLabelsString{localize.get(BASIC_NONE)}; std::vector fallbackLabels{fallbackLabelsString[0].c_str()}; @@ -221,12 +222,11 @@ namespace anm2ed::imgui::wizard auto regionIt = document.regionBySpritesheet.find(spritesheetID); if (regionIt != document.regionBySpritesheet.end()) regionStorage = ®ionIt->second; } - auto regionIds = regionStorage && !regionStorage->ids.empty() ? regionStorage->ids : fallbackIds; auto regionLabels = regionStorage && !regionStorage->labels.empty() ? regionStorage->labels : fallbackLabels; - PROPERTIES_WIDGET(combo_id_mapped(localize.get(BASIC_REGION), ®ionId, regionIds, regionLabels), "##Is Region", isRegion); + ImGui::EndDisabled(); bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible); diff --git a/src/imgui/wizard/render_animation.cpp b/src/imgui/wizard/render_animation.cpp index 86e78f6..a616b2a 100644 --- a/src/imgui/wizard/render_animation.cpp +++ b/src/imgui/wizard/render_animation.cpp @@ -231,6 +231,7 @@ namespace anm2ed::imgui::wizard { toasts.push(localize.get(TOAST_PNG_FORMAT_INVALID)); logger.error(localize.get(TOAST_PNG_FORMAT_INVALID, anm2ed::ENGLISH)); + return false; } return true; }; @@ -269,12 +270,12 @@ namespace anm2ed::imgui::wizard }; if (!path_valid_check()) return false; + if (!png_format_valid_check()) return false; switch (type) { case render::PNGS: if (!png_directory_valid_check()) return false; - if (!png_format_valid_check()) return false; format.replace_extension(render::EXTENSIONS[render::SPRITESHEET]); break; case render::SPRITESHEET: diff --git a/src/playback.cpp b/src/playback.cpp index 8d4be14..55a80c8 100644 --- a/src/playback.cpp +++ b/src/playback.cpp @@ -1,5 +1,8 @@ #include "playback.h" +#include +#include + #include namespace anm2ed @@ -9,24 +12,38 @@ namespace anm2ed if (isFinished) time = 0.0f; isFinished = false; isPlaying = !isPlaying; + timing_reset(); } + void Playback::timing_reset() { tickAccumulator = 0.0f; } + void Playback::clamp(int length) { time = glm::clamp(time, 0.0f, (float)length - 1.0f); } - void Playback::tick(int fps, int length, bool isLoop) + void Playback::tick(int fps, int length, bool isLoop, float deltaSeconds) { - if (isFinished) return; + if (isFinished || !isPlaying || fps <= 0 || length <= 0) return; + if (deltaSeconds <= 0.0f) return; - time += (float)fps / 30.0f; + auto frameDuration = 1.0f / (float)fps; + tickAccumulator += deltaSeconds; + auto steps = (int)std::floor(tickAccumulator / frameDuration); + if (steps <= 0) return; + tickAccumulator -= frameDuration * (float)steps; + + time += (float)steps; if (time > (float)length - 1.0f) { if (isLoop) - time = 0.0f; + { + time = std::fmod(time, (float)length); + } else { + time = (float)length - 1.0f; isPlaying = false; isFinished = true; + timing_reset(); } } } @@ -35,11 +52,13 @@ namespace anm2ed { --time; clamp(length); + timing_reset(); } void Playback::increment(int length) { ++time; clamp(length); + timing_reset(); } -} \ No newline at end of file +} diff --git a/src/playback.h b/src/playback.h index ad4034c..db94c5e 100644 --- a/src/playback.h +++ b/src/playback.h @@ -10,9 +10,13 @@ namespace anm2ed bool isFinished{}; void toggle(); + void timing_reset(); void clamp(int); - void tick(int, int, bool); + void tick(int, int, bool, float); void decrement(int); void increment(int); + + private: + float tickAccumulator{}; }; } diff --git a/src/render.cpp b/src/render.cpp index 914168e..9149458 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -1,8 +1,10 @@ #include "render.h" +#include #include #include #include +#include #include #include @@ -12,16 +14,32 @@ #include "sdl.h" #include "string_.h" -using namespace anm2ed::resource; using namespace anm2ed::util; -using namespace glm; namespace anm2ed { - bool animation_render(const std::filesystem::path& ffmpegPath, const std::filesystem::path& path, - std::vector& frames, AudioStream& audioStream, render::Type type, ivec2 size) + namespace { - if (frames.empty() || size.x <= 0 || size.y <= 0 || ffmpegPath.empty() || path.empty()) return false; + std::string ffmpeg_concat_escape(const std::string& value) + { + std::string escaped{}; + escaped.reserve(value.size()); + for (auto character : value) + { + if (character == '\'') escaped += "'\\''"; + else + escaped += character; + } + return escaped; + } + } + + bool animation_render(const std::filesystem::path& ffmpegPath, const std::filesystem::path& path, + const std::vector& framePaths, AudioStream& audioStream, + render::Type type, int fps) + { + if (framePaths.empty() || ffmpegPath.empty() || path.empty()) return false; + fps = std::max(fps, 1); auto pathString = path::to_utf8(path); auto ffmpegPathString = path::to_utf8(ffmpegPath); @@ -85,8 +103,29 @@ namespace anm2ed } } - command = std::format("\"{0}\" -y -f rawvideo -pix_fmt rgba -s {1}x{2} -r 30 -i pipe:0", ffmpegPathString, size.x, - size.y); + auto framesListPath = std::filesystem::temp_directory_path() / path::from_utf8(std::format( + "anm2ed_frames_{}_{}.txt", std::hash{}(pathString), SDL_GetTicks())) ; + std::ofstream framesListFile(framesListPath); + if (!framesListFile) + { + audio_remove(); + return false; + } + + auto frameDuration = 1.0 / (double)fps; + for (const auto& framePath : framePaths) + { + auto framePathString = path::to_utf8(framePath); + framesListFile << "file '" << ffmpeg_concat_escape(framePathString) << "'\n"; + framesListFile << "duration " << std::format("{:.9f}", frameDuration) << "\n"; + } + auto lastFramePathString = path::to_utf8(framePaths.back()); + framesListFile << "file '" << ffmpeg_concat_escape(lastFramePathString) << "'\n"; + framesListFile.close(); + + auto framesListPathString = path::to_utf8(framesListPath); + command = std::format("\"{0}\" -y -f concat -safe 0 -i \"{1}\"", ffmpegPathString, framesListPathString); + command += std::format(" -fps_mode cfr -r {}", fps); if (!audioInputArguments.empty()) command += " " + audioInputArguments; @@ -110,7 +149,11 @@ namespace anm2ed command += std::format(" \"{}\"", pathString); break; default: + { + std::error_code ec; + std::filesystem::remove(framesListPath, ec); return false; + } } #if _WIN32 @@ -127,23 +170,16 @@ namespace anm2ed if (!process.get()) { + std::error_code ec; + std::filesystem::remove(framesListPath, ec); audio_remove(); return false; } - for (auto& frame : frames) - { - auto frameSize = frame.pixel_size_get(); - - if (fwrite(frame.pixels.data(), 1, frameSize, process.get()) != frameSize) - { - audio_remove(); - return false; - } - } - process.close(); + std::error_code ec; + std::filesystem::remove(framesListPath, ec); audio_remove(); return true; } diff --git a/src/render.h b/src/render.h index c3c9e89..5b41883 100644 --- a/src/render.h +++ b/src/render.h @@ -36,6 +36,6 @@ namespace anm2ed::render namespace anm2ed { std::filesystem::path ffmpeg_log_path(); - bool animation_render(const std::filesystem::path&, const std::filesystem::path&, std::vector&, - AudioStream&, render::Type, glm::ivec2); -} \ No newline at end of file + bool animation_render(const std::filesystem::path&, const std::filesystem::path&, + const std::vector&, AudioStream&, render::Type, int); +} diff --git a/src/resource/strings.h b/src/resource/strings.h index 4a3dcac..646166b 100644 --- a/src/resource/strings.h +++ b/src/resource/strings.h @@ -83,6 +83,7 @@ namespace anm2ed X(BASIC_ROTATION, "Rotation", "Rotacion", "Поворот", "旋转", "회전") \ X(BASIC_SAVE, "Save", "Guardar", "Сохранить", "保存", "저장") \ X(BASIC_SCALE, "Scale", "Escalar", "Масштаб", "缩放", "크기") \ + X(BASIC_SET_FILE_PATH, "Set File Path", "Set File Path", "Set File Path", "Set File Path", "Set File Path") \ X(BASIC_SIZE, "Size", "Tamaño", "Размер", "大小", "비율") \ X(BASIC_SOUND, "Sound", "Sonido", "Звук", "声音", "사운드") \ X(BASIC_TRIM, "Trim", "Recortar contenido", "Обрезать по содержимому", "修剪", "내용으로 자르기") \ @@ -162,6 +163,7 @@ namespace anm2ed X(EDIT_RENAME_EVENT, "Rename Event", "Renombrar Evento", "Переименовать событие", "重命名事件", "이벤트 이름 바꾸기") \ X(EDIT_REPLACE_SPRITESHEET, "Replace Spritesheet", "Reemplazar Spritesheet", "Заменить спрайт-лист", "替换图集", "스프라이트 시트 교체") \ X(EDIT_REPLACE_SOUND, "Replace Sound", "Reemplazar Sonido", "Заменить звук", "替换声音", "사운드 교체") \ + X(EDIT_SET_SPRITESHEET_FILE_PATH, "Set Spritesheet File Path", "Set Spritesheet File Path", "Set Spritesheet File Path", "Set Spritesheet File Path", "Set Spritesheet File Path") \ X(EDIT_SET_LAYER_PROPERTIES, "Set Layer Properties", "Establecer Propiedades de Capa", "Установить свойства слоя", "更改动画层属性", "레이어 속성 설정") \ X(EDIT_SET_REGION_PROPERTIES, "Set Region Properties", "Establecer propiedades de región", "Установить свойства региона", "更改区域属性", "영역 속성 설정") \ X(EDIT_TRIM_REGIONS, "Trim Regions", "Recortar regiones", "Обрезать регионы", "修剪区域", "영역 트리밍") \ diff --git a/src/resource/texture.cpp b/src/resource/texture.cpp index 3ce0aa2..381f8a9 100644 --- a/src/resource/texture.cpp +++ b/src/resource/texture.cpp @@ -147,6 +147,20 @@ namespace anm2ed::resource return false; } + bool Texture::write_pixels_png(const std::filesystem::path& path, ivec2 size, const uint8_t* data) + { + if (!data || size.x <= 0 || size.y <= 0) return false; + + File file(path, "wb"); + if (auto handle = file.get()) + { + auto write_func = [](void* context, void* bytes, int count) + { fwrite(bytes, 1, count, static_cast(context)); }; + return stbi_write_png_to_func(write_func, handle, size.x, size.y, CHANNELS, data, size.x * CHANNELS) != 0; + } + return false; + } + Texture Texture::merge_append(const Texture& base, const Texture& append, bool isAppendRight) { if (base.size.x <= 0 || base.size.y <= 0) return append;