FFmpeg and sound support; refactor, etc.

This commit is contained in:
2025-11-01 00:54:22 -04:00
parent 729d5fb216
commit 99b7d9f49d
10 changed files with 146 additions and 15 deletions

3
.gitmodules vendored
View File

@@ -14,3 +14,6 @@
[submodule "external/lunasvg"] [submodule "external/lunasvg"]
path = external/lunasvg path = external/lunasvg
url = https://github.com/sammycage/lunasvg url = https://github.com/sammycage/lunasvg
[submodule "external/SDL_mixer"]
path = external/SDL_mixer
url = https://github.com/libsdl-org/SDL_mixer

1
external/SDL_mixer vendored Submodule

Submodule external/SDL_mixer added at 8c516fcd2e

View File

@@ -1356,12 +1356,28 @@ namespace anm2ed::anm2
return unused; return unused;
} }
std::vector<std::string> Anm2::event_names_get()
{
std::vector<std::string> names{};
for (auto& event : content.events | std::views::values)
names.push_back(event.name);
return names;
}
std::vector<std::string> Anm2::animation_names_get()
{
std::vector<std::string> names{};
for (auto& animation : animations.items)
names.push_back(animation.name);
return names;
}
std::vector<std::string> Anm2::spritesheet_names_get() std::vector<std::string> Anm2::spritesheet_names_get()
{ {
std::vector<std::string> spritesheets{}; std::vector<std::string> names{};
for (auto& [id, spritesheet] : content.spritesheets) for (auto& [id, spritesheet] : content.spritesheets)
spritesheets.push_back(std::format("#{} {}", id, spritesheet.path.c_str())); names.push_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
return spritesheets; return names;
} }
void Anm2::bake(Reference reference, int interval, bool isRoundScale, bool isRoundRotation) void Anm2::bake(Reference reference, int interval, bool isRoundScale, bool isRoundRotation)

View File

@@ -24,7 +24,8 @@ namespace anm2ed::anm2
constexpr auto NO_PATH = "(No Path)"; constexpr auto NO_PATH = "(No Path)";
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})"; constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
constexpr auto NULL_FORMAT = "#{} {}"; constexpr auto NULL_FORMAT = "#{} {}";
constexpr auto SPRITESHEET_FORMAT = "#%d %s"; constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
enum Type enum Type
{ {
@@ -254,7 +255,9 @@ namespace anm2ed::anm2
std::set<int> events_unused(Reference = REFERENCE_DEFAULT); std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
std::set<int> layers_unused(Reference = REFERENCE_DEFAULT); std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT); std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
std::vector<std::string> animation_names_get();
std::vector<std::string> spritesheet_names_get(); std::vector<std::string> spritesheet_names_get();
std::vector<std::string> event_names_get();
void bake(Reference, int = 1, bool = true, bool = true); void bake(Reference, int = 1, bool = true, bool = true);
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int); void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
}; };

View File

@@ -1,11 +1,15 @@
#include "canvas.h" #include "canvas.h"
#include "math.h" #include <algorithm>
#include <cmath>
#include <glm/ext/matrix_clip_space.hpp> #include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp> #include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include "math.h"
#include "texture.h"
using namespace glm; using namespace glm;
using namespace anm2ed::shader; using namespace anm2ed::shader;
@@ -283,6 +287,21 @@ namespace anm2ed::canvas
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
std::vector<unsigned char> Canvas::pixels_get()
{
auto count = size.x * size.y * texture::CHANNELS;
std::vector<unsigned char> pixels(count);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
return pixels;
}
void Canvas::zoom_set(float& zoom, vec2& pan, vec2 focus, float step) void Canvas::zoom_set(float& zoom, vec2& pan, vec2 focus, float step)
{ {
auto zoomFactor = math::percent_to_unit(zoom); auto zoomFactor = math::percent_to_unit(zoom);
@@ -295,6 +314,19 @@ namespace anm2ed::canvas
} }
} }
vec4 Canvas::pixel_read(vec2 position, vec2 framebufferSize)
{
uint8_t rgba[4]{};
glBindTexture(GL_READ_FRAMEBUFFER, fbo);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(position.x, framebufferSize.y - 1 - position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
return vec4(math::uint8_to_float(rgba[0]), math::uint8_to_float(rgba[1]), math::uint8_to_float(rgba[2]),
math::uint8_to_float(rgba[3]));
}
vec2 Canvas::position_translate(float& zoom, vec2& pan, vec2 position) vec2 Canvas::position_translate(float& zoom, vec2& pan, vec2 position)
{ {
auto zoomFactor = math::percent_to_unit(zoom); auto zoomFactor = math::percent_to_unit(zoom);

View File

@@ -18,6 +18,9 @@ namespace anm2ed::canvas
constexpr auto DASH_GAP = 1.0f; constexpr auto DASH_GAP = 1.0f;
constexpr auto DASH_OFFSET = 1.0f; constexpr auto DASH_OFFSET = 1.0f;
constexpr auto STEP = 1.0f;
constexpr auto STEP_FAST = 5.0f;
class Canvas class Canvas
{ {
public: public:
@@ -43,6 +46,7 @@ namespace anm2ed::canvas
void framebuffer_set(); void framebuffer_set();
void framebuffer_resize_check(); void framebuffer_resize_check();
void size_set(glm::vec2); void size_set(glm::vec2);
glm::vec4 pixel_read(glm::vec2, glm::vec2);
glm::mat4 transform_get(float = 100.0f, glm::vec2 = {}); glm::mat4 transform_get(float = 100.0f, glm::vec2 = {});
void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f)); void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {}, void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
@@ -58,5 +62,6 @@ namespace anm2ed::canvas
void zoom_set(float&, glm::vec2&, glm::vec2, float); void zoom_set(float&, glm::vec2&, glm::vec2, float);
glm::vec2 position_translate(float&, glm::vec2&, glm::vec2); glm::vec2 position_translate(float&, glm::vec2&, glm::vec2);
void set_to_rect(float& zoom, glm::vec2& pan, glm::vec4 rect); void set_to_rect(float& zoom, glm::vec2& pan, glm::vec4 rect);
std::vector<unsigned char> pixels_get();
}; };
} }

View File

@@ -2,6 +2,15 @@
namespace anm2ed::snapshots namespace anm2ed::snapshots
{ {
namespace
{
void textures_ensure(anm2::Anm2& anm2)
{
for (auto& [id, spritesheet] : anm2.content.spritesheets)
spritesheet.texture.ensure_pixels();
}
}
bool SnapshotStack::is_empty() bool SnapshotStack::is_empty()
{ {
return top == 0; return top == 0;
@@ -31,6 +40,7 @@ namespace anm2ed::snapshots
void Snapshots::push(const anm2::Anm2& anm2, anm2::Reference reference, const std::string& message) void Snapshots::push(const anm2::Anm2& anm2, anm2::Reference reference, const std::string& message)
{ {
textures_ensure(const_cast<anm2::Anm2&>(anm2));
Snapshot snapshot = {anm2, reference, message}; Snapshot snapshot = {anm2, reference, message};
undoStack.push(snapshot); undoStack.push(snapshot);
redoStack.clear(); redoStack.clear();
@@ -40,8 +50,10 @@ namespace anm2ed::snapshots
{ {
if (auto current = undoStack.pop()) if (auto current = undoStack.pop())
{ {
textures_ensure(anm2);
Snapshot snapshot = {anm2, reference, message}; Snapshot snapshot = {anm2, reference, message};
redoStack.push(snapshot); redoStack.push(snapshot);
textures_ensure(current->anm2);
anm2 = current->anm2; anm2 = current->anm2;
reference = current->reference; reference = current->reference;
message = current->message; message = current->message;
@@ -52,8 +64,10 @@ namespace anm2ed::snapshots
{ {
if (auto current = redoStack.pop()) if (auto current = redoStack.pop())
{ {
textures_ensure(anm2);
Snapshot snapshot = {anm2, reference, message}; Snapshot snapshot = {anm2, reference, message};
undoStack.push(snapshot); undoStack.push(snapshot);
textures_ensure(current->anm2);
anm2 = current->anm2; anm2 = current->anm2;
reference = current->reference; reference = current->reference;
message = current->message; message = current->message;

View File

@@ -14,6 +14,8 @@ using namespace glm;
namespace anm2ed::spritesheet_editor namespace anm2ed::spritesheet_editor
{ {
constexpr auto PIVOT_COLOR = color::PINK;
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2()) SpritesheetEditor::SpritesheetEditor() : Canvas(vec2())
{ {
} }
@@ -115,7 +117,7 @@ namespace anm2ed::spritesheet_editor
auto pivotTransform = auto pivotTransform =
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f); transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color::RED); texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
} }
} }
@@ -145,7 +147,7 @@ namespace anm2ed::spritesheet_editor
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow); auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow); auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift); auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto step = isMod ? step::FAST : step::NORMAL; auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
auto useTool = tool; auto useTool = tool;
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left); auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
@@ -195,12 +197,26 @@ namespace anm2ed::spritesheet_editor
case tool::ERASE: case tool::ERASE:
{ {
if (!spritesheet) break; if (!spritesheet) break;
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
auto color = tool == tool::DRAW ? toolColor : vec4(); auto color = tool == tool::DRAW ? toolColor : vec4();
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color); if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased) document.change(change::FRAMES); if (isMouseReleased) document.change(change::FRAMES);
break; break;
} }
case tool::COLOR_PICKER:
{
if (isMouseDown)
{
auto position = to_vec2(ImGui::GetMousePos());
toolColor = pixel_read(position, {settings.windowSize.x, settings.windowSize.y});
if (ImGui::BeginTooltip())
{
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
ImGui::EndTooltip();
}
}
break;
}
default: default:
break; break;
} }

View File

@@ -32,12 +32,20 @@ namespace anm2ed::texture
return id != 0; return id != 0;
} }
std::vector<uint8_t> Texture::pixels_get()
{
ensure_pixels();
return pixels;
}
void Texture::download() void Texture::download()
{ {
pixels.resize(size.x * size.y * CHANNELS); if (size.x <= 0 || size.y <= 0 || !is_valid()) return;
pixels.resize(static_cast<size_t>(size.x) * static_cast<size_t>(size.y) * CHANNELS);
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
isPixelsDirty = false;
} }
void Texture::upload(const uint8_t* data) void Texture::upload(const uint8_t* data)
@@ -66,6 +74,7 @@ namespace anm2ed::texture
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
isPixelsDirty = false;
} }
Texture::Texture() = default; Texture::Texture() = default;
@@ -91,6 +100,7 @@ namespace anm2ed::texture
{ {
if (is_valid()) glDeleteTextures(1, &id); if (is_valid()) glDeleteTextures(1, &id);
id = 0; id = 0;
other.ensure_pixels();
size = other.size; size = other.size;
filter = other.filter; filter = other.filter;
channels = other.channels; channels = other.channels;
@@ -110,6 +120,9 @@ namespace anm2ed::texture
filter = other.filter; filter = other.filter;
channels = other.channels; channels = other.channels;
pixels = std::move(other.pixels); pixels = std::move(other.pixels);
isPixelsDirty = other.isPixelsDirty;
other.isPixelsDirty = true;
if (!pixels.empty()) upload();
} }
return *this; return *this;
} }
@@ -129,9 +142,15 @@ namespace anm2ed::texture
upload(bitmap.data()); upload(bitmap.data());
} }
Texture::Texture(const uint8_t* data, ivec2 size)
{
this->size = size;
upload(data);
}
Texture::Texture(const std::string& pngPath) Texture::Texture(const std::string& pngPath)
{ {
if (const uint8* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data) if (const uint8_t* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
{ {
upload(data); upload(data);
stbi_image_free((void*)data); stbi_image_free((void*)data);
@@ -140,6 +159,7 @@ namespace anm2ed::texture
bool Texture::write_png(const std::string& path) bool Texture::write_png(const std::string& path)
{ {
ensure_pixels();
return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS); return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS);
} }
@@ -147,6 +167,7 @@ namespace anm2ed::texture
{ {
if (position.x < 0 || position.y < 0 || position.x >= size.x || position.y >= size.y) return; if (position.x < 0 || position.y < 0 || position.x >= size.x || position.y >= size.y) return;
ensure_pixels();
uint8 rgba8[4] = {(uint8)float_to_uint8(color.r), (uint8)float_to_uint8(color.g), (uint8)float_to_uint8(color.b), uint8 rgba8[4] = {(uint8)float_to_uint8(color.r), (uint8)float_to_uint8(color.g), (uint8)float_to_uint8(color.b),
(uint8)float_to_uint8(color.a)}; (uint8)float_to_uint8(color.a)};
@@ -154,14 +175,23 @@ namespace anm2ed::texture
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8); glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
if (!pixels.empty())
{
auto index = (position.y * size.x + position.x) * CHANNELS;
pixels[index + 0] = rgba8[0];
pixels[index + 1] = rgba8[1];
pixels[index + 2] = rgba8[2];
pixels[index + 3] = rgba8[3];
isPixelsDirty = false;
}
else
isPixelsDirty = true;
} }
void Texture::pixel_line(ivec2 start, ivec2 end, vec4 color) void Texture::pixel_line(ivec2 start, ivec2 end, vec4 color)
{ {
auto plot = [&](ivec2 pos) ensure_pixels();
{ auto plot = [&](ivec2 pos) { pixel_set(pos, color); };
pixel_set(pos, color);
};
int x0 = start.x; int x0 = start.x;
int y0 = start.y; int y0 = start.y;
@@ -192,6 +222,13 @@ namespace anm2ed::texture
} }
} }
void Texture::ensure_pixels() const
{
if (size.x <= 0 || size.y <= 0) return;
if (!pixels.empty() && !isPixelsDirty) return;
const_cast<Texture*>(this)->download();
}
void Texture::bind(GLuint unit) void Texture::bind(GLuint unit)
{ {
glActiveTexture(GL_TEXTURE0 + unit); glActiveTexture(GL_TEXTURE0 + unit);

View File

@@ -17,7 +17,8 @@ namespace anm2ed::texture
glm::ivec2 size{}; glm::ivec2 size{};
GLint filter = GL_NEAREST; GLint filter = GL_NEAREST;
int channels{}; int channels{};
std::vector<uint8_t> pixels{}; mutable std::vector<uint8_t> pixels{};
mutable bool isPixelsDirty{true};
bool is_valid(); bool is_valid();
void download(); void download();
@@ -30,10 +31,13 @@ namespace anm2ed::texture
Texture(Texture&&); Texture(Texture&&);
Texture& operator=(const Texture&); Texture& operator=(const Texture&);
Texture& operator=(Texture&&); Texture& operator=(Texture&&);
Texture(const uint8_t*, glm::ivec2);
Texture(const char*, size_t, glm::ivec2); Texture(const char*, size_t, glm::ivec2);
Texture(const std::string&); Texture(const std::string&);
bool write_png(const std::string&); bool write_png(const std::string&);
void pixel_set(glm::ivec2, glm::vec4); void pixel_set(glm::ivec2, glm::vec4);
void ensure_pixels() const;
std::vector<uint8_t> pixels_get();
void pixel_line(glm::ivec2, glm::ivec2, glm::vec4); void pixel_line(glm::ivec2, glm::ivec2, glm::vec4);
void bind(GLuint = 0); void bind(GLuint = 0);
void unbind(GLuint = 0); void unbind(GLuint = 0);