Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/jpeg-xl/lib/jxl/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 83 kB image not shown  

Quelle  encode_test.cc   Sprache: C

 
// 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 <jxl/cms.h>
#include <jxl/cms_interface.h>
#include <jxl/codestream_header.h>
#include <jxl/color_encoding.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <jxl/encode.h>
#include <jxl/encode_cxx.h>
#include <jxl/memory_manager.h>
#include <jxl/types.h>

#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <ostream>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "lib/extras/codec.h"
#include "lib/extras/dec/jxl.h"
#include "lib/extras/metrics.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/c_callback_support.h"
#include "lib/jxl/base/override.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
#include "lib/jxl/enc_params.h"
#include "lib/jxl/encode_internal.h"
#include "lib/jxl/modular/options.h"
#include "lib/jxl/test_image.h"
#include "lib/jxl/test_memory_manager.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"

namespace {
bool SameDecodedPixels(const std::vector<uint8_t>& compressed0,
                       const std::vector<uint8_t>& compressed1) {
  jxl::extras::JXLDecompressParams dparams;
  dparams.accepted_formats = {
      {3, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0},
      {4, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0},
  };
  jxl::extras::PackedPixelFile ppf0;
  EXPECT_TRUE(DecodeImageJXL(compressed0.data(), compressed0.size(), dparams,
                             nullptr, &ppf0, nullptr));
  jxl::extras::PackedPixelFile ppf1;
  EXPECT_TRUE(DecodeImageJXL(compressed1.data(), compressed1.size(), dparams,
                             nullptr, &ppf1, nullptr));
  return jxl::test::SamePixels(ppf0, ppf1);
}
}  // namespace

TEST(EncodeTest, AddFrameAfterCloseInputTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  JxlEncoderCloseInput(enc.get());

  size_t xsize = 64;
  size_t ysize = 64;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);

  jxl::CodecInOut input_io =
      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);

  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.uses_original_profile = 0;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  EXPECT_EQ(JXL_ENC_ERROR,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
}

TEST(EncodeTest, AddJPEGAfterCloseTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  JxlEncoderCloseInput(enc.get());

  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);

  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);

  EXPECT_EQ(JXL_ENC_ERROR,
            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
}

TEST(EncodeTest, AddFrameBeforeBasicInfoTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  size_t xsize = 64;
  size_t ysize = 64;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);

  jxl::CodecInOut input_io =
      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);

  JxlColorEncoding color_encoding;
  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
  EXPECT_EQ(JXL_ENC_ERROR,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  EXPECT_EQ(JXL_ENC_ERROR,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
}

TEST(EncodeTest, DefaultAllocTest) {
  JxlEncoder* enc = JxlEncoderCreate(nullptr);
  EXPECT_NE(nullptr, enc);
  JxlEncoderDestroy(enc);
}

TEST(EncodeTest, CustomAllocTest) {
  struct CalledCounters {
    int allocs = 0;
    int frees = 0;
  } counters;

  JxlMemoryManager mm;
  mm.opaque = &counters;
  mm.alloc = [](void* opaque, size_t size) {
    reinterpret_cast<CalledCounters*>(opaque)->allocs++;
    return malloc(size);
  };
  mm.free = [](void* opaque, void* address) {
    reinterpret_cast<CalledCounters*>(opaque)->frees++;
    free(address);
  };

  {
    JxlEncoderPtr enc = JxlEncoderMake(&mm);
    EXPECT_NE(nullptr, enc.get());
    EXPECT_LE(1, counters.allocs);
    EXPECT_EQ(0, counters.frees);
  }
  EXPECT_LE(1, counters.frees);
}

TEST(EncodeTest, DefaultParallelRunnerTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetParallelRunner(enc.get(), nullptr, nullptr));
}

void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
                         const JxlEncoderFrameSettings* frame_settings,
                         size_t max_compressed_size,
                         bool lossy_use_original_profile) {
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);

  jxl::CodecInOut input_io =
      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);

  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  if (frame_settings->values.lossless || lossy_use_original_profile) {
    basic_info.uses_original_profile = JXL_TRUE;
  } else {
    basic_info.uses_original_profile = JXL_FALSE;
  }
  // 16-bit alpha means this requires level 10
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding, JXL_TRUE);
  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetColorEncoding(enc, &color_encoding));
  JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
  pixel_format.num_channels = 1;
  EXPECT_EQ(JXL_ENC_ERROR,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  pixel_format.num_channels = 4;
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseInput(enc);

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  compressed.resize(next_out - compressed.data());
  EXPECT_LE(compressed.size(), max_compressed_size);
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
  jxl::CodecInOut decoded_io{jxl::test::MemoryManager()};
  EXPECT_TRUE(jxl::test::DecodeFile(
      {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io));

  static constexpr double kMaxButteraugli =
#if JXL_HIGH_PRECISION
      3.2;
#else
      8.7;
#endif
  EXPECT_LE(
      ComputeDistance2(input_io.Main(), decoded_io.Main(), *JxlGetDefaultCms()),
      kMaxButteraugli);
}

void VerifyFrameEncoding(JxlEncoder* enc,
                         const JxlEncoderFrameSettings* frame_settings) {
  VerifyFrameEncoding(63, 129, enc, frame_settings, 27000,
                      /*lossy_use_original_profile=*/false);
}

TEST(EncodeTest, FrameEncodingTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());
  VerifyFrameEncoding(enc.get(),
                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr));
}

TEST(EncodeTest, EncoderResetTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());
  VerifyFrameEncoding(50, 200, enc.get(),
                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 4599,
                      false);
  // Encoder should become reusable for a new image from scratch after using
  // reset.
  JxlEncoderReset(enc.get());
  VerifyFrameEncoding(157, 77, enc.get(),
                      JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 2300,
                      false);
}

TEST(EncodeTest, CmsTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());
  bool cms_called = false;
  JxlCmsInterface cms = *JxlGetDefaultCms();
  struct InitData {
    void* original_init_data;
    jpegxl_cms_init_func original_init;
    bool* cms_called;
  };
  InitData init_data = {/*original_init_data=*/cms.init_data,
                        /*original_init=*/cms.init,
                        /*cms_called=*/&cms_called};
  cms.init_data = &init_data;
  cms.init = +[](void* raw_init_data, size_t num_threads,
                 size_t pixels_per_thread, const JxlColorProfile* input_profile,
                 const JxlColorProfile* output_profile,
                 float intensity_target) {
    const InitData* init_data = static_cast<const InitData*>(raw_init_data);
    *init_data->cms_called = true;
    return init_data->original_init(init_data->original_init_data, num_threads,
                                    pixels_per_thread, input_profile,
                                    output_profile, intensity_target);
  };
  JxlEncoderSetCms(enc.get(), cms);
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
  ASSERT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderFrameSettingsSetOption(frame_settings,
                                             JXL_ENC_FRAME_SETTING_EFFORT, 8));
  VerifyFrameEncoding(enc.get(), frame_settings);
  EXPECT_TRUE(cms_called);
}

TEST(EncodeTest, FrameSettingsTest) {
  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 5));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(jxl::SpeedTier::kHare, enc->last_used_cparams.speed_tier);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    const size_t nb_options = 23;
    const JxlEncoderFrameSettingId options[nb_options] = {
        JXL_ENC_FRAME_SETTING_EFFORT,
        JXL_ENC_FRAME_SETTING_BROTLI_EFFORT,
        JXL_ENC_FRAME_SETTING_DECODING_SPEED,
        JXL_ENC_FRAME_SETTING_RESAMPLING,
        JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING,
        JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED,
        JXL_ENC_FRAME_SETTING_EPF,
        JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X,
        JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y,
        JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC,
        JXL_ENC_FRAME_SETTING_PALETTE_COLORS,
        JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM,
        JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE,
        JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE,
        JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR,
        JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS,
        JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL,
        JXL_ENC_FRAME_INDEX_BOX,
        JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES,
        JXL_ENC_FRAME_SETTING_BUFFERING,
        JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF,
        JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP,
        JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF};
    const int too_low[nb_options] = {0,  -2, -2, 3,  -2, -2, -2, -2,
                                     -2, -2, -2, -2, -2, -2, -2, -2,
                                     -2, -1, -2, -2, -2, -2, -2};
    const int too_high[nb_options] = {11, 12, 5,     16, 6,  2, 4,  -3,
                                      -3, 3,  70914, 3,  42, 4, 16, 12,
                                      2,  2,  2,     4,  2,  2, 2};
    const int in_range[nb_options] = {5,  5, 3,  1,  1,  1,  3,  -1,
                                      0,  1, -1, -1, 3,  2,  15, -1,
                                      -1, 1, 0,  0,  -1, -1, -1};
    for (size_t i = 0; i < nb_options; i++) {
      // Lower than currently supported values
      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption(
                                   frame_settings, options[i], too_low[i]));
      // Higher than currently supported values
      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption(
                                   frame_settings, options[i], too_high[i]));
      // Using SetFloatOption on integer options
      EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetFloatOption(
                                   frame_settings, options[i], 1.0f));
      // Within range of the currently supported values
      EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFrameSettingsSetOption(
                                     frame_settings, options[i], in_range[i]));
    }
    // Effort 11 should only work when expert options are allowed
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11));
    JxlEncoderAllowExpertOptions(enc.get());
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11));

    // Non-existing option
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0.f));

    // Float options
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, -1.0f));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 100.0f));
    EXPECT_EQ(
        JXL_ENC_ERROR,
        JxlEncoderFrameSettingsSetFloatOption(
            frame_settings,
            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 101.0f));
    EXPECT_EQ(
        JXL_ENC_ERROR,
        JxlEncoderFrameSettingsSetFloatOption(
            frame_settings,
            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -2.0f));
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetFloatOption(
            frame_settings,
            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -1.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 101.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -2.0f));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -1.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 101.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -2.0f));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -1.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 50.0f));
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 50.0f));

    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3700, false);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3600, false);
    EXPECT_EQ(true, enc->last_used_cparams.IsLossless());
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetFrameDistance(frame_settings, 0.5));
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3200, false);
    EXPECT_EQ(0.5, enc->last_used_cparams.butteraugli_distance);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    // Disallowed negative distance
    EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetFrameDistance(frame_settings, -1));
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(2u, enc->last_used_cparams.decoding_speed_tier);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_ERROR,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 100));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 1));
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetOption(
            frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X, 5));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(true, enc->last_used_cparams.centerfirst);
    EXPECT_EQ(5, enc->last_used_cparams.center_x);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_RESPONSIVE, 0));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, -1));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2));
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3430,
                        /*lossy_use_original_profile=*/false);
    EXPECT_EQ(false, enc->last_used_cparams.responsive);
    EXPECT_EQ(jxl::Override::kOn, enc->last_used_cparams.progressive_mode);
    EXPECT_EQ(2, enc->last_used_cparams.progressive_dc);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetFloatOption(
            frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 1777.777));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_NEAR(1777.777f, enc->last_used_cparams.photon_noise_iso, 1E-4);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 55.0f));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetFloatOption(
                  frame_settings,
                  JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 25.0f));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PALETTE_COLORS, 70000));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, 1));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_NEAR(55.0f,
                enc->last_used_cparams.channel_colors_pre_transform_percent,
                1E-6);
    EXPECT_NEAR(25.0f, enc->last_used_cparams.channel_colors_percent, 1E-6);
    EXPECT_EQ(70000, enc->last_used_cparams.palette_colors);
    EXPECT_EQ(true, enc->last_used_cparams.lossy_palette);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetOption(
            frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, 30));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, 2));
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 14));
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetFloatOption(
            frame_settings,
            JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 77.0f));
    EXPECT_EQ(
        JXL_ENC_SUCCESS,
        JxlEncoderFrameSettingsSetOption(
            frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 7));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(30, enc->last_used_cparams.colorspace);
    EXPECT_EQ(2, enc->last_used_cparams.modular_group_size_shift);
    EXPECT_EQ(jxl::Predictor::Best, enc->last_used_cparams.options.predictor);
    EXPECT_NEAR(0.77f, enc->last_used_cparams.options.nb_repeats, 1E-6);
    EXPECT_EQ(7, enc->last_used_cparams.options.max_properties);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 0));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(false, enc->last_used_cparams.force_cfl_jpeg_recompression);
  }

  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    EXPECT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 1));
    VerifyFrameEncoding(enc.get(), frame_settings);
    EXPECT_EQ(true, enc->last_used_cparams.force_cfl_jpeg_recompression);
  }
}

TEST(EncodeTest, LossyEncoderUseOriginalProfileTest) {
  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    ASSERT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7897, true);
  }
  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    ASSERT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    EXPECT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2));
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 8310, true);
  }
  {
    JxlEncoderPtr enc = JxlEncoderMake(nullptr);
    ASSERT_NE(nullptr, enc.get());
    JxlEncoderFrameSettings* frame_settings =
        JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
    ASSERT_EQ(JXL_ENC_SUCCESS,
              JxlEncoderFrameSettingsSetOption(
                  frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8));
    VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7228, true);
  }
}

namespace {
// Returns a copy of buf from offset to offset+size, or a new zeroed vector if
// the result would have been out of bounds taking integer overflow into
// account.
std::vector<uint8_t> SliceSpan(const jxl::Span<const uint8_t>& buf,
                               size_t offset, size_t size) {
  if (offset + size >= buf.size()) {
    return std::vector<uint8_t>(size, 0);
  }
  if (offset + size < offset) {
    return std::vector<uint8_t>(size, 0);
  }
  return std::vector<uint8_t>(buf.data() + offset, buf.data() + offset + size);
}

struct Box {
  // The type of the box.
  // If "uuid", use extended_type instead
  char type[4] = {0, 0, 0, 0};

  // The extended_type is only used when type == "uuid".
  // Extended types are not used in JXL. However, the box format itself
  // supports this so they are handled correctly.
  char extended_type[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  // Box data.
  jxl::Span<const uint8_t> data = jxl::Bytes(nullptr, 0);

  // If the size is not given, the datasize extends to the end of the file.
  // If this field is false, the size field is not encoded when the box is
  // serialized.
  bool data_size_given = true;

  // If successful, returns true and sets `in` to be the rest data (if any).
  // If `in` contains a box with a size larger than `in.size()`, will not
  // modify `in`, and will return true but the data `Span<uint8_t>` will
  // remain set to nullptr.
  // If unsuccessful, returns error and doesn't modify `in`.
  jxl::Status Decode(jxl::Span<const uint8_t>* in) {
    // Total box_size including this header itself.
    uint64_t box_size = LoadBE32(SliceSpan(*in, 0, 4).data());
    size_t pos = 4;

    memcpy(type, SliceSpan(*in, pos, 4).data(), 4);
    pos += 4;

    if (box_size == 1) {
      // If the size is 1, it indicates extended size read from 64-bit integer.
      box_size = LoadBE64(SliceSpan(*in, pos, 8).data());
      pos += 8;
    }

    if (!memcmp("uuid", type, 4)) {
      memcpy(extended_type, SliceSpan(*in, pos, 16).data(), 16);
      pos += 16;
    }

    // This is the end of the box header, the box data begins here. Handle
    // the data size now.
    const size_t header_size = pos;

    if (box_size != 0) {
      if (box_size < header_size) {
        return JXL_FAILURE("Invalid box size");
      }
      if (box_size > in->size()) {
        // The box is fine, but the input is too short.
        return true;
      }
      data_size_given = true;
      data = jxl::Bytes(in->data() + header_size, box_size - header_size);
    } else {
      data_size_given = false;
      data = jxl::Bytes(in->data() + header_size, in->size() - header_size);
    }

    *in = jxl::Bytes(in->data() + header_size + data.size(),
                     in->size() - header_size - data.size());
    return true;
  }
};

struct Container {
  std::vector<Box> boxes;

  // If successful, returns true and sets `in` to be the rest data (if any).
  // If unsuccessful, returns error and doesn't modify `in`.
  jxl::Status Decode(jxl::Span<const uint8_t>* in) {
    boxes.clear();

    Box signature_box;
    JXL_RETURN_IF_ERROR(signature_box.Decode(in));
    if (memcmp("JXL ", signature_box.type, 4) != 0) {
      return JXL_FAILURE("Invalid magic signature");
    }
    if (signature_box.data.size() != 4)
      return JXL_FAILURE("Invalid magic signature");
    if (signature_box.data[0] != 0xd || signature_box.data[1] != 0xa ||
        signature_box.data[2] != 0x87 || signature_box.data[3] != 0xa) {
      return JXL_FAILURE("Invalid magic signature");
    }

    Box ftyp_box;
    JXL_RETURN_IF_ERROR(ftyp_box.Decode(in));
    if (memcmp("ftyp", ftyp_box.type, 4) != 0) {
      return JXL_FAILURE("Invalid ftyp");
    }
    if (ftyp_box.data.size() != 12) return JXL_FAILURE("Invalid ftyp");
    const char* expected = "jxl \0\0\0\0jxl ";
    if (memcmp(expected, ftyp_box.data.data(), 12) != 0)
      return JXL_FAILURE("Invalid ftyp");

    while (!in->empty()) {
      Box box = {};
      JXL_RETURN_IF_ERROR(box.Decode(in));
      if (box.data.data() == nullptr) {
        // The decoding encountered a box, but not enough data yet.
        return true;
      }
      boxes.emplace_back(box);
    }

    return true;
  }
};

}  // namespace

TEST(EncodeTest, SingleFrameBoundedJXLCTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), true));
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);

  size_t xsize = 71;
  size_t ysize = 23;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);

  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.uses_original_profile = JXL_FALSE;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding,
                            /*is_gray=*/JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseInput(enc.get());

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  compressed.resize(next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);

  Container container = {};
  jxl::Span<const uint8_t> encoded_span =
      jxl::Bytes(compressed.data(), compressed.size());
  EXPECT_TRUE(container.Decode(&encoded_span));
  EXPECT_EQ(0u, encoded_span.size());
  bool found_jxlc = false;
  bool found_jxlp = false;
  // The encoder is allowed to either emit a jxlc or one or more jxlp.
  for (const auto& box : container.boxes) {
    if (memcmp("jxlc", box.type, 4) == 0) {
      EXPECT_EQ(false, found_jxlc);  // Max 1 jxlc
      EXPECT_EQ(false, found_jxlp);  // Can't mix jxlc and jxlp
      found_jxlc = true;
    }
    if (memcmp("jxlp", box.type, 4) == 0) {
      EXPECT_EQ(false, found_jxlc);  // Can't mix jxlc and jxlp
      found_jxlp = true;
    }
    // The encoder shouldn't create an unbounded box in this case, with the
    // single frame it knows the full size in time, so can help make decoding
    // more efficient by giving the full box size of the final box.
    EXPECT_EQ(true, box.data_size_given);
  }
  EXPECT_EQ(true, found_jxlc || found_jxlp);
}

TEST(EncodeTest, CodestreamLevelTest) {
  size_t xsize = 64;
  size_t ysize = 64;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);

  jxl::CodecInOut input_io =
      jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize);

  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.uses_original_profile = 0;

  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);

  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3);
  JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseInput(enc.get());

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  compressed.resize(next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);

  Container container = {};
  jxl::Span<const uint8_t> encoded_span =
      jxl::Bytes(compressed.data(), compressed.size());
  EXPECT_TRUE(container.Decode(&encoded_span));
  EXPECT_EQ(0u, encoded_span.size());
  EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4));
}

TEST(EncodeTest, CodestreamLevelVerificationTest) {
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};

  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = 64;
  basic_info.ysize = 64;
  basic_info.uses_original_profile = JXL_FALSE;

  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));

  EXPECT_EQ(5, JxlEncoderGetRequiredCodestreamLevel(enc.get()));

  // Set an image dimension that is too large for level 5, but fits in level 10

  basic_info.xsize = 1ull << 30ull;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 5));
  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get()));

  // Set an image dimension that is too large even for level 10

  basic_info.xsize = 1ull << 31ull;
  EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
}

JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGReconstructionTest) {
  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);

  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);

  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
  JxlEncoderCloseInput(enc.get());

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  compressed.resize(next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);

  jxl::extras::JXLDecompressParams dparams;
  jxl::test::DefaultAcceptedFormats(dparams);
  std::vector<uint8_t> decoded_jpeg_bytes;
  jxl::extras::PackedPixelFile ppf;
  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
                             nullptr, &ppf, &decoded_jpeg_bytes));

  EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size());
  EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
}

JXL_TRANSCODE_JPEG_TEST(EncodeTest, ProgressiveJPEGReconstructionTest) {
  const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
  const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);

  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);

  frame_settings->values.cparams.progressive_mode = jxl::Override::kOn;

  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
  JxlEncoderCloseInput(enc.get());

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  compressed.resize(next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);

  jxl::extras::JXLDecompressParams dparams;
  jxl::test::DefaultAcceptedFormats(dparams);
  std::vector<uint8_t> decoded_jpeg_bytes;
  jxl::extras::PackedPixelFile ppf;
  EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
                             nullptr, &ppf, &decoded_jpeg_bytes));

  EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size());
  EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
}

static void ProcessEncoder(JxlEncoder* enc, std::vector<uint8_t>& compressed,
                           uint8_t*& next_out, size_t& avail_out) {
  JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
  while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
    process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
    if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
      size_t offset = next_out - compressed.data();
      compressed.resize(compressed.size() * 2);
      next_out = compressed.data() + offset;
      avail_out = compressed.size() - offset;
    }
  }
  size_t offset = next_out - compressed.data();
  compressed.resize(next_out - compressed.data());
  next_out = compressed.data() + offset;
  avail_out = compressed.size() - offset;
  EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
}

TEST(EncodeTest, BasicInfoTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  size_t xsize = 1;
  size_t ysize = 1;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.uses_original_profile = 0;
  basic_info.have_animation = 1;
  basic_info.intensity_target = 123.4;
  basic_info.min_nits = 5.0;
  basic_info.linear_below = 12.7;
  basic_info.orientation = JXL_ORIENT_ROTATE_90_CW;
  basic_info.intrinsic_xsize = 88;
  basic_info.intrinsic_ysize = 99;
  basic_info.animation.tps_numerator = 55;
  basic_info.animation.tps_denominator = 77;
  basic_info.animation.num_loops = 10;
  basic_info.animation.have_timecodes = JXL_TRUE;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseFrames(enc.get());
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
  EXPECT_NE(nullptr, dec.get());
  EXPECT_EQ(JXL_DEC_SUCCESS,
            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO));
  // Allow testing the orientation field, without this setting it will be
  // overridden to identity.
  JxlDecoderSetKeepOrientation(dec.get(), JXL_TRUE);
  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
  JxlDecoderCloseInput(dec.get());

  for (;;) {
    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
    if (status == JXL_DEC_ERROR) {
      FAIL();
    } else if (status == JXL_DEC_SUCCESS) {
      break;
    } else if (status == JXL_DEC_BASIC_INFO) {
      JxlBasicInfo basic_info2;
      EXPECT_EQ(JXL_DEC_SUCCESS,
                JxlDecoderGetBasicInfo(dec.get(), &basic_info2));
      EXPECT_EQ(basic_info.xsize, basic_info2.xsize);
      EXPECT_EQ(basic_info.ysize, basic_info2.ysize);
      EXPECT_EQ(basic_info.bits_per_sample, basic_info2.bits_per_sample);
      EXPECT_EQ(basic_info.exponent_bits_per_sample,
                basic_info2.exponent_bits_per_sample);
      EXPECT_NEAR(basic_info.intensity_target, basic_info2.intensity_target,
                  0.5);
      EXPECT_NEAR(basic_info.min_nits, basic_info2.min_nits, 0.5);
      EXPECT_NEAR(basic_info.linear_below, basic_info2.linear_below, 0.5);
      EXPECT_EQ(basic_info.relative_to_max_display,
                basic_info2.relative_to_max_display);
      EXPECT_EQ(basic_info.uses_original_profile,
                basic_info2.uses_original_profile);
      EXPECT_EQ(basic_info.orientation, basic_info2.orientation);
      EXPECT_EQ(basic_info.intrinsic_xsize, basic_info2.intrinsic_xsize);
      EXPECT_EQ(basic_info.intrinsic_ysize, basic_info2.intrinsic_ysize);
      EXPECT_EQ(basic_info.num_color_channels, basic_info2.num_color_channels);
      // TODO(lode): also test num_extra_channels, but currently there may be a
      // mismatch between 0 and 1 if there is alpha, until encoder support for
      // extra channels is fully implemented.
      EXPECT_EQ(basic_info.alpha_bits, basic_info2.alpha_bits);
      EXPECT_EQ(basic_info.alpha_exponent_bits,
                basic_info2.alpha_exponent_bits);
      EXPECT_EQ(basic_info.alpha_premultiplied,
                basic_info2.alpha_premultiplied);

      EXPECT_EQ(basic_info.have_preview, basic_info2.have_preview);
      if (basic_info.have_preview) {
        EXPECT_EQ(basic_info.preview.xsize, basic_info2.preview.xsize);
        EXPECT_EQ(basic_info.preview.ysize, basic_info2.preview.ysize);
      }

      EXPECT_EQ(basic_info.have_animation, basic_info2.have_animation);
      if (basic_info.have_animation) {
        EXPECT_EQ(basic_info.animation.tps_numerator,
                  basic_info2.animation.tps_numerator);
        EXPECT_EQ(basic_info.animation.tps_denominator,
                  basic_info2.animation.tps_denominator);
        EXPECT_EQ(basic_info.animation.num_loops,
                  basic_info2.animation.num_loops);
        EXPECT_EQ(basic_info.animation.have_timecodes,
                  basic_info2.animation.have_timecodes);
      }
    } else {
      FAIL();  // unexpected status
    }
  }
}

TEST(EncodeTest, AnimationHeaderTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  size_t xsize = 1;
  size_t ysize = 1;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.have_animation = JXL_TRUE;
  basic_info.animation.tps_numerator = 1000;
  basic_info.animation.tps_denominator = 1;
  basic_info.animation.have_timecodes = JXL_TRUE;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));

  std::string frame_name = "test frame";
  JxlFrameHeader header;
  JxlEncoderInitFrameHeader(&header);
  header.duration = 50;
  header.timecode = 800;
  header.layer_info.blend_info.blendmode = JXL_BLEND_BLEND;
  header.layer_info.blend_info.source = 2;
  header.layer_info.blend_info.clamp = 1;
  JxlBlendInfo extra_channel_blend_info;
  JxlEncoderInitBlendInfo(&extra_channel_blend_info);
  extra_channel_blend_info.blendmode = JXL_BLEND_MULADD;
  JxlEncoderSetFrameHeader(frame_settings, &header);
  JxlEncoderSetExtraChannelBlendInfo(frame_settings, 0,
                                     &extra_channel_blend_info);
  JxlEncoderSetFrameName(frame_settings, frame_name.c_str());

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseFrames(enc.get());
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
  EXPECT_NE(nullptr, dec.get());

  // To test the blend_info fields, coalescing must be set to false in the
  // decoder.
  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE));
  EXPECT_EQ(JXL_DEC_SUCCESS,
            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME));
  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
  JxlDecoderCloseInput(dec.get());

  bool seen_frame = false;

  for (;;) {
    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
    if (status == JXL_DEC_ERROR) {
      FAIL();
    } else if (status == JXL_DEC_SUCCESS) {
      break;
    } else if (status == JXL_DEC_FRAME) {
      seen_frame = true;
      JxlFrameHeader header2;
      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2));
      EXPECT_EQ(header.duration, header2.duration);
      EXPECT_EQ(header.timecode, header2.timecode);
      EXPECT_EQ(header.layer_info.blend_info.blendmode,
                header2.layer_info.blend_info.blendmode);
      EXPECT_EQ(header.layer_info.blend_info.clamp,
                header2.layer_info.blend_info.clamp);
      EXPECT_EQ(header.layer_info.blend_info.source,
                header2.layer_info.blend_info.source);
      EXPECT_EQ(frame_name.size(), header2.name_length);
      JxlBlendInfo extra_channel_blend_info2;
      JxlDecoderGetExtraChannelBlendInfo(dec.get(), 0,
                                         &extra_channel_blend_info2);
      EXPECT_EQ(extra_channel_blend_info.blendmode,
                extra_channel_blend_info2.blendmode);
      if (header2.name_length > 0) {
        std::string frame_name2(header2.name_length + 1, '\0');
        EXPECT_EQ(JXL_DEC_SUCCESS,
                  JxlDecoderGetFrameName(dec.get(), &frame_name2.front(),
                                         frame_name2.size()));
        frame_name2.resize(header2.name_length);
        EXPECT_EQ(frame_name, frame_name2);
      }
    } else {
      FAIL();  // unexpected status
    }
  }

  EXPECT_EQ(true, seen_frame);
}
TEST(EncodeTest, CroppedFrameTest) {
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  size_t xsize = 300;
  size_t ysize = 300;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
  std::vector<uint8_t> pixels2(pixels.size());
  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  // Encoding a 300x300 frame in an image that is only 100x100
  basic_info.xsize = 100;
  basic_info.ysize = 100;
  basic_info.uses_original_profile = JXL_TRUE;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));

  JxlFrameHeader header;
  JxlEncoderInitFrameHeader(&header);
  header.layer_info.have_crop = JXL_TRUE;
  header.layer_info.xsize = xsize;
  header.layer_info.ysize = ysize;
  header.layer_info.crop_x0 = -50;
  header.layer_info.crop_y0 = -250;
  JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE);
  JxlEncoderSetFrameHeader(frame_settings, &header);
  JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT,
                                   1);

  std::vector<uint8_t> compressed = std::vector<uint8_t>(100);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  JxlEncoderCloseFrames(enc.get());
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
  EXPECT_NE(nullptr, dec.get());
  // Non-coalesced decoding so we can get the full uncropped frame
  EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE));
  EXPECT_EQ(
      JXL_DEC_SUCCESS,
      JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
  JxlDecoderCloseInput(dec.get());

  bool seen_frame = false;
  bool checked_frame = false;
  for (;;) {
    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
    if (status == JXL_DEC_ERROR) {
      FAIL();
    } else if (status == JXL_DEC_SUCCESS) {
      break;
    } else if (status == JXL_DEC_FRAME) {
      seen_frame = true;
      JxlFrameHeader header2;
      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2));
      EXPECT_EQ(header.layer_info.xsize, header2.layer_info.xsize);
      EXPECT_EQ(header.layer_info.ysize, header2.layer_info.ysize);
      EXPECT_EQ(header.layer_info.crop_x0, header2.layer_info.crop_x0);
      EXPECT_EQ(header.layer_info.crop_y0, header2.layer_info.crop_y0);
    } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
      EXPECT_EQ(JXL_DEC_SUCCESS,
                JxlDecoderSetImageOutBuffer(dec.get(), &pixel_format,
                                            pixels2.data(), pixels2.size()));
    } else if (status == JXL_DEC_FULL_IMAGE) {
      EXPECT_EQ(0, memcmp(pixels.data(), pixels2.data(), pixels.size()));
      checked_frame = true;
    } else {
      FAIL();  // unexpected status
    }
  }
  EXPECT_EQ(true, checked_frame);
  EXPECT_EQ(true, seen_frame);
}

struct EncodeBoxTest : public testing::TestWithParam<std::tuple<bool, size_t>> {
};

JXL_BOXES_TEST_P(EncodeBoxTest, BoxTest) {
  // Test with uncompressed boxes and with brob boxes
  bool compress_box = std::get<0>(GetParam());
  size_t xml_box_size = std::get<1>(GetParam());
  // TODO(firsching): use xml_box_size
  (void)xml_box_size;
  // Tests adding two metadata boxes with the encoder: an exif box before the
  // image frame, and an xml box after the image frame. Then verifies the
  // decoder can decode them, they are in the expected place, and have the
  // correct content after decoding.
  JxlEncoderPtr enc = JxlEncoderMake(nullptr);
  EXPECT_NE(nullptr, enc.get());

  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseBoxes(enc.get()));

  JxlEncoderFrameSettings* frame_settings =
      JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
  size_t xsize = 50;
  size_t ysize = 17;
  JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
  std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
  JxlBasicInfo basic_info;
  jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
  basic_info.xsize = xsize;
  basic_info.ysize = ysize;
  basic_info.uses_original_profile = JXL_FALSE;
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
  EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
  JxlColorEncoding color_encoding;
  JxlColorEncodingSetToSRGB(&color_encoding,
                            /*is_gray=*/JXL_FALSE);
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderSetColorEncoding(enc.get(), &color_encoding));

  std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
  uint8_t* next_out = compressed.data();
  size_t avail_out = compressed.size() - (next_out - compressed.data());

  // Add an early metadata box. Also add a valid 4-byte TIFF offset header
  // before the fake exif data of these box contents.
  constexpr const char* exif_test_string = "\0\0\0\0exif test data";
  const uint8_t* exif_data = reinterpret_cast<const uint8_t*>(exif_test_string);
  // Skip the 4 zeroes for strlen
  const size_t exif_size = 4 + strlen(exif_test_string + 4);
  JxlEncoderAddBox(enc.get(), "Exif", exif_data, exif_size,
                   TO_JXL_BOOL(compress_box));

  // Write to output
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  // Add image frame
  EXPECT_EQ(JXL_ENC_SUCCESS,
            JxlEncoderAddImageFrame(frame_settings, &pixel_format,
                                    pixels.data(), pixels.size()));
  // Indicate this is the last frame
  JxlEncoderCloseFrames(enc.get());

  // Write to output
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  // Add a late metadata box
  constexpr const char* xml_test_string = "";
  const uint8_t* xml_data = reinterpret_cast<const uint8_t*>(xml_test_string);
  size_t xml_size = strlen(xml_test_string);
  JxlEncoderAddBox(enc.get(), "XML ", xml_data, xml_size,
                   TO_JXL_BOOL(compress_box));

  // Indicate this is the last box
  JxlEncoderCloseBoxes(enc.get());

  // Write to output
  ProcessEncoder(enc.get(), compressed, next_out, avail_out);

  // Decode to verify the boxes, we don't decode to pixels, only the boxes.
  JxlDecoderPtr dec = JxlDecoderMake(nullptr);
  EXPECT_NE(nullptr, dec.get());

  if (compress_box) {
    EXPECT_EQ(JXL_DEC_SUCCESS,
              JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE));
  }

  EXPECT_EQ(JXL_DEC_SUCCESS,
            JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_BOX));

  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
  JxlDecoderCloseInput(dec.get());

  std::vector<uint8_t> dec_exif_box(exif_size);
  std::vector<uint8_t> dec_xml_box(xml_size);

  for (bool post_frame = false;;) {
    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
    if (status == JXL_DEC_ERROR) {
      FAIL();
    } else if (status == JXL_DEC_SUCCESS) {
      EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get()));
      break;
    } else if (status == JXL_DEC_FRAME) {
      post_frame = true;
    } else if (status == JXL_DEC_BOX) {
      // Since we gave the exif/xml box output buffer of the exact known
      // correct size, 0 bytes should be released. Same when no buffer was
      // set.
      EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get()));
      JxlBoxType type;
      EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec.get(), type, true));
      if (memcmp(type, "Exif", 4) == 0) {
        // This box should have been encoded before the image frame
        EXPECT_EQ(false, post_frame);
        JxlDecoderSetBoxBuffer(dec.get(), dec_exif_box.data(),
                               dec_exif_box.size());
      } else if (memcmp(type, "XML ", 4) == 0) {
        // This box should have been encoded after the image frame
        EXPECT_EQ(true, post_frame);
        JxlDecoderSetBoxBuffer(dec.get(), dec_xml_box.data(),
                               dec_xml_box.size());
      }
    } else {
      FAIL();  // unexpected status
    }
  }

  EXPECT_EQ(0, memcmp(exif_data, dec_exif_box.data(), exif_size));
  EXPECT_EQ(0, memcmp(xml_data, dec_xml_box.data(), xml_size));
}

std::string nameBoxTest(
    const ::testing::TestParamInfo<std::tuple<bool, size_t>>& info) {
  return (std::get<0>(info.param) ? "C" : "Unc") + std::string("ompressed") +
         "_BoxSize_" + std::to_string((std::get<1>(info.param)));
}

JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
    EncodeBoxParamsTest, EncodeBoxTest,
    testing::Combine(testing::Values(falsetrue),
                     testing::Values(256,
                                     jxl::kLargeBoxContentSizeThreshold + 77)),
    nameBoxTest);

JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGFrameTest) {
  TEST_LIBJPEG_SUPPORT();
  for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) {
    for (int skip_color_encoding = 0; skip_color_encoding < 2;
         skip_color_encoding++) {
      // cannot set color encoding if basic info is not set
      if (skip_basic_info && !skip_color_encoding) continue;
      const std::string jpeg_path = "jxl/flower/flower_cropped.jpg";
      const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
      jxl::extras::PackedPixelFile orig_ppf;
      ASSERT_TRUE(
          DecodeBytes(jxl::Bytes(orig), jxl::extras::ColorHints(), &orig_ppf));

      JxlEncoderPtr enc = JxlEncoderMake(nullptr);
      JxlEncoderFrameSettings* frame_settings =
          JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
      JxlEncoderFrameSettingsSetOption(frame_settings,
                                       JXL_ENC_FRAME_SETTING_EFFORT, 1);
      if (!skip_basic_info) {
        JxlBasicInfo basic_info;
        JxlEncoderInitBasicInfo(&basic_info);
        basic_info.xsize = orig_ppf.xsize();
        basic_info.ysize = orig_ppf.ysize();
        basic_info.uses_original_profile = JXL_TRUE;
        EXPECT_EQ(JXL_ENC_SUCCESS,
                  JxlEncoderSetBasicInfo(enc.get(), &basic_info));
      }
      if (!skip_color_encoding) {
        JxlColorEncoding color_encoding;
        JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE);
        EXPECT_EQ(JXL_ENC_SUCCESS,
                  JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
      }
      EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderAddJPEGFrame(
                                     frame_settings, orig.data(), orig.size()));
      JxlEncoderCloseInput(enc.get());

      std::vector<uint8_t> compressed = std::vector<uint8_t>(64);
      uint8_t* next_out = compressed.data();
      size_t avail_out = compressed.size() - (next_out - compressed.data());
      JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
      while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
        process_result =
            JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
        if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
          size_t offset = next_out - compressed.data();
          compressed.resize(compressed.size() * 2);
          next_out = compressed.data() + offset;
          avail_out = compressed.size() - offset;
        }
      }
      compressed.resize(next_out - compressed.data());
      EXPECT_EQ(JXL_ENC_SUCCESS, process_result);

      jxl::extras::PackedPixelFile decoded_ppf;
      EXPECT_TRUE(DecodeBytes(jxl::Bytes(compressed.data(), compressed.size()),
                              jxl::extras::ColorHints(), &decoded_ppf));

      EXPECT_LE(jxl::test::ComputeDistance2(orig_ppf, decoded_ppf), 3.5);
    }
  }
}

namespace {
class JxlStreamingAdapter {
 public:
  JxlStreamingAdapter(JxlEncoder* encoder, bool return_large_buffers,
                      bool can_seek)
      : return_large_buffers_(return_large_buffers) {
    struct JxlEncoderOutputProcessor output_processor;
    output_processor.opaque = this;
    output_processor.get_buffer =
        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::GetBuffer);
    if (can_seek) {
      output_processor.seek = METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::Seek);
    } else {
      output_processor.seek = nullptr;
    }
    output_processor.set_finalized_position =
        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::SetFinalizedPosition);
    output_processor.release_buffer =
        METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::ReleaseBuffer);
    EXPECT_EQ(JxlEncoderSetOutputProcessor(encoder, output_processor),
              JXL_ENC_SUCCESS);
  }

  std::vector<uint8_t> output() && {
    output_.resize(position_);
    return std::move(output_);
  }

  void* GetBuffer(size_t* size) {
    if (!return_large_buffers_) {
      *size = 1;
    }
    if (position_ + *size > output_.size()) {
      output_.resize(position_ + *size, 0xDA);
    }
    if (return_large_buffers_) {
      *size = output_.size() - position_;
    }
    return output_.data() + position_;
  }

  void ReleaseBuffer(size_t written_bytes) {
    // TODO(veluca): check no more bytes were written.
    Seek(position_ + written_bytes);
  }

  void Seek(uint64_t position) {
    EXPECT_GE(position, finalized_position_);
    position_ = position;
  }

  void SetFinalizedPosition(uint64_t finalized_position) {
    EXPECT_GE(finalized_position, finalized_position_);
    finalized_position_ = finalized_position;
    EXPECT_GE(position_, finalized_position_);
  }

  void CheckFinalWatermarkPosition() const {
    EXPECT_EQ(finalized_position_, position_);
  }

 private:
  std::vector<uint8_t> output_;
  size_t position_ = 0;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=95 G=93

¤ Dauer der Verarbeitung: 0.7 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.