Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca-svg.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
43#ifndef CA_SVG_H
44#define CA_SVG_H
45
46#include <array>
47#include <cmath>
48#include <concepts>
49#include <cstdint>
50#include <filesystem>
51#include <fstream>
52#include <limits>
53#include <ostream>
54#include <sstream>
55#include <string>
56#include <utility>
57
58#include <ah-errors.H>
59#include <tpl_array.H>
60
61#include <ca-frame-stream.H>
62#include <ca-io.H>
63#include <tpl_ca_concepts.H>
64#include <tpl_ca_hex_lattice.H>
66
67namespace Aleph {
68namespace CA {
69
76{
77 double cell_size = 18.0;
78 double stroke_width = 0.75;
79 bool draw_grid = true;
80 bool omit_dead_cells = false;
81 RGB8 stroke_colour{190, 190, 190};
82};
83
84namespace ca_svg_detail {
85
86[[nodiscard]] inline std::string rgb_hex(const RGB8 c)
87{
88 constexpr char digits[] = "0123456789abcdef";
89 std::string out = "#000000";
90 out[1] = digits[c.r >> 4];
91 out[2] = digits[c.r & 0x0f];
92 out[3] = digits[c.g >> 4];
93 out[4] = digits[c.g & 0x0f];
94 out[5] = digits[c.b >> 4];
95 out[6] = digits[c.b & 0x0f];
96 return out;
97}
98
99struct Bounds
100{
101 double min_x = std::numeric_limits<double>::infinity();
102 double min_y = std::numeric_limits<double>::infinity();
103 double max_x = -std::numeric_limits<double>::infinity();
104 double max_y = -std::numeric_limits<double>::infinity();
105
106 void include(const double x, const double y) noexcept
107 {
108 min_x = std::min(min_x, x);
109 min_y = std::min(min_y, y);
110 max_x = std::max(max_x, x);
111 max_y = std::max(max_y, y);
112 }
113};
114
115inline void write_svg_header(std::ostream &out, const Bounds &b, const SVG_Render_Options &opts)
116{
117 const double pad = opts.draw_grid ? opts.stroke_width : 0.0;
118 const double x = b.min_x - pad;
119 const double y = b.min_y - pad;
120 const double w = std::max(1.0, b.max_x - b.min_x + 2.0 * pad);
121 const double h = std::max(1.0, b.max_y - b.min_y + 2.0 * pad);
122 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
123 out << "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" << x << ' ' << y << ' ' << w << ' '
124 << h << "\" width=\"" << w << "\" height=\"" << h << "\">\n";
125}
126
127inline void write_style(std::ostream &out, const SVG_Render_Options &opts)
128{
129 if (opts.draw_grid)
130 out << " stroke=\"" << rgb_hex(opts.stroke_colour) << "\" stroke-width=\"" << opts.stroke_width
131 << '"';
132 else
133 out << " stroke=\"none\"";
134}
135
136inline void write_points(std::ostream &out, const Array<std::array<double, 2>> &pts)
137{
138 for (std::size_t i = 0; i < pts.size(); ++i)
139 {
140 if (i != 0)
141 out << ' ';
142 out << pts(i)[0] << ',' << pts(i)[1];
143 }
144}
145
147{
148 const auto centre = axial_to_pixel_pointy(a, radius);
150 for (int k = 0; k < 6; ++k)
151 {
152 constexpr double pi = 3.14159265358979323846;
153 const double angle = (60.0 * static_cast<double>(k) - 30.0) * pi / 180.0;
154 pts.append(std::array<double, 2>{centre[0] + radius * std::cos(angle),
155 centre[1] + radius * std::sin(angle)});
156 }
157 return pts;
158}
159
161{
162 constexpr double sqrt3 = 1.7320508075688772;
163 const double h = side * sqrt3 / 2.0;
164 const auto centre = tri_pixel_centre(c, side);
165 // Built with explicit appends: Array(3) preallocates exact capacity,
166 // and the three vertices read clearer than a braced list-init.
167 using Pt = std::array<double, 2>;
168 Array<Pt> pts(3);
169 if (tri_is_up(c))
170 {
171 pts.append(Pt{centre[0], centre[1] - (2.0 * h / 3.0)});
172 pts.append(Pt{centre[0] - side / 2.0, centre[1] + h / 3.0});
173 pts.append(Pt{centre[0] + side / 2.0, centre[1] + h / 3.0});
174 }
175 else
176 {
177 pts.append(Pt{centre[0], centre[1] + (2.0 * h / 3.0)});
178 pts.append(Pt{centre[0] - side / 2.0, centre[1] - h / 3.0});
179 pts.append(Pt{centre[0] + side / 2.0, centre[1] - h / 3.0});
180 }
181 return pts;
182}
183
184} // namespace ca_svg_detail
185
204template <typename Lattice, typename Palette>
205inline void render_lattice_svg(std::ostream &out,
206 const Lattice &frame,
208 const SVG_Render_Options &opts = {},
209 const typename Lattice::state_type &dead_state
210 = typename Lattice::state_type{})
211 requires requires(Palette &&p, const typename Lattice::state_type &v) {
212 { p(v) } -> std::convertible_to<RGB8>;
213 }
214{
215 static_assert(LatticeLike<Lattice>, "render_lattice_svg requires a LatticeLike frame");
216 static_assert(Lattice::rank == 2, "render_lattice_svg requires a rank-2 frame");
217 ah_domain_error_if(opts.cell_size <= 0.0) << "render_lattice_svg: cell_size must be positive";
218
219 ca_svg_detail::Bounds b;
220 if constexpr (HexRenderable<Lattice>)
221 {
222 for (ca_size_t r = 0; r < frame.rows(); ++r)
223 for (ca_size_t q = 0; q < frame.cols(); ++q)
224 for (const auto &p : ca_svg_detail::hex_points(
225 Hex_Axial{static_cast<ca_index_t>(q), static_cast<ca_index_t>(r)}, opts.cell_size))
226 b.include(p[0], p[1]);
227 // Empty hex grid → seed a degenerate but finite box so the SVG
228 // header does not serialise inf/nan.
229 if (frame.rows() == 0 or frame.cols() == 0)
230 {
231 b.include(0.0, 0.0);
232 b.include(static_cast<double>(frame.cols()) * opts.cell_size,
233 static_cast<double>(frame.rows()) * opts.cell_size);
234 }
235 }
236 else if constexpr (TriRenderable<Lattice>)
237 {
238 for (ca_size_t r = 0; r < frame.rows(); ++r)
239 for (ca_size_t c = 0; c < frame.cols(); ++c)
240 for (const auto &p : ca_svg_detail::tri_points(
241 Tri_Coord{static_cast<ca_index_t>(r), static_cast<ca_index_t>(c)}, opts.cell_size))
242 b.include(p[0], p[1]);
243 if (frame.rows() == 0 or frame.cols() == 0)
244 {
245 b.include(0.0, 0.0);
246 b.include(static_cast<double>(frame.cols()) * opts.cell_size,
247 static_cast<double>(frame.rows()) * opts.cell_size);
248 }
249 }
250 else
251 {
252 b.include(0.0, 0.0);
253 b.include(static_cast<double>(frame.size(1)) * opts.cell_size,
254 static_cast<double>(frame.size(0)) * opts.cell_size);
255 }
256
258 if constexpr (HexRenderable<Lattice>)
259 {
260 for (ca_size_t r = 0; r < frame.rows(); ++r)
261 for (ca_size_t q = 0; q < frame.cols(); ++q)
262 {
263 const Hex_Axial a{static_cast<ca_index_t>(q), static_cast<ca_index_t>(r)};
264 const auto state = frame.at_axial(a);
265 if (opts.omit_dead_cells and state == dead_state)
266 continue;
267 out << " <polygon points=\"";
269 out << "\" fill=\"" << ca_svg_detail::rgb_hex(palette(state)) << '"';
271 out << "/>\n";
272 }
273 }
274 else if constexpr (TriRenderable<Lattice>)
275 {
276 for (ca_size_t r = 0; r < frame.rows(); ++r)
277 for (ca_size_t c = 0; c < frame.cols(); ++c)
278 {
279 const Tri_Coord tc{static_cast<ca_index_t>(r), static_cast<ca_index_t>(c)};
280 const auto state = frame.at_tri(tc);
281 if (opts.omit_dead_cells and state == dead_state)
282 continue;
283 out << " <polygon points=\"";
285 out << "\" fill=\"" << ca_svg_detail::rgb_hex(palette(state)) << '"';
287 out << "/>\n";
288 }
289 }
290 else
291 {
292 using coord_t = typename Lattice::coord_type;
293 for (ca_size_t r = 0; r < frame.size(0); ++r)
294 for (ca_size_t c = 0; c < frame.size(1); ++c)
295 {
296 const auto state
297 = frame.at(coord_t{static_cast<ca_index_t>(r), static_cast<ca_index_t>(c)});
298 if (opts.omit_dead_cells and state == dead_state)
299 continue;
300 out << " <rect x=\"" << static_cast<double>(c) * opts.cell_size << "\" y=\""
301 << static_cast<double>(r) * opts.cell_size << "\" width=\"" << opts.cell_size
302 << "\" height=\"" << opts.cell_size << "\" fill=\""
303 << ca_svg_detail::rgb_hex(palette(state)) << '"';
305 out << "/>\n";
306 }
307 }
308 out << "</svg>\n";
309 ah_runtime_error_if(not out) << "render_lattice_svg: output stream failed";
310}
311
321template <typename Lattice>
322inline void render_lattice_svg(std::ostream &out,
323 const Lattice &frame,
324 const SVG_Render_Options &opts = {},
325 const typename Lattice::state_type &dead_state
326 = typename Lattice::state_type{})
327{
329 dead_state);
330}
331
343template <typename Lattice, typename Palette>
344[[nodiscard]] inline std::string render_lattice_svg_string(const Lattice &frame,
346 const SVG_Render_Options &opts = {},
347 const typename Lattice::state_type
349 = typename Lattice::state_type{})
350 requires requires(Palette &&p, const typename Lattice::state_type &v) {
351 { p(v) } -> std::convertible_to<RGB8>;
352 }
353{
354 std::ostringstream out;
355 render_lattice_svg(out, frame, std::forward<Palette>(palette), opts, dead_state);
356 return out.str();
357}
358
363template <typename Palette>
365{
366 std::filesystem::path pattern_;
369 std::size_t zero_pad_ = 6;
370
371public:
378 Svg_Frame_Sink(std::filesystem::path path_pattern,
381 const std::size_t zero_pad = 6)
382 : pattern_(std::move(path_pattern)), palette_(std::move(palette)), opts_(std::move(opts)),
383 zero_pad_(zero_pad)
384 {}
385
400 template <typename Lattice>
401 void accept(const std::size_t step, const Lattice &frame)
402 {
403 accept(step, frame, typename Lattice::state_type{});
404 }
405
419 template <typename Lattice>
420 void accept(const std::size_t step,
421 const Lattice &frame,
422 const typename Lattice::state_type &dead_state)
423 {
424 const std::filesystem::path path = format_step_path(pattern_, step, zero_pad_);
425 if (const auto parent = path.parent_path(); not parent.empty())
426 std::filesystem::create_directories(parent);
427 std::ofstream out(path);
428 ah_runtime_error_if(not out) << "Svg_Frame_Sink::accept: cannot open '" << path.string() << "'";
430 }
431
433 static void flush() {}
434};
435
436template <typename Palette>
437Svg_Frame_Sink(std::filesystem::path, Palette, SVG_Render_Options = {}, std::size_t = 6)
439
440} // namespace CA
441} // namespace Aleph
442
443#endif // CA_SVG_H
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:271
long double h
Definition btreepic.C:154
long double w
Definition btreepic.C:153
size_t size_t int32_t * out
Definition ca-c-api.h:120
Directory-backed frame sinks for CA trajectories.
File-format readers and writers for cellular-automata frames.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
Lattice that adds boundary-aware access on top of a storage.
typename Storage::state_type state_type
typename Storage::coord_type coord_type
static constexpr std::size_t rank
ca_size_t size() const noexcept
state_type at(const coord_type &c) const
Strict access: throws if c is out of range.
Path-pattern sink that writes one SVG file per accepted frame.
Definition ca-svg.H:365
void accept(const std::size_t step, const Lattice &frame)
Write one SVG frame using the default dead state.
Definition ca-svg.H:401
std::filesystem::path pattern_
Definition ca-svg.H:366
void accept(const std::size_t step, const Lattice &frame, const typename Lattice::state_type &dead_state)
Write one SVG frame, threading the lattice-specific dead state.
Definition ca-svg.H:420
static void flush()
SVG frames are written eagerly; this is a no-op.
Definition ca-svg.H:433
SVG_Render_Options opts_
Definition ca-svg.H:368
Svg_Frame_Sink(std::filesystem::path path_pattern, Palette palette, SVG_Render_Options opts={}, const std::size_t zero_pad=6)
Build an SVG frame sink.
Definition ca-svg.H:378
std::size_t zero_pad_
Definition ca-svg.H:369
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
static mpfr_t y
Definition mpfr_mul_d.c:3
void write_svg_header(std::ostream &out, const Bounds &b, const SVG_Render_Options &opts)
Definition ca-svg.H:115
Array< std::array< double, 2 > > hex_points(Hex_Axial a, const double radius)
Definition ca-svg.H:146
void write_points(std::ostream &out, const Array< std::array< double, 2 > > &pts)
Definition ca-svg.H:136
Array< std::array< double, 2 > > tri_points(Tri_Coord c, const double side)
Definition ca-svg.H:160
std::string rgb_hex(const RGB8 c)
Definition ca-svg.H:86
void write_style(std::ostream &out, const SVG_Render_Options &opts)
Definition ca-svg.H:127
std::array< double, 2 > tri_pixel_centre(const Tri_Coord &c, const double side) noexcept
Cartesian centre of a triangle in (i, j) units.
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
std::array< double, 2 > axial_to_pixel_pointy(Hex_Axial a, double radius) noexcept
Pointy-top pixel coordinates of an axial cell.
std::filesystem::path format_step_path(const std::filesystem::path &pattern, const std::size_t step, const std::size_t zero_pad=6)
Format a step-indexed file path.
void render_lattice_svg(std::ostream &out, const Lattice &frame, Palette &&palette, const SVG_Render_Options &opts={}, const typename Lattice::state_type &dead_state=typename Lattice::state_type{})
Render a supported rank-2 CA frame as SVG.
Definition ca-svg.H:205
constexpr bool tri_is_up(const Tri_Coord &c) noexcept
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
std::string render_lattice_svg_string(const Lattice &frame, Palette &&palette, const SVG_Render_Options &opts={}, const typename Lattice::state_type &dead_state=typename Lattice::state_type{})
Return a frame rendered as SVG.
Definition ca-svg.H:344
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
STL namespace.
Axial integer coordinates (q, r) of a hex cell.
RGB byte triplet used by PPM exporters.
Definition ca-io.H:85
std::uint8_t g
green channel
Definition ca-io.H:87
std::uint8_t b
blue channel
Definition ca-io.H:88
std::uint8_t r
red channel
Definition ca-io.H:86
SVG rendering options.
Definition ca-svg.H:76
double stroke_width
grid stroke width
Definition ca-svg.H:78
RGB8 stroke_colour
grid colour
Definition ca-svg.H:81
double cell_size
logical cell size in SVG units
Definition ca-svg.H:77
bool draw_grid
draw cell borders
Definition ca-svg.H:79
bool omit_dead_cells
skip cells equal to dead_state
Definition ca-svg.H:80
Coordinates of a triangular cell as (i, j) integers.
void include(const double x, const double y) noexcept
Definition ca-svg.H:106
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
C++20 concepts for the Cellular Automata module.
Hexagonal lattice with axial / offset / cube coordinate conversions and a TikZ-compatible pixel mappi...
Triangular lattice with up/down parity helpers and pixel mapping suitable for visualisation.