summaryrefslogtreecommitdiff
path: root/media/libjxl/src/lib/extras/enc/exr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/lib/extras/enc/exr.cc')
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.cc167
1 files changed, 167 insertions, 0 deletions
diff --git a/media/libjxl/src/lib/extras/enc/exr.cc b/media/libjxl/src/lib/extras/enc/exr.cc
new file mode 100644
index 0000000000..3d88404888
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/exr.cc
@@ -0,0 +1,167 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/enc/exr.h"
+
+#include <ImfChromaticitiesAttribute.h>
+#include <ImfIO.h>
+#include <ImfRgbaFile.h>
+#include <ImfStandardAttributes.h>
+
+#include <vector>
+
+#include "lib/jxl/alpha.h"
+#include "lib/jxl/color_encoding_internal.h"
+#include "lib/jxl/color_management.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_image_bundle.h"
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
+namespace Imath = IMATH_NAMESPACE;
+
+// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
+// uint64_t as recommended causes build failures with previous OpenEXR versions
+// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
+// to uint64_t. This alternative should work in all cases.
+using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
+
+size_t GetNumThreads(ThreadPool* pool) {
+ size_t exr_num_threads = 1;
+ JXL_CHECK(RunOnPool(
+ pool, 0, 1,
+ [&](size_t num_threads) {
+ exr_num_threads = num_threads;
+ return true;
+ },
+ [&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
+ return exr_num_threads;
+}
+
+class InMemoryOStream : public OpenEXR::OStream {
+ public:
+ // `bytes` must outlive the InMemoryOStream.
+ explicit InMemoryOStream(PaddedBytes* const bytes)
+ : OStream(/*fileName=*/""), bytes_(*bytes) {}
+
+ void write(const char c[], const int n) override {
+ if (bytes_.size() < pos_ + n) {
+ bytes_.resize(pos_ + n);
+ }
+ std::copy_n(c, n, bytes_.begin() + pos_);
+ pos_ += n;
+ }
+
+ ExrInt64 tellp() override { return pos_; }
+ void seekp(const ExrInt64 pos) override {
+ if (bytes_.size() + 1 < pos) {
+ bytes_.resize(pos - 1);
+ }
+ pos_ = pos;
+ }
+
+ private:
+ PaddedBytes& bytes_;
+ size_t pos_ = 0;
+};
+
+} // namespace
+
+Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
+ ThreadPool* pool, PaddedBytes* bytes) {
+ // As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
+ // actual OpenEXR I/O.
+ OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
+
+ ColorEncoding c_linear = c_desired;
+ c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
+ JXL_RETURN_IF_ERROR(c_linear.CreateICC());
+ ImageMetadata metadata = io->metadata.m;
+ ImageBundle store(&metadata);
+ const ImageBundle* linear;
+ JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
+ &store, &linear));
+
+ const bool has_alpha = io->Main().HasAlpha();
+ const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
+
+ OpenEXR::Header header(io->xsize(), io->ysize());
+ const PrimariesCIExy& primaries = c_linear.HasPrimaries()
+ ? c_linear.GetPrimaries()
+ : ColorEncoding::SRGB().GetPrimaries();
+ OpenEXR::Chromaticities chromaticities;
+ chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
+ chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
+ chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
+ chromaticities.white =
+ Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
+ OpenEXR::addChromaticities(header, chromaticities);
+ OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
+
+ // Ensure that the destructor of RgbaOutputFile has run before we look at the
+ // size of `bytes`.
+ {
+ InMemoryOStream os(bytes);
+ OpenEXR::RgbaOutputFile output(
+ os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
+ // How many rows to write at once. Again, the OpenEXR documentation
+ // recommends writing the whole image in one call.
+ const int y_chunk_size = io->ysize();
+ std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
+
+ for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
+ // Inclusive.
+ const size_t end_y =
+ std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
+ output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
+ /*xStride=*/1, /*yStride=*/io->xsize());
+ JXL_RETURN_IF_ERROR(RunOnPool(
+ pool, start_y, end_y + 1, ThreadPool::NoInit,
+ [&](const uint32_t y, size_t /* thread */) {
+ const float* const JXL_RESTRICT input_rows[] = {
+ linear->color().ConstPlaneRow(0, y),
+ linear->color().ConstPlaneRow(1, y),
+ linear->color().ConstPlaneRow(2, y),
+ };
+ OpenEXR::Rgba* const JXL_RESTRICT row_data =
+ &output_rows[(y - start_y) * io->xsize()];
+ if (has_alpha) {
+ const float* const JXL_RESTRICT alpha_row =
+ io->Main().alpha().ConstRow(y);
+ if (alpha_is_premultiplied) {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] =
+ OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
+ input_rows[2][x], alpha_row[x]);
+ }
+ } else {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
+ alpha_row[x] * input_rows[1][x],
+ alpha_row[x] * input_rows[2][x],
+ alpha_row[x]);
+ }
+ }
+ } else {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
+ input_rows[2][x], 1.f);
+ }
+ }
+ },
+ "EncodeImageEXR"));
+ output.writePixels(/*numScanLines=*/end_y - start_y + 1);
+ }
+ }
+
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl