From bbfafd733142946b99669f5c309301110f4dbfdd Mon Sep 17 00:00:00 2001 From: shweet Date: Sun, 15 Mar 2026 13:22:53 -0400 Subject: [PATCH] Render animation fixes, vs code tasks --- .vscode/launch.json | 2 ++ .vscode/tasks.json | 48 ++++++++++++++++++++++++-- README.md | 3 ++ src/anm2/anm2.hpp | 2 +- src/anm2/anm2_items.cpp | 26 ++++++++++---- src/anm2/frame.cpp | 5 +-- src/imgui/window/animation_preview.cpp | 22 ++++++++++++ src/imgui/window/regions.cpp | 2 -- src/imgui/window/timeline.cpp | 5 +-- src/render.cpp | 3 +- 10 files changed, 102 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ad226b0..cdfd141 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,6 +5,7 @@ "name": "Debug", "type": "cppdbg", "request": "launch", + "preLaunchTask": "build-debug", "program": "${workspaceFolder}/out/build/linux-debug/bin/anm2ed", "args": [], "stopAtEntry": false, @@ -28,6 +29,7 @@ "name": "Release", "type": "cppdbg", "request": "launch", + "preLaunchTask": "build-release", "program": "${workspaceFolder}/out/build/linux-release/bin/anm2ed", "args": [], "stopAtEntry": false, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fb2d21f..87f59fa 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,10 +1,32 @@ { "version": "2.0.0", "tasks": [ + { + "label": "run-debug", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + ".", + "-B", + "out/build/linux-debug", + "-DCMAKE_BUILD_TYPE=Debug" + ], + "problemMatcher": [ + "$gcc" + ] + }, { "label": "build-debug", "type": "shell", - "command": "cmake -S . -B out/build/linux-debug -DCMAKE_BUILD_TYPE=Debug && cmake --build out/build/linux-debug --target anm2ed", + "command": "cmake", + "args": [ + "--build", + "out/build/linux-debug", + "--target", + "anm2ed" + ], + "dependsOn": "run-debug", "group": { "kind": "build", "isDefault": true @@ -13,10 +35,32 @@ "$gcc" ] }, + { + "label": "run-release", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + ".", + "-B", + "out/build/linux-release", + "-DCMAKE_BUILD_TYPE=Release" + ], + "problemMatcher": [ + "$gcc" + ] + }, { "label": "build-release", "type": "shell", - "command": "cmake -S . -B out/build/linux-release -DCMAKE_BUILD_TYPE=Release && cmake --build out/build/linux-release --target anm2ed", + "command": "cmake", + "args": [ + "--build", + "out/build/linux-release", + "--target", + "anm2ed" + ], + "dependsOn": "run-release", "group": "build", "problemMatcher": [ "$gcc" diff --git a/README.md b/README.md index a8303a6..868f536 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations. +### Clarification +This application was partly vibe-coded by an LLM (let's say 70% me/30% AI sloppa). Sorry not sorry, I'm just lazy. + ## Features - Extended version of the original proprietary Nicalis animation editor - Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc. You might be familiar with it from [Repentogon](https://repentogon.com/). diff --git a/src/anm2/anm2.hpp b/src/anm2/anm2.hpp index 3ffe3ff..4ac11ad 100644 --- a/src/anm2/anm2.hpp +++ b/src/anm2/anm2.hpp @@ -80,7 +80,7 @@ namespace anm2ed::anm2 bool animations_deserialize(const std::string&, int, std::set&, std::string* = nullptr); Item* item_get(int, Type, int = -1); - Reference layer_animation_add(Reference = {}, std::string = {}, int = 0, + Reference layer_animation_add(Reference = {}, int = -1, std::string = {}, int = 0, types::destination::Type = types::destination::ALL); Reference null_animation_add(Reference = {}, std::string = {}, bool = false, types::destination::Type = types::destination::ALL); diff --git a/src/anm2/anm2_items.cpp b/src/anm2/anm2_items.cpp index b62cb87..47ba193 100644 --- a/src/anm2/anm2_items.cpp +++ b/src/anm2/anm2_items.cpp @@ -30,7 +30,7 @@ namespace anm2ed::anm2 return nullptr; } - Reference Anm2::layer_animation_add(Reference reference, std::string name, int spritesheetID, + Reference Anm2::layer_animation_add(Reference reference, int insertBeforeID, std::string name, int spritesheetID, destination::Type destination) { auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID; @@ -39,21 +39,35 @@ namespace anm2ed::anm2 layer.name = !name.empty() ? name : layer.name; layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0; - auto add = [&](Animation* animation, int id) + auto add = [&](Animation* animation, int id, bool insertBeforeReference) { animation->layerAnimations[id] = Item(); + + if (insertBeforeReference && insertBeforeID != -1) + { + auto it = std::find(animation->layerOrder.begin(), animation->layerOrder.end(), insertBeforeID); + if (it != animation->layerOrder.end()) + { + animation->layerOrder.insert(it, id); + return; + } + } + animation->layerOrder.push_back(id); }; if (destination == destination::ALL) { - for (auto& animation : animations.items) - if (!animation.layerAnimations.contains(id)) add(&animation, id); + for (size_t index = 0; index < animations.items.size(); ++index) + { + auto& animation = animations.items[index]; + if (!animation.layerAnimations.contains(id)) add(&animation, id, true); + } } else if (destination == destination::THIS) { if (auto animation = animation_get(reference.animationIndex)) - if (!animation->layerAnimations.contains(id)) add(animation, id); + if (!animation->layerAnimations.contains(id)) add(animation, id, true); } return {reference.animationIndex, LAYER, id}; @@ -82,4 +96,4 @@ namespace anm2ed::anm2 return {reference.animationIndex, NULL_, id}; } -} \ No newline at end of file +} diff --git a/src/anm2/frame.cpp b/src/anm2/frame.cpp index 7295b97..4415869 100644 --- a/src/anm2/frame.cpp +++ b/src/anm2/frame.cpp @@ -104,9 +104,10 @@ namespace anm2ed::anm2 { bool noRegions = has_flag(flags, NO_REGIONS); bool frameNoRegionValues = has_flag(flags, FRAME_NO_REGION_VALUES); - bool writeRegionValues = !frameNoRegionValues || noRegions; + bool hasValidRegion = !noRegions && regionID != -1; + bool writeRegionValues = !frameNoRegionValues || !hasValidRegion; - if (!noRegions && regionID != -1) element->SetAttribute("RegionId", regionID); + if (hasValidRegion) element->SetAttribute("RegionId", regionID); element->SetAttribute("XPosition", position.x); element->SetAttribute("YPosition", position.y); if (writeRegionValues) diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index bdfe0d5..6167df7 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -85,6 +85,27 @@ namespace anm2ed::imgui frames.clear(); } + void pixels_unpremultiply_alpha(std::vector& pixels) + { + for (size_t index = 0; index + 3 < pixels.size(); index += 4) + { + auto alpha = pixels[index + 3]; + if (alpha == 0) + { + pixels[index + 0] = 0; + pixels[index + 1] = 0; + pixels[index + 2] = 0; + continue; + } + if (alpha == 255) continue; + + float alphaUnit = (float)alpha / 255.0f; + pixels[index + 0] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 0] / alphaUnit), 0.0f, 255.0f); + pixels[index + 1] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 1] / alphaUnit), 0.0f, 255.0f); + pixels[index + 2] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 2] / alphaUnit), 0.0f, 255.0f); + } + } + bool render_audio_stream_generate(AudioStream& audioStream, std::map& sounds, const std::vector& frameSoundIDs, int fps) { @@ -320,6 +341,7 @@ namespace anm2ed::imgui bind(); auto pixels = pixels_get(); + if (settings.renderIsRawAnimation) pixels_unpremultiply_alpha(pixels); auto frameIndex = (int)renderTempFrames.size(); auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex); if (Texture::write_pixels_png(framePath, size, pixels.data())) diff --git a/src/imgui/window/regions.cpp b/src/imgui/window/regions.cpp index 38ef50e..261c3fb 100644 --- a/src/imgui/window/regions.cpp +++ b/src/imgui/window/regions.cpp @@ -3,14 +3,12 @@ #include #include -#include #include #include "document.hpp" #include "log.hpp" #include "map_.hpp" #include "math_.hpp" -#include "path_.hpp" #include "strings.hpp" #include "toast.hpp" #include "vector_.hpp" diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index 76288ab..986aa42 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -1780,11 +1780,12 @@ namespace anm2ed::imgui if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) { anm2::Reference addReference{}; + int insertBeforeID = reference.itemType == anm2::LAYER ? reference.itemID : -1; document.snapshot(localize.get(EDIT_ADD_ITEM)); if (type == anm2::LAYER) - addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName, - addItemSpritesheetID, (destination::Type)destination); + addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, insertBeforeID, + addItemName, addItemSpritesheetID, (destination::Type)destination); else if (type == anm2::NULL_) addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName, addItemIsShowRect, (destination::Type)destination); diff --git a/src/render.cpp b/src/render.cpp index 008b7bf..702d98e 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -149,7 +149,8 @@ namespace anm2ed { case render::GIF: command += - " -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full[p];[s1][p]paletteuse=dither=floyd_steinberg\"" + " -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full:reserve_transparent=1[p];" + "[s1][p]paletteuse=dither=floyd_steinberg:alpha_threshold=128\"" " -loop 0"; command += std::format(" \"{}\"", pathString); break;