Foray Library
rapid prototyping framework for crossplatform development of vulkan hardware ray tracing applications
Loading...
Searching...
No Matches
foray_imageloader_exr.inl
Go to the documentation of this file.
1#pragma once
2#include "../foray_logger.hpp"
3#include "../osi/foray_env.hpp"
5#include <tinyexr/tinyexr.h>
6
7// Disclaimer: Most of the code here is heavily inspired or copied from how Godot engine incorporates the tinyexr image loader
8
9namespace foray::util {
10
11 namespace impl {
13 {
14 public:
15 EXRVersion Version{};
16 EXRHeader Header{};
17 int32_t ChannelIndices[5];
18 std::unordered_map<std::string, uint32_t> Channels;
19
21 {
22 InitEXRHeader(&Header);
23 for(int32_t i = 0; i < 5; i++)
24 {
25 ChannelIndices[i] = -1;
26 }
27 }
28 inline explicit ExrLoaderCache(EXRHeader& header) { Header = header; }
29
30 inline ~ExrLoaderCache() { FreeEXRHeader(&Header); }
31 };
32
33
34 template <typename FORMAT_TRAITS>
35 void lReadExr(std::vector<uint8_t>& out,
36 const EXRHeader& header,
37 const EXRImage& image,
38 const EXRTile* tiles,
39 uint32_t numTiles,
40 uint32_t tileHeight,
41 uint32_t tileWidth,
42 int32_t channels[5])
43 {
44 using component_t = typename FORMAT_TRAITS::COMPONENT_TRAITS::COMPONENT;
45 const uint32_t stride = FORMAT_TRAITS::COMPONENT_COUNT;
46
47 component_t* writeData = reinterpret_cast<component_t*>(out.data());
48
49 for(int32_t tile_index = 0; tile_index < (int32_t)numTiles; tile_index++)
50 {
51 const EXRTile& tile = tiles[tile_index];
52 int tw = tile.width;
53 int th = tile.height;
54
55 const component_t* readStart_r = nullptr;
56 const component_t* readStart_g = nullptr;
57 const component_t* readStart_b = nullptr;
58 const component_t* readStart_a = nullptr;
59
60 if(channels[(int)EImageChannel::R] >= 0)
61 {
62 readStart_r = reinterpret_cast<const component_t*>(tile.images[channels[(int)EImageChannel::R]]);
63 }
64 if(channels[(int)EImageChannel::G] >= 0)
65 {
66 readStart_g = reinterpret_cast<const component_t*>(tile.images[channels[(int)EImageChannel::G]]);
67 }
68 if(channels[(int)EImageChannel::B] >= 0)
69 {
70 readStart_b = reinterpret_cast<const component_t*>(tile.images[channels[(int)EImageChannel::B]]);
71 }
72 if(channels[(int)EImageChannel::A] >= 0)
73 {
74 readStart_a = reinterpret_cast<const component_t*>(tile.images[channels[(int)EImageChannel::A]]);
75 }
76
77 component_t* writeStart = writeData + (tile.offset_y * tileHeight * image.width + tile.offset_x * tileWidth) * stride;
78
79 for(int y = 0; y < th; y++)
80 {
81 const component_t* readRowPos_r = nullptr;
82 const component_t* readRowPos_g = nullptr;
83 const component_t* readRowPos_b = nullptr;
84 const component_t* readRowPos_a = nullptr;
85 if(readStart_r)
86 {
87 readRowPos_r = readStart_r + y * tw;
88 }
89 if(readStart_g)
90 {
91 readRowPos_g = readStart_g + y * tw;
92 }
93 if(readStart_b)
94 {
95 readRowPos_b = readStart_b + y * tw;
96 }
97 if(readStart_a)
98 {
99 readRowPos_a = readStart_a + y * tw;
100 }
101
102 component_t* writeRowPos = writeStart + (y * image.width * stride);
103
104 for(int x = 0; x < tw; x++)
105 {
106 component_t r = 0;
107 if(readRowPos_r)
108 {
109 r = *readRowPos_r++;
110 }
111 component_t g = 0;
112 if(readRowPos_g)
113 {
114 g = *readRowPos_g++;
115 }
116 component_t b = 0;
117 if(readRowPos_b)
118 {
119 b = *readRowPos_b++;
120 }
121 component_t a = FORMAT_TRAITS::COMPONENT_TRAITS::ALPHA_FALLBACK;
122 if(readRowPos_a)
123 {
124 a = *readRowPos_a++;
125 }
126 FORMAT_TRAITS::WriteColor(writeRowPos, r, g, b, a);
127 writeRowPos += stride;
128 }
129 }
130 }
131 }
132
133 void DeleteExrLoaderCache(void* loaderCache);
134
135 } // namespace impl
136
137 template <VkFormat FORMAT>
139 {
140 const char* exrError = nullptr;
141 using namespace impl;
142
143 mCustomLoaderInfo = new ExrLoaderCache();
144 mCustomLoaderInfoDeleter = DeleteExrLoaderCache;
145 auto& loaderCache = *(reinterpret_cast<ExrLoaderCache*>(mCustomLoaderInfo));
146
147 // STEP #1: Get EXR version and header information
148
149 bool success = true;
150 success = success && ParseEXRVersionFromFile(&loaderCache.Version, mInfo.Utf8Path.GetPath().c_str()) == TINYEXR_SUCCESS;
151 success = success && ParseEXRHeaderFromFile(&loaderCache.Header, &loaderCache.Version, mInfo.Utf8Path.GetPath().c_str(), &exrError) == TINYEXR_SUCCESS;
152 if(!success)
153 {
154 if(!!exrError)
155 {
156 logger()->warn("Failed to read image file \"{}\". Failed to read Exr header. TinyExr error: {}", mInfo.Utf8Path, exrError);
157 FreeEXRErrorMessage(exrError);
158 }
159 else
160 {
161 logger()->warn("Failed to read image file \"{}\". Failed to read Exr header.", mInfo.Utf8Path);
162 }
163 return false;
164 }
165
166
167 auto& header = loaderCache.Header;
168
169 auto stringToChannel = std::unordered_map<std::string_view, EImageChannel>({
170 {"R", EImageChannel::R},
171 {"G", EImageChannel::G},
172 {"B", EImageChannel::B},
173 {"A", EImageChannel::A},
174 {"Y", EImageChannel::Y},
175 });
176
177 // STEP #2: Process header information
178 // Map channel names to indices
179
180 if(header.num_channels > 0)
181 {
182 switch(header.pixel_types[0])
183 {
184 case TINYEXR_PIXELTYPE_UINT:
185 Assert(std::is_same_v<typename FORMAT_TRAITS::COMPONENT_TRAITS, ComponentTraits_UInt32>, "EXR component type is Uint32, loader component type does not match!");
186 break;
187 case TINYEXR_PIXELTYPE_HALF:
188 Assert(std::is_same_v<typename FORMAT_TRAITS::COMPONENT_TRAITS, ComponentTraits_Fp16>, "EXR component type is Fp16, loader component type does not match!");
189 break;
190 case TINYEXR_PIXELTYPE_FLOAT:
191 Assert(std::is_same_v<typename FORMAT_TRAITS::COMPONENT_TRAITS, ComponentTraits_Fp32>, "EXR component type is Fp32, loader component type does not match!");
192 break;
193 }
194 }
195
196 for(uint32_t channelIndex = 0; channelIndex < (uint32_t)header.num_channels; channelIndex++)
197 {
198 auto& channel = header.channels[channelIndex];
199 if(header.pixel_types[channelIndex] != header.pixel_types[0])
200 {
201 FORAY_THROWFMT("Image Loader: .EXR image \"{}\" has different pixel types per channel, which is not supported", mInfo.Name);
202 }
203 auto iter = stringToChannel.find(channel.name);
204 if(iter != stringToChannel.end())
205 {
206 EImageChannel channel = iter->second;
207 mInfo.Channels.push_back(channel);
208 loaderCache.ChannelIndices[(int)channel] = channelIndex;
209 }
210 else
211 {
212 mInfo.Channels.push_back(EImageChannel::Unknown);
213 }
214 }
215
216 mInfo.Valid = true;
217 return true;
218 }
219
220 template <VkFormat FORMAT>
222 {
223 using namespace impl;
224
225 const char* exrError = nullptr;
226 EXRImage image{};
227 InitEXRImage(&image);
228
229 auto& loaderCache = *(reinterpret_cast<ExrLoaderCache*>(mCustomLoaderInfo));
230 auto& header = loaderCache.Header;
231
232 if(LoadEXRImageFromFile(&image, &header, mInfo.Utf8Path.GetPath().c_str(), &exrError) != TINYEXR_SUCCESS)
233 {
234 if(!!exrError)
235 {
236 logger()->warn("Failed to read image file \"{}\". Failed to load file. TinyExr error: {}", mInfo.Utf8Path, exrError);
237 FreeEXRErrorMessage(exrError);
238 }
239 else
240 {
241 logger()->warn("Failed to read image file \"{}\". Failed to load file.", mInfo.Utf8Path);
242 }
243 return false;
244 }
245
246 mInfo.Extent.width = image.width;
247 mInfo.Extent.height = image.height;
248
249 // STEP #4: Move EXR lib data to custom data vector
250
251 // Storing as VkFormat::VK_FORMAT_R16G16B16A16_SFLOAT, so resize to pixelcount * 4 components * x bytes per component (2 or 4)
252 mRawData.resize(FORMAT_TRAITS::BYTESTRIDE * mInfo.Extent.width * mInfo.Extent.height);
253
254 EXRTile single_image_tile;
255 int num_tiles;
256 int tile_width = 0;
257 int tile_height = 0;
258
259 const EXRTile* exr_tiles;
260
261 if(!header.tiled)
262 {
263 // In order to be able to read tiled and non-tiled images the same, "simulate" a massive single tile
264 single_image_tile.images = image.images;
265 single_image_tile.width = image.width;
266 single_image_tile.height = image.height;
267 single_image_tile.level_x = image.width;
268 single_image_tile.level_y = image.height;
269 single_image_tile.offset_x = 0;
270 single_image_tile.offset_y = 0;
271
272 exr_tiles = &single_image_tile;
273 num_tiles = 1;
274 tile_width = image.width;
275 tile_height = image.height;
276 }
277 else
278 {
279 tile_width = header.tile_size_x;
280 tile_height = header.tile_size_y;
281 num_tiles = image.num_tiles;
282 exr_tiles = image.tiles;
283 }
284
285 lReadExr<FORMAT_TRAITS>(mRawData, header, image, exr_tiles, num_tiles, tile_height, tile_width, loaderCache.ChannelIndices);
286
287 return true;
288 }
289
290} // namespace foray::util
bool PopulateImageInfo_TinyExr()
Definition foray_imageloader_exr.inl:138
bool Load_TinyExr()
Definition foray_imageloader_exr.inl:221
Definition foray_imageloader_exr.inl:13
~ExrLoaderCache()
Definition foray_imageloader_exr.inl:30
EXRVersion Version
Definition foray_imageloader_exr.inl:15
EXRHeader Header
Definition foray_imageloader_exr.inl:16
std::unordered_map< std::string, uint32_t > Channels
Definition foray_imageloader_exr.inl:18
ExrLoaderCache(EXRHeader &header)
Definition foray_imageloader_exr.inl:28
ExrLoaderCache()
Definition foray_imageloader_exr.inl:20
int32_t ChannelIndices[5]
Definition foray_imageloader_exr.inl:17
#define FORAY_THROWFMT(fmt,...)
Macro for throwing an exception with formatted error message argument.
Definition foray_exception.hpp:81
void lReadExr(std::vector< uint8_t > &out, const EXRHeader &header, const EXRImage &image, const EXRTile *tiles, uint32_t numTiles, uint32_t tileHeight, uint32_t tileWidth, int32_t channels[5])
Definition foray_imageloader_exr.inl:35
void DeleteExrLoaderCache(void *loaderCache)
Definition foray_dualbuffer.hpp:5
EImageChannel
Definition foray_imageloader.hpp:13
void Assert(bool condition, const source_location location=source_location::current())
Asserts condition. Throws a generic error message if conditition is false.
Definition foray_exception.hpp:52
spdlog::logger * logger()
Gives access to a global available logger object. The logger writes to console & to a file next to th...