Render animation fixes, vs code tasks

This commit is contained in:
2026-03-15 13:22:53 -04:00
parent 1b5ba6b584
commit bbfafd7331
10 changed files with 102 additions and 16 deletions

2
.vscode/launch.json vendored
View File

@@ -5,6 +5,7 @@
"name": "Debug", "name": "Debug",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"preLaunchTask": "build-debug",
"program": "${workspaceFolder}/out/build/linux-debug/bin/anm2ed", "program": "${workspaceFolder}/out/build/linux-debug/bin/anm2ed",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
@@ -28,6 +29,7 @@
"name": "Release", "name": "Release",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"preLaunchTask": "build-release",
"program": "${workspaceFolder}/out/build/linux-release/bin/anm2ed", "program": "${workspaceFolder}/out/build/linux-release/bin/anm2ed",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,

48
.vscode/tasks.json vendored
View File

@@ -1,10 +1,32 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{
"label": "run-debug",
"type": "shell",
"command": "cmake",
"args": [
"-S",
".",
"-B",
"out/build/linux-debug",
"-DCMAKE_BUILD_TYPE=Debug"
],
"problemMatcher": [
"$gcc"
]
},
{ {
"label": "build-debug", "label": "build-debug",
"type": "shell", "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": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true
@@ -13,10 +35,32 @@
"$gcc" "$gcc"
] ]
}, },
{
"label": "run-release",
"type": "shell",
"command": "cmake",
"args": [
"-S",
".",
"-B",
"out/build/linux-release",
"-DCMAKE_BUILD_TYPE=Release"
],
"problemMatcher": [
"$gcc"
]
},
{ {
"label": "build-release", "label": "build-release",
"type": "shell", "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", "group": "build",
"problemMatcher": [ "problemMatcher": [
"$gcc" "$gcc"

View File

@@ -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. 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 ## Features
- Extended version of the original proprietary Nicalis animation editor - 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/). - 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/).

View File

@@ -80,7 +80,7 @@ namespace anm2ed::anm2
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr); bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
Item* item_get(int, Type, int = -1); 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); types::destination::Type = types::destination::ALL);
Reference null_animation_add(Reference = {}, std::string = {}, bool = false, Reference null_animation_add(Reference = {}, std::string = {}, bool = false,
types::destination::Type = types::destination::ALL); types::destination::Type = types::destination::ALL);

View File

@@ -30,7 +30,7 @@ namespace anm2ed::anm2
return nullptr; 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) destination::Type destination)
{ {
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID; 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.name = !name.empty() ? name : layer.name;
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0; 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(); 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); animation->layerOrder.push_back(id);
}; };
if (destination == destination::ALL) if (destination == destination::ALL)
{ {
for (auto& animation : animations.items) for (size_t index = 0; index < animations.items.size(); ++index)
if (!animation.layerAnimations.contains(id)) add(&animation, id); {
auto& animation = animations.items[index];
if (!animation.layerAnimations.contains(id)) add(&animation, id, true);
}
} }
else if (destination == destination::THIS) else if (destination == destination::THIS)
{ {
if (auto animation = animation_get(reference.animationIndex)) 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}; return {reference.animationIndex, LAYER, id};
@@ -82,4 +96,4 @@ namespace anm2ed::anm2
return {reference.animationIndex, NULL_, id}; return {reference.animationIndex, NULL_, id};
} }
} }

View File

@@ -104,9 +104,10 @@ namespace anm2ed::anm2
{ {
bool noRegions = has_flag(flags, NO_REGIONS); bool noRegions = has_flag(flags, NO_REGIONS);
bool frameNoRegionValues = has_flag(flags, FRAME_NO_REGION_VALUES); 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("XPosition", position.x);
element->SetAttribute("YPosition", position.y); element->SetAttribute("YPosition", position.y);
if (writeRegionValues) if (writeRegionValues)

View File

@@ -85,6 +85,27 @@ namespace anm2ed::imgui
frames.clear(); frames.clear();
} }
void pixels_unpremultiply_alpha(std::vector<uint8_t>& 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<int, anm2::Sound>& sounds, bool render_audio_stream_generate(AudioStream& audioStream, std::map<int, anm2::Sound>& sounds,
const std::vector<int>& frameSoundIDs, int fps) const std::vector<int>& frameSoundIDs, int fps)
{ {
@@ -320,6 +341,7 @@ namespace anm2ed::imgui
bind(); bind();
auto pixels = pixels_get(); auto pixels = pixels_get();
if (settings.renderIsRawAnimation) pixels_unpremultiply_alpha(pixels);
auto frameIndex = (int)renderTempFrames.size(); auto frameIndex = (int)renderTempFrames.size();
auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex); auto framePath = renderTempDirectory / render_frame_filename(settings.renderFormat, frameIndex);
if (Texture::write_pixels_png(framePath, size, pixels.data())) if (Texture::write_pixels_png(framePath, size, pixels.data()))

View File

@@ -3,14 +3,12 @@
#include <algorithm> #include <algorithm>
#include <ranges> #include <ranges>
#include <filesystem>
#include <format> #include <format>
#include "document.hpp" #include "document.hpp"
#include "log.hpp" #include "log.hpp"
#include "map_.hpp" #include "map_.hpp"
#include "math_.hpp" #include "math_.hpp"
#include "path_.hpp"
#include "strings.hpp" #include "strings.hpp"
#include "toast.hpp" #include "toast.hpp"
#include "vector_.hpp" #include "vector_.hpp"

View File

@@ -1780,11 +1780,12 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) if (ImGui::Button(localize.get(BASIC_ADD), widgetSize))
{ {
anm2::Reference addReference{}; anm2::Reference addReference{};
int insertBeforeID = reference.itemType == anm2::LAYER ? reference.itemID : -1;
document.snapshot(localize.get(EDIT_ADD_ITEM)); document.snapshot(localize.get(EDIT_ADD_ITEM));
if (type == anm2::LAYER) if (type == anm2::LAYER)
addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName, addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, insertBeforeID,
addItemSpritesheetID, (destination::Type)destination); addItemName, addItemSpritesheetID, (destination::Type)destination);
else if (type == anm2::NULL_) else if (type == anm2::NULL_)
addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName, addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName,
addItemIsShowRect, (destination::Type)destination); addItemIsShowRect, (destination::Type)destination);

View File

@@ -149,7 +149,8 @@ namespace anm2ed
{ {
case render::GIF: case render::GIF:
command += 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"; " -loop 0";
command += std::format(" \"{}\"", pathString); command += std::format(" \"{}\"", pathString);
break; break;