15 std::string extension_of(
const std::string& filepath)
17 auto ext = std::filesystem::path(filepath).extension().string();
18 if (!ext.empty() && ext[0] ==
'.') {
21 std::ranges::transform(ext, ext.begin(),
22 [](
unsigned char c) { return std::tolower(c); });
26 bool flush_to_disk(
const std::string& filepath,
const unsigned char* bytes,
size_t size)
29 std::ofstream out(resolved, std::ios::binary | std::ios::trunc);
33 out.write(
reinterpret_cast<const char*
>(bytes),
34 static_cast<std::streamsize
>(size));
38 int resolve_compression(
int requested)
41 return TINYEXR_COMPRESSIONTYPE_ZIP;
44 case TINYEXR_COMPRESSIONTYPE_NONE:
45 case TINYEXR_COMPRESSIONTYPE_RLE:
46 case TINYEXR_COMPRESSIONTYPE_ZIPS:
47 case TINYEXR_COMPRESSIONTYPE_ZIP:
48 case TINYEXR_COMPRESSIONTYPE_PIZ:
51 return TINYEXR_COMPRESSIONTYPE_ZIP;
66 []() -> std::unique_ptr<ImageWriter> {
67 return std::make_unique<EXRWriter>();
71 "EXRWriter registered for: exr");
80 return extension_of(filepath) ==
"exr";
98 "EXRWriter: channel_names size ({}) does not match channels ({}); "
99 "falling back to defaults",
112 return {
"B",
"G",
"R" };
114 return {
"A",
"B",
"G",
"R" };
116 std::vector<std::string> names;
118 for (uint32_t i = 0; i < data.
channels; ++i) {
119 names.emplace_back(
"C" + std::to_string(i));
131 const std::vector<float>& src,
132 uint32_t
width, uint32_t height, uint32_t channels,
133 bool flip_vertically)
135 const size_t pixel_count =
static_cast<size_t>(
width) * height;
136 std::vector<std::vector<float>> planes(channels, std::vector<float>(pixel_count));
138 for (uint32_t y = 0; y < height; ++y) {
139 const uint32_t src_row = flip_vertically ? (height - 1 - y) : y;
140 const size_t src_row_offset =
static_cast<size_t>(src_row) *
width * channels;
141 const size_t dst_row_offset =
static_cast<size_t>(y) *
width;
143 for (uint32_t x = 0; x <
width; ++x) {
144 const size_t src_pixel = src_row_offset +
static_cast<size_t>(x) * channels;
145 const size_t dst_pixel = dst_row_offset + x;
147 for (uint32_t c = 0; c < channels; ++c) {
148 planes[c][dst_pixel] = src[src_pixel + c];
161 const std::string& filepath,
168 m_last_error =
"ImageData variant does not match declared ImageFormat";
181 m_last_error =
"EXR requires float ImageData (uint8 and uint16 not supported)";
186 const size_t expected_elements =
static_cast<size_t>(data.
width) * data.
height * data.
channels;
187 if (src->size() < expected_elements) {
189 + std::to_string(expected_elements)
190 +
" got " + std::to_string(src->size());
203 InitEXRImage(&
image);
209 std::vector<float*> plane_ptrs(data.
channels);
210 for (uint32_t c = 0; c < data.
channels; ++c) {
211 plane_ptrs[c] = planes[c].data();
213 image.images =
reinterpret_cast<unsigned char**
>(plane_ptrs.data());
219 InitEXRHeader(&header);
221 header.num_channels =
static_cast<int>(data.
channels);
222 header.compression_type = resolve_compression(options.
compression);
224 std::vector<EXRChannelInfo> channel_infos(data.
channels);
225 std::vector<int> pixel_types(data.
channels, TINYEXR_PIXELTYPE_FLOAT);
226 std::vector<int> requested_pixel_types(data.
channels, TINYEXR_PIXELTYPE_FLOAT);
228 for (uint32_t c = 0; c < data.
channels; ++c) {
229 std::memset(&channel_infos[c], 0,
sizeof(EXRChannelInfo));
230 const std::string& n = names[c];
231 const size_t copy_len = std::min<size_t>(n.size(), 255);
232 std::memcpy(channel_infos[c].name, n.data(), copy_len);
233 channel_infos[c].name[copy_len] =
'\0';
236 header.channels = channel_infos.data();
237 header.pixel_types = pixel_types.data();
238 header.requested_pixel_types = requested_pixel_types.data();
243 unsigned char* encoded =
nullptr;
244 const char* err =
nullptr;
246 const size_t encoded_size = SaveEXRImageToMemory(
247 &
image, &header, &encoded, &err);
249 if (encoded_size == 0 || encoded ==
nullptr) {
251 :
"SaveEXRImageToMemory returned 0 bytes";
253 FreeEXRErrorMessage(err);
259 const bool ok = flush_to_disk(filepath, encoded, encoded_size);
270 "Wrote EXR: {} ({}x{}, {} channels, compression {}, {} bytes)",
272 header.compression_type, encoded_size);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
std::vector< std::string > resolve_channel_names(const ImageData &data, const ImageWriteOptions &options) const
Resolve channel name list based on options and channel count.
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).
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.
static ImageWriterRegistry & instance()
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
Raw image data loaded from file.
std::vector< std::string > channel_names
Configuration for image writing.