Bug 1709917 - Update jpeg-xl to 040eae8105b61b312a67791213091103f4c0d034 r=saschanaz

Differential Revision: https://phabricator.services.mozilla.com/D114525
This commit is contained in:
Updatebot 2021-05-07 21:12:50 +00:00
parent 8ee31eee8c
commit 5f99f0ed3b
34 changed files with 813 additions and 458 deletions

View File

@ -1,53 +1,53 @@
# Version of this schema
schema: 1
bugzilla:
# Bugzilla product and component for this directory and subdirectories
product: Core
component: "ImageLib"
# Document the source of externally hosted code
origin:
# Short name of the package/library
name: jpeg-xl
description: JPEG XL image format reference implementation
# Full URL for the package's homepage/etc
# Usually different from repository url
url: https://gitlab.com/wg1/jpeg-xl
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: commit 9a8f5195e4d1c45112fd65f184ebe115f4163ba2 (2021-05-04T13:15:00.000+02:00).
# Revision to pull in
# Must be a long or short commit SHA (long preferred)
# NOTE(krosylight): Update highway together when updating this!
revision: 9a8f5195e4d1c45112fd65f184ebe115f4163ba2
# The package's license, where possible using the mnemonic from
# https://spdx.org/licenses/
# Multiple licenses can be specified (as a YAML list)
# A "LICENSE" file must exist containing the full license text
license: Apache-2.0
license-file: LICENSE
updatebot:
maintainer-phab: saschanaz
maintainer-bz: krosylight@mozilla.com
tasks:
- type: vendoring
enabled: True
vendoring:
url: https://gitlab.com/wg1/jpeg-xl.git
source-hosting: gitlab
vendor-directory: third_party/jpeg-xl
exclude:
- doc/
- third_party/testdata/
- tools/
# Version of this schema
schema: 1
bugzilla:
# Bugzilla product and component for this directory and subdirectories
product: Core
component: "ImageLib"
# Document the source of externally hosted code
origin:
# Short name of the package/library
name: jpeg-xl
description: JPEG XL image format reference implementation
# Full URL for the package's homepage/etc
# Usually different from repository url
url: https://gitlab.com/wg1/jpeg-xl
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: commit 040eae8105b61b312a67791213091103f4c0d034 (2021-05-06T14:11:52.000+02:00).
# Revision to pull in
# Must be a long or short commit SHA (long preferred)
# NOTE(krosylight): Update highway together when updating this!
revision: 040eae8105b61b312a67791213091103f4c0d034
# The package's license, where possible using the mnemonic from
# https://spdx.org/licenses/
# Multiple licenses can be specified (as a YAML list)
# A "LICENSE" file must exist containing the full license text
license: Apache-2.0
license-file: LICENSE
updatebot:
maintainer-phab: saschanaz
maintainer-bz: krosylight@mozilla.com
tasks:
- type: vendoring
enabled: True
vendoring:
url: https://gitlab.com/wg1/jpeg-xl.git
source-hosting: gitlab
vendor-directory: third_party/jpeg-xl
exclude:
- doc/
- third_party/testdata/
- tools/

0
third_party/jpeg-xl/bash_test.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/ci.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/debian/rules vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/deps.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/docker/build.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/docker/scripts/emsdk_install.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/docker/scripts/jpegxl_builder.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/docker/scripts/msan_install.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/docker/scripts/qemu_install.sh vendored Normal file → Executable file
View File

0
third_party/jpeg-xl/js-wasm-wrapper.sh vendored Normal file → Executable file
View File

View File

@ -293,11 +293,12 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
PaddedBytes pixels(ib.xsize() * ib.ysize() *
(bits_per_sample / kBitsPerByte));
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, bits_per_sample,
/*float_out=*/false,
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool, pixels.data(),
pixels.size(), metadata.GetOrientation()));
JXL_RETURN_IF_ERROR(
ConvertToExternal(*transformed, bits_per_sample,
/*float_out=*/false,
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
pixels.data(), pixels.size(), /*out_callback=*/nullptr,
/*out_opaque=*/nullptr, metadata.GetOrientation()));
char header[kMaxHeaderSize];
int header_size = 0;

View File

@ -826,7 +826,8 @@ Status EncodeImagePNG(const CodecInOut* io, const ColorEncoding& c_desired,
JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, bits_per_sample, /*float_out=*/false,
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
pool, raw_bytes.data(), raw_bytes.size(), metadata.GetOrientation()));
pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr,
/*out_opaque=*/nullptr, metadata.GetOrientation()));
PNGState state;
// For maximum compatibility, still store 8-bit even if pixels are all zero.

View File

@ -407,6 +407,7 @@ Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
endianness, stride, pool, pixels.data(), pixels.size(),
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
metadata.GetOrientation()));
char header[kMaxHeaderSize];

View File

@ -729,14 +729,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
* @param opaque optional user data, as given to JxlDecoderSetImageOutCallback.
* @param x horizontal position of leftmost pixel of the pixel data.
* @param y vertical position of the pixel data.
* @param xsize amount of pixels included in the pixel data, horizontally.
* @param num_pixels amount of pixels included in the pixel data, horizontally.
* This is not the same as xsize of the full image, it may be smaller.
* @param pixels pixel data as a horizontal stripe, in the format passed to
* JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is
* only valid during the time the callback is running.
*/
typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
size_t xsize, const void* pixels);
size_t num_pixels, const void* pixels);
/**
* Sets pixel output callback. This is an alternative to

View File

@ -77,16 +77,6 @@ void PerformAlphaWeightedAdd(const float* bg, const float* fg, const float* fga,
}
}
void PerformMulBlending(const AlphaBlendingInputLayer& bg,
const AlphaBlendingInputLayer& fg,
const AlphaBlendingOutput& out, size_t num_pixels) {
for (size_t x = 0; x < num_pixels; ++x) {
out.r[x] = bg.r[x] * fg.r[x];
out.g[x] = bg.g[x] * fg.g[x];
out.b[x] = bg.b[x] * fg.b[x];
out.a[x] = bg.a[x] * fg.a[x];
}
}
void PerformMulBlending(const float* bg, const float* fg, float* out,
size_t num_pixels) {
for (size_t x = 0; x < num_pixels; ++x) {

View File

@ -78,20 +78,11 @@ TEST(AlphaTest, AlphaWeightedAdd) {
}
TEST(AlphaTest, Mul) {
const float bg_rgb[3] = {100, 110, 120};
const float bg_a = 180.f / 255;
const float fg_rgb[3] = {25, 21, 23};
const float fg_a = 1.f / 4;
float out_rgb[3];
float out_a;
PerformMulBlending(
/*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
/*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a},
/*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1);
EXPECT_THAT(out_rgb, ElementsAre(FloatNear(100.f * 25.f, .05f),
FloatNear(110.f * 21.f, .05f),
FloatNear(120.f * 23.f, .05f)));
EXPECT_NEAR(out_a, bg_a * fg_a, 1e-5);
const float bg = 100;
const float fg = 25;
float out;
PerformMulBlending(&bg, &fg, &out, 1);
EXPECT_THAT(out, FloatNear(fg * bg, .05f));
}
TEST(AlphaTest, PremultiplyAndUnpremultiply) {

View File

@ -302,7 +302,8 @@ Status ImageBlender::RectBlender::DoBlending(size_t y) const {
}
}
if (info_.mode == BlendMode::kAdd) {
if (info_.mode == BlendMode::kAdd ||
(info_.mode == BlendMode::kAlphaWeightedAdd && !foreground_.HasAlpha())) {
for (int p = 0; p < 3; p++) {
AddTo(overlap_row, foreground_.color().Plane(p), cropbox_row,
&dest_->color()->Plane(p));
@ -347,21 +348,20 @@ Status ImageBlender::RectBlender::DoBlending(size_t y) const {
PerformAlphaWeightedAdd(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
/*out=*/{r, g, b, a}, cropbox_row.xsize());
} else if (info_.mode == BlendMode::kMul) {
// Foreground.
const float* JXL_RESTRICT a1 = overlap_row.ConstRow(foreground_.alpha(), 0);
const float* JXL_RESTRICT r1 =
overlap_row.ConstRow(foreground_.color().Plane(0), 0);
const float* JXL_RESTRICT g1 =
overlap_row.ConstRow(foreground_.color().Plane(1), 0);
const float* JXL_RESTRICT b1 =
overlap_row.ConstRow(foreground_.color().Plane(2), 0);
// Background & destination.
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
float* JXL_RESTRICT r = cropbox_row.Row(&dest_->color()->Plane(0), 0);
float* JXL_RESTRICT g = cropbox_row.Row(&dest_->color()->Plane(1), 0);
float* JXL_RESTRICT b = cropbox_row.Row(&dest_->color()->Plane(2), 0);
PerformMulBlending(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
/*out=*/{r, g, b, a}, cropbox_row.xsize());
for (int p = 0; p < 3; p++) {
// Foreground.
const float* JXL_RESTRICT c1 =
overlap_row.ConstRow(foreground_.color().Plane(p), 0);
// Background & destination.
float* JXL_RESTRICT c = cropbox_row.Row(&dest_->color()->Plane(p), 0);
PerformMulBlending(c, c1, c, cropbox_row.xsize());
}
if (foreground_.HasAlpha()) {
const float* JXL_RESTRICT a1 =
overlap_row.ConstRow(foreground_.alpha(), 0);
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
PerformMulBlending(a, a1, a, cropbox_row.xsize());
}
} else { // kReplace
CopyImageTo(overlap_row, foreground_.color(), cropbox_row, dest_->color());
if (foreground_.HasAlpha()) {

View File

@ -201,6 +201,12 @@ struct CustomTransferFunction : public Fields {
bool IsHLG() const {
return !have_gamma_ && (transfer_function_ == TransferFunction::kHLG);
}
bool Is709() const {
return !have_gamma_ && (transfer_function_ == TransferFunction::k709);
}
bool IsDCI() const {
return !have_gamma_ && (transfer_function_ == TransferFunction::kDCI);
}
bool IsSame(const CustomTransferFunction& other) const {
if (have_gamma_ != other.have_gamma_) return false;
if (have_gamma_) {

View File

@ -86,9 +86,17 @@ struct PassesDecoderState {
// Whether to use int16 float-XYB-to-uint8-srgb conversion.
bool fast_xyb_srgb8_conversion;
// If true, rgb_output is RGBA using 4 instead of 3 bytes per pixel.
// If true, rgb_output or callback output is RGBA using 4 instead of 3 bytes
// per pixel.
bool rgb_output_is_rgba;
// Callback for line-by-line output.
std::function<void(const float*, size_t, size_t, size_t)> pixel_callback;
// Buffer of upsampling * kApplyImageFeaturesTileDim ones.
std::vector<float> opaque_alpha;
// One row per thread
std::vector<std::vector<float>> pixel_callback_rows;
// Seed for noise, to have different noise per-frame.
size_t noise_seed = 0;
@ -180,7 +188,7 @@ struct PassesDecoderState {
ZeroFillImage(&group_data.back());
#endif
}
if (rgb_output) {
if (rgb_output || pixel_callback) {
size_t log2_upsampling = CeilLog2Nonzero(shared->frame_header.upsampling);
for (size_t _ = output_pixel_data_storage[log2_upsampling].size();
_ < num_threads; _++) {
@ -188,6 +196,16 @@ struct PassesDecoderState {
kApplyImageFeaturesTileDim << log2_upsampling,
kApplyImageFeaturesTileDim << log2_upsampling);
}
opaque_alpha.resize(
kApplyImageFeaturesTileDim * shared->frame_header.upsampling, 1.0f);
if (pixel_callback) {
pixel_callback_rows.resize(num_threads);
for (size_t i = 0; i < pixel_callback_rows.size(); ++i) {
pixel_callback_rows[i].resize(kApplyImageFeaturesTileDim *
shared->frame_header.upsampling *
(rgb_output_is_rgba ? 4 : 3));
}
}
}
}
@ -202,6 +220,7 @@ struct PassesDecoderState {
std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f);
rgb_output = nullptr;
pixel_callback = nullptr;
rgb_output_is_rgba = false;
fast_xyb_srgb8_conversion = false;
used_acs = 0;

View File

@ -258,10 +258,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
bool float_out, size_t num_channels,
JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image,
size_t out_size, jxl::Orientation undo_orientation) {
size_t out_size, JxlImageOutCallback out_callback,
void* out_opaque, jxl::Orientation undo_orientation) {
if (bits_per_sample < 1 || bits_per_sample > 32) {
return JXL_FAILURE("Invalid bits_per_sample value.");
}
if (!!out_image == !!out_callback) {
return JXL_FAILURE(
"Must provide either an out_image or an out_callback, but not both.");
}
// TODO(deymo): Implement 1-bit per pixel packed in 8 samples per byte.
if (bits_per_sample == 1) {
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
@ -269,8 +274,6 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
size_t xsize = ib.xsize();
size_t ysize = ib.ysize();
uint8_t* out = reinterpret_cast<uint8_t*>(out_image);
bool want_alpha = num_channels == 2 || num_channels == 4;
size_t color_channels = num_channels <= 2 ? 1 : 3;
@ -283,6 +286,16 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
const ImageF* alpha = ib.HasAlpha() ? &ib.alpha() : nullptr;
ImageF temp_alpha;
std::vector<std::vector<uint8_t>> row_out_callback;
auto InitOutCallback = [&](size_t num_threads) {
if (out_callback) {
row_out_callback.resize(num_threads);
for (size_t i = 0; i < num_threads; ++i) {
row_out_callback[i].resize(stride);
}
}
};
if (undo_orientation != Orientation::kIdentity) {
Image3F transformed;
for (size_t c = 0; c < color_channels; ++c) {
@ -325,6 +338,7 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
[&](size_t num_threads) {
f16_cache =
Plane<hwy::float16_t>(xsize, num_channels * num_threads);
InitOutCallback(num_threads);
return true;
},
[&](const int task, int thread) {
@ -344,30 +358,42 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
HWY_DYNAMIC_DISPATCH(FloatToF16)
(row_in[r], row_f16[r], xsize);
}
uint8_t* row_out =
out_callback
? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
// interleave the one scanline
hwy::float16_t* f16_out = &(reinterpret_cast<hwy::float16_t*>(
out_image))[y * xsize * num_channels];
hwy::float16_t* row_f16_out =
reinterpret_cast<hwy::float16_t*>(row_out);
for (size_t x = 0; x < xsize; x++) {
for (size_t r = 0; r < c; r++) {
f16_out[x * num_channels + r] = row_f16[r][x];
row_f16_out[x * num_channels + r] = row_f16[r][x];
}
}
if (swap_endianness) {
uint8_t* u8_out = &(reinterpret_cast<uint8_t*>(
out_image))[y * xsize * num_channels * 2];
size_t size = xsize * num_channels * 2;
for (size_t i = 0; i < size; i += 2) {
std::swap(u8_out[i + 0], u8_out[i + 1]);
std::swap(row_out[i + 0], row_out[i + 1]);
}
}
if (out_callback) {
(*out_callback)(out_opaque, 0, y, xsize, row_out);
}
},
"ConvertF16");
} else if (bits_per_sample == 32) {
RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
[&](const int task, int /*thread*/) {
pool, 0, static_cast<uint32_t>(ysize),
[&](size_t num_threads) {
InitOutCallback(num_threads);
return true;
},
[&](const int task, int thread) {
const int64_t y = task;
size_t i = stride * y;
uint8_t* row_out =
out_callback
? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
const float* JXL_RESTRICT row_in[4];
size_t c = 0;
for (; c < color_channels; c++) {
@ -378,9 +404,12 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
}
JXL_ASSERT(c == num_channels);
if (little_endian) {
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, out + i);
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, row_out);
} else {
StoreFloatRow<StoreBEFloat>(row_in, c, xsize, out + i);
StoreFloatRow<StoreBEFloat>(row_in, c, xsize, row_out);
}
if (out_callback) {
(*out_callback)(out_opaque, 0, y, xsize, row_out);
}
},
"ConvertFloat");
@ -396,11 +425,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
pool, 0, static_cast<uint32_t>(ysize),
[&](size_t num_threads) {
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
InitOutCallback(num_threads);
return true;
},
[&](const int task, int thread) {
const int64_t y = task;
size_t i = stride * y;
uint8_t* row_out =
out_callback
? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
const float* JXL_RESTRICT row_in[4];
size_t c = 0;
for (; c < color_channels; c++) {
@ -418,26 +451,29 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
}
// TODO(deymo): add bits_per_sample == 1 case here.
if (bits_per_sample <= 8) {
StoreUintRow<Store8>(row_u32, c, xsize, 1, out + i);
StoreUintRow<Store8>(row_u32, c, xsize, 1, row_out);
} else if (bits_per_sample <= 16) {
if (little_endian) {
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, out + i);
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, row_out);
} else {
StoreUintRow<StoreBE16>(row_u32, c, xsize, 2, out + i);
StoreUintRow<StoreBE16>(row_u32, c, xsize, 2, row_out);
}
} else if (bits_per_sample <= 24) {
if (little_endian) {
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, out + i);
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, row_out);
} else {
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, out + i);
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, row_out);
}
} else {
if (little_endian) {
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, out + i);
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, row_out);
} else {
StoreUintRow<StoreBE32>(row_u32, c, xsize, 4, out + i);
StoreUintRow<StoreBE32>(row_u32, c, xsize, 4, row_out);
}
}
if (out_callback) {
(*out_callback)(out_opaque, 0, y, xsize, row_out);
}
},
"ConvertUint");
}

View File

@ -20,6 +20,7 @@
#include <stddef.h>
#include <stdint.h>
#include "jxl/decode.h"
#include "jxl/types.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
@ -37,7 +38,7 @@ namespace jxl {
// TODO(lode): support 1-bit output (bits_per_sample == 1)
// TODO(lode): support rectangle crop.
// stride_out is output scanline size in bytes, must be >=
// output_xsize * bytes_per_pixel.
// output_xsize * output_bytes_per_pixel.
// undo_orientation is an EXIF orientation to undo. Depending on the
// orientation, the output xsize and ysize are swapped compared to input
// xsize and ysize.
@ -45,7 +46,8 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
bool float_out, size_t num_channels,
JxlEndianness endianness, size_t stride_out,
jxl::ThreadPool* thread_pool, void* out_image,
size_t out_size, jxl::Orientation undo_orientation);
size_t out_size, JxlImageOutCallback out_callback,
void* out_opaque, jxl::Orientation undo_orientation);
} // namespace jxl

View File

@ -47,6 +47,7 @@ void BM_DecExternalImage_ConvertImageRGBA(benchmark::State& state) {
/*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN,
/*stride*/ bytes_per_row,
/*thread_pool=*/nullptr, interleaved.data(), interleaved.size(),
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
/*undo_orientation=*/jxl::Orientation::kIdentity));
}
}

View File

@ -429,11 +429,12 @@ void FrameDecoder::FinalizeDC() {
void FrameDecoder::AllocateOutput() {
const CodecMetadata& metadata = *frame_header_.nonserialized_metadata;
if (dec_state_->rgb_output == nullptr) {
if (dec_state_->rgb_output == nullptr && !dec_state_->pixel_callback) {
decoded_->SetFromImage(Image3F(frame_dim_.xsize_upsampled_padded,
frame_dim_.ysize_upsampled_padded),
dec_state_->output_encoding_info.color_encoding);
}
dec_state_->extra_channels.clear();
if (metadata.m.num_extra_channels > 0) {
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
const auto eci = metadata.m.extra_channel_info[i];
@ -456,7 +457,8 @@ void FrameDecoder::AllocateOutput() {
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
JXL_CHECK(finalized_dc_);
JXL_CHECK(decoded_->HasColor() || dec_state_->rgb_output != nullptr);
JXL_CHECK(decoded_->HasColor() || dec_state_->rgb_output != nullptr ||
!!dec_state_->pixel_callback);
// Decode AC group.
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
@ -765,6 +767,12 @@ Status FrameDecoder::Flush() {
if (has_blending && !is_finalized_) {
return false;
}
// No early Flush() - nothing to do - if the frame is a kSkipProgressive
// frame.
if (frame_header_.frame_type == FrameType::kSkipProgressive &&
!is_finalized_) {
return true;
}
if (decoded_->IsJPEG()) {
// Nothing to do.
return true;

View File

@ -133,6 +133,7 @@ class FrameDecoder {
// blending, the current frame cannot be referenced by future frames, sets the
// buffer to which uint8 sRGB pixels will be decoded to.
// TODO(veluca): reduce this set of restrictions.
// If an output callback is set, this function *must not* be called.
void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride,
bool is_rgba) const {
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
@ -148,6 +149,7 @@ class FrameDecoder {
dec_state_->rgb_output = rgb_output;
dec_state_->rgb_output_is_rgba = is_rgba;
dec_state_->rgb_stride = stride;
JXL_ASSERT(dec_state_->pixel_callback == nullptr);
#if !JXL_HIGH_PRECISION
if (!is_rgba && decoded_->metadata()->xyb_encoded &&
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
@ -158,9 +160,34 @@ class FrameDecoder {
#endif
}
// Same as MaybeSetRGB8OutputBuffer, but with a float callback.
// If a RGB8 output buffer is set, this function *must not* be called.
void MaybeSetFloatCallback(
const std::function<void(const float* pixels, size_t x, size_t y,
size_t num_pixels)>& cb,
bool is_rgba) const {
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
return;
}
if (frame_header_.blending_info.mode != BlendMode::kReplace ||
frame_header_.custom_size_or_origin) {
return;
}
if (frame_header_.CanBeReferenced()) {
return;
}
dec_state_->pixel_callback = cb;
dec_state_->rgb_output_is_rgba = is_rgba;
JXL_ASSERT(dec_state_->rgb_output == nullptr);
}
// Returns true if the rgb output buffer passed by MaybeSetRGB8OutputBuffer
// has been/will be populated by Flush() / FinalizeFrame().
bool HasRGBBuffer() const { return dec_state_->rgb_output != nullptr; }
// has been/will be populated by Flush() / FinalizeFrame(), or if a pixel
// callback has been used.
bool HasRGBBuffer() const {
return dec_state_->rgb_output != nullptr ||
dec_state_->pixel_callback != nullptr;
}
private:
Status ProcessDCGlobal(BitReader* br);

View File

@ -114,7 +114,40 @@ Status UndoXYBInPlace(Image3F* idct, const Rect& rect,
Store(TF_PQ().EncodedFromDisplay(d, linear_g), d, row1 + x);
Store(TF_PQ().EncodedFromDisplay(d, linear_b), d, row2 + x);
}
} else if (output_encoding_info.color_encoding.tf.IsGamma()) {
} else if (output_encoding_info.color_encoding.tf.IsHLG()) {
for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
const auto in_opsin_x = Load(d, row0 + x);
const auto in_opsin_y = Load(d, row1 + x);
const auto in_opsin_b = Load(d, row2 + x);
JXL_COMPILER_FENCE;
auto linear_r = Undefined(d);
auto linear_g = Undefined(d);
auto linear_b = Undefined(d);
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b,
output_encoding_info.opsin_params, &linear_r, &linear_g,
&linear_b);
Store(TF_HLG().EncodedFromDisplay(d, linear_r), d, row0 + x);
Store(TF_HLG().EncodedFromDisplay(d, linear_g), d, row1 + x);
Store(TF_HLG().EncodedFromDisplay(d, linear_b), d, row2 + x);
}
} else if (output_encoding_info.color_encoding.tf.Is709()) {
for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
const auto in_opsin_x = Load(d, row0 + x);
const auto in_opsin_y = Load(d, row1 + x);
const auto in_opsin_b = Load(d, row2 + x);
JXL_COMPILER_FENCE;
auto linear_r = Undefined(d);
auto linear_g = Undefined(d);
auto linear_b = Undefined(d);
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b,
output_encoding_info.opsin_params, &linear_r, &linear_g,
&linear_b);
Store(TF_709().EncodedFromDisplay(d, linear_r), d, row0 + x);
Store(TF_709().EncodedFromDisplay(d, linear_g), d, row1 + x);
Store(TF_709().EncodedFromDisplay(d, linear_b), d, row2 + x);
}
} else if (output_encoding_info.color_encoding.tf.IsGamma() ||
output_encoding_info.color_encoding.tf.IsDCI()) {
auto gamma_tf = [&](hwy::HWY_NAMESPACE::Vec<decltype(d)> v) {
return IfThenZeroElse(
v <= Set(d, 1e-5f),
@ -402,7 +435,9 @@ Status FinalizeImageRect(
// enough border available. (rect_for_if_input)
Image3F* output_color =
dec_state->rgb_output == nullptr ? output_image->color() : nullptr;
dec_state->rgb_output == nullptr && dec_state->pixel_callback == nullptr
? output_image->color()
: nullptr;
Image3F* storage_for_if = output_color;
Rect rect_for_if = output_rect;
@ -490,7 +525,7 @@ Status FinalizeImageRect(
// +-------------------------------------------------------------------+
Image3F* output_pixel_data_storage = output_color;
Rect upsampled_output_rect_for_storage = upsampled_output_rect;
if (dec_state->rgb_output) {
if (dec_state->rgb_output || dec_state->pixel_callback) {
size_t log2_upsampling = CeilLog2Nonzero(frame_header.upsampling);
if (storage_for_if == output_color) {
storage_for_if =
@ -520,12 +555,7 @@ Status FinalizeImageRect(
if (ec < metadata.extra_channel_info.size()) {
JXL_ASSERT(ec < extra_channels.size());
alpha = extra_channels[ec].first;
JXL_ASSERT(upsampled_output_rect.x0() >= extra_channels[ec].second.x0());
JXL_ASSERT(upsampled_output_rect.y0() >= extra_channels[ec].second.y0());
alpha_rect =
Rect(upsampled_output_rect.x0() - extra_channels[ec].second.x0(),
upsampled_output_rect.y0() - extra_channels[ec].second.y0(),
upsampled_output_rect.xsize(), upsampled_output_rect.ysize());
alpha_rect = extra_channels[ec].second;
}
// +----------------------------- STEP 3 ------------------------------+
@ -712,6 +742,40 @@ Status FinalizeImageRect(
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize)),
dec_state->rgb_output, dec_state->rgb_stride);
}
if (dec_state->pixel_callback != nullptr) {
Rect alpha_line_rect = alpha_rect.Lines(available_y, num_ys);
Rect color_input_line_rect =
upsampled_output_rect_for_storage.Lines(available_y, num_ys);
Rect image_line_rect =
upsampled_output_rect.Lines(available_y, num_ys)
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize));
const float* line_buffers[4];
for (size_t iy = 0; iy < image_line_rect.ysize(); iy++) {
for (size_t c = 0; c < 3; c++) {
line_buffers[c] = color_input_line_rect.ConstPlaneRow(
*output_pixel_data_storage, c, iy);
}
if (alpha) {
line_buffers[3] = alpha_line_rect.ConstRow(*alpha, iy);
} else {
line_buffers[3] = dec_state->opaque_alpha.data();
}
std::vector<float>& interleaved =
dec_state->pixel_callback_rows[thread];
size_t j = 0;
for (size_t i = 0; i < image_line_rect.xsize(); i++) {
interleaved[j++] = line_buffers[0][i];
interleaved[j++] = line_buffers[1][i];
interleaved[j++] = line_buffers[2][i];
if (dec_state->rgb_output_is_rgba) {
interleaved[j++] = line_buffers[3][i];
}
}
dec_state->pixel_callback(interleaved.data(), image_line_rect.x0(),
image_line_rect.y0() + iy,
image_line_rect.xsize());
}
}
}
}

View File

@ -346,11 +346,16 @@ Status OutputEncodingInfo::Set(const ImageMetadata& metadata) {
// TODO(veluca): keep in sync with dec_reconstruct.cc
if (!orig_color_encoding.tf.IsPQ() && !orig_color_encoding.tf.IsSRGB() &&
!orig_color_encoding.tf.IsGamma() &&
!orig_color_encoding.tf.IsLinear()) {
!orig_color_encoding.tf.IsLinear() &&
!orig_color_encoding.tf.IsHLG() && !orig_color_encoding.tf.IsDCI() &&
!orig_color_encoding.tf.Is709()) {
break;
}
if (orig_color_encoding.tf.IsGamma()) {
inverse_gamma = 1.0f / orig_color_encoding.tf.GetGamma();
inverse_gamma = orig_color_encoding.tf.GetGamma();
}
if (orig_color_encoding.tf.IsDCI()) {
inverse_gamma = 1.0f / 2.6f;
}
if (orig_color_encoding.IsGray() &&
orig_color_encoding.white_point != WhitePoint::kD65) {

View File

@ -39,6 +39,7 @@ struct OpsinParams {
struct OutputEncodingInfo {
ColorEncoding color_encoding;
// Used for Gamma and DCI transfer functions.
float inverse_gamma;
// Contains an opsin matrix that converts to the primaries of the output
// encoding.

View File

@ -360,6 +360,8 @@ struct JxlDecoderStruct {
void* preview_out_buffer;
void* dc_out_buffer;
void* image_out_buffer;
JxlImageOutCallback image_out_callback;
void* image_out_opaque;
size_t preview_out_size;
size_t dc_out_size;
@ -467,6 +469,8 @@ void JxlDecoderReset(JxlDecoder* dec) {
dec->preview_out_buffer = nullptr;
dec->dc_out_buffer = nullptr;
dec->image_out_buffer = nullptr;
dec->image_out_callback = nullptr;
dec->image_out_opaque = nullptr;
dec->preview_out_size = 0;
dec->dc_out_size = 0;
dec->image_out_size = 0;
@ -719,7 +723,9 @@ static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format,
static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
const jxl::ImageBundle& frame,
const JxlPixelFormat& format,
void* out_image, size_t out_size) {
void* out_image, size_t out_size,
JxlImageOutCallback out_callback,
void* out_opaque) {
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
// color/grayscale format
const auto& metadata = dec->metadata.m;
@ -736,7 +742,8 @@ static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
jxl::Status status = jxl::ConvertToExternal(
frame, BitsPerChannel(format.data_type), float_format,
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
out_image, out_size, undo_orientation);
out_image, out_size, /*out_callback=*/out_callback,
/*out_opaque=*/out_opaque, undo_orientation);
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
@ -928,7 +935,8 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
if (dec->preview_out_buffer) {
JxlDecoderStatus status = ConvertImageInternal(
dec, ib, dec->preview_out_format, dec->preview_out_buffer,
dec->preview_out_size);
dec->preview_out_size, /*out_callback=*/nullptr,
/*out_opaque=*/nullptr);
if (status != JXL_DEC_SUCCESS) return status;
}
return JXL_DEC_PREVIEW_IMAGE;
@ -1005,13 +1013,6 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
/*allow_partial_frames=*/false, /*allow_partial_dc_global=*/false);
if (!status) JXL_API_RETURN_IF_ERROR(status);
if (dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
dec->image_out_format.num_channels >= 3) {
bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetRGB8OutputBuffer(
reinterpret_cast<uint8_t*>(dec->image_out_buffer),
GetStride(dec, dec->image_out_format), is_rgba);
}
size_t sections_begin =
DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
@ -1038,9 +1039,43 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
(dec->next_jpeg_reconstruction_out == nullptr ||
dec->ib->jpeg_data == nullptr) &&
dec->is_last_of_still) {
// TODO(lode): remove the dec->is_last_of_still condition if the
// frame decoder needs the image buffer as working space for decoding
// non-visible or blending frames too
return JXL_DEC_NEED_IMAGE_OUT_BUFFER;
}
}
if (dec->image_out_buffer_set && !!dec->image_out_buffer &&
dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
dec->image_out_format.num_channels >= 3) {
bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetRGB8OutputBuffer(
reinterpret_cast<uint8_t*>(dec->image_out_buffer),
GetStride(dec, dec->image_out_format), is_rgba);
}
const bool little_endian =
dec->image_out_format.endianness == JXL_LITTLE_ENDIAN ||
(dec->image_out_format.endianness == JXL_NATIVE_ENDIAN &&
IsLittleEndian());
bool swap_endianness = little_endian != IsLittleEndian();
// TODO(lode): Support more formats than just native endian float32 for
// the low-memory callback path
if (dec->image_out_buffer_set && !!dec->image_out_callback &&
dec->image_out_format.data_type == JXL_TYPE_FLOAT &&
dec->image_out_format.num_channels >= 3 && !swap_endianness &&
dec->frame_dec_in_progress) {
bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetFloatCallback(
[dec](const float* pixels, size_t x, size_t y, size_t num_pixels) {
dec->image_out_callback(dec->image_out_opaque, x, y, num_pixels,
pixels);
},
is_rgba);
}
size_t pos = dec->frame_start - dec->codestream_pos;
bool get_dc = dec->is_last_of_still &&
@ -1118,9 +1153,10 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
dc_bundle.SetFromImage(
std::move(dc),
dec->passes_state->output_encoding_info.color_encoding);
JXL_API_RETURN_IF_ERROR(
ConvertImageInternal(dec, dc_bundle, dec->dc_out_format,
dec->dc_out_buffer, dec->dc_out_size));
JXL_API_RETURN_IF_ERROR(ConvertImageInternal(
dec, dc_bundle, dec->dc_out_format, dec->dc_out_buffer,
dec->dc_out_size,
/*out_callback=*/nullptr, /*out_opaque=*/nullptr));
dec->frame_stage = FrameStage::kFull;
return JXL_DEC_DC_IMAGE;
}
@ -1168,7 +1204,8 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
// Copy pixels if desired.
JxlDecoderStatus status = ConvertImageInternal(
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
dec->image_out_size);
dec->image_out_size, dec->image_out_callback,
dec->image_out_opaque);
if (status != JXL_DEC_SUCCESS) return status;
}
dec->image_out_buffer_set = false;
@ -1901,9 +1938,10 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
return JXL_DEC_SUCCESS;
}
JxlDecoderStatus status =
jxl::ConvertImageInternal(dec, *dec->ib, dec->image_out_format,
dec->image_out_buffer, dec->image_out_size);
JxlDecoderStatus status = jxl::ConvertImageInternal(
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
dec->image_out_size,
/*out_callback=*/nullptr, /*out_opaque=*/nullptr);
if (status != JXL_DEC_SUCCESS) return status;
return JXL_DEC_SUCCESS;
}
@ -2013,6 +2051,10 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) {
return JXL_API_ERROR("No image out buffer needed at this time");
}
if (dec->image_out_buffer_set && !!dec->image_out_callback) {
return JXL_API_ERROR(
"Cannot change from image out callback to image out buffer");
}
size_t min_size;
// This also checks whether the format is valid and supported and basic info
// is available.
@ -2027,14 +2069,23 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
dec->image_out_size = size;
dec->image_out_format = *format;
if (format->data_type == JXL_TYPE_UINT8 && format->num_channels >= 3 &&
dec->frame_dec_in_progress) {
bool is_rgba = format->num_channels == 4;
dec->frame_dec->MaybeSetRGB8OutputBuffer(reinterpret_cast<uint8_t*>(buffer),
jxl::GetStride(dec, *format),
is_rgba);
return JXL_DEC_SUCCESS;
}
JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec,
const JxlPixelFormat* format,
JxlImageOutCallback callback,
void* opaque) {
if (dec->image_out_buffer_set && !!dec->image_out_buffer) {
return JXL_API_ERROR(
"Cannot change from image out buffer to image out callback");
}
dec->image_out_buffer_set = true;
dec->image_out_callback = callback;
dec->image_out_opaque = opaque;
dec->image_out_format = *format;
return JXL_DEC_SUCCESS;
}

View File

@ -30,6 +30,7 @@
#include "lib/jxl/base/file_io.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
#include "lib/jxl/dec_file.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_external_image.h"
@ -55,6 +56,96 @@ void AppendU32BE(uint32_t u32, jxl::PaddedBytes* bytes) {
bytes->push_back(u32 >> 0);
}
bool Near(double expected, double value, double max_dist) {
double dist = expected > value ? expected - value : value - expected;
return dist <= max_dist;
}
// Loads a Big-Endian float
float LoadBEFloat(const uint8_t* p) {
uint32_t u = LoadBE32(p);
float result;
memcpy(&result, &u, 4);
return result;
}
// Loads a Little-Endian float
float LoadLEFloat(const uint8_t* p) {
uint32_t u = LoadLE32(p);
float result;
memcpy(&result, &u, 4);
return result;
}
// Based on highway scalar implementation, for testing
float LoadFloat16(uint16_t bits16) {
const uint32_t sign = bits16 >> 15;
const uint32_t biased_exp = (bits16 >> 10) & 0x1F;
const uint32_t mantissa = bits16 & 0x3FF;
// Subnormal or zero
if (biased_exp == 0) {
const float subnormal = (1.0f / 16384) * (mantissa * (1.0f / 1024));
return sign ? -subnormal : subnormal;
}
// Normalized: convert the representation directly (faster than ldexp/tables).
const uint32_t biased_exp32 = biased_exp + (127 - 15);
const uint32_t mantissa32 = mantissa << (23 - 10);
const uint32_t bits32 = (sign << 31) | (biased_exp32 << 23) | mantissa32;
float result;
memcpy(&result, &bits32, 4);
return result;
}
float LoadLEFloat16(const uint8_t* p) {
uint16_t bits16 = LoadLE16(p);
return LoadFloat16(bits16);
}
float LoadBEFloat16(const uint8_t* p) {
uint16_t bits16 = LoadBE16(p);
return LoadFloat16(bits16);
}
size_t GetPrecision(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT:
// Floating point mantissa precision
return 24;
case JXL_TYPE_FLOAT16:
return 11;
}
JXL_ASSERT(false); // unknown type
}
size_t GetDataBits(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT:
return 32;
case JXL_TYPE_FLOAT16:
return 16;
}
JXL_ASSERT(false); // unknown type
}
// What type of codestream format in the boxes to use for testing
enum CodeStreamBoxFormat {
// Do not use box format at all, only pure codestream
@ -323,14 +414,18 @@ PaddedBytes CreateTestJXLCodestream(Span<const uint8_t> pixels, size_t xsize,
// Decodes one-shot with the API for non-streaming decoding tests.
std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
Span<const uint8_t> compressed,
const JxlPixelFormat& format) {
const JxlPixelFormat& format,
bool use_callback, bool set_buffer_early) {
void* runner = JxlThreadParallelRunnerCreate(
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(
dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE));
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(
dec, JXL_DEC_BASIC_INFO | (set_buffer_early ? JXL_DEC_FRAME : 0) |
JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FULL_IMAGE));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
@ -342,10 +437,54 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
std::vector<uint8_t> pixels(buffer_size);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
size_t bytes_per_pixel =
format.num_channels * GetDataBits(format.data_type) / jxl::kBitsPerByte;
size_t stride = bytes_per_pixel * info.xsize;
if (format.align > 1) {
stride = jxl::DivCeil(stride, format.align) * format.align;
}
auto callback = [&](size_t x, size_t y, size_t num_pixels,
const void* pixels_row) {
memcpy(pixels.data() + stride * y + bytes_per_pixel * x, pixels_row,
num_pixels * bytes_per_pixel);
};
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
dec, &format, pixels.data(), pixels.size()));
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
std::vector<uint8_t> preview;
if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size));
preview.resize(buffer_size);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetPreviewOutBuffer(dec, &format, preview.data(),
preview.size()));
EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec));
status = JxlDecoderProcessInput(dec);
}
if (set_buffer_early) {
EXPECT_EQ(JXL_DEC_FRAME, status);
} else {
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, status);
}
if (use_callback) {
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutCallback(
dec, &format,
[](void* opaque, size_t x, size_t y, size_t xsize,
const void* pixels_row) {
auto cb = static_cast<decltype(&callback)>(opaque);
(*cb)(x, y, xsize, pixels_row);
},
/*opaque=*/&callback));
} else {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
dec, &format, pixels.data(), pixels.size()));
}
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
@ -360,9 +499,11 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
// Decodes one-shot with the API for non-streaming decoding tests.
std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
const JxlPixelFormat& format) {
const JxlPixelFormat& format,
bool use_callback, bool set_buffer_early) {
JxlDecoder* dec = JxlDecoderCreate(NULL);
std::vector<uint8_t> pixels = DecodeWithAPI(dec, compressed, format);
std::vector<uint8_t> pixels =
DecodeWithAPI(dec, compressed, format, use_callback, set_buffer_early);
JxlDecoderDestroy(dec);
return pixels;
}
@ -371,95 +512,6 @@ std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
} // namespace jxl
namespace {
bool Near(double expected, double value, double max_dist) {
double dist = expected > value ? expected - value : value - expected;
return dist <= max_dist;
}
// Loads a Big-Endian float
float LoadBEFloat(const uint8_t* p) {
uint32_t u = LoadBE32(p);
float result;
memcpy(&result, &u, 4);
return result;
}
// Loads a Little-Endian float
float LoadLEFloat(const uint8_t* p) {
uint32_t u = LoadLE32(p);
float result;
memcpy(&result, &u, 4);
return result;
}
// Based on highway scalar implementation, for testing
float LoadFloat16(uint16_t bits16) {
const uint32_t sign = bits16 >> 15;
const uint32_t biased_exp = (bits16 >> 10) & 0x1F;
const uint32_t mantissa = bits16 & 0x3FF;
// Subnormal or zero
if (biased_exp == 0) {
const float subnormal = (1.0f / 16384) * (mantissa * (1.0f / 1024));
return sign ? -subnormal : subnormal;
}
// Normalized: convert the representation directly (faster than ldexp/tables).
const uint32_t biased_exp32 = biased_exp + (127 - 15);
const uint32_t mantissa32 = mantissa << (23 - 10);
const uint32_t bits32 = (sign << 31) | (biased_exp32 << 23) | mantissa32;
float result;
memcpy(&result, &bits32, 4);
return result;
}
float LoadLEFloat16(const uint8_t* p) {
uint16_t bits16 = LoadLE16(p);
return LoadFloat16(bits16);
}
float LoadBEFloat16(const uint8_t* p) {
uint16_t bits16 = LoadBE16(p);
return LoadFloat16(bits16);
}
size_t GetPrecision(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT:
// Floating point mantissa precision
return 24;
case JXL_TYPE_FLOAT16:
return 11;
}
JXL_ASSERT(false); // unknown type
}
size_t GetDataBits(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8:
return 8;
case JXL_TYPE_UINT16:
return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT:
return 32;
case JXL_TYPE_FLOAT16:
return 16;
}
JXL_ASSERT(false); // unknown type
}
// Procedure to convert pixels to double precision, not efficient, but
// well-controlled for testing. It uses double, to be able to represent all
@ -1303,109 +1355,189 @@ TEST(DecodeTest, ICCPartialTest) {
JxlDecoderDestroy(dec);
}
TEST(DecodeTest, PixelTest) {
struct PixelTestConfig {
// Input image definition.
bool grayscale;
bool include_alpha;
size_t xsize;
size_t ysize;
bool add_preview;
// Output format.
JxlEndianness endianness;
JxlDataType data_type;
uint32_t output_channels;
// Container options.
CodeStreamBoxFormat add_container;
// Decoding mode.
bool use_callback;
bool set_buffer_early;
};
class DecodeTestParam : public ::testing::TestWithParam<PixelTestConfig> {};
TEST_P(DecodeTestParam, PixelTest) {
PixelTestConfig config = GetParam();
JxlDecoder* dec = JxlDecoderCreate(NULL);
for (int include_alpha = 0; include_alpha <= 1; include_alpha++) {
uint32_t orig_channels = include_alpha ? 4 : 3;
for (size_t box = 0; box < kCSBF_NUM_ENTRIES; ++box) {
CodeStreamBoxFormat add_container = (CodeStreamBoxFormat)box;
size_t xsize = 123, ysize = 77;
size_t num_pixels = xsize * ysize;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, orig_channels, 0);
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16,
JXL_BIG_ENDIAN, 0};
jxl::CompressParams cparams;
// Lossless to verify pixels exactly after roundtrip.
cparams.SetLossless();
// For variation: some have container and no preview, others have preview
// and no container.
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
orig_channels, cparams, add_container, false);
jxl::PaddedBytes compressed_with_preview = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
orig_channels, cparams, add_container, true);
size_t num_pixels = config.xsize * config.ysize;
uint32_t orig_channels =
(config.grayscale ? 1 : 3) + (config.include_alpha ? 1 : 0);
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(config.xsize, config.ysize, orig_channels, 0);
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
0};
jxl::CompressParams cparams;
// Lossless to verify pixels exactly after roundtrip.
cparams.SetLossless();
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), config.xsize,
config.ysize, orig_channels, cparams, config.add_container,
config.add_preview);
const JxlEndianness endiannesses[] = {JXL_NATIVE_ENDIAN,
JXL_LITTLE_ENDIAN, JXL_BIG_ENDIAN};
for (JxlEndianness endianness : endiannesses) {
for (uint32_t channels = 3; channels <= orig_channels; ++channels) {
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, endianness, 0};
JxlPixelFormat format = {config.output_channels, config.data_type,
config.endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec,
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
}
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT16, endianness, 0};
// Test with the container for one of the pixel formats.
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec,
jxl::Span<const uint8_t>(compressed_with_preview.data(),
compressed_with_preview.size()),
format);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
}
#if 0 // Disabled since external_image doesn't currently support uint32_t
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT32, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(dec,
jxl::Span<const uint8_t>(compressed.data(),
compressed.size()), format);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(),
xsize, ysize, format_orig, format));
}
#endif
{
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec,
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
}
{
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT16, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec,
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
}
}
}
}
}
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, config.use_callback, config.set_buffer_early);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * config.output_channels *
GetDataBits(config.data_type) / jxl::kBitsPerByte,
pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), config.xsize,
config.ysize, format_orig, format));
JxlDecoderDestroy(dec);
}
std::vector<PixelTestConfig> GeneratePixelTests() {
std::vector<PixelTestConfig> all_tests;
struct ChannelInfo {
bool grayscale;
bool include_alpha;
size_t output_channels;
};
ChannelInfo ch_info[] = {
{false, true, 4}, // RGBA -> RGBA
{true, false, 1}, // G -> G
{true, true, 1}, // GA -> G
{true, true, 2}, // GA -> GA
{false, false, 3}, // RGB -> RGB
{false, true, 3}, // RGBA -> RGB
{false, false, 4}, // RGB -> RGBA
};
struct OutputFormat {
JxlEndianness endianness;
JxlDataType data_type;
};
OutputFormat out_formats[] = {
{JXL_NATIVE_ENDIAN, JXL_TYPE_UINT8},
{JXL_LITTLE_ENDIAN, JXL_TYPE_UINT16},
{JXL_BIG_ENDIAN, JXL_TYPE_UINT16},
{JXL_NATIVE_ENDIAN, JXL_TYPE_FLOAT16},
{JXL_LITTLE_ENDIAN, JXL_TYPE_FLOAT},
{JXL_BIG_ENDIAN, JXL_TYPE_FLOAT},
};
auto make_test = [&](ChannelInfo ch, size_t xsize, size_t ysize, bool preview,
CodeStreamBoxFormat box, OutputFormat format,
bool use_callback, bool set_buffer_early) {
PixelTestConfig c;
c.grayscale = ch.grayscale;
c.include_alpha = ch.include_alpha;
c.add_preview = preview;
c.xsize = xsize;
c.ysize = ysize;
c.add_container = (CodeStreamBoxFormat)box;
c.output_channels = ch.output_channels;
c.data_type = format.data_type;
c.endianness = format.endianness;
c.use_callback = use_callback;
c.set_buffer_early = set_buffer_early;
all_tests.push_back(c);
};
// Test output formats and methods.
for (ChannelInfo ch : ch_info) {
for (int use_callback = 0; use_callback <= 1; use_callback++) {
for (OutputFormat fmt : out_formats) {
make_test(ch, 301, 33, /*add_preview=*/false,
CodeStreamBoxFormat::kCSBF_None, fmt, use_callback,
/*set_buffer_early=*/false);
}
}
}
// Test codestream formats.
for (size_t box = 1; box < kCSBF_NUM_ENTRIES; ++box) {
make_test(ch_info[0], 77, 33, /*add_preview=*/false,
(CodeStreamBoxFormat)box, out_formats[0], /*use_callback=*/false,
/*set_buffer_early=*/false);
}
// Test previews.
for (int add_preview = 0; add_preview <= 1; add_preview++) {
make_test(ch_info[0], 77, 33, add_preview, CodeStreamBoxFormat::kCSBF_None,
out_formats[0],
/*use_callback=*/false, /*set_buffer_early=*/false);
}
// Test setting buffers early.
make_test(ch_info[0], 300, 33, /*add_preview=*/false,
CodeStreamBoxFormat::kCSBF_None, out_formats[0],
/*use_callback=*/false, /*set_buffer_early=*/true);
return all_tests;
}
std::string PixelTestDescription(
const testing::TestParamInfo<DecodeTestParam::ParamType>& info) {
PixelTestConfig c = info.param;
std::string name;
name += std::to_string(c.xsize) + "x" + std::to_string(c.ysize);
const char* colors[] = {"", "G", "GA", "RGB", "RGBA"};
name += colors[(c.grayscale ? 1 : 3) + (c.include_alpha ? 1 : 0)];
name += "to";
name += colors[c.output_channels];
switch (c.data_type) {
case JXL_TYPE_UINT8:
name += "u8";
break;
case JXL_TYPE_UINT16:
name += "u16";
break;
case JXL_TYPE_FLOAT:
name += "f32";
break;
case JXL_TYPE_FLOAT16:
name += "f16";
break;
case JXL_TYPE_UINT32:
name += "u32";
break;
case JXL_TYPE_BOOLEAN:
name += "b";
break;
};
if (GetDataBits(c.data_type) > jxl::kBitsPerByte) {
if (c.endianness == JXL_NATIVE_ENDIAN) {
// add nothing
} else if (c.endianness == JXL_BIG_ENDIAN) {
name += "BE";
} else if (c.endianness == JXL_LITTLE_ENDIAN) {
name += "LE";
}
}
if (c.add_container != CodeStreamBoxFormat::kCSBF_None) {
name += "Box" + std::to_string((size_t)c.add_container);
}
if (c.add_preview) name += "Preview";
if (c.use_callback) name += "Callback";
if (c.set_buffer_early) name += "EarlyBuffer";
return name;
}
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(DecodeTest, DecodeTestParam,
testing::ValuesIn(GeneratePixelTests()),
PixelTestDescription);
TEST(DecodeTest, PixelTestWithICCProfileLossless) {
JxlDecoder* dec = JxlDecoderCreate(NULL);
@ -1428,7 +1560,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/false, /*set_buffer_early=*/false);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
@ -1440,7 +1572,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
// Test with the container for one of the pixel formats.
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/true, /*set_buffer_early=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
@ -1452,7 +1584,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/false, /*set_buffer_early=*/false);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
@ -1480,7 +1612,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/false, /*set_buffer_early=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
@ -1534,7 +1666,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/true, /*set_buffer_early=*/false);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
@ -1596,7 +1728,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
format, /*use_callback=*/false, /*set_buffer_early=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
@ -1638,84 +1770,6 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
}
}
TEST(DecodeTest, GrayscaleTest) {
size_t xsize = 123, ysize = 77;
size_t num_pixels = xsize * ysize;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 2, 0);
JxlPixelFormat format_orig = {2, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
jxl::CompressParams cparams;
cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 2,
cparams, kCSBF_None, true);
const JxlEndianness endiannesses[] = {JXL_NATIVE_ENDIAN, JXL_LITTLE_ENDIAN,
JXL_BIG_ENDIAN};
for (JxlEndianness endianness : endiannesses) {
// The compressed image is grayscale, but the output can be tested with
// up to 4 channels (RGBA)
for (uint32_t channels = 1; channels <= 4; ++channels) {
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
EXPECT_EQ(num_pixels * channels, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT16, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
#if 0 // Disabled since external_image doesn't currently support uint32_t
{
JxlPixelFormat format = {channels, JXL_TYPE_UINT32, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(),
compressed.size()), format);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
#endif
{
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
{
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT16, endianness, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
}
}
}
void TestPartialStream(bool reconstructible_jpeg) {
size_t xsize = 123, ysize = 77;
uint32_t channels = 4;
@ -2134,7 +2188,7 @@ TEST(DecodeTest, PreviewTest) {
// ButteraugliComparator::Diffmap in butteraugli.cc.
EXPECT_LE(ButteraugliDistance(io0, io1, ba,
/*distmap=*/nullptr, nullptr),
0.9f);
1.4f);
JxlDecoderDestroy(dec);
}
@ -2155,11 +2209,14 @@ TEST(DecodeTest, AlignTest) {
// On purpose not using jxl::RoundUpTo to test it independently.
size_t expected_line_bytes = (1 * 3 * xsize + align - 1) / align * align;
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format);
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
for (int use_callback = 0; use_callback <= 1; ++use_callback) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format,
use_callback, /*set_buffer_early=*/false);
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
format_orig, format));
}
}
TEST(DecodeTest, AnimationTest) {

View File

@ -157,6 +157,40 @@ HWY_NOINLINE void TestFastPQEFD() {
printf("max abs err %e\n", static_cast<double>(max_abs_err));
}
HWY_NOINLINE void TestFastHLGEFD() {
constexpr size_t kNumTrials = 1 << 23;
std::mt19937 rng(1);
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
float max_abs_err = 0;
HWY_FULL(float) d;
for (size_t i = 0; i < kNumTrials; i++) {
const float f = dist(rng);
const float actual = GetLane(TF_HLG().EncodedFromDisplay(d, Set(d, f)));
const float expected = TF_HLG().EncodedFromDisplay(f);
const float abs_err = std::abs(expected - actual);
EXPECT_LT(abs_err, 5e-7) << "f = " << f;
max_abs_err = std::max(max_abs_err, abs_err);
}
printf("max abs err %e\n", static_cast<double>(max_abs_err));
}
HWY_NOINLINE void TestFast709EFD() {
constexpr size_t kNumTrials = 1 << 23;
std::mt19937 rng(1);
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
float max_abs_err = 0;
HWY_FULL(float) d;
for (size_t i = 0; i < kNumTrials; i++) {
const float f = dist(rng);
const float actual = GetLane(TF_709().EncodedFromDisplay(d, Set(d, f)));
const float expected = TF_709().EncodedFromDisplay(f);
const float abs_err = std::abs(expected - actual);
EXPECT_LT(abs_err, 2e-6) << "f = " << f;
max_abs_err = std::max(max_abs_err, abs_err);
}
printf("max abs err %e\n", static_cast<double>(max_abs_err));
}
HWY_NOINLINE void TestFastPQDFE() {
constexpr size_t kNumTrials = 1 << 23;
std::mt19937 rng(1);
@ -246,6 +280,8 @@ HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastErf);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastSRGB);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQDFE);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQEFD);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastHLGEFD);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFast709EFD);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastXYB);
} // namespace jxl

View File

@ -121,13 +121,24 @@ Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data) {
JXL_RETURN_IF_ERROR(br_read(jpeg_data->inter_marker_data[i]));
}
JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data));
if (result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
return JXL_FAILURE("Invalid brotli-compressed data");
}
if (!BrotliDecoderIsFinished(brotli_dec)) {
// Check if there is more decompressed output.
size_t available_out = 1;
uint64_t dummy;
uint8_t* next_out = reinterpret_cast<uint8_t*>(&dummy);
result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in,
&available_out, &next_out, nullptr);
if (available_out == 0 ||
result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
return JXL_FAILURE("Excess data in compressed stream");
}
if (result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
return JXL_FAILURE("Incomplete brotli-stream");
}
if (!BrotliDecoderIsFinished(brotli_dec) ||
result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
return JXL_FAILURE("Corrupted brotli-stream");
}
if (available_in != 0) {
return JXL_FAILURE("Unused data after brotli stream");
}

View File

@ -17,6 +17,8 @@
// Macros and functions useful for tests.
#include <random>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "jxl/codestream_header.h"
@ -273,7 +275,8 @@ std::vector<ColorEncodingDescriptor> AllEncodings() {
std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
size_t num_channels, uint16_t seed) {
// Cause more significant image difference for successive seeds.
seed = static_cast<uint16_t>(seed * 77);
std::mt19937 rng(seed);
std::uniform_int_distribution<uint16_t> dark(0, 32767);
size_t num_pixels = xsize * ysize;
// 16 bits per channel, big endian, 4 channels
std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
@ -281,14 +284,16 @@ std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
// can be compared after roundtrip.
for (size_t y = 0; y < ysize; y++) {
for (size_t x = 0; x < xsize; x++) {
uint16_t r = (65535 - x * y) ^ seed;
uint16_t g = (x << 8) + y + seed;
uint16_t b = (y << 8) + x * seed;
uint16_t a = 32768 + x * 256 - y;
uint16_t r = dark(rng);
uint16_t g = dark(rng);
uint16_t b = dark(rng);
uint16_t a = dark(rng);
// put some shape in there for visual debugging
if (x * x + y * y < 1000) {
std::swap(r, g);
b = 0;
r = (65535 - x * y) ^ seed;
g = (x << 8) + y + seed;
b = (y << 8) + x * seed;
a = 32768 + x * 256 - y;
}
size_t i = (y * xsize + x) * 2 * num_channels;
pixels[i + 0] = (r >> 8);

View File

@ -61,6 +61,24 @@ class TF_HLG {
return e;
}
// Maximum error 5e-7.
template <class D, class V>
JXL_INLINE V EncodedFromDisplay(D d, V x) const {
const hwy::HWY_NAMESPACE::Rebind<uint32_t, D> du;
const V kSign = BitCast(d, Set(du, 0x80000000u));
const V original_sign = And(x, kSign);
x = AndNot(kSign, x); // abs
const V below_div12 = Sqrt(Set(d, 3.0f) * x);
const V e =
MulAdd(Set(d, kA * 0.693147181f),
FastLog2f(d, MulAdd(Set(d, 12), x, Set(d, -kB))), Set(d, kC));
const V magnitude = IfThenElse(x <= Set(d, kDiv12), below_div12, e);
const V lifted = Or(AndNot(kSign, magnitude), original_sign);
const V kMul = Set(d, 1.0f / (1.0f - kBeta));
const V kAdd = Set(d, -kBeta / (1.0f - kBeta));
return MulAdd(kMul, lifted, kAdd);
}
private:
// OETF (defines the HLG approach). s = scene, returns encoded.
JXL_INLINE double OETF(double s) const {
@ -113,6 +131,30 @@ class TF_HLG {
static constexpr double kDiv12 = 1.0 / 12;
};
class TF_709 {
public:
JXL_INLINE double EncodedFromDisplay(const double d) const {
if (d < kThresh) return kMulLow * d;
return kMulHi * std::pow(d, kPowHi) + kSub;
}
// Maximum error 1e-6.
template <class D, class V>
JXL_INLINE V EncodedFromDisplay(D d, V x) const {
auto low = Set(d, kMulLow) * x;
auto hi =
MulAdd(Set(d, kMulHi), FastPowf(d, x, Set(d, kPowHi)), Set(d, kSub));
return IfThenElse(x <= Set(d, kThresh), low, hi);
}
private:
static constexpr double kThresh = 0.018;
static constexpr double kMulLow = 4.5;
static constexpr double kMulHi = 1.099;
static constexpr double kPowHi = 0.45;
static constexpr double kSub = -0.099;
};
// Perceptual Quantization
class TF_PQ {
public: