svg_renderer.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /* svg_renderer.cpp - SVG "renderer"
  2. * Copyright (C) 2017 caryoscelus
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <cstdlib>
  18. #include <chrono>
  19. #include <thread>
  20. #include <fstream>
  21. #include <sys/wait.h>
  22. #include <boost/filesystem.hpp>
  23. #include <fmt/ostream.h>
  24. #include <fmt/format.h>
  25. #include <core/renderers/svg_renderer.h>
  26. #include <core/document.h>
  27. #include <core/color/color.h>
  28. #include <core/renderable.h>
  29. #include <core/node_info.h>
  30. #include <core/node/proxy_node.h>
  31. #include <core/class_init.h>
  32. #include <core/os/fork_pipe.h>
  33. #include "svg_module.h"
  34. #include <geom_helpers/knots.h>
  35. #include <morphing/morphing.h>
  36. using namespace fmt::literals;
  37. namespace rainynite::core {
  38. namespace renderers {
  39. const string svg_template =
  40. R"(<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  41. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  42. "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  43. <svg xmlns="http://www.w3.org/2000/svg"
  44. xmlns:xlink="http://www.w3.org/1999/xlink"
  45. version="1.1"
  46. width="{}px"
  47. height="{}px"
  48. viewBox="0 0 {} {}">
  49. {}
  50. {}
  51. </svg>
  52. )";
  53. struct SvgRenderer::Impl {
  54. Impl(SvgRenderer* parent_) :
  55. parent(parent_)
  56. {}
  57. ~Impl();
  58. void render(Context&& context_);
  59. void prepare_render();
  60. void render_frame(shared_ptr<Context> context);
  61. void finish_render();
  62. // string definitions(shared_ptr<Context> context) const;
  63. string frame_to_svg(shared_ptr<Context> context) const;
  64. string node_to_svg(NodeInContext nic) const;
  65. void start_png(bool force=false);
  66. void restart_png() {
  67. start_png(true);
  68. }
  69. void render_png(string const& svg, string const& png);
  70. void quit_png(bool force=false);
  71. bool finished = false;
  72. Context context;
  73. shared_ptr<Document> document;
  74. SvgRendererSettings settings;
  75. boost::filesystem::path render_path { "renders/" };
  76. boost::filesystem::path base_path;
  77. FILE* png_renderer_pipe;
  78. FILE* png_renderer_pipe_output;
  79. pid_t png_renderer_pid;
  80. size_t rendered_frames_count;
  81. bool subprocess_initialized = false;
  82. bool requested_to_stop = false;
  83. bool svgs_finished = false;
  84. bool read_thread_quit = false;
  85. std::thread read_thread;
  86. SvgRenderer* parent;
  87. };
  88. SvgRenderer::SvgRenderer() :
  89. impl(make_unique<Impl>(this))
  90. {
  91. }
  92. SvgRenderer::~SvgRenderer() {
  93. }
  94. void SvgRenderer::render(Context&& context_) {
  95. impl->render(std::move(context_));
  96. }
  97. bool SvgRenderer::is_finished() const {
  98. return impl->finished;
  99. }
  100. void SvgRenderer::stop() {
  101. impl->requested_to_stop = true;
  102. }
  103. SvgRenderer::Impl::~Impl() {
  104. read_thread_quit = true;
  105. quit_png(true);
  106. if (read_thread.joinable())
  107. read_thread.join();
  108. }
  109. void SvgRenderer::Impl::render(Context&& context_) {
  110. finished = false;
  111. rendered_frames_count = 0;
  112. std::cout << "SvgRenderer start" << std::endl;
  113. context = std::move(context_);
  114. document = context.get_document();
  115. if (!document) {
  116. throw RenderFailure("No document present");
  117. }
  118. any maybe_settings = context.get_render_settings();
  119. if (any_has_value(maybe_settings)) {
  120. settings = any_cast<SvgRendererSettings>(maybe_settings);
  121. }
  122. prepare_render();
  123. for (auto time : context.get_period()) {
  124. if (requested_to_stop)
  125. break;
  126. auto ctx = make_shared<Context>(context);
  127. ctx->set_time(time);
  128. render_frame(ctx);
  129. ++rendered_frames_count;
  130. }
  131. finish_render();
  132. std::cout << "SvgRenderer done" << std::endl;
  133. finished = true;
  134. }
  135. void SvgRenderer::Impl::prepare_render() {
  136. if (!settings.path.empty()) {
  137. base_path = settings.path;
  138. base_path.remove_filename();
  139. boost::filesystem::current_path(base_path);
  140. if (settings.render_pngs)
  141. restart_png();
  142. }
  143. if (!boost::filesystem::exists(render_path)) {
  144. if (!boost::filesystem::create_directory(render_path))
  145. throw RenderFailure("Cannot create render directory");
  146. }
  147. svgs_finished = false;
  148. if (settings.render_pngs)
  149. start_png();
  150. }
  151. void SvgRenderer::Impl::render_frame(shared_ptr<Context> context) {
  152. auto time = context->get_time();
  153. auto base_name = "renders/{:.3f}"_format(time.get_seconds());
  154. auto svg_name = base_name+".svg";
  155. std::cout << svg_name << std::endl;
  156. std::ofstream f(svg_name);
  157. auto size = document->get_size()->get(context);
  158. auto viewport_size = document->get_property_value<Geom::Point>("_svg_viewport_size", context).value_or(size);
  159. auto definitions = document->get_property_value<string>("_svg_definitions", context).value_or("");
  160. fmt::print(f, svg_template, size.x(), size.y(), viewport_size.x(), viewport_size.y(), definitions, frame_to_svg(context));
  161. f.close();
  162. if (settings.render_pngs)
  163. render_png(svg_name, (base_path / (base_name+".png")).string());
  164. parent->finished_frame()(time);
  165. }
  166. string SvgRenderer::Impl::frame_to_svg(shared_ptr<Context> context) const {
  167. return node_to_svg({document->get_root(), context});
  168. }
  169. string get_extra_style(AbstractNode const& node, shared_ptr<Context> ctx, SvgRendererSettings const& settings) {
  170. if (settings.extra_style)
  171. return node.get_property_value<string>("_svg_style", ctx).value_or("");
  172. return "";
  173. }
  174. string get_extra_style(shared_ptr<BaseValue<Shading>> value, shared_ptr<Context> ctx, SvgRendererSettings const& settings) {
  175. if (auto node = dynamic_cast<AbstractNode*>(value.get())) {
  176. return get_extra_style(*node, ctx, settings);
  177. }
  178. return "";
  179. }
  180. string node_to_svg(NodeInContext nic, SvgRendererSettings const& settings) {
  181. auto node_ptr = nic.node;
  182. auto context = nic.context;
  183. if (!node_ptr)
  184. return "";
  185. if (node_ptr->get_type() != typeid(Renderable)) {
  186. std::cerr << "ERROR: node isn't renderable" << std::endl;
  187. // throw
  188. return "";
  189. }
  190. auto node = dynamic_cast<AbstractNode*>(node_ptr.get());
  191. auto name = node_name(*node_ptr);
  192. try {
  193. return class_init::name_info<SvgRendererModule>(name)(*node, context, settings);
  194. } catch (class_init::TypeLookupError const&) {
  195. if (auto proxy = dynamic_cast<ProxyNode<Renderable>*>(node)) {
  196. string result;
  197. result = node_to_svg(proxy->get_proxy(context), settings);
  198. return result;
  199. }
  200. std::cerr << "ERROR: node type isn't supported" << std::endl;
  201. // throw
  202. return "";
  203. }
  204. }
  205. string SvgRenderer::Impl::node_to_svg(NodeInContext nic) const {
  206. return renderers::node_to_svg(nic, settings);
  207. }
  208. void SvgRenderer::Impl::finish_render() {
  209. svgs_finished = true;
  210. document.reset();
  211. if (settings.render_pngs)
  212. quit_png();
  213. requested_to_stop = false;
  214. }
  215. void SvgRenderer::Impl::start_png(bool force) {
  216. if (read_thread.joinable())
  217. read_thread.join();
  218. read_thread = std::thread([this]() {
  219. auto buff = make_unique<char[]>(256);
  220. string sbuff;
  221. size_t frames_count = 0;
  222. while ((!svgs_finished || frames_count < rendered_frames_count) && !read_thread_quit) {
  223. // TODO: replace with blocking read?
  224. std::this_thread::sleep_for(std::chrono::milliseconds(64));
  225. size_t read_chars;
  226. while ((read_chars = fread((void*)buff.get(), 1, 256, png_renderer_pipe_output)) > 0 && !read_thread_quit) {
  227. auto s = string(buff.get(), read_chars);
  228. std::cout << s;
  229. sbuff += s;
  230. size_t found;
  231. while ((found = sbuff.find("Bitmap saved as:")) != string::npos) {
  232. ++frames_count;
  233. sbuff = sbuff.substr(found+1);
  234. }
  235. }
  236. }
  237. });
  238. if (subprocess_initialized) {
  239. if (!force)
  240. return;
  241. quit_png(true);
  242. }
  243. png_renderer_pid = fork_pipe(png_renderer_pipe, png_renderer_pipe_output, {"/usr/bin/env", "inkscape", "--shell", "-z"});
  244. subprocess_initialized = true;
  245. }
  246. void SvgRenderer::Impl::render_png(string const& svg, string const& png) {
  247. fputs("{} {} {}\n"_format(svg, "-e", png).c_str(), png_renderer_pipe);
  248. fflush(png_renderer_pipe);
  249. }
  250. void SvgRenderer::Impl::quit_png(bool force) {
  251. bool quit_inkscape = force || !settings.keep_alive || requested_to_stop;
  252. if (quit_inkscape) {
  253. subprocess_initialized = false;
  254. fputs("quit\n", png_renderer_pipe);
  255. fflush(png_renderer_pipe);
  256. }
  257. // now wait until inkscape renders all the frames..
  258. if (read_thread.joinable())
  259. read_thread.join();
  260. if (quit_inkscape) {
  261. int status;
  262. waitpid(png_renderer_pid, &status, 0);
  263. }
  264. }
  265. } // namespace filters
  266. } // namespace rainynite::core