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"]
path = external/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;
}
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> spritesheets{};
std::vector<std::string> names{};
for (auto& [id, spritesheet] : content.spritesheets)
spritesheets.push_back(std::format("#{} {}", id, spritesheet.path.c_str()));
return spritesheets;
names.push_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
return names;
}
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 LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
constexpr auto NULL_FORMAT = "#{} {}";
constexpr auto SPRITESHEET_FORMAT = "#%d %s";
constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
enum Type
{
@@ -254,7 +255,9 @@ namespace anm2ed::anm2
std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
std::set<int> layers_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> event_names_get();
void bake(Reference, int = 1, bool = true, bool = true);
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
};

View File

@@ -1,11 +1,15 @@
#include "canvas.h"
#include "math.h"
#include <algorithm>
#include <cmath>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "math.h"
#include "texture.h"
using namespace glm;
using namespace anm2ed::shader;
@@ -283,6 +287,21 @@ namespace anm2ed::canvas
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)
{
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)
{
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_OFFSET = 1.0f;
constexpr auto STEP = 1.0f;
constexpr auto STEP_FAST = 5.0f;
class Canvas
{
public:
@@ -43,6 +46,7 @@ namespace anm2ed::canvas
void framebuffer_set();
void framebuffer_resize_check();
void size_set(glm::vec2);
glm::vec4 pixel_read(glm::vec2, 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 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);
glm::vec2 position_translate(float&, glm::vec2&, glm::vec2);
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
{
void textures_ensure(anm2::Anm2& anm2)
{
for (auto& [id, spritesheet] : anm2.content.spritesheets)
spritesheet.texture.ensure_pixels();
}
}
bool SnapshotStack::is_empty()
{
return top == 0;
@@ -31,6 +40,7 @@ namespace anm2ed::snapshots
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};
undoStack.push(snapshot);
redoStack.clear();
@@ -40,8 +50,10 @@ namespace anm2ed::snapshots
{
if (auto current = undoStack.pop())
{
textures_ensure(anm2);
Snapshot snapshot = {anm2, reference, message};
redoStack.push(snapshot);
textures_ensure(current->anm2);
anm2 = current->anm2;
reference = current->reference;
message = current->message;
@@ -52,8 +64,10 @@ namespace anm2ed::snapshots
{
if (auto current = redoStack.pop())
{
textures_ensure(anm2);
Snapshot snapshot = {anm2, reference, message};
undoStack.push(snapshot);
textures_ensure(current->anm2);
anm2 = current->anm2;
reference = current->reference;
message = current->message;

View File

@@ -14,6 +14,8 @@ using namespace glm;
namespace anm2ed::spritesheet_editor
{
constexpr auto PIVOT_COLOR = color::PINK;
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2())
{
}
@@ -115,7 +117,7 @@ namespace anm2ed::spritesheet_editor
auto pivotTransform =
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 isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
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 isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
@@ -195,12 +197,26 @@ namespace anm2ed::spritesheet_editor
case tool::ERASE:
{
if (!spritesheet) break;
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
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 (isMouseReleased) document.change(change::FRAMES);
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:
break;
}

View File

@@ -32,12 +32,20 @@ namespace anm2ed::texture
return id != 0;
}
std::vector<uint8_t> Texture::pixels_get()
{
ensure_pixels();
return pixels;
}
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);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glBindTexture(GL_TEXTURE_2D, 0);
isPixelsDirty = false;
}
void Texture::upload(const uint8_t* data)
@@ -66,6 +74,7 @@ namespace anm2ed::texture
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
isPixelsDirty = false;
}
Texture::Texture() = default;
@@ -91,6 +100,7 @@ namespace anm2ed::texture
{
if (is_valid()) glDeleteTextures(1, &id);
id = 0;
other.ensure_pixels();
size = other.size;
filter = other.filter;
channels = other.channels;
@@ -110,6 +120,9 @@ namespace anm2ed::texture
filter = other.filter;
channels = other.channels;
pixels = std::move(other.pixels);
isPixelsDirty = other.isPixelsDirty;
other.isPixelsDirty = true;
if (!pixels.empty()) upload();
}
return *this;
}
@@ -129,9 +142,15 @@ namespace anm2ed::texture
upload(bitmap.data());
}
Texture::Texture(const uint8_t* data, ivec2 size)
{
this->size = size;
upload(data);
}
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);
stbi_image_free((void*)data);
@@ -140,6 +159,7 @@ namespace anm2ed::texture
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);
}
@@ -147,6 +167,7 @@ namespace anm2ed::texture
{
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)float_to_uint8(color.a)};
@@ -154,14 +175,23 @@ namespace anm2ed::texture
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
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)
{
auto plot = [&](ivec2 pos)
{
pixel_set(pos, color);
};
ensure_pixels();
auto plot = [&](ivec2 pos) { pixel_set(pos, color); };
int x0 = start.x;
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)
{
glActiveTexture(GL_TEXTURE0 + unit);

View File

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