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
4
5#define STB_IMAGE_WRITE_IMPLEMENTATION
6// #define STBI_WRITE_NO_STDIO
7
8#if __has_include("stb/stb_image_write.h")
9#include "stb/stb_image_write.h"
10#elif __has_include("stb_image_write.h")
11#include "stb_image_write.h"
12#else
13#error "stb_image_write.h not found"
14#endif
15
16#include <fstream>
17
18namespace MayaFlux::IO {
19
20namespace {
21
22 std::string extension_of(const std::string& filepath)
23 {
24 auto ext = std::filesystem::path(filepath).extension().string();
25 if (!ext.empty() && ext[0] == '.') {
26 ext = ext.substr(1);
27 }
28 std::ranges::transform(ext, ext.begin(),
29 [](unsigned char c) { return std::tolower(c); });
30 return ext;
31 }
32
33 /**
34 * @brief stbi_write callback appending encoded bytes to a std::vector.
35 */
36 void stbi_memory_writer(void* context, void* data, int size)
37 {
38 auto* buf = static_cast<std::vector<uint8_t>*>(context);
39 const auto* bytes = static_cast<const uint8_t*>(data);
40 buf->insert(buf->end(), bytes, bytes + size);
41 }
42
43 /**
44 * @brief Flush an encoded byte buffer to disk atomically.
45 */
46 bool flush_to_disk(const std::string& filepath, const std::vector<uint8_t>& buffer)
47 {
48 std::ofstream out(filepath, std::ios::binary | std::ios::trunc);
49 if (!out.is_open()) {
50 return false;
51 }
52 out.write(reinterpret_cast<const char*>(buffer.data()),
53 static_cast<std::streamsize>(buffer.size()));
54 return out.good();
55 }
56
57} // namespace
58
59// ============================================================================
60// Registry hook
61// ============================================================================
62
64{
66 reg.register_writer(
67 { "png", "jpg", "jpeg", "bmp", "tga", "hdr" },
68 []() -> std::unique_ptr<ImageWriter> {
69 return std::make_unique<STBImageWriter>();
70 });
71
73 "STBImageWriter registered for: png, jpg, jpeg, bmp, tga, hdr");
74}
75
76// ============================================================================
77// Public interface
78// ============================================================================
79
80bool STBImageWriter::can_write(const std::string& filepath) const
81{
82 const auto ext = extension_of(filepath);
83 static const std::vector<std::string> supported = {
84 "png", "jpg", "jpeg", "bmp", "tga", "hdr"
85 };
86 return std::ranges::find(supported, ext) != supported.end();
87}
88
89std::vector<std::string> STBImageWriter::get_supported_extensions() const
90{
91 return { "png", "jpg", "jpeg", "bmp", "tga", "hdr" };
92}
93
95 const std::string& filepath,
96 const ImageData& data,
97 const ImageWriteOptions& options)
98{
99 m_last_error.clear();
100
101 if (!data.is_consistent()) {
102 m_last_error = "ImageData variant does not match declared ImageFormat";
104 return false;
105 }
106
107 if (data.width == 0 || data.height == 0 || data.channels == 0) {
108 m_last_error = "ImageData has zero dimensions";
110 return false;
111 }
112
113 const auto ext = extension_of(filepath);
114
115 if (ext == "png")
116 return write_png(filepath, data, options);
117 if (ext == "jpg" || ext == "jpeg")
118 return write_jpg(filepath, data, options);
119 if (ext == "bmp")
120 return write_bmp(filepath, data);
121 if (ext == "tga")
122 return write_tga(filepath, data);
123 if (ext == "hdr")
124 return write_hdr(filepath, data);
125
126 m_last_error = "Unsupported extension: " + ext;
128 return false;
129}
130
131// ============================================================================
132// Format dispatch
133// ============================================================================
134
136 const std::string& filepath,
137 const ImageData& data,
138 const ImageWriteOptions& options)
139{
140 const auto* src = data.as_uint8();
141 if (!src) {
142 m_last_error = "PNG requires uint8 ImageData";
144 return false;
145 }
146
147 const int compression = (options.compression >= 0 && options.compression <= 9)
148 ? options.compression
149 : 6;
150 stbi_write_png_compression_level = compression;
151 stbi_flip_vertically_on_write(options.flip_vertically ? 1 : 0);
152
153 std::vector<uint8_t> encoded;
154 encoded.reserve(static_cast<size_t>(data.width) * data.height * data.channels);
155
156 const int stride = static_cast<int>(data.width * data.channels);
157 const int ok = stbi_write_png_to_func(
158 &stbi_memory_writer,
159 &encoded,
160 static_cast<int>(data.width),
161 static_cast<int>(data.height),
162 static_cast<int>(data.channels),
163 src->data(),
164 stride);
165
166 if (!ok || encoded.empty()) {
167 m_last_error = "stbi_write_png_to_func failed";
169 return false;
170 }
171
172 if (!flush_to_disk(filepath, encoded)) {
173 m_last_error = "Failed to write PNG file: " + filepath;
175 return false;
176 }
177
179 "Wrote PNG: {} ({}x{}, {} channels, {} bytes)",
180 filepath, data.width, data.height, data.channels, encoded.size());
181 return true;
182}
183
185 const std::string& filepath,
186 const ImageData& data,
187 const ImageWriteOptions& options)
188{
189 const auto* src = data.as_uint8();
190 if (!src) {
191 m_last_error = "JPG requires uint8 ImageData";
193 return false;
194 }
195
196 if (data.channels != 1 && data.channels != 3) {
197 m_last_error = "JPG supports only 1 or 3 channels (got "
198 + std::to_string(data.channels) + ")";
200 return false;
201 }
202
203 const int quality = std::clamp(options.quality, 1, 100);
204 stbi_flip_vertically_on_write(options.flip_vertically ? 1 : 0);
205
206 std::vector<uint8_t> encoded;
207 const int ok = stbi_write_jpg_to_func(
208 &stbi_memory_writer,
209 &encoded,
210 static_cast<int>(data.width),
211 static_cast<int>(data.height),
212 static_cast<int>(data.channels),
213 src->data(),
214 quality);
215
216 if (!ok || encoded.empty()) {
217 m_last_error = "stbi_write_jpg_to_func failed";
219 return false;
220 }
221
222 if (!flush_to_disk(filepath, encoded)) {
223 m_last_error = "Failed to write JPG file: " + filepath;
225 return false;
226 }
227
229 "Wrote JPG: {} ({}x{}, quality {}, {} bytes)",
230 filepath, data.width, data.height, quality, encoded.size());
231 return true;
232}
233
234bool STBImageWriter::write_bmp(const std::string& filepath, const ImageData& data)
235{
236 const auto* src = data.as_uint8();
237 if (!src) {
238 m_last_error = "BMP requires uint8 ImageData";
240 return false;
241 }
242
243 std::vector<uint8_t> encoded;
244 const int ok = stbi_write_bmp_to_func(
245 &stbi_memory_writer,
246 &encoded,
247 static_cast<int>(data.width),
248 static_cast<int>(data.height),
249 static_cast<int>(data.channels),
250 src->data());
251
252 if (!ok || encoded.empty()) {
253 m_last_error = "stbi_write_bmp_to_func failed";
255 return false;
256 }
257
258 if (!flush_to_disk(filepath, encoded)) {
259 m_last_error = "Failed to write BMP file: " + filepath;
261 return false;
262 }
263
265 "Wrote BMP: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
266 return true;
267}
268
269bool STBImageWriter::write_tga(const std::string& filepath, const ImageData& data)
270{
271 const auto* src = data.as_uint8();
272 if (!src) {
273 m_last_error = "TGA requires uint8 ImageData";
275 return false;
276 }
277
278 std::vector<uint8_t> encoded;
279 const int ok = stbi_write_tga_to_func(
280 &stbi_memory_writer,
281 &encoded,
282 static_cast<int>(data.width),
283 static_cast<int>(data.height),
284 static_cast<int>(data.channels),
285 src->data());
286
287 if (!ok || encoded.empty()) {
288 m_last_error = "stbi_write_tga_to_func failed";
290 return false;
291 }
292
293 if (!flush_to_disk(filepath, encoded)) {
294 m_last_error = "Failed to write TGA file: " + filepath;
296 return false;
297 }
298
300 "Wrote TGA: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
301 return true;
302}
303
304bool STBImageWriter::write_hdr(const std::string& filepath, const ImageData& data)
305{
306 const auto* src = data.as_float();
307 if (!src) {
308 m_last_error = "HDR requires float ImageData";
310 return false;
311 }
312
313 if (data.channels != 3 && data.channels != 4) {
314 m_last_error = "HDR supports only 3 or 4 channels (got "
315 + std::to_string(data.channels) + ")";
317 return false;
318 }
319
320 std::vector<uint8_t> encoded;
321 const int ok = stbi_write_hdr_to_func(
322 &stbi_memory_writer,
323 &encoded,
324 static_cast<int>(data.width),
325 static_cast<int>(data.height),
326 static_cast<int>(data.channels),
327 src->data());
328
329 if (!ok || encoded.empty()) {
330 m_last_error = "stbi_write_hdr_to_func failed";
332 return false;
333 }
334
335 if (!flush_to_disk(filepath, encoded)) {
336 m_last_error = "Failed to write HDR file: " + filepath;
338 return false;
339 }
340
342 "Wrote HDR: {} ({}x{}, {} channels)", filepath, data.width, data.height, data.channels);
343 return true;
344}
345
346} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
Range size
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)
@ 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.