Start implementing the SVG animation library (and tool)
This commit is contained in:
parent
e1af823502
commit
4e87a67296
5 changed files with 298 additions and 11 deletions
|
@ -6,6 +6,11 @@
|
||||||
namespace Kanimaji
|
namespace Kanimaji
|
||||||
{
|
{
|
||||||
bool Animate(const std::string& source, const std::string& destination);
|
bool Animate(const std::string& source, const std::string& destination);
|
||||||
|
|
||||||
|
constexpr bool Animate(const std::string& filename)
|
||||||
|
{
|
||||||
|
return Animate(filename, filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // KANIMAJI_KANIMAJI_HPP
|
#endif // KANIMAJI_KANIMAJI_HPP
|
||||||
|
|
|
@ -1,20 +1,150 @@
|
||||||
#include "Kanimaji/Kanimaji.hpp"
|
#include "Kanimaji/Kanimaji.hpp"
|
||||||
|
#include "SVG.hpp"
|
||||||
|
|
||||||
#include <ctre.hpp>
|
#include <ctre.hpp>
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
|
#include <print>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace Kanimaji
|
namespace Kanimaji
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr std::string_view strokeBorderWidth = "4.5";
|
||||||
|
constexpr std::string_view strokeBorderColour = "#666";
|
||||||
|
|
||||||
|
constexpr std::string_view strokeUnfilledWidth = "3";
|
||||||
|
constexpr std::string_view strokeUnfilledColour = "#EEE";
|
||||||
|
constexpr std::string_view strokeFillingColour = "#F00";
|
||||||
|
constexpr std::string_view strokeFilledWidth = "3.1";
|
||||||
|
constexpr std::string_view strokeFilledColour = "#000";
|
||||||
|
|
||||||
|
constexpr std::string_view brushColour = "#F00";
|
||||||
|
constexpr std::string_view brushWidth = "5.5";
|
||||||
|
constexpr std::string_view brushBorderColour = "#666";
|
||||||
|
constexpr std::string_view brushBorderWidth = "7";
|
||||||
|
}
|
||||||
|
|
||||||
bool Animate(const std::string& source, const std::string& destination)
|
bool Animate(const std::string& source, const std::string& destination)
|
||||||
{
|
{
|
||||||
pugi::xml_document doc;
|
pugi::xml_document doc;
|
||||||
pugi::xml_parse_result res = doc.load_file(source.c_str());
|
pugi::xml_parse_result readResult = doc.load_file(source.c_str(), pugi::parse_full);
|
||||||
if (!res) {
|
if (!readResult) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pugi::xml_node comment = doc.find_child([](pugi::xml_node& node) {
|
||||||
|
return node.type() == pugi::node_comment;
|
||||||
|
});
|
||||||
|
std::string text = comment.value();
|
||||||
|
text.append(
|
||||||
|
"\n"
|
||||||
|
"===============================================================================\n"
|
||||||
|
"\n"
|
||||||
|
"This file has been modified by the Kanimaji tool\n"
|
||||||
|
"avilable here: (TODO)\n\n"
|
||||||
|
"The Kanimaji tool is based on the original kanimaji.py\n"
|
||||||
|
"script available here: https://github.com/maurimo/kanimaji\n\n"
|
||||||
|
"Modifications (compared to the original KanjiVG file):\n"
|
||||||
|
"* The stroke numbers were removed\n"
|
||||||
|
"* Special Styles section (and accompanying <g> and <use> tags) were generated\n"
|
||||||
|
);
|
||||||
|
comment.set_value(text);
|
||||||
|
|
||||||
|
pugi::xml_node svg = doc.child("svg");
|
||||||
|
pugi::xml_attribute xmlns = svg.attribute("xmlns");
|
||||||
|
svg.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink";
|
||||||
|
svg.append_attribute("xlink:used") = "";
|
||||||
|
|
||||||
return false;
|
svg.remove_child(svg.find_child([](pugi::xml_node& node) {
|
||||||
|
return std::string_view(node.attribute("id").as_string()).contains("StrokeNumbers");
|
||||||
|
}));
|
||||||
|
|
||||||
|
pugi::xml_node paths = svg.find_child([](pugi::xml_node& node) {
|
||||||
|
return std::string_view(node.attribute("id").as_string()).contains("StrokePaths");
|
||||||
|
});
|
||||||
|
std::string baseId = paths.attribute("id").as_string();
|
||||||
|
baseId.erase(0, baseId.find('_') + 1);
|
||||||
|
std::println("{}", baseId);
|
||||||
|
|
||||||
|
// 1st pass for getting information
|
||||||
|
float totalLength = 50;
|
||||||
|
for (const auto& path : svg.select_nodes("//path")) {
|
||||||
|
std::println("{}", path.node().attribute("d").as_string());
|
||||||
|
std::string_view d = path.node().attribute("d").as_string();
|
||||||
|
totalLength += SVG::Path(d).Length();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string styles = "/* Styles autogenerated by Kanimaji, please do not edit manually. */";
|
||||||
|
|
||||||
|
pugi::xml_node background = svg.append_child("g");
|
||||||
|
background.append_attribute("id") = "kvg:Kanimaji_Background_" + baseId;
|
||||||
|
background.append_attribute("style") = std::format(
|
||||||
|
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
|
||||||
|
strokeUnfilledColour, strokeUnfilledWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
pugi::xml_node brushBackground = svg.append_child("g");
|
||||||
|
brushBackground.append_attribute("id") = "kvg:Kanimaji_BrushBackground_" + baseId;
|
||||||
|
brushBackground.append_attribute("style") = std::format(
|
||||||
|
"fill:none;stroke:{};stroke-width:{},stroke-linecap:round;stroke-linejoin:round;",
|
||||||
|
brushBorderColour, brushBorderWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
pugi::xml_node animation = svg.append_child("g");
|
||||||
|
animation.append_attribute("id") = "kvg:Kanimaji_Animation_" + baseId;
|
||||||
|
animation.append_attribute("style") = std::format(
|
||||||
|
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
|
||||||
|
strokeFilledColour, strokeFilledWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
pugi::xml_node brush = svg.append_child("g");
|
||||||
|
brush.append_attribute("id") = "kvg:Kanimaji_Brush_" + baseId;
|
||||||
|
brush.append_attribute("style") = std::format(
|
||||||
|
"fill:none;stroke:{};stroke-width:{};stroke-linecap:round;stroke-linejoin:round;",
|
||||||
|
brushColour, brushWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2nd pass to prepare the CSS
|
||||||
|
for (const auto& xpath : svg.select_nodes("//path")) {
|
||||||
|
pugi::xml_node path = xpath.node();
|
||||||
|
std::string id = path.attribute("id").as_string();
|
||||||
|
std::string css = id;
|
||||||
|
css.replace(css.find(":"), 1, "\\\\3a ");
|
||||||
|
|
||||||
|
// Generate the actual styles
|
||||||
|
// - A few simplifications for now
|
||||||
|
// - Do not use the path lengths and appropriate scaled time,
|
||||||
|
// use a simplified version based solely on the stroke number.
|
||||||
|
|
||||||
|
pugi::xml_node useBackground = background.append_child("use");
|
||||||
|
useBackground.append_attribute("id") = id + "-background";
|
||||||
|
useBackground.append_attribute("xlink:href") = "#" + id;
|
||||||
|
|
||||||
|
pugi::xml_node useBrushBackground = brushBackground.append_child("use");
|
||||||
|
useBrushBackground.append_attribute("id") = id + "-brush-background";
|
||||||
|
useBrushBackground.append_attribute("xlink:href") = "#" + id;
|
||||||
|
|
||||||
|
pugi::xml_node useAnimation = animation.append_child("use");
|
||||||
|
useAnimation.append_attribute("id") = id + "-animation";
|
||||||
|
useAnimation.append_attribute("xlink:href") = "#" + id;
|
||||||
|
|
||||||
|
pugi::xml_node useBrush = brush.append_child("use");
|
||||||
|
useBrush.append_attribute("id") = id + "-brush";
|
||||||
|
useBrush.append_attribute("xlink:href") = "#" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is how we add styles - prepare the whole thing in a separate buffer first.
|
||||||
|
pugi::xml_node style = svg.prepend_child("style");
|
||||||
|
style.append_attribute("id") = "kvg:Kanimaji_Style";
|
||||||
|
style.append_child(pugi::node_pcdata).set_value(styles);
|
||||||
|
|
||||||
|
bool writeResult = doc.save_file(destination.c_str());
|
||||||
|
if (!writeResult) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
117
Libraries/Kanimaji/Source/SVG.cpp
Normal file
117
Libraries/Kanimaji/Source/SVG.cpp
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
#include "SVG.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace Kanimaji::SVG
|
||||||
|
{
|
||||||
|
struct Vec2
|
||||||
|
{
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
|
||||||
|
friend Vec2 operator+(const Vec2& u, const Vec2& v)
|
||||||
|
{
|
||||||
|
return Vec2 {
|
||||||
|
.x = u.x + v.x,
|
||||||
|
.y = u.y + v.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Path::Element
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Element() = default;
|
||||||
|
|
||||||
|
virtual double Length(Vec2 origin) const = 0;
|
||||||
|
virtual Vec2 Endpoint(Vec2 origin) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class ParseError : public Error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ParseError(std::size_t current, std::string_view message)
|
||||||
|
: Error(std::format("[At: {}] {}", current, message))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Move : public Path::Element
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Move(bool relative, Vec2 move)
|
||||||
|
: mRelative(relative), mMove(move)
|
||||||
|
{}
|
||||||
|
|
||||||
|
double Length(Vec2 origin) const override
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Endpoint(Vec2 origin) const override
|
||||||
|
{
|
||||||
|
return mRelative ? origin + mMove : mMove;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool mRelative; // 7B padding, oof
|
||||||
|
Vec2 mMove;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CubicBezier : public Path::Element
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CubicBezier(bool relative, Vec2 control1, Vec2 control2, Vec2 end)
|
||||||
|
: mRelative(relative), mControl1(control1), mControl2(control2), mEnd(end)
|
||||||
|
{}
|
||||||
|
|
||||||
|
double Length(Vec2 origin) const override
|
||||||
|
{
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Endpoint(Vec2 origin) const override
|
||||||
|
{
|
||||||
|
return mEnd;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool mRelative; // 7B padding, oof
|
||||||
|
Vec2 mControl1;
|
||||||
|
Vec2 mControl2;
|
||||||
|
Vec2 mEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ElementPtr = std::unique_ptr<Path::Element>;
|
||||||
|
void Parse(std::string_view definition, std::vector<ElementPtr>& elements)
|
||||||
|
{
|
||||||
|
std::size_t current = 0;
|
||||||
|
|
||||||
|
while (current < definition.size()) {
|
||||||
|
switch (definition[current]) {
|
||||||
|
case 'M': [[fallthrough]];
|
||||||
|
case 'm':
|
||||||
|
// ParseMove
|
||||||
|
break;
|
||||||
|
case 'C': [[fallthrough]];
|
||||||
|
case 'c':
|
||||||
|
// ParseCubicBezier
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ParseError(current, "Unrecognized character");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Path::Path(std::string_view definition)
|
||||||
|
{
|
||||||
|
Parse(definition, mSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path::~Path() = default;
|
||||||
|
|
||||||
|
double Path::Length() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
35
Libraries/Kanimaji/Source/SVG.hpp
Normal file
35
Libraries/Kanimaji/Source/SVG.hpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef KANIMAJI_SVG_HPP
|
||||||
|
#define KANIMAJI_SVG_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Kanimaji::SVG
|
||||||
|
{
|
||||||
|
class Error : public std::runtime_error
|
||||||
|
{
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Path
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Element;
|
||||||
|
public:
|
||||||
|
Path(std::string_view definition);
|
||||||
|
|
||||||
|
Path(const Path&) = delete;
|
||||||
|
Path& operator=(const Path&) = delete;
|
||||||
|
Path(Path&&) = default;
|
||||||
|
Path& operator=(Path&&) = default;
|
||||||
|
~Path();
|
||||||
|
|
||||||
|
double Length() const;
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<Element>> mSegments;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // KANIMAJI_SVG_HPP
|
|
@ -4,15 +4,15 @@
|
||||||
|
|
||||||
int main(const int argc, const char *argv[])
|
int main(const int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
if (argc != 3) {
|
//if (argc != 3) {
|
||||||
std::println("Usage: KanimajiTool SOURCE DESTINATION\n\n"
|
// std::println("Usage: KanimajiTool SOURCE DESTINATION\n\n"
|
||||||
"SOURCE - Path to a file from the KanjiVG dataset.\n"
|
// "SOURCE - Path to a file from the KanjiVG dataset.\n"
|
||||||
"DESTINATION - Path to write the animated SVG.\n"
|
// "DESTINATION - Path to write the animated SVG.\n"
|
||||||
);
|
// );
|
||||||
return 1;
|
// return 1;
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (!Kanimaji::Animate(argv[1], argv[2])) {
|
if (!Kanimaji::Animate("084b8.svg", "084b8-out.svg")) {
|
||||||
std::println("Could not animate the specified file.");
|
std::println("Could not animate the specified file.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue