Coverage Report

Created: 2026-08-14 08:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/extras/enc/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/enc/exr.h"
7
8
#include <memory>
9
10
// IWYU pragma: no_include "ImathConfig.h"
11
// IWYU pragma: no_include "ImfChromaticities.h"
12
// IWYU pragma: no_include "ImfHeader.h"
13
// IWYU pragma: no_include "OpenEXRConfig.h"
14
#include "lib/extras/enc/encode.h"
15
16
#if !JPEGXL_ENABLE_EXR
17
18
namespace jxl {
19
namespace extras {
20
0
std::unique_ptr<Encoder> GetEXREncoder() { return nullptr; }
21
}  // namespace extras
22
}  // namespace jxl
23
24
#else  // JPEGXL_ENABLE_EXR
25
26
#include <ImathVec.h>
27
#include <ImfIO.h>
28
#include <ImfNamespace.h>  // IWYU pragma: keep
29
#include <ImfRgba.h>
30
#include <ImfRgbaFile.h>
31
#include <ImfStandardAttributes.h>
32
#include <ImfThreading.h>
33
#include <jxl/codestream_header.h>
34
#include <jxl/color_encoding.h>
35
#include <jxl/types.h>
36
37
#include <algorithm>
38
#include <cstddef>
39
#include <cstdint>
40
#include <cstring>
41
#include <utility>
42
#include <vector>
43
44
#include "lib/extras/packed_image.h"
45
#include "lib/jxl/base/byte_order.h"
46
#include "lib/jxl/base/common.h"
47
#include "lib/jxl/base/compiler_specific.h"
48
#include "lib/jxl/base/data_parallel.h"
49
#include "lib/jxl/base/status.h"
50
51
namespace jxl {
52
namespace extras {
53
namespace {
54
55
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
56
namespace Imath = IMATH_NAMESPACE;
57
58
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
59
// uint64_t as recommended causes build failures with previous OpenEXR versions
60
// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
61
// to uint64_t. This alternative should work in all cases.
62
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
63
64
class InMemoryOStream : public OpenEXR::OStream {
65
 public:
66
  // `bytes` must outlive the InMemoryOStream.
67
  explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
68
      : OStream(/*fileName=*/""), bytes_(*bytes) {}
69
70
  void write(const char c[], const int n) override {
71
    if (bytes_.size() < pos_ + n) {
72
      bytes_.resize(pos_ + n);
73
    }
74
    std::copy_n(c, n, bytes_.begin() + pos_);
75
    pos_ += n;
76
  }
77
78
  ExrInt64 tellp() override { return pos_; }
79
  void seekp(const ExrInt64 pos) override {
80
    if (bytes_.size() + 1 < pos) {
81
      bytes_.resize(pos - 1);
82
    }
83
    pos_ = pos;
84
  }
85
86
 private:
87
  std::vector<uint8_t>& bytes_;
88
  size_t pos_ = 0;
89
};
90
91
// Loads a Big-Endian float
92
float LoadBEFloat(const uint8_t* p) {
93
  uint32_t u = LoadBE32(p);
94
  float result;
95
  memcpy(&result, &u, 4);
96
  return result;
97
}
98
99
// Loads a Little-Endian float
100
float LoadLEFloat(const uint8_t* p) {
101
  uint32_t u = LoadLE32(p);
102
  float result;
103
  memcpy(&result, &u, 4);
104
  return result;
105
}
106
107
Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info,
108
                      const JxlColorEncoding& c_enc, ThreadPool* pool,
109
                      std::vector<uint8_t>* bytes) {
110
  OpenEXR::setGlobalThreadCount(0);
111
112
  const size_t xsize = image.xsize;
113
  const size_t ysize = image.ysize;
114
  const bool has_alpha = info.alpha_bits > 0;
115
  const bool alpha_is_premultiplied = FROM_JXL_BOOL(info.alpha_premultiplied);
116
117
  if (info.num_color_channels != 3) {
118
    return JXL_FAILURE("OpenEXR encoding: expected 3 color channels, got %u",
119
                       static_cast<unsigned>(info.num_color_channels));
120
  }
121
  if (c_enc.color_space != JXL_COLOR_SPACE_RGB &&
122
      c_enc.color_space != JXL_COLOR_SPACE_GRAY) {
123
    return JXL_FAILURE(
124
        "OpenEXR encoding: expected RGB (%d) or grayscale (%d) colorspace, got "
125
        "%d",
126
        static_cast<int>(JXL_COLOR_SPACE_RGB),
127
        static_cast<int>(JXL_COLOR_SPACE_GRAY),
128
        static_cast<int>(c_enc.color_space));
129
  }
130
  if (c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) {
131
    return JXL_FAILURE(
132
        "OpenEXR encoding: expected linear transfer function (%d), got %d",
133
        static_cast<int>(JXL_TRANSFER_FUNCTION_LINEAR),
134
        static_cast<int>(c_enc.transfer_function));
135
  }
136
137
  const size_t num_channels = 3 + (has_alpha ? 1 : 0);
138
  const JxlPixelFormat format = image.format;
139
140
  if (format.data_type != JXL_TYPE_FLOAT) {
141
    return JXL_FAILURE("Unsupported pixel format for OpenEXR output");
142
  }
143
144
  const uint8_t* in = static_cast<const uint8_t*>(image.pixels());
145
  size_t in_stride = num_channels * 4 * xsize;
146
147
  OpenEXR::Header header(xsize, ysize);
148
  if (c_enc.color_space == JXL_COLOR_SPACE_RGB) {
149
    OpenEXR::Chromaticities chromaticities;
150
    chromaticities.red =
151
        Imath::V2f(c_enc.primaries_red_xy[0], c_enc.primaries_red_xy[1]);
152
    chromaticities.green =
153
        Imath::V2f(c_enc.primaries_green_xy[0], c_enc.primaries_green_xy[1]);
154
    chromaticities.blue =
155
        Imath::V2f(c_enc.primaries_blue_xy[0], c_enc.primaries_blue_xy[1]);
156
    chromaticities.white =
157
        Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]);
158
    OpenEXR::addChromaticities(header, chromaticities);
159
  }
160
  OpenEXR::addWhiteLuminance(header, info.intensity_target);
161
162
  auto loadFloat =
163
      format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat;
164
  auto loadAlpha =
165
      has_alpha ? loadFloat : [](const uint8_t* p) -> float { return 1.0f; };
166
167
  // Ensure that the destructor of RgbaOutputFile has run before we look at the
168
  // size of `bytes`.
169
  {
170
    InMemoryOStream os(bytes);
171
    OpenEXR::RgbaOutputFile output(
172
        os, header,
173
        c_enc.color_space == JXL_COLOR_SPACE_GRAY
174
            ? (has_alpha ? OpenEXR::WRITE_YA : OpenEXR::WRITE_Y)
175
            : (has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB));
176
    // How many rows to write at once. Again, the OpenEXR documentation
177
    // recommends writing the whole image in one call.
178
    const int y_chunk_size = ysize;
179
    std::vector<OpenEXR::Rgba> output_rows(xsize * y_chunk_size);
180
181
    for (size_t start_y = 0; start_y < ysize; start_y += y_chunk_size) {
182
      // Inclusive.
183
      const size_t end_y = std::min(start_y + y_chunk_size - 1, ysize - 1);
184
      output.setFrameBuffer(output_rows.data() - start_y * xsize,
185
                            /*xStride=*/1, /*yStride=*/xsize);
186
      for (size_t y = start_y; y <= end_y; ++y) {
187
        const uint8_t* in_row = &in[(y - start_y) * in_stride];
188
        OpenEXR::Rgba* const JXL_RESTRICT row_data =
189
            &output_rows[(y - start_y) * xsize];
190
        for (size_t x = 0; x < xsize; ++x) {
191
          const uint8_t* in_pixel = &in_row[4 * num_channels * x];
192
          float r = loadFloat(&in_pixel[0]);
193
          float g = loadFloat(&in_pixel[4]);
194
          float b = loadFloat(&in_pixel[8]);
195
          const float alpha = loadAlpha(&in_pixel[12]);
196
          if (!alpha_is_premultiplied) {
197
            r *= alpha;
198
            g *= alpha;
199
            b *= alpha;
200
          }
201
          row_data[x] = OpenEXR::Rgba(r, g, b, alpha);
202
        }
203
      }
204
      output.writePixels(/*numScanLines=*/end_y - start_y + 1);
205
    }
206
  }
207
208
  return true;
209
}
210
211
class EXREncoder : public Encoder {
212
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
213
    std::vector<JxlPixelFormat> formats;
214
    for (const uint32_t num_channels : {3, 4}) {
215
      for (const JxlDataType data_type : {JXL_TYPE_FLOAT}) {
216
        for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
217
          formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
218
                                           /*data_type=*/data_type,
219
                                           /*endianness=*/endianness,
220
                                           /*align=*/0});
221
        }
222
      }
223
    }
224
    return formats;
225
  }
226
  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
227
                ThreadPool* pool) const override {
228
    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
229
    encoded_image->icc.clear();
230
    encoded_image->bitstreams.clear();
231
    encoded_image->bitstreams.reserve(ppf.frames.size());
232
    for (const auto& frame : ppf.frames) {
233
      JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
234
      encoded_image->bitstreams.emplace_back();
235
      JXL_RETURN_IF_ERROR(EncodeImageEXR(frame.color, ppf.info,
236
                                         ppf.color_encoding, pool,
237
                                         &encoded_image->bitstreams.back()));
238
    }
239
    return true;
240
  }
241
};
242
243
}  // namespace
244
245
std::unique_ptr<Encoder> GetEXREncoder() {
246
  return jxl::make_unique<EXREncoder>();
247
}
248
249
}  // namespace extras
250
}  // namespace jxl
251
252
#endif  // JPEGXL_ENABLE_EXR