Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca-gif.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
42#ifndef CA_GIF_H
43#define CA_GIF_H
44
45#include <algorithm>
46#include <concepts>
47#include <cstdint>
48#include <filesystem>
49#include <fstream>
50#include <limits>
51#include <ostream>
52#include <string>
53#include <utility>
54
55#include <ah-errors.H>
56#include <tpl_array.H>
57
58#include <ca-io.H>
59#include <tpl_ca_concepts.H>
60
61namespace Aleph {
62namespace CA {
63
66{
67 unsigned delay_cs = 6;
68 bool loop = true;
69};
70
71namespace ca_gif_detail {
72
79
80inline void put_le16(std::ostream &out, const std::uint16_t value)
81{
82 out.put(static_cast<char>(value & 0xffu));
83 out.put(static_cast<char>((value >> 8) & 0xffu));
84}
85
86[[nodiscard]] inline std::uint32_t dist2(const RGB8 a, const RGB8 b) noexcept
87{
88 const int dr = static_cast<int>(a.r) - static_cast<int>(b.r);
89 const int dg = static_cast<int>(a.g) - static_cast<int>(b.g);
90 const int db = static_cast<int>(a.b) - static_cast<int>(b.b);
91 return static_cast<std::uint32_t>(dr * dr + dg * dg + db * db);
92}
93
94[[nodiscard]] inline std::uint8_t palette_index(Array<RGB8> &palette, const RGB8 c)
95{
96 for (std::size_t i = 0; i < palette.size(); ++i)
97 if (palette(i) == c)
98 return static_cast<std::uint8_t>(i);
99 if (palette.size() < 256)
100 {
101 palette.append(c);
102 return static_cast<std::uint8_t>(palette.size() - 1);
103 }
104
105 std::size_t best = 0;
106 std::uint32_t best_d = std::numeric_limits<std::uint32_t>::max();
107 for (std::size_t i = 0; i < palette.size(); ++i)
108 {
109 const std::uint32_t d = dist2(palette(i), c);
110 if (d < best_d)
111 {
112 best = i;
113 best_d = d;
114 }
115 }
116 return static_cast<std::uint8_t>(best);
117}
118
123[[nodiscard]] inline std::uint8_t lookup_palette_index(const Array<RGB8> &palette,
124 const RGB8 c) noexcept
125{
126 for (std::size_t i = 0; i < palette.size(); ++i)
127 if (palette(i) == c)
128 return static_cast<std::uint8_t>(i);
129
130 std::size_t best = 0;
131 std::uint32_t best_d = std::numeric_limits<std::uint32_t>::max();
132 for (std::size_t i = 0; i < palette.size(); ++i)
133 {
134 const std::uint32_t d = dist2(palette(i), c);
135 if (d < best_d)
136 {
137 best = i;
138 best_d = d;
139 }
140 }
141 return static_cast<std::uint8_t>(best);
142}
143
144[[nodiscard]] inline unsigned ceil_log2(std::size_t n)
145{
146 unsigned bits = 0;
147 std::size_t p = 1;
148 while (p < n)
149 {
150 p <<= 1;
151 ++bits;
152 }
153 return bits;
154}
155
157{
159 std::uint32_t buffer_ = 0;
160 unsigned bits_ = 0;
161
162public:
163 void write(const unsigned code, const unsigned width)
164 {
165 buffer_ |= static_cast<std::uint32_t>(code) << bits_;
166 bits_ += width;
167 while (bits_ >= 8)
168 {
169 bytes_.append(static_cast<std::uint8_t>(buffer_ & 0xffu));
170 buffer_ >>= 8;
171 bits_ -= 8;
172 }
173 }
174
176 {
177 if (bits_ != 0)
178 bytes_.append(static_cast<std::uint8_t>(buffer_ & 0xffu));
179 return std::move(bytes_);
180 }
181};
182
184 const unsigned min_code_size)
185{
186 const unsigned clear = 1u << min_code_size;
187 const unsigned end = clear + 1u;
188 unsigned code_size = min_code_size + 1u;
189 unsigned next_code = end + 1u;
190 bool first = true;
191
192 Bit_Writer bits;
193 bits.write(clear, code_size);
194 for (std::size_t i = 0; i < indices.size(); ++i)
195 {
196 bits.write(indices(i), code_size);
197 if (first)
198 {
199 first = false;
200 continue;
201 }
202 if (next_code < 4096u)
203 {
204 ++next_code;
205 if (next_code == (1u << code_size) and code_size < 12u)
206 ++code_size;
207 }
208 }
209 bits.write(end, code_size);
210 return bits.finish();
211}
212
213inline void write_subblocks(std::ostream &out, const Array<std::uint8_t> &bytes)
214{
215 std::size_t pos = 0;
216 while (pos < bytes.size())
217 {
218 const std::size_t n = std::min<std::size_t>(255, bytes.size() - pos);
219 out.put(static_cast<char>(n));
220 out.write(reinterpret_cast<const char *>(&bytes.base() + pos), static_cast<std::streamsize>(n));
221 pos += n;
222 }
223 out.put('\0');
224}
225
227{
229 palette.reserve(256);
230 for (const Frame &f : frames)
231 for (std::size_t i = 0; i < f.pixels.size(); ++i)
232 (void) palette_index(palette, f.pixels(i));
233 if (palette.is_empty())
234 palette.append(RGB8{255, 255, 255});
235 if (palette.size() == 1)
236 palette.append(palette(0) == RGB8{0, 0, 0} ? RGB8{255, 255, 255} : RGB8{0, 0, 0});
237 return palette;
238}
239
240} // namespace ca_gif_detail
241
253inline void write_gif(std::ostream &out,
254 const Array<ca_gif_detail::Frame> &frames,
255 const GIF_Write_Options &opts = {})
256{
257 ah_domain_error_if(frames.is_empty()) << "write_gif: at least one frame is required";
258 const ca_size_t width = frames(0).width;
259 const ca_size_t height = frames(0).height;
260 ah_domain_error_if(width == 0 or height == 0) << "write_gif: dimensions must be non-zero";
261 for (const auto &f : frames)
262 ah_domain_error_if(f.width != width or f.height != height or f.pixels.size() != width * height)
263 << "write_gif: all frames must have identical dimensions";
264
266 const unsigned palette_bits = std::max(1u, ca_gif_detail::ceil_log2(palette.size()));
267 const std::size_t table_size = std::size_t{1} << palette_bits;
268 while (palette.size() < table_size) // pad table to a power of two
269 palette.append(RGB8{0, 0, 0});
270 const unsigned min_code_size = std::max(2u, palette_bits);
271
272 out.write("GIF89a", 6);
273 ca_gif_detail::put_le16(out, static_cast<std::uint16_t>(width));
274 ca_gif_detail::put_le16(out, static_cast<std::uint16_t>(height));
275 const unsigned gct_code = palette_bits - 1u;
276 out.put(static_cast<char>(0x80u | 0x70u | gct_code));
277 out.put('\0');
278 out.put('\0');
279 for (std::size_t i = 0; i < palette.size(); ++i)
280 {
281 const RGB8 c = palette(i);
282 out.put(static_cast<char>(c.r));
283 out.put(static_cast<char>(c.g));
284 out.put(static_cast<char>(c.b));
285 }
286
287 if (opts.loop)
288 {
289 out.put(0x21);
290 out.put(0xff);
291 out.put(0x0b);
292 out.write("NETSCAPE2.0", 11);
293 out.put(0x03);
294 out.put(0x01);
296 out.put('\0');
297 }
298
299 for (const auto &frame : frames)
300 {
302 indices.reserve(frame.pixels.size());
303 // The palette is finalised (build_palette already saw every
304 // colour); use the read-only lookup so we don't pay one
305 // palette copy per frame.
306 for (std::size_t i = 0; i < frame.pixels.size(); ++i)
307 indices.append(ca_gif_detail::lookup_palette_index(palette, frame.pixels(i)));
308
309 out.put(0x21);
310 out.put(0xf9);
311 out.put(0x04);
312 out.put(0x00);
313 ca_gif_detail::put_le16(out, static_cast<std::uint16_t>(opts.delay_cs));
314 out.put(0x00);
315 out.put(0x00);
316
317 out.put(0x2c);
320 ca_gif_detail::put_le16(out, static_cast<std::uint16_t>(width));
321 ca_gif_detail::put_le16(out, static_cast<std::uint16_t>(height));
322 out.put(0x00);
323 out.put(static_cast<char>(min_code_size));
325 }
326
327 out.put(';');
328 ah_runtime_error_if(not out) << "write_gif: output stream failed";
329}
330
335template <typename Mapper>
337{
338 std::filesystem::path path_;
342
343public:
349 Gif_Frame_Sink(std::filesystem::path path, Mapper mapper, GIF_Write_Options opts = {})
350 : path_(std::move(path)), mapper_(std::move(mapper)), opts_(opts)
351 {}
352
358 template <typename Lattice>
359 void accept(const std::size_t step, const Lattice &frame)
360 {
361 (void) step;
362 static_assert(LatticeLike<Lattice>, "Gif_Frame_Sink requires LatticeLike frames");
363 static_assert(Lattice::rank == 2, "Gif_Frame_Sink requires rank-2 frames");
364 using coord_t = typename Lattice::coord_type;
365
367 f.width = frame.size(1);
368 f.height = frame.size(0);
369 f.pixels.reserve(f.width * f.height);
370 for (ca_size_t r = 0; r < frame.size(0); ++r)
371 for (ca_size_t c = 0; c < frame.size(1); ++c)
372 f.pixels.append(
373 mapper_(frame.at(coord_t{static_cast<ca_index_t>(r), static_cast<ca_index_t>(c)})));
374 if (not frames_.is_empty())
375 ah_domain_error_if(frames_(0).width != f.width or frames_(0).height != f.height)
376 << "Gif_Frame_Sink::accept: frame dimensions changed";
377 frames_.append(std::move(f));
378 }
379
383 void flush() const
384 {
385 if (const auto parent = path_.parent_path(); not parent.empty())
386 std::filesystem::create_directories(parent);
387 std::ofstream out(path_, std::ios::binary);
388 ah_runtime_error_if(not out) << "Gif_Frame_Sink::flush: cannot open '" << path_.string() << "'";
389 write_gif(out, frames_, opts_);
390 }
391
395 [[nodiscard]] std::size_t size() const noexcept
396 {
397 return frames_.size();
398 }
399};
400
401template <typename Mapper>
403
404} // namespace CA
405} // namespace Aleph
406
407#endif // CA_GIF_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
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
File-format readers and writers for cellular-automata frames.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & base()
Return a reference to the first element of array.
Definition tpl_array.H:326
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Sink that buffers frames and writes an animated GIF on flush.
Definition ca-gif.H:337
Gif_Frame_Sink(std::filesystem::path path, Mapper mapper, GIF_Write_Options opts={})
Build a GIF frame sink.
Definition ca-gif.H:349
Array< ca_gif_detail::Frame > frames_
Definition ca-gif.H:341
void accept(const std::size_t step, const Lattice &frame)
Collect one frame.
Definition ca-gif.H:359
GIF_Write_Options opts_
Definition ca-gif.H:340
std::filesystem::path path_
Definition ca-gif.H:338
std::size_t size() const noexcept
Return buffered frame count.
Definition ca-gif.H:395
void flush() const
Write the animated GIF.
Definition ca-gif.H:383
Lattice that adds boundary-aware access on top of a storage.
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.
void write(const unsigned code, const unsigned width)
Definition ca-gif.H:163
Array< std::uint8_t > finish()
Definition ca-gif.H:175
Array< std::uint8_t > bytes_
Definition ca-gif.H:158
Storage + topology that carries the cell values.
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
std::uint8_t palette_index(Array< RGB8 > &palette, const RGB8 c)
Definition ca-gif.H:94
unsigned ceil_log2(std::size_t n)
Definition ca-gif.H:144
std::uint8_t lookup_palette_index(const Array< RGB8 > &palette, const RGB8 c) noexcept
Read-only lookup variant: assumes the palette is already finalised (every colour either present or to...
Definition ca-gif.H:123
Array< RGB8 > build_palette(const Array< Frame > &frames)
Definition ca-gif.H:226
void write_subblocks(std::ostream &out, const Array< std::uint8_t > &bytes)
Definition ca-gif.H:213
std::uint32_t dist2(const RGB8 a, const RGB8 b) noexcept
Definition ca-gif.H:86
void put_le16(std::ostream &out, const std::uint16_t value)
Definition ca-gif.H:80
Array< std::uint8_t > lzw_bytes(const Array< std::uint8_t > &indices, const unsigned min_code_size)
Definition ca-gif.H:183
void write_gif(std::ostream &out, const Array< ca_gif_detail::Frame > &frames, const GIF_Write_Options &opts={})
Write a sequence of RGB frames as an animated GIF.
Definition ca-gif.H:253
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
std::string code(Node *root)
Compute a string with the Lukasiewicz`s word of a tree.
STL namespace.
Options for animated GIF output.
Definition ca-gif.H:66
bool loop
add Netscape infinite-loop extension
Definition ca-gif.H:68
unsigned delay_cs
frame delay in centiseconds
Definition ca-gif.H:67
RGB byte triplet used by PPM exporters.
Definition ca-io.H:85
Array< RGB8 > pixels
row-major RGB pixels
Definition ca-gif.H:77
gsl_rng * r
Dynamic array container with automatic resizing.
C++20 concepts for the Cellular Automata module.