MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
STBImageWriter.cpp
Go to the documentation of this file.
1#include "STBImageWriter.hpp"
2
3#include "FileWriter.hpp"
4
6
7#define STB_IMAGE_WRITE_IMPLEMENTATION
8// #define STBI_WRITE_NO_STDIO
9
10#if __has_include("stb/stb_image_write.h")
11#include "stb/stb_image_write.h"
12#elif __has_include("stb_image_write.h")
13#include "stb_image_write.h"
14#else
15#error "stb_image_write.h not found"
16#endif
17
18#include <fstream>
19
20namespace MayaFlux::IO {
21
22namespace {
23
24 std::string extension_of(const std::string& filepath)
25 {
26 auto ext = std::filesystem::path(filepath).extension().string();
27 if (!ext.empty() && ext[0] == '.') {
28 ext = ext.substr(1);
29 }
30 std::ranges::transform(ext, ext.begin(),
31 [](unsigned char c) { return std::tolower(c); });
32 return ext;
33 }
34
35 /**
36 * @brief stbi_write callback appending encoded bytes to a std::vector.
37 */
38 void stbi_memory_writer(void* context, void* data, int size)
39 {
40 auto* buf = static_cast<std::vector<uint8_t>*>(context);
41 const auto* bytes = static_cast<const uint8_t*>(data);
42 buf->insert(buf->end(), bytes, bytes + size);
43 }
44
45 /**
46 * @brief Flush an encoded byte buffer to disk atomically.
47 */
48 bool flush_to_disk(const std::string& filepath, const std::vector<uint8_t>& buffer)
49 {
50 const auto resolved = resolve_write_path(filepath);
51 std::ofstream out(resolved, std::ios::binary | std::ios::trunc);
52 if (!out.is_open()) {
53 return false;
54 }
55 out.write(reinterpret_cast<const char*>(buffer.data()),
56 static_cast<std::streamsize>(buffer.size()));
57 return out.good();
58 }
59
60} // namespace
61
62// ============================================================================
63// Registry hook
64// ============================================================================
65
67{
69 reg.register_writer(
70 { "png", "jpg", "jpeg", "bmp", "tga", "hdr" },
71 []() -> std::unique_ptr<ImageWriter> {
72 return std::make_unique<STBImageWriter>();
73 });
74
76 "STBImageWriter registered for: png, jpg, jpeg, bmp, tga, hdr");
77}
78
79// ============================================================================
80// Public interface
81// ============================================================================
82
83bool STBImageWriter::can_write(const std::string& filepath) const
84{
85 const auto ext = extension_of(filepath);
86 static const std::vector<std::string> supported = {
87 "png", "jpg", "jpeg", "bmp", "tga", "hdr"
88 };
89 return std::ranges::find(supported, ext) != supported.end();
90}
91
92std::vector<std::string> STBImageWriter::get_supported_extensions() const
93{
94 return { "png", "jpg", "jpeg", "bmp", "tga", "hdr" };
95}
96
98 const std::string& filepath,
99 const ImageData& data,
100 const ImageWriteOptions& options)
101{
102 m_last_error.clear();
103
104 if (!data.is_consistent()) {
105 m_last_error = "ImageData variant does not match declared ImageFormat";
107 return false;
108 }
109
110 if (data.width == 0 || data.height == 0 || data.channels == 0) {
111 m_last_error = "ImageData has zero dimensions";
113 return false;
114 }
115
116 const auto ext = extension_of(filepath);
117
118 if (ext == "png")
119 return write_png(filepath, data, options);
120 if (ext == "jpg" || ext == "jpeg")
121 return write_jpg(filepath, data, options);
122 if (ext == "bmp")
123 return write_bmp(filepath, data);
124 if (ext == "tga")
125 return write_tga(filepath, data);
126 if (ext == "hdr")
127 return write_hdr(filepath, data);
128
129 m_last_error = "Unsupported extension: " + ext;
131 return false;
132}
133
134// ============================================================================
135// Format dispatch
136// ============================================================================
137
139 const std::string& filepath,
140 const ImageData& data,
141 const ImageWriteOptions& options)
142{
143 const auto* src = data.as_uint8();
144 if (!src) {
145 m_last_error = "PNG requires uint8 ImageData";
147 return false;
148 }
149
150 const int compression = (options.compression >= 0 && options.compression <= 9)
151 ? options.compression
152 : 6;
153 stbi_write_png_compression_level = compression;
154 stbi_flip_vertically_on_write(options.flip_vertically ? 1 : 0);
155
156 std::vector<uint8_t> encoded;
157 encoded.reserve(static_cast<size_t>(data.width) * data.height * data.channels);
158
159 const int stride = static_cast<int>(data.width * data.channels);
160 const int ok = stbi_write_png_to_func(
161 &stbi_memory_writer,
162 &encoded,
163 static_cast<int>(data.width),
164 static_cast<int>(data.height),
165 static_cast<int>(data.channels),
166 src->data(),
167 stride);
168
169 if (!ok || encoded.empty()) {
170 m_last_error = "stbi_write_png_to_func failed";
172 return false;
173 }
174
175 if (!flush_to_disk(filepath, encoded)) {
176 m_last_error = "Failed to write PNG file: " + filepath;
178 return false;
179 }
180
182 "Wrote PNG: {} ({}x{}, {} channels, {} bytes)",
183 filepath, data.width, data.height, data.channels, encoded.size());
184 return true;
185}
186
188 const std::string& filepath,
189 const ImageData& data,
190 const ImageWriteOptions& options)
191{
192 const auto* src = data.as_uint8();
193 if (!src) {
194 m_last_error = "JPG requires uint8 ImageData";
196 return false;
197 }
198
199 if (data.channels != 1 && data.channels != 3) {
200 m_last_error = "JPG supports only 1 or 3 channels (got "
201 + std::to_string(data.channels) + ")";
203 return false;
204 }
205
206 const int quality = std::clamp(options.quality, 1, 100);
207 stbi_flip_vertically_on_write(options.flip_vertically ? 1 : 0);
208
209 std::vector<uint8_t> encoded;
210 const int ok = stbi_write_jpg_to_func(
211 &stbi_memory_writer,
212 &encoded,
213 static_cast<int>(data.width),
214 static_cast<int>(data.height),
215 static_cast<int>(data.channels),
216 src->data(),
217 quality);
218
219 if (!ok || encoded.empty()) {
220 m_last_error = "stbi_write_jpg_to_func failed";
222 return false;
223 }
224
225 if (!flush_to_disk(filepath, encoded)) {
226 m_last_error = "Failed to write JPG file: " + filepath;
228 return false;
229 }
230
232 "Wrote JPG: {} ({}x{}, quality {}, {} bytes)",
233 filepath, data.width, data.height, quality, encoded.size());
234 return true;
235}
236
237bool STBImageWriter::write_bmp(const std::string& filepath, const ImageData& data)
238{
239 const auto* src = data.as_uint8();
240 if (!src) {
241 m_last_error = "BMP requires uint8 ImageData";
243 return false;
244 }
245
246 std::vector<uint8_t> encoded;
247 const int ok = stbi_write_bmp_to_func(
248 &stbi_memory_writer,
249 &encoded,
250 static_cast<int>(data.width),
251 static_cast<int>(data.height),
252 static_cast<int>(data.channels),
253 src->data());
254
255 if (!ok || encoded.empty()) {
256 m_last_error = "stbi_write_bmp_to_func failed";
258 return false;
259 }
260
261 if (!flush_to_disk(filepath, encoded)) {
262 m_last_error = "Failed to write BMP file: " + filepath;
264 return false;
265 }
266
268 "Wrote BMP: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
269 return true;
270}
271
272bool STBImageWriter::write_tga(const std::string& filepath, const ImageData& data)
273{
274 const auto* src = data.as_uint8();
275 if (!src) {
276 m_last_error = "TGA requires uint8 ImageData";
278 return false;
279 }
280
281 std::vector<uint8_t> encoded;
282 const int ok = stbi_write_tga_to_func(
283 &stbi_memory_writer,
284 &encoded,
285 static_cast<int>(data.width),
286 static_cast<int>(data.height),
287 static_cast<int>(data.channels),
288 src->data());
289
290 if (!ok || encoded.empty()) {
291 m_last_error = "stbi_write_tga_to_func failed";
293 return false;
294 }
295
296 if (!flush_to_disk(filepath, encoded)) {
297 m_last_error = "Failed to write TGA file: " + filepath;
299 return false;
300 }
301
303 "Wrote TGA: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
304 return true;
305}
306
307bool STBImageWriter::write_hdr(const std::string& filepath, const ImageData& data)
308{
309 const auto* src = data.as_float();
310 if (!src) {
311 m_last_error = "HDR requires float ImageData";
313 return false;
314 }
315
316 if (data.channels != 3 && data.channels != 4) {
317 m_last_error = "HDR supports only 3 or 4 channels (got "
318 + std::to_string(data.channels) + ")";
320 return false;
321 }
322
323 std::vector<uint8_t> encoded;
324 const int ok = stbi_write_hdr_to_func(
325 &stbi_memory_writer,
326 &encoded,
327 static_cast<int>(data.width),
328 static_cast<int>(data.height),
329 static_cast<int>(data.channels),
330 src->data());
331
332 if (!ok || encoded.empty()) {
333 m_last_error = "stbi_write_hdr_to_func failed";
335 return false;
336 }
337
338 if (!flush_to_disk(filepath, encoded)) {
339 m_last_error = "Failed to write HDR file: " + filepath;
341 return false;
342 }
343
345 "Wrote HDR: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
346 return true;
347}
348
349} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
static ImageWriterRegistry & instance()
bool write_bmp(const std::string &filepath, const ImageData &data)
bool can_write(const std::string &filepath) const override
Check whether this writer handles the given filepath.
static void register_with_registry()
Register this writer with the ImageWriterRegistry.
bool write_jpg(const std::string &filepath, const ImageData &data, const ImageWriteOptions &options)
bool write_png(const std::string &filepath, const ImageData &data, const ImageWriteOptions &options)
std::vector< std::string > get_supported_extensions() const override
File extensions handled by this writer (without dot).
bool write(const std::string &filepath, const ImageData &data, const ImageWriteOptions &options={}) override
Write image data to disk.
bool write_tga(const std::string &filepath, const ImageData &data)
bool write_hdr(const std::string &filepath, const ImageData &data)
std::string resolve_write_path(const std::string &filepath)
Anchor a relative output path to Config::SOURCE_DIR.
@ FileIO
Filesystem I/O operations.
@ Init
Engine/subsystem initialization.
@ IO
Networking, file handling, streaming.
bool is_consistent() const
Check that the active variant matches the declared format.
const std::vector< float > * as_float() const
const std::vector< uint8_t > * as_uint8() const
Typed accessors.
Raw image data loaded from file.
Configuration for image writing.