MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
EXRWriter.cpp
Go to the documentation of this file.
1#include "EXRWriter.hpp"
2
4
5#include <tinyexr.h>
6
7#include <fstream>
8
9namespace MayaFlux::IO {
10
11namespace {
12
13 std::string extension_of(const std::string& filepath)
14 {
15 auto ext = std::filesystem::path(filepath).extension().string();
16 if (!ext.empty() && ext[0] == '.') {
17 ext = ext.substr(1);
18 }
19 std::ranges::transform(ext, ext.begin(),
20 [](unsigned char c) { return std::tolower(c); });
21 return ext;
22 }
23
24 bool flush_to_disk(const std::string& filepath, const unsigned char* bytes, size_t size)
25 {
26 std::ofstream out(filepath, std::ios::binary | std::ios::trunc);
27 if (!out.is_open()) {
28 return false;
29 }
30 out.write(reinterpret_cast<const char*>(bytes),
31 static_cast<std::streamsize>(size));
32 return out.good();
33 }
34
35 int resolve_compression(int requested)
36 {
37 if (requested < 0) {
38 return TINYEXR_COMPRESSIONTYPE_ZIP;
39 }
40 switch (requested) {
41 case TINYEXR_COMPRESSIONTYPE_NONE:
42 case TINYEXR_COMPRESSIONTYPE_RLE:
43 case TINYEXR_COMPRESSIONTYPE_ZIPS:
44 case TINYEXR_COMPRESSIONTYPE_ZIP:
45 case TINYEXR_COMPRESSIONTYPE_PIZ:
46 return requested;
47 default:
48 return TINYEXR_COMPRESSIONTYPE_ZIP;
49 }
50 }
51
52} // namespace
53
54// ============================================================================
55// Registry hook
56// ============================================================================
57
59{
61 reg.register_writer(
62 { "exr" },
63 []() -> std::unique_ptr<ImageWriter> {
64 return std::make_unique<EXRWriter>();
65 });
66
68 "EXRWriter registered for: exr");
69}
70
71// ============================================================================
72// Public interface
73// ============================================================================
74
75bool EXRWriter::can_write(const std::string& filepath) const
76{
77 return extension_of(filepath) == "exr";
78}
79
80std::vector<std::string> EXRWriter::get_supported_extensions() const
81{
82 return { "exr" };
83}
84
85// ============================================================================
86// Channel name resolution
87// ============================================================================
88
89std::vector<std::string> EXRWriter::resolve_channel_names(
90 const ImageData& data, const ImageWriteOptions& options) const
91{
92 if (!options.channel_names.empty()) {
93 if (options.channel_names.size() != data.channels) {
95 "EXRWriter: channel_names size ({}) does not match channels ({}); "
96 "falling back to defaults",
97 options.channel_names.size(), data.channels);
98 } else {
99 return options.channel_names;
100 }
101 }
102
103 switch (data.channels) {
104 case 1:
105 return { "Y" };
106 case 2:
107 return { "Y", "A" };
108 case 3:
109 return { "B", "G", "R" };
110 case 4:
111 return { "A", "B", "G", "R" };
112 default: {
113 std::vector<std::string> names;
114 names.reserve(data.channels);
115 for (uint32_t i = 0; i < data.channels; ++i) {
116 names.emplace_back("C" + std::to_string(i));
117 }
118 return names;
119 }
120 }
121}
122
123// ============================================================================
124// Deinterleave
125// ============================================================================
126
127std::vector<std::vector<float>> EXRWriter::deinterleave(
128 const std::vector<float>& src,
129 uint32_t width, uint32_t height, uint32_t channels,
130 bool flip_vertically)
131{
132 const size_t pixel_count = static_cast<size_t>(width) * height;
133 std::vector<std::vector<float>> planes(channels, std::vector<float>(pixel_count));
134
135 for (uint32_t y = 0; y < height; ++y) {
136 const uint32_t src_row = flip_vertically ? (height - 1 - y) : y;
137 const size_t src_row_offset = static_cast<size_t>(src_row) * width * channels;
138 const size_t dst_row_offset = static_cast<size_t>(y) * width;
139
140 for (uint32_t x = 0; x < width; ++x) {
141 const size_t src_pixel = src_row_offset + static_cast<size_t>(x) * channels;
142 const size_t dst_pixel = dst_row_offset + x;
143
144 for (uint32_t c = 0; c < channels; ++c) {
145 planes[c][dst_pixel] = src[src_pixel + c];
146 }
147 }
148 }
149
150 return planes;
151}
152
153// ============================================================================
154// Write
155// ============================================================================
156
158 const std::string& filepath,
159 const ImageData& data,
160 const ImageWriteOptions& options)
161{
162 m_last_error.clear();
163
164 if (!data.is_consistent()) {
165 m_last_error = "ImageData variant does not match declared ImageFormat";
167 return false;
168 }
169
170 if (data.width == 0 || data.height == 0 || data.channels == 0) {
171 m_last_error = "ImageData has zero dimensions";
173 return false;
174 }
175
176 const auto* src = data.as_float();
177 if (!src) {
178 m_last_error = "EXR requires float ImageData (uint8 and uint16 not supported)";
180 return false;
181 }
182
183 const size_t expected_elements = static_cast<size_t>(data.width) * data.height * data.channels;
184 if (src->size() < expected_elements) {
185 m_last_error = "EXR source buffer too small: expected "
186 + std::to_string(expected_elements)
187 + " got " + std::to_string(src->size());
189 return false;
190 }
191
192 const auto names = resolve_channel_names(data, options);
193 auto planes = deinterleave(*src, data.width, data.height, data.channels,
194 options.flip_vertically);
195
196 // ------------------------------------------------------------------------
197 // Populate EXRImage
198 // ------------------------------------------------------------------------
199 EXRImage image;
200 InitEXRImage(&image);
201
202 image.num_channels = static_cast<int>(data.channels);
203 image.width = static_cast<int>(data.width);
204 image.height = static_cast<int>(data.height);
205
206 std::vector<float*> plane_ptrs(data.channels);
207 for (uint32_t c = 0; c < data.channels; ++c) {
208 plane_ptrs[c] = planes[c].data();
209 }
210 image.images = reinterpret_cast<unsigned char**>(plane_ptrs.data());
211
212 // ------------------------------------------------------------------------
213 // Populate EXRHeader
214 // ------------------------------------------------------------------------
215 EXRHeader header;
216 InitEXRHeader(&header);
217
218 header.num_channels = static_cast<int>(data.channels);
219 header.compression_type = resolve_compression(options.compression);
220
221 std::vector<EXRChannelInfo> channel_infos(data.channels);
222 std::vector<int> pixel_types(data.channels, TINYEXR_PIXELTYPE_FLOAT);
223 std::vector<int> requested_pixel_types(data.channels, TINYEXR_PIXELTYPE_FLOAT);
224
225 for (uint32_t c = 0; c < data.channels; ++c) {
226 std::memset(&channel_infos[c], 0, sizeof(EXRChannelInfo));
227 const std::string& n = names[c];
228 const size_t copy_len = std::min<size_t>(n.size(), 255);
229 std::memcpy(channel_infos[c].name, n.data(), copy_len);
230 channel_infos[c].name[copy_len] = '\0';
231 }
232
233 header.channels = channel_infos.data();
234 header.pixel_types = pixel_types.data();
235 header.requested_pixel_types = requested_pixel_types.data();
236
237 // ------------------------------------------------------------------------
238 // Encode to memory, then flush
239 // ------------------------------------------------------------------------
240 unsigned char* encoded = nullptr;
241 const char* err = nullptr;
242
243 const size_t encoded_size = SaveEXRImageToMemory(
244 &image, &header, &encoded, &err);
245
246 if (encoded_size == 0 || encoded == nullptr) {
247 m_last_error = err ? std::string("tinyexr: ") + err
248 : "SaveEXRImageToMemory returned 0 bytes";
249 if (err) {
250 FreeEXRErrorMessage(err);
251 }
253 return false;
254 }
255
256 const bool ok = flush_to_disk(filepath, encoded, encoded_size);
257
258 std::free(encoded);
259
260 if (!ok) {
261 m_last_error = "Failed to write EXR file: " + filepath;
263 return false;
264 }
265
267 "Wrote EXR: {} ({}x{}, {} channels, compression {}, {} bytes)",
268 filepath, data.width, data.height, data.channels,
269 header.compression_type, encoded_size);
270
271 return true;
272}
273
274} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
IO::ImageData image
uint32_t width
Range size
std::vector< std::string > resolve_channel_names(const ImageData &data, const ImageWriteOptions &options) const
Resolve channel name list based on options and channel count.
Definition EXRWriter.cpp:89
static std::vector< std::vector< float > > deinterleave(const std::vector< float > &src, uint32_t width, uint32_t height, uint32_t channels, bool flip_vertically)
Deinterleave source float buffer into N planar channel buffers.
bool write(const std::string &filepath, const ImageData &data, const ImageWriteOptions &options={}) override
Write image data to disk.
std::vector< std::string > get_supported_extensions() const override
File extensions handled by this writer (without dot).
Definition EXRWriter.cpp:80
bool can_write(const std::string &filepath) const override
Check whether this writer handles the given filepath.
Definition EXRWriter.cpp:75
static void register_with_registry()
Register this writer with the ImageWriterRegistry.
Definition EXRWriter.cpp:58
static ImageWriterRegistry & instance()
@ 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
Raw image data loaded from file.
std::vector< std::string > channel_names
Configuration for image writing.