exr.cc (8039B)
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 <cstdint> 9 10 #include "lib/extras/dec/color_hints.h" 11 #include "lib/extras/packed_image.h" 12 #include "lib/jxl/base/common.h" 13 #include "lib/jxl/base/span.h" 14 #include "lib/jxl/base/status.h" 15 16 #if !JPEGXL_ENABLE_EXR 17 18 namespace jxl { 19 namespace extras { 20 bool CanDecodeEXR() { return false; } 21 22 Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, 23 PackedPixelFile* ppf, 24 const SizeConstraints* constraints) { 25 (void)bytes; 26 (void)color_hints; 27 (void)ppf; 28 (void)constraints; 29 return JXL_FAILURE("EXR is not supported"); 30 } 31 } // namespace extras 32 } // namespace jxl 33 34 #else // JPEGXL_ENABLE_EXR 35 36 #include <ImfChromaticitiesAttribute.h> 37 #include <ImfIO.h> 38 #include <ImfRgbaFile.h> 39 #include <ImfStandardAttributes.h> 40 41 #include <vector> 42 43 #ifdef __EXCEPTIONS 44 #include <stdexcept> 45 #define JXL_EXR_THROW_LENGTH_ERROR() throw std::length_error(""); 46 #else // __EXCEPTIONS 47 #define JXL_EXR_THROW_LENGTH_ERROR() JXL_CRASH() 48 #endif // __EXCEPTIONS 49 50 namespace jxl { 51 namespace extras { 52 53 namespace { 54 55 namespace OpenEXR = OPENEXR_IMF_NAMESPACE; 56 57 // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using 58 // uint64_t as recommended causes build failures with previous OpenEXR versions 59 // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent 60 // to uint64_t. This alternative should work in all cases. 61 using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg()); 62 63 constexpr int kExrBitsPerSample = 16; 64 constexpr int kExrAlphaBits = 16; 65 66 class InMemoryIStream : public OpenEXR::IStream { 67 public: 68 // The data pointed to by `bytes` must outlive the InMemoryIStream. 69 explicit InMemoryIStream(const Span<const uint8_t> bytes) 70 : IStream(/*fileName=*/""), bytes_(bytes) {} 71 72 bool isMemoryMapped() const override { return true; } 73 char* readMemoryMapped(const int n) override { 74 if (pos_ + n < pos_ || pos_ + n > bytes_.size()) { 75 JXL_EXR_THROW_LENGTH_ERROR(); 76 } 77 char* const result = 78 const_cast<char*>(reinterpret_cast<const char*>(bytes_.data() + pos_)); 79 pos_ += n; 80 return result; 81 } 82 bool read(char c[], const int n) override { 83 std::copy_n(readMemoryMapped(n), n, c); 84 return pos_ < bytes_.size(); 85 } 86 87 ExrInt64 tellg() override { return pos_; } 88 void seekg(const ExrInt64 pos) override { 89 if (pos >= bytes_.size()) { 90 JXL_EXR_THROW_LENGTH_ERROR(); 91 } 92 pos_ = pos; 93 } 94 95 private: 96 const Span<const uint8_t> bytes_; 97 size_t pos_ = 0; 98 }; 99 100 } // namespace 101 102 bool CanDecodeEXR() { return true; } 103 104 Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, 105 PackedPixelFile* ppf, 106 const SizeConstraints* constraints) { 107 InMemoryIStream is(bytes); 108 109 #ifdef __EXCEPTIONS 110 std::unique_ptr<OpenEXR::RgbaInputFile> input_ptr; 111 try { 112 input_ptr = jxl::make_unique<OpenEXR::RgbaInputFile>(is); 113 } catch (...) { 114 // silently return false if it is not an EXR file 115 return false; 116 } 117 OpenEXR::RgbaInputFile& input = *input_ptr; 118 #else 119 OpenEXR::RgbaInputFile input(is); 120 #endif 121 122 if ((input.channels() & OpenEXR::RgbaChannels::WRITE_RGB) != 123 OpenEXR::RgbaChannels::WRITE_RGB) { 124 return JXL_FAILURE("only RGB OpenEXR files are supported"); 125 } 126 const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) == 127 OpenEXR::RgbaChannels::WRITE_A; 128 129 const float intensity_target = OpenEXR::hasWhiteLuminance(input.header()) 130 ? OpenEXR::whiteLuminance(input.header()) 131 : 0; 132 133 auto image_size = input.displayWindow().size(); 134 // Size is computed as max - min, but both bounds are inclusive. 135 ++image_size.x; 136 ++image_size.y; 137 138 ppf->info.xsize = image_size.x; 139 ppf->info.ysize = image_size.y; 140 ppf->info.num_color_channels = 3; 141 142 const JxlDataType data_type = 143 kExrBitsPerSample == 16 ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT; 144 const JxlPixelFormat format{ 145 /*num_channels=*/3u + (has_alpha ? 1u : 0u), 146 /*data_type=*/data_type, 147 /*endianness=*/JXL_NATIVE_ENDIAN, 148 /*align=*/0, 149 }; 150 ppf->frames.clear(); 151 // Allocates the frame buffer. 152 { 153 JXL_ASSIGN_OR_RETURN( 154 PackedFrame frame, 155 PackedFrame::Create(image_size.x, image_size.y, format)); 156 ppf->frames.emplace_back(std::move(frame)); 157 } 158 const auto& frame = ppf->frames.back(); 159 160 const int row_size = input.dataWindow().size().x + 1; 161 // Number of rows to read at a time. 162 // https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf 163 // recommends reading the whole file at once. 164 const int y_chunk_size = input.displayWindow().size().y + 1; 165 std::vector<OpenEXR::Rgba> input_rows(row_size * y_chunk_size); 166 for (int start_y = 167 std::max(input.dataWindow().min.y, input.displayWindow().min.y); 168 start_y <= 169 std::min(input.dataWindow().max.y, input.displayWindow().max.y); 170 start_y += y_chunk_size) { 171 // Inclusive. 172 const int end_y = std::min( 173 start_y + y_chunk_size - 1, 174 std::min(input.dataWindow().max.y, input.displayWindow().max.y)); 175 input.setFrameBuffer( 176 input_rows.data() - input.dataWindow().min.x - start_y * row_size, 177 /*xStride=*/1, /*yStride=*/row_size); 178 input.readPixels(start_y, end_y); 179 for (int exr_y = start_y; exr_y <= end_y; ++exr_y) { 180 const int image_y = exr_y - input.displayWindow().min.y; 181 const OpenEXR::Rgba* const JXL_RESTRICT input_row = 182 &input_rows[(exr_y - start_y) * row_size]; 183 uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) + 184 frame.color.stride * image_y; 185 const uint32_t pixel_size = 186 (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8; 187 for (int exr_x = 188 std::max(input.dataWindow().min.x, input.displayWindow().min.x); 189 exr_x <= 190 std::min(input.dataWindow().max.x, input.displayWindow().max.x); 191 ++exr_x) { 192 const int image_x = exr_x - input.displayWindow().min.x; 193 // TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable 194 memcpy(row + image_x * pixel_size, 195 input_row + (exr_x - input.dataWindow().min.x), pixel_size); 196 } 197 } 198 } 199 200 ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; 201 ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; 202 ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; 203 ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; 204 if (OpenEXR::hasChromaticities(input.header())) { 205 ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM; 206 ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM; 207 const auto& chromaticities = OpenEXR::chromaticities(input.header()); 208 ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x; 209 ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y; 210 ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x; 211 ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y; 212 ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x; 213 ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y; 214 ppf->color_encoding.white_point_xy[0] = chromaticities.white.x; 215 ppf->color_encoding.white_point_xy[1] = chromaticities.white.y; 216 } 217 218 // EXR uses binary16 or binary32 floating point format. 219 ppf->info.bits_per_sample = kExrBitsPerSample; 220 ppf->info.exponent_bits_per_sample = kExrBitsPerSample == 16 ? 5 : 8; 221 if (has_alpha) { 222 ppf->info.alpha_bits = kExrAlphaBits; 223 ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; 224 ppf->info.alpha_premultiplied = JXL_TRUE; 225 } 226 ppf->info.intensity_target = intensity_target; 227 return true; 228 } 229 230 } // namespace extras 231 } // namespace jxl 232 233 #endif // JPEGXL_ENABLE_EXR