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

Quelle  codec_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/codestream_header.h>
#include <jxl/color_encoding.h>
#include <jxl/encode.h>
#include <jxl/types.h>

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "lib/extras/common.h"
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/dec/decode.h"
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"

namespace jxl {

using ::jxl::test::ThreadPoolForTests;

namespace extras {

Status PnmParseSigned(Bytes str, double* v);
Status PnmParseUnsigned(Bytes str, size_t* v);

namespace {

Span<const uint8_t> MakeSpan(const char* str) {
  return Bytes(reinterpret_cast<const uint8_t*>(str), strlen(str));
}

std::string ExtensionFromCodec(Codec codec, const bool is_gray,
                               const bool has_alpha,
                               const size_t bits_per_sample) {
  switch (codec) {
    case Codec::kJPG:
      return ".jpg";
    case Codec::kPGX:
      return ".pgx";
    case Codec::kPNG:
      return ".png";
    case Codec::kPNM:
      if (bits_per_sample == 32) return ".pfm";
      if (has_alpha) return ".pam";
      return is_gray ? ".pgm" : ".ppm";
    case Codec::kEXR:
      return ".exr";
    default:
      return std::string();
  }
}

void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
                     const PackedImage& im1, size_t bits_per_sample1,
                     bool lossless = true) {
  ASSERT_EQ(im0.xsize, im1.xsize);
  ASSERT_EQ(im0.ysize, im1.ysize);
  ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
  auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
    return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
  };
  double factor0 = get_factor(im0.format, bits_per_sample0);
  double factor1 = get_factor(im1.format, bits_per_sample1);
  const auto* pixels0 = static_cast<const uint8_t*>(im0.pixels());
  const auto* pixels1 = static_cast<const uint8_t*>(im1.pixels());
  auto rgba0 =
      test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
  auto rgba1 =
      test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
  double tolerance =
      lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
  if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
    tolerance = 0.5 * std::max(factor0, factor1);
  }
  for (size_t y = 0; y < im0.ysize; ++y) {
    for (size_t x = 0; x < im0.xsize; ++x) {
      for (size_t c = 0; c < im0.format.num_channels; ++c) {
        size_t ix = (y * im0.xsize + x) * 4 + c;
        double val0 = rgba0[ix];
        double val1 = rgba1[ix];
        ASSERT_NEAR(val1, val0, tolerance)
            << "y = " << y << " x = " << x << " c = " << c;
      }
    }
  }
}

JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
  JxlColorEncoding c;
  c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
  c.white_point = JXL_WHITE_POINT_D65;
  c.primaries = JXL_PRIMARIES_P3;
  c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
  c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
  // Roundtrip through internal color encoding to fill in primaries and white
  // point CIE xy coordinates.
  ColorEncoding c_internal;
  EXPECT_TRUE(c_internal.FromExternal(c));
  c = c_internal.ToExternal();
  return c;
}

std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
  ColorEncoding c;
  EXPECT_TRUE(c.FromExternal(color_encoding));
  EXPECT_TRUE(!c.ICC().empty());
  return c.ICC();
}

void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
                      size_t bits_per_sample) {
  uint64_t max_val = (1ull << bits_per_sample) - 1;
  if (format.data_type == JXL_TYPE_UINT8) {
    *out = rng->UniformU(0, max_val);
  } else if (format.data_type == JXL_TYPE_UINT16) {
    uint32_t val = rng->UniformU(0, max_val);
    if (format.endianness == JXL_BIG_ENDIAN) {
      StoreBE16(val, out);
    } else {
      StoreLE16(val, out);
    }
  } else {
    ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
    float val = rng->UniformF(0.0, 1.0);
    uint32_t uval;
    memcpy(&uval, &val, 4);
    if (format.endianness == JXL_BIG_ENDIAN) {
      StoreBE32(uval, out);
    } else {
      StoreLE32(uval, out);
    }
  }
}

void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
  JxlPixelFormat format = image->format;
  size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
  uint8_t* out = static_cast<uint8_t*>(image->pixels());
  size_t stride = image->xsize * format.num_channels * bytes_per_channel;
  ASSERT_EQ(image->pixels_size, image->ysize * stride);
  Rng rng(129);
  for (size_t y = 0; y < image->ysize; ++y) {
    for (size_t x = 0; x < image->xsize; ++x) {
      for (size_t c = 0; c < format.num_channels; ++c) {
        StoreRandomValue(out, &rng, format, bits_per_sample);
        out += bytes_per_channel;
      }
    }
  }
}

struct TestImageParams {
  Codec codec;
  size_t xsize;
  size_t ysize;
  size_t bits_per_sample;
  bool is_gray;
  bool add_alpha;
  bool big_endian;
  bool add_extra_channels;

  bool ShouldTestRoundtrip() const {
    if (codec == Codec::kPNG) {
      return bits_per_sample <= 16;
    } else if (codec == Codec::kPNM) {
      // TODO(szabadka) Make PNM encoder endianness-aware.
      return ((bits_per_sample <= 16 && big_endian) ||
              (bits_per_sample == 32 && !add_alpha && !big_endian));
    } else if (codec == Codec::kPGX) {
      return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
              !add_alpha);
    } else if (codec == Codec::kEXR) {
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
    defined(THREAD_SANITIZER)
      // OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
      return false;
#else
      return bits_per_sample == 32 && !is_gray;
#endif
    } else if (codec == Codec::kJPG) {
      return bits_per_sample == 8 && !add_alpha;
    } else {
      return false;
    }
  }

  JxlPixelFormat PixelFormat() const {
    JxlPixelFormat format;
    format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
    format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
                        : bits_per_sample > 8 ? JXL_TYPE_UINT16
                                              : JXL_TYPE_UINT8);
    format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
    format.align = 0;
    return format;
  }

  std::string DebugString() const {
    std::ostringstream os;
    os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
       << " be: " << big_endian << " ec: " << add_extra_channels;
    return os.str();
  }
};

void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
  ppf->info.xsize = params.xsize;
  ppf->info.ysize = params.ysize;
  ppf->info.bits_per_sample = params.bits_per_sample;
  ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
  ppf->info.num_color_channels = params.is_gray ? 1 : 3;
  ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
  ppf->info.alpha_premultiplied = TO_JXL_BOOL(params.codec == Codec::kEXR);

  JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
  ppf->icc = GenerateICC(color_encoding);
  ppf->color_encoding = color_encoding;

  JXL_TEST_ASSIGN_OR_DIE(
      PackedFrame frame,
      PackedFrame::Create(params.xsize, params.ysize, params.PixelFormat()));
  FillPackedImage(params.bits_per_sample, &frame.color);
  if (params.add_extra_channels) {
    for (size_t i = 0; i < 7; ++i) {
      JxlPixelFormat ec_format = params.PixelFormat();
      ec_format.num_channels = 1;
      JXL_TEST_ASSIGN_OR_DIE(
          PackedImage ec,
          PackedImage::Create(params.xsize, params.ysize, ec_format));
      FillPackedImage(params.bits_per_sample, &ec);
      frame.extra_channels.emplace_back(std::move(ec));
      PackedExtraChannel pec;
      pec.ec_info.bits_per_sample = params.bits_per_sample;
      pec.ec_info.type = static_cast<JxlExtraChannelType>(i);
      ppf->extra_channels_info.emplace_back(std::move(pec));
    }
  }
  ppf->frames.emplace_back(std::move(frame));
}

// Ensures reading a newly written file leads to the same image pixels.
void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
  if (!params.ShouldTestRoundtrip()) return;

  std::string extension = ExtensionFromCodec(
      params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
  printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());

  PackedPixelFile ppf_in;
  CreateTestImage(params, &ppf_in);

  EncodedImage encoded;
  auto encoder = Encoder::FromExtension(extension);
  if (!encoder) {
    fprintf(stderr, "Skipping test because of missing codec support.\n");
    return;
  }
  ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
  ASSERT_EQ(encoded.bitstreams.size(), 1);

  PackedPixelFile ppf_out;
  ColorHints color_hints;
  if (params.codec == Codec::kPNM || params.codec == Codec::kPGX) {
    color_hints.Add("color_space",
                    params.is_gray ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
  }
  ASSERT_TRUE(DecodeBytes(Bytes(encoded.bitstreams[0]), color_hints, &ppf_out));
  if (params.codec == Codec::kPNG && ppf_out.icc.empty()) {
    // Decoding a PNG may drop the ICC profile if there's a valid cICP chunk.
    // Rendering intent is not preserved in this case.
    EXPECT_EQ(ppf_in.color_encoding.color_space,
              ppf_out.color_encoding.color_space);
    EXPECT_EQ(ppf_in.color_encoding.white_point,
              ppf_out.color_encoding.white_point);
    if (ppf_in.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) {
      EXPECT_EQ(ppf_in.color_encoding.primaries,
                ppf_out.color_encoding.primaries);
    }
    EXPECT_EQ(ppf_in.color_encoding.transfer_function,
              ppf_out.color_encoding.transfer_function);
    EXPECT_EQ(ppf_out.color_encoding.rendering_intent,
              JXL_RENDERING_INTENT_RELATIVE);
  } else if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
             params.codec != Codec::kEXR) {
    EXPECT_EQ(ppf_in.icc, ppf_out.icc);
  }

  ASSERT_EQ(ppf_out.frames.size(), 1);
  const auto& frame_in = ppf_in.frames[0];
  const auto& frame_out = ppf_out.frames[0];
  VerifySameImage(frame_in.color, ppf_in.info.bits_per_sample, frame_out.color,
                  ppf_out.info.bits_per_sample,
                  /*lossless=*/params.codec != Codec::kJPG);
  ASSERT_EQ(frame_in.extra_channels.size(), frame_out.extra_channels.size());
  ASSERT_EQ(ppf_out.extra_channels_info.size(),
            frame_out.extra_channels.size());
  for (size_t i = 0; i < frame_in.extra_channels.size(); ++i) {
    VerifySameImage(frame_in.extra_channels[i], ppf_in.info.bits_per_sample,
                    frame_out.extra_channels[i], ppf_out.info.bits_per_sample,
                    /*lossless=*/true);
    EXPECT_EQ(ppf_out.extra_channels_info[i].ec_info.type,
              ppf_in.extra_channels_info[i].ec_info.type);
  }
}

TEST(CodecTest, TestRoundTrip) {
  ThreadPoolForTests pool(12);

  TestImageParams params;
  params.xsize = 7;
  params.ysize = 4;

  for (Codec codec :
       {Codec::kPNG, Codec::kPNM, Codec::kPGX, Codec::kEXR, Codec::kJPG}) {
    for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
      for (bool is_gray : {falsetrue}) {
        for (bool add_alpha : {falsetrue}) {
          for (bool big_endian : {falsetrue}) {
            params.codec = codec;
            params.bits_per_sample = static_cast<size_t>(bits_per_sample);
            params.is_gray = is_gray;
            params.add_alpha = add_alpha;
            params.big_endian = big_endian;
            params.add_extra_channels = false;
            TestRoundTrip(params, pool.get());
            if (codec == Codec::kPNM && add_alpha) {
              params.add_extra_channels = true;
              TestRoundTrip(params, pool.get());
            }
          }
        }
      }
    }
  }
}

TEST(CodecTest, LosslessPNMRoundtrip) {
  ThreadPoolForTests pool(12);

  static const char* kChannels[] = {"""g""ga""rgb""rgba"};
  static const char* kExtension[] = {""".pgm"".pam"".ppm"".pam"};
  for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) {
    for (size_t channels = 1; channels <= 4; ++channels) {
      if (bit_depth == 1 && (channels == 2 || channels == 4)) continue;
      std::string extension(kExtension[channels]);
      std::string filename = "jxl/flower/flower_small." +
                             std::string(kChannels[channels]) + ".depth" +
                             std::to_string(bit_depth) + extension;
      const std::vector<uint8_t> orig = jxl::test::ReadTestData(filename);

      PackedPixelFile ppf;
      ColorHints color_hints;
      color_hints.Add("color_space",
                      channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
      ASSERT_TRUE(
          DecodeBytes(Bytes(orig.data(), orig.size()), color_hints, &ppf));

      EncodedImage encoded;
      auto encoder = Encoder::FromExtension(extension);
      ASSERT_TRUE(encoder.get());
      ASSERT_TRUE(encoder->Encode(ppf, &encoded, pool.get()));
      ASSERT_EQ(encoded.bitstreams.size(), 1);
      ASSERT_EQ(orig.size(), encoded.bitstreams[0].size());
      EXPECT_EQ(0,
                memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size()));
    }
  }
}

TEST(CodecTest, TestPNM) {
  size_t u = 77777;  // Initialized to wrong value.
  double d = 77.77;
// Failing to parse invalid strings results in a crash if `JXL_CRASH_ON_ERROR`
// is defined and hence the tests fail. Therefore we only run these tests if
// `JXL_CRASH_ON_ERROR` is not defined.
#if (!JXL_CRASH_ON_ERROR)
  ASSERT_FALSE(PnmParseUnsigned(MakeSpan(""), &u));
  ASSERT_FALSE(PnmParseUnsigned(MakeSpan("+"), &u));
  ASSERT_FALSE(PnmParseUnsigned(MakeSpan("-"), &u));
  ASSERT_FALSE(PnmParseUnsigned(MakeSpan("A"), &u));

  ASSERT_FALSE(PnmParseSigned(MakeSpan(""), &d));
  ASSERT_FALSE(PnmParseSigned(MakeSpan("+"), &d));
  ASSERT_FALSE(PnmParseSigned(MakeSpan("-"), &d));
  ASSERT_FALSE(PnmParseSigned(MakeSpan("A"), &d));
#endif
  ASSERT_TRUE(PnmParseUnsigned(MakeSpan("1"), &u));
  ASSERT_TRUE(u == 1);

  ASSERT_TRUE(PnmParseUnsigned(MakeSpan("32"), &u));
  ASSERT_TRUE(u == 32);

  ASSERT_TRUE(PnmParseSigned(MakeSpan("1"), &d));
  ASSERT_TRUE(d == 1.0);
  ASSERT_TRUE(PnmParseSigned(MakeSpan("+2"), &d));
  ASSERT_TRUE(d == 2.0);
  ASSERT_TRUE(PnmParseSigned(MakeSpan("-3"), &d));
  ASSERT_TRUE(std::abs(d - -3.0) < 1E-15);
  ASSERT_TRUE(PnmParseSigned(MakeSpan("3.141592"), &d));
  ASSERT_TRUE(std::abs(d - 3.141592) < 1E-15);
  ASSERT_TRUE(PnmParseSigned(MakeSpan("-3.141592"), &d));
  ASSERT_TRUE(std::abs(d - -3.141592) < 1E-15);
}

TEST(CodecTest, FormatNegotiation) {
  const std::vector<JxlPixelFormat> accepted_formats = {
      {/*num_channels=*/4,
       /*data_type=*/JXL_TYPE_UINT16,
       /*endianness=*/JXL_NATIVE_ENDIAN,
       /*align=*/0},
      {/*num_channels=*/3,
       /*data_type=*/JXL_TYPE_UINT8,
       /*endianness=*/JXL_NATIVE_ENDIAN,
       /*align=*/0},
      {/*num_channels=*/3,
       /*data_type=*/JXL_TYPE_UINT16,
       /*endianness=*/JXL_NATIVE_ENDIAN,
       /*align=*/0},
      {/*num_channels=*/1,
       /*data_type=*/JXL_TYPE_UINT8,
       /*endianness=*/JXL_NATIVE_ENDIAN,
       /*align=*/0},
  };

  JxlBasicInfo info;
  JxlEncoderInitBasicInfo(&info);
  info.bits_per_sample = 12;
  info.num_color_channels = 2;

  JxlPixelFormat format;
  EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));

  info.num_color_channels = 3;
  ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
  EXPECT_EQ(format.num_channels, info.num_color_channels);
  // 16 is the smallest accepted format that can accommodate the 12-bit data.
  EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
}

TEST(CodecTest, EncodeToPNG) {
  ThreadPool* const pool = nullptr;

  std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
  if (!png_encoder) {
    fprintf(stderr, "Skipping test because of missing codec support.\n");
    return;
  }

  const std::vector<uint8_t> original_png = jxl::test::ReadTestData(
      "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
  PackedPixelFile ppf;
  ASSERT_TRUE(extras::DecodeBytes(Bytes(original_png), ColorHints(), &ppf));

  const JxlPixelFormat& format = ppf.frames.front().color.format;
  const auto& format_matcher = [&format](const JxlPixelFormat& candidate) {
    return (candidate.num_channels == format.num_channels) &&
           (candidate.data_type == format.data_type) &&
           (candidate.endianness == format.endianness);
  };
  const auto formats = png_encoder->AcceptedFormats();
  ASSERT_TRUE(std::any_of(formats.begin(), formats.end(), format_matcher));
  EncodedImage encoded_png;
  ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
  EXPECT_TRUE(encoded_png.icc.empty());
  ASSERT_EQ(encoded_png.bitstreams.size(), 1);

  PackedPixelFile decoded_ppf;
  ASSERT_TRUE(extras::DecodeBytes(Bytes(encoded_png.bitstreams.front()),
                                  ColorHints(), &decoded_ppf));

  ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
  ASSERT_EQ(decoded_ppf.frames.size(), 1);
  VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
                  decoded_ppf.frames[0].color,
                  decoded_ppf.info.bits_per_sample);
}

}  // namespace
}  // namespace extras
}  // namespace jxl

Messung V0.5
C=96 H=87 G=91

¤ Dauer der Verarbeitung: 0.11 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.