Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
exr_io_common.ipp
Go to the documentation of this file.
1#pragma once
2
4#include "Frame/TFrame.h"
5
6#include <Common/exceptions.h>
7
8namespace ph
9{
10
11#if PH_THIRD_PARTY_HAS_OPENEXR
12
13template<typename T>
14inline void copy_frame_to_imf_array(
15 const HdrRgbFrame& frame,
16 HdrComponent alphaValue,
17 Imf::Array2D<TImfPixel<T>>& array2D)
18{
19 static_assert(std::is_same_v<T, half> || std::is_same_v<T, float>);
20
21 const auto dataWidth = static_cast<long>(frame.widthPx());
22 const auto dataHeight = static_cast<long>(frame.heightPx());
23
24 array2D.resizeErase(dataWidth, dataHeight);
25 frame.forEachPixel(
26 [&array2D, alphaValue]
27 (const uint32 x, const uint32 y, const HdrRgbFrame::PixelType& framePixel)
28 {
29 TImfPixel<T>& pixel = array2D[static_cast<long>(y)][x];
30
31 pixel.r = framePixel[0];
32 pixel.g = framePixel[1];
33 pixel.b = framePixel[2];
34 pixel.a = alphaValue;
35 });
36}
37
38template<typename T, std::size_t N>
39inline void create_imf_header_for_frame(
40 Imf::Header& header,
41 const TFrame<T, N>& frame,
42 const std::array<std::string_view, N>& channelNames)
43{
44 static_assert(std::is_same_v<T, half> || std::is_same_v<T, float>);
45 constexpr Imf::PixelType VALUE_TYPE = std::is_same_v<T, half> ? Imf::HALF : Imf::FLOAT;
46
47 // OpenEXR's origin is on upper-left. In OpenEXR's term, Photon's frame starts with the highest
48 // y-coordinate, hence the scanline should be stored in `DECREASING_Y` order for best (read)
49 // performance. Note that line order has nothing to do with whether the image is vertically
50 // flipped or not, the image stays the same no matter what you set line order to. Line order
51 // simply means the order scanlines are stored in file, much like how the endianess does not
52 // affect an integer value but its memory representation.
53 //
54 // An example: for `Imf::INCREASING_Y`, OpenEXR stores the scanline with smallest y (top) first,
55 // and for `Imf::DECREASING_Y`, stores the largest y (bottom) first.
56 //
57 header.lineOrder() = Imf::DECREASING_Y;
58
59 for(std::size_t channelIdx = 0; channelIdx < N; ++channelIdx)
60 {
61 // Skip channels without a name
62 if(channelNames[channelIdx].empty())
63 {
64 continue;
65 }
66
67 header.channels().insert(std::string(channelNames[channelIdx]), Imf::Channel(VALUE_TYPE));
68 }
69}
70
71template<std::size_t N>
72inline auto find_imf_channels(
73 const Imf::Header& header,
74 const std::array<std::string_view, N>& channelNames)
75-> std::array<const Imf::Channel*, N>
76{
77 const Imf::ChannelList& channelList = header.channels();
78
79 std::array<const Imf::Channel*, N> channels;
80 for(std::size_t channelIdx = 0; channelIdx < N; ++channelIdx)
81 {
82 if(!channelNames[channelIdx].empty())
83 {
84 channels[channelIdx] = channelList.findChannel(std::string(channelNames[channelIdx]));
85 }
86 else
87 {
88 channels[channelIdx] = nullptr;
89 }
90 }
91
92 return channels;
93}
94
95template<typename T, std::size_t N>
96inline void map_imf_framebuffer_to_frame(
97 Imf::FrameBuffer& framebuffer,
98 const Imf::Header& header,
99 const TFrame<T, N>& frame,
100 const std::array<std::string_view, N>& channelNames)
101{
102 static_assert(std::is_same_v<T, half> || std::is_same_v<T, float>);
103 constexpr Imf::PixelType VALUE_TYPE = std::is_same_v<T, half> ? Imf::HALF : Imf::FLOAT;
104
105 // Coordinates are descrete, hence the +1 in the end
106 const Imath::Box2i dataWindow = header.dataWindow();
107 const auto dataWidth = dataWindow.max.x - dataWindow.min.x + 1;
108 const auto dataHeight = dataWindow.max.y - dataWindow.min.y + 1;
109 const auto minDataX = dataWindow.min.x;
110 const auto minDataY = dataWindow.min.y;
111
112 if(frame.widthPx() != dataWidth || frame.heightPx() != dataHeight)
113 {
114 throw_formatted<IllegalOperationException>(
115 "cannot map frame data to Imf::Header, dimension mismatch (frame size = {}, header data "
116 "window size: ({}, {}))", frame.getSizePx(), dataWidth, dataHeight);
117 }
118
119 // Recall that we treat `TFrame` to contain the full data window. `Imf::Slice` expects the base
120 // data pointer to point at the first byte of the display window (OpenEXR's origin, on the
121 // upper-right corner). These variables helps the calculation of the offsets. Basically,
122 // OpenEXR calculates the memory address of a pixel by `base + x * xStride + y * yStride` (this
123 // formula is always valid as long as pixel outside of data window is not accessed, even if data
124 // window is smaller than display window).
125 //
126 // See https://openexr.com/en/latest/ReadingAndWritingImageFiles.html#writing-an-image-file
127 //
128 const auto pixelBytes = sizeof(T) * N;
129 const auto scanlineBytes = pixelBytes * dataWidth;
130 const auto xStride = static_cast<std::ptrdiff_t>(pixelBytes);
131 const auto yStride = -static_cast<std::ptrdiff_t>(scanlineBytes);
132
133 PH_ASSERT_GE(dataHeight, 1);
134 const char* byteData = reinterpret_cast<const char*>(frame.getPixelData().data());
135 const char* firstScanlineData = byteData + scanlineBytes * (dataHeight - 1);
136 const char* firstDisplayWindowScanlineData = firstScanlineData + (-minDataY * yStride - minDataX * xStride);
137
138#if PH_DEBUG
139 // Purposely use all variables to calculate the end of scanline bytes
140 const char* scanlineDataEnd = firstScanlineData + yStride * (dataHeight - 1) + xStride * dataWidth;
141 const T* scanlineDataEndExpected = frame.getPixelData().data() + N * frame.widthPx();
142 PH_ASSERT(scanlineDataEnd == reinterpret_cast<const char*>(scanlineDataEndExpected));
143#endif
144
145 for(std::size_t channelIdx = 0; channelIdx < N; ++channelIdx)
146 {
147 // Skip channels without a name
148 if(channelNames[channelIdx].empty())
149 {
150 continue;
151 }
152
153 framebuffer.insert(
154 std::string(channelNames[channelIdx]),
155 Imf::Slice(
156 VALUE_TYPE,
157 // necessary evil here: OpenEXR currently do not have something like `Imf::SliceView`,
158 // so we need to `const_cast` here
159 const_cast<char*>(firstDisplayWindowScanlineData + sizeof(T) * channelIdx),
160 xStride,
161 // another necessary evil: we need to present `frame` with its last scanline first and
162 // use a negative y-stride as OpenEXR expect images in top-down manner; this parameter
163 // is unsigned and we relied on proper overflow behavior, see https://lists.aswf.io/g/openexr-dev/topic/openexr_rgbaoutputfile_is/70222932
164 yStride));
165 }
166}
167
168#endif
169// end PH_THIRD_PARTY_HAS_OPENEXR
170
171}// end namespace ph
TPixelType< HdrComponent > PixelType
Definition TFrame.h:28
IO functions and helpers for processing EXR files.
The root for all renderer implementations.
Definition EEngineProject.h:6
TFrame< HdrComponent, 3 > HdrRgbFrame
Definition frame_fwd.h:17
float32 HdrComponent
Definition frame_fwd.h:14