/src/libjxl/lib/extras/dec/exr.cc
Line | Count | Source |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/extras/dec/exr.h" |
7 | | |
8 | | #include <jxl/codestream_header.h> // JXL_CHANNEL_OPTIONAL |
9 | | |
10 | | #include <cstdint> |
11 | | |
12 | | #include "lib/extras/dec/color_hints.h" |
13 | | #include "lib/extras/packed_image.h" |
14 | | #include "lib/extras/size_constraints.h" |
15 | | #include "lib/jxl/base/span.h" |
16 | | #include "lib/jxl/base/status.h" |
17 | | |
18 | | #if !JPEGXL_ENABLE_EXR |
19 | | |
20 | | namespace jxl { |
21 | | namespace extras { |
22 | 0 | bool CanDecodeEXR() { return false; } |
23 | | |
24 | | Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, |
25 | | PackedPixelFile* ppf, |
26 | 1 | const SizeConstraints* constraints) { |
27 | 1 | (void)bytes; |
28 | 1 | (void)color_hints; |
29 | 1 | (void)ppf; |
30 | 1 | (void)constraints; |
31 | 1 | return JXL_FAILURE("EXR is not supported"); |
32 | 1 | } |
33 | | } // namespace extras |
34 | | } // namespace jxl |
35 | | |
36 | | #else // JPEGXL_ENABLE_EXR |
37 | | |
38 | | #include <ImfChannelList.h> |
39 | | #include <ImfFrameBuffer.h> |
40 | | #include <ImfIO.h> |
41 | | #include <ImfInputFile.h> |
42 | | #include <ImfStandardAttributes.h> |
43 | | #include <OpenEXRConfig.h> |
44 | | #include <jxl/color_encoding.h> |
45 | | #include <jxl/types.h> |
46 | | |
47 | | #include <algorithm> |
48 | | #include <cstddef> |
49 | | #include <cstring> |
50 | | #include <memory> |
51 | | #include <set> |
52 | | #include <string> |
53 | | #include <utility> |
54 | | #include <vector> |
55 | | |
56 | | #include "lib/jxl/base/common.h" |
57 | | #include "lib/jxl/base/compiler_specific.h" |
58 | | |
59 | | #ifdef __EXCEPTIONS |
60 | | #include <IexBaseExc.h> |
61 | | #define JXL_EXR_THROW_LENGTH_ERROR(M) throw Iex::InputExc(M); |
62 | | #else // __EXCEPTIONS |
63 | | #define JXL_EXR_THROW_LENGTH_ERROR(M) JXL_CRASH() |
64 | | #endif // __EXCEPTIONS |
65 | | |
66 | | namespace jxl { |
67 | | namespace extras { |
68 | | |
69 | | namespace { |
70 | | |
71 | | namespace OpenEXR = OPENEXR_IMF_NAMESPACE; |
72 | | |
73 | | // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using |
74 | | // uint64_t as recommended causes build failures with previous OpenEXR versions |
75 | | // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent |
76 | | // to uint64_t. This alternative should work in all cases. |
77 | | using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg()); |
78 | | |
79 | | class InMemoryIStream : public OpenEXR::IStream { |
80 | | public: |
81 | | // The data pointed to by `bytes` must outlive the InMemoryIStream. |
82 | | explicit InMemoryIStream(const Span<const uint8_t> bytes) |
83 | | : IStream(/*fileName=*/""), bytes_(bytes) {} |
84 | | |
85 | | bool isMemoryMapped() const override { return true; } |
86 | | char* readMemoryMapped(const int n) override { |
87 | | if (pos_ + n < pos_) { |
88 | | JXL_EXR_THROW_LENGTH_ERROR("Overflow"); |
89 | | } |
90 | | if (pos_ + n > bytes_.size()) { |
91 | | JXL_EXR_THROW_LENGTH_ERROR("Read past end of file"); |
92 | | } |
93 | | char* const result = |
94 | | const_cast<char*>(reinterpret_cast<const char*>(bytes_.data() + pos_)); |
95 | | pos_ += n; |
96 | | return result; |
97 | | } |
98 | | bool read(char c[/*n*/], int n) override { |
99 | | // That is not stated in documentation, but the OpenEXR code expects that |
100 | | // when requested amount is not accessible and exception is thrown, all |
101 | | // the accessible data is read. |
102 | | if (pos_ + n < pos_) { |
103 | | JXL_EXR_THROW_LENGTH_ERROR("Overflow"); |
104 | | } |
105 | | if (pos_ + n > bytes_.size()) { |
106 | | int can_read = static_cast<int>(bytes_.size() - pos_); |
107 | | std::copy_n(readMemoryMapped(can_read), can_read, c); |
108 | | JXL_EXR_THROW_LENGTH_ERROR("Read past end of file"); |
109 | | } else { |
110 | | std::copy_n(readMemoryMapped(n), n, c); |
111 | | } |
112 | | return pos_ < bytes_.size(); |
113 | | } |
114 | | |
115 | | ExrInt64 tellg() override { return pos_; } |
116 | | void seekg(const ExrInt64 pos) override { |
117 | | if (pos >= bytes_.size()) { |
118 | | JXL_EXR_THROW_LENGTH_ERROR("Seeks past end of file"); |
119 | | } |
120 | | pos_ = pos; |
121 | | } |
122 | | |
123 | | private: |
124 | | const Span<const uint8_t> bytes_; |
125 | | size_t pos_ = 0; |
126 | | }; |
127 | | |
128 | | } // namespace |
129 | | |
130 | | bool CanDecodeEXR() { return true; } |
131 | | |
132 | | static std::string FindColorLayerPrefix(const OpenEXR::ChannelList& channels) { |
133 | | // check if just R,G,B channels exist; use those if present |
134 | | if (channels.findChannel("R") != nullptr && |
135 | | channels.findChannel("G") != nullptr && |
136 | | channels.findChannel("B") != nullptr) { |
137 | | return ""; |
138 | | } |
139 | | |
140 | | // check channels for presence of R,G,B with common prefix ("layer name"), |
141 | | // use the first one that is present |
142 | | for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); |
143 | | it != channels.end(); ++it) { |
144 | | std::string name = it.name(); |
145 | | size_t dotIndex = name.rfind('.'); |
146 | | if (dotIndex == std::string::npos) continue; |
147 | | std::string suffix = name.substr(dotIndex + 1); |
148 | | if (suffix != "R" && suffix != "G" && suffix != "B") continue; |
149 | | std::string prefix = name.substr(0, dotIndex + 1); |
150 | | if (channels.findChannel((prefix + "R").c_str()) != nullptr && |
151 | | channels.findChannel((prefix + "G").c_str()) != nullptr && |
152 | | channels.findChannel((prefix + "B").c_str()) != nullptr) { |
153 | | return prefix; |
154 | | } |
155 | | } |
156 | | |
157 | | return ""; |
158 | | } |
159 | | |
160 | | Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, |
161 | | PackedPixelFile* ppf, |
162 | | const SizeConstraints* constraints) { |
163 | | InMemoryIStream is(bytes); |
164 | | |
165 | | #ifdef __EXCEPTIONS |
166 | | std::unique_ptr<OpenEXR::InputFile> input_ptr; |
167 | | try { |
168 | | input_ptr = jxl::make_unique<OpenEXR::InputFile>(is); |
169 | | } catch (...) { |
170 | | // silently return false if it is not an EXR file |
171 | | return false; |
172 | | } |
173 | | OpenEXR::InputFile& input = *input_ptr; |
174 | | #else |
175 | | OpenEXR::InputFile input(is); |
176 | | #endif |
177 | | |
178 | | const OpenEXR::Header& header = input.header(); |
179 | | const OpenEXR::ChannelList& channels = header.channels(); |
180 | | |
181 | | // we don't support subsampled or UINT channels yet |
182 | | // TODO: support common cases of subsampling (2x, 4x) |
183 | | for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); |
184 | | it != channels.end(); ++it) { |
185 | | const OpenEXR::Channel& ch = it.channel(); |
186 | | if (ch.type == OpenEXR::UINT) { |
187 | | return JXL_FAILURE("OpenEXR files with UINT channels are not supported"); |
188 | | } |
189 | | if (ch.xSampling != 1 || ch.ySampling != 1) { |
190 | | return JXL_FAILURE( |
191 | | "OpenEXR files sub-sampled channels are not supported"); |
192 | | } |
193 | | } |
194 | | |
195 | | const std::string color_prefix = FindColorLayerPrefix(channels); |
196 | | const std::string ch_name_r = color_prefix + "R"; |
197 | | const OpenEXR::Channel* ch_r = channels.findChannel(ch_name_r.c_str()); |
198 | | const std::string ch_name_g = color_prefix + "G"; |
199 | | const OpenEXR::Channel* ch_g = channels.findChannel(ch_name_g.c_str()); |
200 | | const std::string ch_name_b = color_prefix + "B"; |
201 | | const OpenEXR::Channel* ch_b = channels.findChannel(ch_name_b.c_str()); |
202 | | const std::string ch_name_a = color_prefix + "A"; |
203 | | const OpenEXR::Channel* ch_a = channels.findChannel(ch_name_a.c_str()); |
204 | | // If we don't have RGB (same type) channels, we'll treat the first |
205 | | // channel as grayscale. |
206 | | bool has_rgb = (ch_r != nullptr) && (ch_g != nullptr) && (ch_b != nullptr); |
207 | | bool is_gray = !has_rgb; |
208 | | const OpenEXR::Channel* ch_gray = |
209 | | is_gray ? &channels.begin().channel() : nullptr; |
210 | | const std::string ch_name_gray = is_gray ? channels.begin().name() : ""; |
211 | | |
212 | | const auto color_type = (is_gray ? ch_gray : ch_r)->type; |
213 | | if (has_rgb) { |
214 | | if (ch_g->type != color_type || ch_b->type != color_type) { |
215 | | return JXL_FAILURE( |
216 | | "OpenEXR color channels with different types are not supported yet"); |
217 | | } |
218 | | } |
219 | | |
220 | | bool has_alpha = (ch_a != nullptr) && (ch_a != ch_gray); |
221 | | if (has_alpha) { |
222 | | if (ch_a->type != color_type) { |
223 | | return JXL_FAILURE( |
224 | | "OpenEXR color channels with different types are not supported yet"); |
225 | | } |
226 | | } |
227 | | |
228 | | const float intensity_target = |
229 | | OpenEXR::hasWhiteLuminance(header) ? OpenEXR::whiteLuminance(header) : 0; |
230 | | |
231 | | const Imath::Box2i display_window = header.displayWindow(); |
232 | | const Imath::Box2i data_window = header.dataWindow(); |
233 | | // display_window / data_window bounds come straight from the EXR file header |
234 | | // and are not otherwise constrained: an arbitrary range of ints is legal in |
235 | | // the file format. Reject coordinates outside +/-2^30 to keep the pointer |
236 | | // arithmetic below sane. JXL level 10 already caps image sizes at 2^30, so |
237 | | // this is not a real-world limitation. The inclusive window sizes are still |
238 | | // computed in 64 bits, because the span of two in-range coordinates can |
239 | | // reach 2^31, which does not fit in `int`. |
240 | | constexpr int kEXRCoordBound = 1 << 30; |
241 | | auto OutOfRange = [](int v) { |
242 | | return v < -kEXRCoordBound || v > kEXRCoordBound; |
243 | | }; |
244 | | if (OutOfRange(display_window.min.x) || OutOfRange(display_window.max.x) || |
245 | | OutOfRange(display_window.min.y) || OutOfRange(display_window.max.y) || |
246 | | OutOfRange(data_window.min.x) || OutOfRange(data_window.max.x) || |
247 | | OutOfRange(data_window.min.y) || OutOfRange(data_window.max.y)) { |
248 | | return JXL_FAILURE("EXR: window coordinates out of range"); |
249 | | } |
250 | | // TODO(eustas): empty data_window could be valid use case. |
251 | | if (display_window.isEmpty() || data_window.isEmpty()) { |
252 | | return JXL_FAILURE("EXR: empty window"); |
253 | | } |
254 | | // Size is computed as max - min, but both bounds are inclusive. Compute in |
255 | | // 64 bits: `Box2i::size()` subtracts two `int` coordinates, which overflows |
256 | | // when the (in-range) span reaches 2^31. |
257 | | const int64_t image_width = |
258 | | static_cast<int64_t>(display_window.max.x) - display_window.min.x + 1; |
259 | | const int64_t image_height = |
260 | | static_cast<int64_t>(display_window.max.y) - display_window.min.y + 1; |
261 | | |
262 | | if (!VerifyDimensions<uint32_t>(constraints, image_width, image_height)) { |
263 | | return JXL_FAILURE("image too big"); |
264 | | } |
265 | | |
266 | | // Apply the same constraints to data_window, since its dimensions drive |
267 | | // input buffer allocations and per-row pointer arithmetic below. |
268 | | const int64_t data_width = |
269 | | static_cast<int64_t>(data_window.max.x) - data_window.min.x + 1; |
270 | | const int64_t data_height = |
271 | | static_cast<int64_t>(data_window.max.y) - data_window.min.y + 1; |
272 | | if (!VerifyDimensions<uint32_t>(constraints, data_width, data_height)) { |
273 | | return JXL_FAILURE("EXR: data_window too big"); |
274 | | } |
275 | | |
276 | | // https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf |
277 | | // recommends reading the whole file at once. |
278 | | size_t num_pixels; |
279 | | // Width must correspond to data scanlines in file. |
280 | | // TODO(eustas): if projection of data_window on X axis is inside of |
281 | | // projection of display_window, then we can avoid extra memcpy. |
282 | | if (!SafeMul(image_height, data_width, num_pixels)) { |
283 | | return JXL_FAILURE("EXR: image too big"); |
284 | | } |
285 | | |
286 | | // Intersect data and display window. |
287 | | const int x1 = std::max(data_window.min.x, display_window.min.x); |
288 | | const int x2 = std::min(data_window.max.x, display_window.max.x); |
289 | | const int y1 = std::max(data_window.min.y, display_window.min.y); |
290 | | const int y2 = std::min(data_window.max.y, display_window.max.y); |
291 | | const int x_span = x2 - x1 + 1; |
292 | | const int y_span = y2 - y1 + 1; |
293 | | |
294 | | const auto get_pixel_type = [](OpenEXR::PixelType t) -> JxlDataType { |
295 | | return (t == OpenEXR::HALF) ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT; |
296 | | }; |
297 | | |
298 | | const auto get_pixel_stride = [](OpenEXR::PixelType t) -> size_t { |
299 | | return (t == OpenEXR::HALF) ? 2 : 4; |
300 | | }; |
301 | | |
302 | | const auto get_bpp = [](OpenEXR::PixelType t) -> size_t { |
303 | | return (t == OpenEXR::HALF) ? 16 : 32; |
304 | | }; |
305 | | |
306 | | const auto get_exponent_bits = [](OpenEXR::PixelType t) -> size_t { |
307 | | return (t == OpenEXR::HALF) ? 5 : 8; |
308 | | }; |
309 | | |
310 | | uint32_t num_color_channels = is_gray ? 1 : 3; |
311 | | |
312 | | ppf->info.xsize = image_width; |
313 | | ppf->info.ysize = image_height; |
314 | | ppf->info.num_color_channels = num_color_channels; |
315 | | |
316 | | const JxlPixelFormat format{ |
317 | | /*num_channels=*/num_color_channels + (has_alpha ? 1u : 0u), |
318 | | /*data_type=*/get_pixel_type(color_type), |
319 | | /*endianness=*/JXL_NATIVE_ENDIAN, |
320 | | /*align=*/0, |
321 | | }; |
322 | | ppf->frames.clear(); |
323 | | // Allocates the frame buffer. |
324 | | { |
325 | | JXL_ASSIGN_OR_RETURN( |
326 | | PackedFrame frame, |
327 | | PackedFrame::Create(image_width, image_height, format)); |
328 | | ppf->frames.emplace_back(std::move(frame)); |
329 | | } |
330 | | auto& frame = ppf->frames.back(); |
331 | | |
332 | | // Allocate extra channel images. |
333 | | std::set<const OpenEXR::Channel*> ec_set; |
334 | | std::vector<std::vector<char> > ec_data; |
335 | | for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); |
336 | | it != channels.end(); ++it) { |
337 | | const std::string name = it.name(); |
338 | | // Skip {RGB|Gray}(A) |
339 | | if (is_gray && (name == ch_name_gray)) continue; |
340 | | if (has_alpha && (name == ch_name_a)) continue; |
341 | | if (has_rgb) { |
342 | | if ((name == ch_name_r) || (name == ch_name_g) || (name == ch_name_b)) { |
343 | | continue; |
344 | | } |
345 | | } |
346 | | const OpenEXR::Channel* ch = &it.channel(); |
347 | | OpenEXR::PixelType t = ch->type; |
348 | | |
349 | | ec_set.insert(ch); |
350 | | size_t pixel_stride = get_pixel_stride(t); |
351 | | size_t volume; |
352 | | if (!SafeMul(pixel_stride, num_pixels, volume)) { |
353 | | return JXL_FAILURE("EXR: image too big"); |
354 | | } |
355 | | std::vector<char> storage(volume); |
356 | | ec_data.emplace_back(std::move(storage)); |
357 | | |
358 | | const JxlPixelFormat ec_format{/*num_channels=*/1, |
359 | | /*data_type=*/get_pixel_type(t), |
360 | | /*endianness=*/JXL_NATIVE_ENDIAN, |
361 | | /*align=*/0}; |
362 | | JXL_ASSIGN_OR_RETURN( |
363 | | PackedImage ec, |
364 | | PackedImage::Create(image_width, image_height, ec_format)); |
365 | | frame.extra_channels.emplace_back(std::move(ec)); |
366 | | JXL_DASSERT(frame.extra_channels.back().pixel_stride() == pixel_stride); |
367 | | |
368 | | PackedExtraChannel pec = {}; |
369 | | pec.ec_info.bits_per_sample = get_bpp(t); |
370 | | pec.ec_info.exponent_bits_per_sample = get_exponent_bits(t); |
371 | | // TODO: detect channel types (depth etc.) based on naming convention |
372 | | pec.ec_info.type = JXL_CHANNEL_OPTIONAL; |
373 | | pec.name = name; |
374 | | ppf->extra_channels_info.emplace_back(std::move(pec)); |
375 | | } |
376 | | ppf->info.num_extra_channels = |
377 | | (has_alpha ? 1 : 0) + frame.extra_channels.size(); |
378 | | |
379 | | const size_t color_channel_bytes = get_pixel_stride(color_type); |
380 | | const size_t color_pixel_bytes = color_channel_bytes * format.num_channels; |
381 | | size_t color_data_size; |
382 | | if (!SafeMul(color_pixel_bytes, num_pixels, color_data_size)) { |
383 | | return JXL_FAILURE("EXR: image too big"); |
384 | | } |
385 | | // Interleaved RGB{A} / Gray{A} |
386 | | std::vector<char> color_data(color_data_size); |
387 | | |
388 | | // If intersection is empty, then image is just zeroes. |
389 | | if (x_span > 0 && y_span > 0) { |
390 | | // Setup framebuffer: color/grayscale |
391 | | OpenEXR::FrameBuffer fb; |
392 | | size_t x_stride = color_pixel_bytes; |
393 | | size_t y_stride = x_stride * static_cast<size_t>(data_width); |
394 | | char* virtual_image_origin = color_data.data(); |
395 | | // Offset to match output to allocation start; when EXR puts pixel at |
396 | | // (data_window.min.x, y1) it goes to 0-th element. |
397 | | virtual_image_origin -= |
398 | | static_cast<ptrdiff_t>(y1) * static_cast<ptrdiff_t>(y_stride); |
399 | | virtual_image_origin -= static_cast<ptrdiff_t>(data_window.min.x) * |
400 | | static_cast<ptrdiff_t>(x_stride); |
401 | | if (has_rgb) { |
402 | | fb.insert(ch_name_r.c_str(), |
403 | | OpenEXR::Slice(color_type, |
404 | | virtual_image_origin + color_channel_bytes * 0, |
405 | | x_stride, y_stride)); |
406 | | fb.insert(ch_name_g.c_str(), |
407 | | OpenEXR::Slice(color_type, |
408 | | virtual_image_origin + color_channel_bytes * 1, |
409 | | x_stride, y_stride)); |
410 | | fb.insert(ch_name_b.c_str(), |
411 | | OpenEXR::Slice(color_type, |
412 | | virtual_image_origin + color_channel_bytes * 2, |
413 | | x_stride, y_stride)); |
414 | | } else { |
415 | | fb.insert(ch_name_gray.c_str(), |
416 | | OpenEXR::Slice(color_type, |
417 | | virtual_image_origin + color_channel_bytes * 0, |
418 | | x_stride, y_stride)); |
419 | | } |
420 | | |
421 | | // Setup framebuffer: alpha |
422 | | if (has_alpha) { |
423 | | fb.insert(ch_name_a.c_str(), |
424 | | OpenEXR::Slice(color_type, |
425 | | virtual_image_origin + |
426 | | color_channel_bytes * (has_rgb ? 3 : 1), |
427 | | x_stride, y_stride)); |
428 | | } |
429 | | |
430 | | // Setup framebuffer: extra channels |
431 | | size_t ec_data_idx = 0; |
432 | | for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); |
433 | | it != channels.end(); ++it) { |
434 | | const OpenEXR::Channel* ch = &it.channel(); |
435 | | if (ec_set.find(ch) == ec_set.end()) { |
436 | | continue; |
437 | | } |
438 | | auto& ec = ec_data[ec_data_idx++]; |
439 | | size_t ec_x_stride = get_pixel_stride(ch->type); |
440 | | size_t ec_y_stride = ec_x_stride * static_cast<size_t>(data_width); |
441 | | |
442 | | char* ec_virtual_image_origin = ec.data(); |
443 | | // Offset to match output to allocation start; when EXR puts pixel at |
444 | | // (data_window.min.x, y1) it goes to 0-th element. |
445 | | ec_virtual_image_origin -= |
446 | | static_cast<ptrdiff_t>(y1) * static_cast<ptrdiff_t>(ec_y_stride); |
447 | | ec_virtual_image_origin -= static_cast<ptrdiff_t>(data_window.min.x) * |
448 | | static_cast<ptrdiff_t>(ec_x_stride); |
449 | | |
450 | | fb.insert(it.name(), OpenEXR::Slice(ch->type, ec_virtual_image_origin, |
451 | | ec_x_stride, ec_y_stride)); |
452 | | } |
453 | | |
454 | | // Read EXR data |
455 | | input.setFrameBuffer(fb); |
456 | | input.readPixels(y1, y2); |
457 | | |
458 | | const int x_data = x1 - data_window.min.x; |
459 | | const int x_out = x1 - display_window.min.x; |
460 | | JXL_DASSERT(x_out >= 0); |
461 | | JXL_DASSERT(x_out + x_span <= image_width); |
462 | | |
463 | | // Copy read data into the result image. |
464 | | // TODO(eustas): should we deal with unpopulated pixels? |
465 | | for (int y = y1; y <= y2; ++y) { // Scanline index |
466 | | const int y_data = y - y1; |
467 | | const int y_out = y - display_window.min.y; |
468 | | JXL_DASSERT(y_out >= 0); |
469 | | JXL_DASSERT(y_out < image_height); |
470 | | const char* const JXL_RESTRICT data_ptr = |
471 | | &color_data[x_data * color_pixel_bytes + y_data * y_stride]; |
472 | | uint8_t* pixels = static_cast<uint8_t*>(frame.color.pixels()); |
473 | | uint8_t* image_ptr = |
474 | | pixels + x_out * color_pixel_bytes + y_out * frame.color.stride; |
475 | | memcpy(image_ptr, data_ptr, x_span * color_pixel_bytes); |
476 | | |
477 | | for (size_t ec_idx = 0; ec_idx < frame.extra_channels.size(); ++ec_idx) { |
478 | | PackedImage& ec = frame.extra_channels[ec_idx]; |
479 | | auto& data = ec_data[ec_idx]; |
480 | | size_t ec_x_stride = ec.pixel_stride(); |
481 | | size_t ec_y_stride = ec_x_stride * static_cast<size_t>(data_width); |
482 | | const char* const JXL_RESTRICT ec_data_ptr = |
483 | | &data[x_data * ec_x_stride + y_data * ec_y_stride]; |
484 | | uint8_t* ec_pixels = static_cast<uint8_t*>(ec.pixels()); |
485 | | uint8_t* ec_image_ptr = |
486 | | ec_pixels + x_out * ec_x_stride + y_out * ec.stride; |
487 | | memcpy(ec_image_ptr, ec_data_ptr, x_span * ec_x_stride); |
488 | | } |
489 | | } |
490 | | } |
491 | | |
492 | | ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; |
493 | | ppf->color_encoding.color_space = |
494 | | has_rgb ? JXL_COLOR_SPACE_RGB : JXL_COLOR_SPACE_GRAY; |
495 | | ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; |
496 | | ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; |
497 | | if (OpenEXR::hasChromaticities(header)) { |
498 | | ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM; |
499 | | ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM; |
500 | | const auto& chromaticities = OpenEXR::chromaticities(header); |
501 | | ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x; |
502 | | ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y; |
503 | | ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x; |
504 | | ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y; |
505 | | ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x; |
506 | | ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y; |
507 | | ppf->color_encoding.white_point_xy[0] = chromaticities.white.x; |
508 | | ppf->color_encoding.white_point_xy[1] = chromaticities.white.y; |
509 | | } |
510 | | |
511 | | // EXR uses binary16 or binary32 floating point format. |
512 | | ppf->info.bits_per_sample = get_bpp(color_type); |
513 | | ppf->info.exponent_bits_per_sample = get_exponent_bits(color_type); |
514 | | if (has_alpha) { |
515 | | ppf->info.alpha_bits = ppf->info.bits_per_sample; |
516 | | ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; |
517 | | ppf->info.alpha_premultiplied = JXL_TRUE; |
518 | | } |
519 | | ppf->info.intensity_target = intensity_target; |
520 | | return true; |
521 | | } |
522 | | |
523 | | } // namespace extras |
524 | | } // namespace jxl |
525 | | |
526 | | #endif // JPEGXL_ENABLE_EXR |