зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1709917 - Update jpeg-xl to 040eae8105b61b312a67791213091103f4c0d034 r=saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D114525
This commit is contained in:
Родитель
8ee31eee8c
Коммит
5f99f0ed3b
|
@ -1,53 +1,53 @@
|
||||||
# Version of this schema
|
# Version of this schema
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
||||||
bugzilla:
|
bugzilla:
|
||||||
# Bugzilla product and component for this directory and subdirectories
|
# Bugzilla product and component for this directory and subdirectories
|
||||||
product: Core
|
product: Core
|
||||||
component: "ImageLib"
|
component: "ImageLib"
|
||||||
|
|
||||||
# Document the source of externally hosted code
|
# Document the source of externally hosted code
|
||||||
origin:
|
origin:
|
||||||
|
|
||||||
# Short name of the package/library
|
# Short name of the package/library
|
||||||
name: jpeg-xl
|
name: jpeg-xl
|
||||||
|
|
||||||
description: JPEG XL image format reference implementation
|
description: JPEG XL image format reference implementation
|
||||||
|
|
||||||
# Full URL for the package's homepage/etc
|
# Full URL for the package's homepage/etc
|
||||||
# Usually different from repository url
|
# Usually different from repository url
|
||||||
url: https://gitlab.com/wg1/jpeg-xl
|
url: https://gitlab.com/wg1/jpeg-xl
|
||||||
|
|
||||||
# Human-readable identifier for this version/release
|
# Human-readable identifier for this version/release
|
||||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||||
release: commit 9a8f5195e4d1c45112fd65f184ebe115f4163ba2 (2021-05-04T13:15:00.000+02:00).
|
release: commit 040eae8105b61b312a67791213091103f4c0d034 (2021-05-06T14:11:52.000+02:00).
|
||||||
|
|
||||||
# Revision to pull in
|
# Revision to pull in
|
||||||
# Must be a long or short commit SHA (long preferred)
|
# Must be a long or short commit SHA (long preferred)
|
||||||
# NOTE(krosylight): Update highway together when updating this!
|
# NOTE(krosylight): Update highway together when updating this!
|
||||||
revision: 9a8f5195e4d1c45112fd65f184ebe115f4163ba2
|
revision: 040eae8105b61b312a67791213091103f4c0d034
|
||||||
|
|
||||||
# The package's license, where possible using the mnemonic from
|
# The package's license, where possible using the mnemonic from
|
||||||
# https://spdx.org/licenses/
|
# https://spdx.org/licenses/
|
||||||
# Multiple licenses can be specified (as a YAML list)
|
# Multiple licenses can be specified (as a YAML list)
|
||||||
# A "LICENSE" file must exist containing the full license text
|
# A "LICENSE" file must exist containing the full license text
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
|
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
|
||||||
updatebot:
|
updatebot:
|
||||||
maintainer-phab: saschanaz
|
maintainer-phab: saschanaz
|
||||||
maintainer-bz: krosylight@mozilla.com
|
maintainer-bz: krosylight@mozilla.com
|
||||||
tasks:
|
tasks:
|
||||||
- type: vendoring
|
- type: vendoring
|
||||||
enabled: True
|
enabled: True
|
||||||
|
|
||||||
vendoring:
|
vendoring:
|
||||||
url: https://gitlab.com/wg1/jpeg-xl.git
|
url: https://gitlab.com/wg1/jpeg-xl.git
|
||||||
source-hosting: gitlab
|
source-hosting: gitlab
|
||||||
vendor-directory: third_party/jpeg-xl
|
vendor-directory: third_party/jpeg-xl
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
- doc/
|
- doc/
|
||||||
- third_party/testdata/
|
- third_party/testdata/
|
||||||
- tools/
|
- tools/
|
||||||
|
|
|
@ -293,11 +293,12 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||||
PaddedBytes pixels(ib.xsize() * ib.ysize() *
|
PaddedBytes pixels(ib.xsize() * ib.ysize() *
|
||||||
(bits_per_sample / kBitsPerByte));
|
(bits_per_sample / kBitsPerByte));
|
||||||
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
|
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
|
||||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
JXL_RETURN_IF_ERROR(
|
||||||
*transformed, bits_per_sample,
|
ConvertToExternal(*transformed, bits_per_sample,
|
||||||
/*float_out=*/false,
|
/*float_out=*/false,
|
||||||
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool, pixels.data(),
|
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
|
||||||
pixels.size(), metadata.GetOrientation()));
|
pixels.data(), pixels.size(), /*out_callback=*/nullptr,
|
||||||
|
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||||
|
|
||||||
char header[kMaxHeaderSize];
|
char header[kMaxHeaderSize];
|
||||||
int header_size = 0;
|
int header_size = 0;
|
||||||
|
|
|
@ -826,7 +826,8 @@ Status EncodeImagePNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||||
*transformed, bits_per_sample, /*float_out=*/false,
|
*transformed, bits_per_sample, /*float_out=*/false,
|
||||||
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
|
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;
|
PNGState state;
|
||||||
// For maximum compatibility, still store 8-bit even if pixels are all zero.
|
// For maximum compatibility, still store 8-bit even if pixels are all zero.
|
||||||
|
|
|
@ -407,6 +407,7 @@ Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||||
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
||||||
endianness, stride, pool, pixels.data(), pixels.size(),
|
endianness, stride, pool, pixels.data(), pixels.size(),
|
||||||
|
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||||
metadata.GetOrientation()));
|
metadata.GetOrientation()));
|
||||||
|
|
||||||
char header[kMaxHeaderSize];
|
char header[kMaxHeaderSize];
|
||||||
|
|
|
@ -729,14 +729,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
|
||||||
* @param opaque optional user data, as given to JxlDecoderSetImageOutCallback.
|
* @param opaque optional user data, as given to JxlDecoderSetImageOutCallback.
|
||||||
* @param x horizontal position of leftmost pixel of the pixel data.
|
* @param x horizontal position of leftmost pixel of the pixel data.
|
||||||
* @param y vertical position 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.
|
* 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
|
* @param pixels pixel data as a horizontal stripe, in the format passed to
|
||||||
* JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is
|
* JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is
|
||||||
* only valid during the time the callback is running.
|
* only valid during the time the callback is running.
|
||||||
*/
|
*/
|
||||||
typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
|
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
|
* Sets pixel output callback. This is an alternative to
|
||||||
|
|
|
@ -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,
|
void PerformMulBlending(const float* bg, const float* fg, float* out,
|
||||||
size_t num_pixels) {
|
size_t num_pixels) {
|
||||||
for (size_t x = 0; x < num_pixels; ++x) {
|
for (size_t x = 0; x < num_pixels; ++x) {
|
||||||
|
|
|
@ -78,20 +78,11 @@ TEST(AlphaTest, AlphaWeightedAdd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AlphaTest, Mul) {
|
TEST(AlphaTest, Mul) {
|
||||||
const float bg_rgb[3] = {100, 110, 120};
|
const float bg = 100;
|
||||||
const float bg_a = 180.f / 255;
|
const float fg = 25;
|
||||||
const float fg_rgb[3] = {25, 21, 23};
|
float out;
|
||||||
const float fg_a = 1.f / 4;
|
PerformMulBlending(&bg, &fg, &out, 1);
|
||||||
float out_rgb[3];
|
EXPECT_THAT(out, FloatNear(fg * bg, .05f));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AlphaTest, PremultiplyAndUnpremultiply) {
|
TEST(AlphaTest, PremultiplyAndUnpremultiply) {
|
||||||
|
|
|
@ -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++) {
|
for (int p = 0; p < 3; p++) {
|
||||||
AddTo(overlap_row, foreground_.color().Plane(p), cropbox_row,
|
AddTo(overlap_row, foreground_.color().Plane(p), cropbox_row,
|
||||||
&dest_->color()->Plane(p));
|
&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},
|
PerformAlphaWeightedAdd(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
|
||||||
/*out=*/{r, g, b, a}, cropbox_row.xsize());
|
/*out=*/{r, g, b, a}, cropbox_row.xsize());
|
||||||
} else if (info_.mode == BlendMode::kMul) {
|
} else if (info_.mode == BlendMode::kMul) {
|
||||||
// Foreground.
|
for (int p = 0; p < 3; p++) {
|
||||||
const float* JXL_RESTRICT a1 = overlap_row.ConstRow(foreground_.alpha(), 0);
|
// Foreground.
|
||||||
const float* JXL_RESTRICT r1 =
|
const float* JXL_RESTRICT c1 =
|
||||||
overlap_row.ConstRow(foreground_.color().Plane(0), 0);
|
overlap_row.ConstRow(foreground_.color().Plane(p), 0);
|
||||||
const float* JXL_RESTRICT g1 =
|
// Background & destination.
|
||||||
overlap_row.ConstRow(foreground_.color().Plane(1), 0);
|
float* JXL_RESTRICT c = cropbox_row.Row(&dest_->color()->Plane(p), 0);
|
||||||
const float* JXL_RESTRICT b1 =
|
PerformMulBlending(c, c1, c, cropbox_row.xsize());
|
||||||
overlap_row.ConstRow(foreground_.color().Plane(2), 0);
|
}
|
||||||
// Background & destination.
|
if (foreground_.HasAlpha()) {
|
||||||
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
|
const float* JXL_RESTRICT a1 =
|
||||||
float* JXL_RESTRICT r = cropbox_row.Row(&dest_->color()->Plane(0), 0);
|
overlap_row.ConstRow(foreground_.alpha(), 0);
|
||||||
float* JXL_RESTRICT g = cropbox_row.Row(&dest_->color()->Plane(1), 0);
|
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
|
||||||
float* JXL_RESTRICT b = cropbox_row.Row(&dest_->color()->Plane(2), 0);
|
PerformMulBlending(a, a1, a, cropbox_row.xsize());
|
||||||
PerformMulBlending(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
|
}
|
||||||
/*out=*/{r, g, b, a}, cropbox_row.xsize());
|
|
||||||
} else { // kReplace
|
} else { // kReplace
|
||||||
CopyImageTo(overlap_row, foreground_.color(), cropbox_row, dest_->color());
|
CopyImageTo(overlap_row, foreground_.color(), cropbox_row, dest_->color());
|
||||||
if (foreground_.HasAlpha()) {
|
if (foreground_.HasAlpha()) {
|
||||||
|
|
|
@ -201,6 +201,12 @@ struct CustomTransferFunction : public Fields {
|
||||||
bool IsHLG() const {
|
bool IsHLG() const {
|
||||||
return !have_gamma_ && (transfer_function_ == TransferFunction::kHLG);
|
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 {
|
bool IsSame(const CustomTransferFunction& other) const {
|
||||||
if (have_gamma_ != other.have_gamma_) return false;
|
if (have_gamma_ != other.have_gamma_) return false;
|
||||||
if (have_gamma_) {
|
if (have_gamma_) {
|
||||||
|
|
|
@ -86,9 +86,17 @@ struct PassesDecoderState {
|
||||||
// Whether to use int16 float-XYB-to-uint8-srgb conversion.
|
// Whether to use int16 float-XYB-to-uint8-srgb conversion.
|
||||||
bool fast_xyb_srgb8_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;
|
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.
|
// Seed for noise, to have different noise per-frame.
|
||||||
size_t noise_seed = 0;
|
size_t noise_seed = 0;
|
||||||
|
|
||||||
|
@ -180,7 +188,7 @@ struct PassesDecoderState {
|
||||||
ZeroFillImage(&group_data.back());
|
ZeroFillImage(&group_data.back());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (rgb_output) {
|
if (rgb_output || pixel_callback) {
|
||||||
size_t log2_upsampling = CeilLog2Nonzero(shared->frame_header.upsampling);
|
size_t log2_upsampling = CeilLog2Nonzero(shared->frame_header.upsampling);
|
||||||
for (size_t _ = output_pixel_data_storage[log2_upsampling].size();
|
for (size_t _ = output_pixel_data_storage[log2_upsampling].size();
|
||||||
_ < num_threads; _++) {
|
_ < num_threads; _++) {
|
||||||
|
@ -188,6 +196,16 @@ struct PassesDecoderState {
|
||||||
kApplyImageFeaturesTileDim << log2_upsampling,
|
kApplyImageFeaturesTileDim << log2_upsampling,
|
||||||
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);
|
std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f);
|
||||||
|
|
||||||
rgb_output = nullptr;
|
rgb_output = nullptr;
|
||||||
|
pixel_callback = nullptr;
|
||||||
rgb_output_is_rgba = false;
|
rgb_output_is_rgba = false;
|
||||||
fast_xyb_srgb8_conversion = false;
|
fast_xyb_srgb8_conversion = false;
|
||||||
used_acs = 0;
|
used_acs = 0;
|
||||||
|
|
|
@ -258,10 +258,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
||||||
bool float_out, size_t num_channels,
|
bool float_out, size_t num_channels,
|
||||||
JxlEndianness endianness, size_t stride,
|
JxlEndianness endianness, size_t stride,
|
||||||
jxl::ThreadPool* pool, void* out_image,
|
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) {
|
if (bits_per_sample < 1 || bits_per_sample > 32) {
|
||||||
return JXL_FAILURE("Invalid bits_per_sample value.");
|
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.
|
// TODO(deymo): Implement 1-bit per pixel packed in 8 samples per byte.
|
||||||
if (bits_per_sample == 1) {
|
if (bits_per_sample == 1) {
|
||||||
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
|
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 xsize = ib.xsize();
|
||||||
size_t ysize = ib.ysize();
|
size_t ysize = ib.ysize();
|
||||||
|
|
||||||
uint8_t* out = reinterpret_cast<uint8_t*>(out_image);
|
|
||||||
|
|
||||||
bool want_alpha = num_channels == 2 || num_channels == 4;
|
bool want_alpha = num_channels == 2 || num_channels == 4;
|
||||||
size_t color_channels = num_channels <= 2 ? 1 : 3;
|
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;
|
const ImageF* alpha = ib.HasAlpha() ? &ib.alpha() : nullptr;
|
||||||
ImageF temp_alpha;
|
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) {
|
if (undo_orientation != Orientation::kIdentity) {
|
||||||
Image3F transformed;
|
Image3F transformed;
|
||||||
for (size_t c = 0; c < color_channels; ++c) {
|
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) {
|
[&](size_t num_threads) {
|
||||||
f16_cache =
|
f16_cache =
|
||||||
Plane<hwy::float16_t>(xsize, num_channels * num_threads);
|
Plane<hwy::float16_t>(xsize, num_channels * num_threads);
|
||||||
|
InitOutCallback(num_threads);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[&](const int task, int thread) {
|
[&](const int task, int thread) {
|
||||||
|
@ -344,30 +358,42 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
||||||
HWY_DYNAMIC_DISPATCH(FloatToF16)
|
HWY_DYNAMIC_DISPATCH(FloatToF16)
|
||||||
(row_in[r], row_f16[r], xsize);
|
(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
|
// interleave the one scanline
|
||||||
hwy::float16_t* f16_out = &(reinterpret_cast<hwy::float16_t*>(
|
hwy::float16_t* row_f16_out =
|
||||||
out_image))[y * xsize * num_channels];
|
reinterpret_cast<hwy::float16_t*>(row_out);
|
||||||
for (size_t x = 0; x < xsize; x++) {
|
for (size_t x = 0; x < xsize; x++) {
|
||||||
for (size_t r = 0; r < c; r++) {
|
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) {
|
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;
|
size_t size = xsize * num_channels * 2;
|
||||||
for (size_t i = 0; i < size; i += 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");
|
"ConvertF16");
|
||||||
} else if (bits_per_sample == 32) {
|
} else if (bits_per_sample == 32) {
|
||||||
RunOnPool(
|
RunOnPool(
|
||||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
pool, 0, static_cast<uint32_t>(ysize),
|
||||||
[&](const int task, int /*thread*/) {
|
[&](size_t num_threads) {
|
||||||
|
InitOutCallback(num_threads);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[&](const int task, int thread) {
|
||||||
const int64_t y = task;
|
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];
|
const float* JXL_RESTRICT row_in[4];
|
||||||
size_t c = 0;
|
size_t c = 0;
|
||||||
for (; c < color_channels; c++) {
|
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);
|
JXL_ASSERT(c == num_channels);
|
||||||
if (little_endian) {
|
if (little_endian) {
|
||||||
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, out + i);
|
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, row_out);
|
||||||
} else {
|
} 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");
|
"ConvertFloat");
|
||||||
|
@ -396,11 +425,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
||||||
pool, 0, static_cast<uint32_t>(ysize),
|
pool, 0, static_cast<uint32_t>(ysize),
|
||||||
[&](size_t num_threads) {
|
[&](size_t num_threads) {
|
||||||
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
|
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
|
||||||
|
InitOutCallback(num_threads);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[&](const int task, int thread) {
|
[&](const int task, int thread) {
|
||||||
const int64_t y = task;
|
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];
|
const float* JXL_RESTRICT row_in[4];
|
||||||
size_t c = 0;
|
size_t c = 0;
|
||||||
for (; c < color_channels; c++) {
|
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.
|
// TODO(deymo): add bits_per_sample == 1 case here.
|
||||||
if (bits_per_sample <= 8) {
|
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) {
|
} else if (bits_per_sample <= 16) {
|
||||||
if (little_endian) {
|
if (little_endian) {
|
||||||
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, out + i);
|
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, row_out);
|
||||||
} else {
|
} 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) {
|
} else if (bits_per_sample <= 24) {
|
||||||
if (little_endian) {
|
if (little_endian) {
|
||||||
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, out + i);
|
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, row_out);
|
||||||
} else {
|
} else {
|
||||||
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, out + i);
|
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, row_out);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (little_endian) {
|
if (little_endian) {
|
||||||
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, out + i);
|
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, row_out);
|
||||||
} else {
|
} 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");
|
"ConvertUint");
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "jxl/decode.h"
|
||||||
#include "jxl/types.h"
|
#include "jxl/types.h"
|
||||||
#include "lib/jxl/base/status.h"
|
#include "lib/jxl/base/status.h"
|
||||||
#include "lib/jxl/color_encoding_internal.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 1-bit output (bits_per_sample == 1)
|
||||||
// TODO(lode): support rectangle crop.
|
// TODO(lode): support rectangle crop.
|
||||||
// stride_out is output scanline size in bytes, must be >=
|
// 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
|
// undo_orientation is an EXIF orientation to undo. Depending on the
|
||||||
// orientation, the output xsize and ysize are swapped compared to input
|
// orientation, the output xsize and ysize are swapped compared to input
|
||||||
// xsize and ysize.
|
// 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,
|
bool float_out, size_t num_channels,
|
||||||
JxlEndianness endianness, size_t stride_out,
|
JxlEndianness endianness, size_t stride_out,
|
||||||
jxl::ThreadPool* thread_pool, void* out_image,
|
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
|
} // namespace jxl
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ void BM_DecExternalImage_ConvertImageRGBA(benchmark::State& state) {
|
||||||
/*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN,
|
/*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN,
|
||||||
/*stride*/ bytes_per_row,
|
/*stride*/ bytes_per_row,
|
||||||
/*thread_pool=*/nullptr, interleaved.data(), interleaved.size(),
|
/*thread_pool=*/nullptr, interleaved.data(), interleaved.size(),
|
||||||
|
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||||
/*undo_orientation=*/jxl::Orientation::kIdentity));
|
/*undo_orientation=*/jxl::Orientation::kIdentity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -429,11 +429,12 @@ void FrameDecoder::FinalizeDC() {
|
||||||
|
|
||||||
void FrameDecoder::AllocateOutput() {
|
void FrameDecoder::AllocateOutput() {
|
||||||
const CodecMetadata& metadata = *frame_header_.nonserialized_metadata;
|
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,
|
decoded_->SetFromImage(Image3F(frame_dim_.xsize_upsampled_padded,
|
||||||
frame_dim_.ysize_upsampled_padded),
|
frame_dim_.ysize_upsampled_padded),
|
||||||
dec_state_->output_encoding_info.color_encoding);
|
dec_state_->output_encoding_info.color_encoding);
|
||||||
}
|
}
|
||||||
|
dec_state_->extra_channels.clear();
|
||||||
if (metadata.m.num_extra_channels > 0) {
|
if (metadata.m.num_extra_channels > 0) {
|
||||||
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
|
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
|
||||||
const auto eci = metadata.m.extra_channel_info[i];
|
const auto eci = metadata.m.extra_channel_info[i];
|
||||||
|
@ -456,7 +457,8 @@ void FrameDecoder::AllocateOutput() {
|
||||||
|
|
||||||
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
|
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
|
||||||
JXL_CHECK(finalized_dc_);
|
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.
|
// Decode AC group.
|
||||||
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
||||||
|
@ -765,6 +767,12 @@ Status FrameDecoder::Flush() {
|
||||||
if (has_blending && !is_finalized_) {
|
if (has_blending && !is_finalized_) {
|
||||||
return false;
|
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()) {
|
if (decoded_->IsJPEG()) {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -133,6 +133,7 @@ class FrameDecoder {
|
||||||
// blending, the current frame cannot be referenced by future frames, sets the
|
// blending, the current frame cannot be referenced by future frames, sets the
|
||||||
// buffer to which uint8 sRGB pixels will be decoded to.
|
// buffer to which uint8 sRGB pixels will be decoded to.
|
||||||
// TODO(veluca): reduce this set of restrictions.
|
// 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,
|
void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride,
|
||||||
bool is_rgba) const {
|
bool is_rgba) const {
|
||||||
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
|
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
|
||||||
|
@ -148,6 +149,7 @@ class FrameDecoder {
|
||||||
dec_state_->rgb_output = rgb_output;
|
dec_state_->rgb_output = rgb_output;
|
||||||
dec_state_->rgb_output_is_rgba = is_rgba;
|
dec_state_->rgb_output_is_rgba = is_rgba;
|
||||||
dec_state_->rgb_stride = stride;
|
dec_state_->rgb_stride = stride;
|
||||||
|
JXL_ASSERT(dec_state_->pixel_callback == nullptr);
|
||||||
#if !JXL_HIGH_PRECISION
|
#if !JXL_HIGH_PRECISION
|
||||||
if (!is_rgba && decoded_->metadata()->xyb_encoded &&
|
if (!is_rgba && decoded_->metadata()->xyb_encoded &&
|
||||||
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
|
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
|
||||||
|
@ -158,9 +160,34 @@ class FrameDecoder {
|
||||||
#endif
|
#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
|
// Returns true if the rgb output buffer passed by MaybeSetRGB8OutputBuffer
|
||||||
// has been/will be populated by Flush() / FinalizeFrame().
|
// has been/will be populated by Flush() / FinalizeFrame(), or if a pixel
|
||||||
bool HasRGBBuffer() const { return dec_state_->rgb_output != nullptr; }
|
// callback has been used.
|
||||||
|
bool HasRGBBuffer() const {
|
||||||
|
return dec_state_->rgb_output != nullptr ||
|
||||||
|
dec_state_->pixel_callback != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Status ProcessDCGlobal(BitReader* br);
|
Status ProcessDCGlobal(BitReader* br);
|
||||||
|
|
|
@ -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_g), d, row1 + x);
|
||||||
Store(TF_PQ().EncodedFromDisplay(d, linear_b), d, row2 + 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) {
|
auto gamma_tf = [&](hwy::HWY_NAMESPACE::Vec<decltype(d)> v) {
|
||||||
return IfThenZeroElse(
|
return IfThenZeroElse(
|
||||||
v <= Set(d, 1e-5f),
|
v <= Set(d, 1e-5f),
|
||||||
|
@ -402,7 +435,9 @@ Status FinalizeImageRect(
|
||||||
// enough border available. (rect_for_if_input)
|
// enough border available. (rect_for_if_input)
|
||||||
|
|
||||||
Image3F* output_color =
|
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;
|
Image3F* storage_for_if = output_color;
|
||||||
Rect rect_for_if = output_rect;
|
Rect rect_for_if = output_rect;
|
||||||
|
@ -490,7 +525,7 @@ Status FinalizeImageRect(
|
||||||
// +-------------------------------------------------------------------+
|
// +-------------------------------------------------------------------+
|
||||||
Image3F* output_pixel_data_storage = output_color;
|
Image3F* output_pixel_data_storage = output_color;
|
||||||
Rect upsampled_output_rect_for_storage = upsampled_output_rect;
|
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);
|
size_t log2_upsampling = CeilLog2Nonzero(frame_header.upsampling);
|
||||||
if (storage_for_if == output_color) {
|
if (storage_for_if == output_color) {
|
||||||
storage_for_if =
|
storage_for_if =
|
||||||
|
@ -520,12 +555,7 @@ Status FinalizeImageRect(
|
||||||
if (ec < metadata.extra_channel_info.size()) {
|
if (ec < metadata.extra_channel_info.size()) {
|
||||||
JXL_ASSERT(ec < extra_channels.size());
|
JXL_ASSERT(ec < extra_channels.size());
|
||||||
alpha = extra_channels[ec].first;
|
alpha = extra_channels[ec].first;
|
||||||
JXL_ASSERT(upsampled_output_rect.x0() >= extra_channels[ec].second.x0());
|
alpha_rect = extra_channels[ec].second;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// +----------------------------- STEP 3 ------------------------------+
|
// +----------------------------- STEP 3 ------------------------------+
|
||||||
|
@ -712,6 +742,40 @@ Status FinalizeImageRect(
|
||||||
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize)),
|
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize)),
|
||||||
dec_state->rgb_output, dec_state->rgb_stride);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,11 +346,16 @@ Status OutputEncodingInfo::Set(const ImageMetadata& metadata) {
|
||||||
// TODO(veluca): keep in sync with dec_reconstruct.cc
|
// TODO(veluca): keep in sync with dec_reconstruct.cc
|
||||||
if (!orig_color_encoding.tf.IsPQ() && !orig_color_encoding.tf.IsSRGB() &&
|
if (!orig_color_encoding.tf.IsPQ() && !orig_color_encoding.tf.IsSRGB() &&
|
||||||
!orig_color_encoding.tf.IsGamma() &&
|
!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;
|
break;
|
||||||
}
|
}
|
||||||
if (orig_color_encoding.tf.IsGamma()) {
|
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() &&
|
if (orig_color_encoding.IsGray() &&
|
||||||
orig_color_encoding.white_point != WhitePoint::kD65) {
|
orig_color_encoding.white_point != WhitePoint::kD65) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ struct OpsinParams {
|
||||||
|
|
||||||
struct OutputEncodingInfo {
|
struct OutputEncodingInfo {
|
||||||
ColorEncoding color_encoding;
|
ColorEncoding color_encoding;
|
||||||
|
// Used for Gamma and DCI transfer functions.
|
||||||
float inverse_gamma;
|
float inverse_gamma;
|
||||||
// Contains an opsin matrix that converts to the primaries of the output
|
// Contains an opsin matrix that converts to the primaries of the output
|
||||||
// encoding.
|
// encoding.
|
||||||
|
|
|
@ -360,6 +360,8 @@ struct JxlDecoderStruct {
|
||||||
void* preview_out_buffer;
|
void* preview_out_buffer;
|
||||||
void* dc_out_buffer;
|
void* dc_out_buffer;
|
||||||
void* image_out_buffer;
|
void* image_out_buffer;
|
||||||
|
JxlImageOutCallback image_out_callback;
|
||||||
|
void* image_out_opaque;
|
||||||
|
|
||||||
size_t preview_out_size;
|
size_t preview_out_size;
|
||||||
size_t dc_out_size;
|
size_t dc_out_size;
|
||||||
|
@ -467,6 +469,8 @@ void JxlDecoderReset(JxlDecoder* dec) {
|
||||||
dec->preview_out_buffer = nullptr;
|
dec->preview_out_buffer = nullptr;
|
||||||
dec->dc_out_buffer = nullptr;
|
dec->dc_out_buffer = nullptr;
|
||||||
dec->image_out_buffer = nullptr;
|
dec->image_out_buffer = nullptr;
|
||||||
|
dec->image_out_callback = nullptr;
|
||||||
|
dec->image_out_opaque = nullptr;
|
||||||
dec->preview_out_size = 0;
|
dec->preview_out_size = 0;
|
||||||
dec->dc_out_size = 0;
|
dec->dc_out_size = 0;
|
||||||
dec->image_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,
|
static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
|
||||||
const jxl::ImageBundle& frame,
|
const jxl::ImageBundle& frame,
|
||||||
const JxlPixelFormat& format,
|
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
|
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
|
||||||
// color/grayscale format
|
// color/grayscale format
|
||||||
const auto& metadata = dec->metadata.m;
|
const auto& metadata = dec->metadata.m;
|
||||||
|
@ -736,7 +742,8 @@ static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
|
||||||
jxl::Status status = jxl::ConvertToExternal(
|
jxl::Status status = jxl::ConvertToExternal(
|
||||||
frame, BitsPerChannel(format.data_type), float_format,
|
frame, BitsPerChannel(format.data_type), float_format,
|
||||||
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
|
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;
|
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) {
|
if (dec->preview_out_buffer) {
|
||||||
JxlDecoderStatus status = ConvertImageInternal(
|
JxlDecoderStatus status = ConvertImageInternal(
|
||||||
dec, ib, dec->preview_out_format, dec->preview_out_buffer,
|
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;
|
if (status != JXL_DEC_SUCCESS) return status;
|
||||||
}
|
}
|
||||||
return JXL_DEC_PREVIEW_IMAGE;
|
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);
|
/*allow_partial_frames=*/false, /*allow_partial_dc_global=*/false);
|
||||||
if (!status) JXL_API_RETURN_IF_ERROR(status);
|
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 =
|
size_t sections_begin =
|
||||||
DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
|
DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
|
||||||
|
|
||||||
|
@ -1038,9 +1039,43 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
||||||
(dec->next_jpeg_reconstruction_out == nullptr ||
|
(dec->next_jpeg_reconstruction_out == nullptr ||
|
||||||
dec->ib->jpeg_data == nullptr) &&
|
dec->ib->jpeg_data == nullptr) &&
|
||||||
dec->is_last_of_still) {
|
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;
|
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;
|
size_t pos = dec->frame_start - dec->codestream_pos;
|
||||||
|
|
||||||
bool get_dc = dec->is_last_of_still &&
|
bool get_dc = dec->is_last_of_still &&
|
||||||
|
@ -1118,9 +1153,10 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
||||||
dc_bundle.SetFromImage(
|
dc_bundle.SetFromImage(
|
||||||
std::move(dc),
|
std::move(dc),
|
||||||
dec->passes_state->output_encoding_info.color_encoding);
|
dec->passes_state->output_encoding_info.color_encoding);
|
||||||
JXL_API_RETURN_IF_ERROR(
|
JXL_API_RETURN_IF_ERROR(ConvertImageInternal(
|
||||||
ConvertImageInternal(dec, dc_bundle, dec->dc_out_format,
|
dec, dc_bundle, dec->dc_out_format, dec->dc_out_buffer,
|
||||||
dec->dc_out_buffer, dec->dc_out_size));
|
dec->dc_out_size,
|
||||||
|
/*out_callback=*/nullptr, /*out_opaque=*/nullptr));
|
||||||
dec->frame_stage = FrameStage::kFull;
|
dec->frame_stage = FrameStage::kFull;
|
||||||
return JXL_DEC_DC_IMAGE;
|
return JXL_DEC_DC_IMAGE;
|
||||||
}
|
}
|
||||||
|
@ -1168,7 +1204,8 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
||||||
// Copy pixels if desired.
|
// Copy pixels if desired.
|
||||||
JxlDecoderStatus status = ConvertImageInternal(
|
JxlDecoderStatus status = ConvertImageInternal(
|
||||||
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
|
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;
|
if (status != JXL_DEC_SUCCESS) return status;
|
||||||
}
|
}
|
||||||
dec->image_out_buffer_set = false;
|
dec->image_out_buffer_set = false;
|
||||||
|
@ -1901,9 +1938,10 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
|
||||||
return JXL_DEC_SUCCESS;
|
return JXL_DEC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
JxlDecoderStatus status =
|
JxlDecoderStatus status = jxl::ConvertImageInternal(
|
||||||
jxl::ConvertImageInternal(dec, *dec->ib, dec->image_out_format,
|
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
|
||||||
dec->image_out_buffer, dec->image_out_size);
|
dec->image_out_size,
|
||||||
|
/*out_callback=*/nullptr, /*out_opaque=*/nullptr);
|
||||||
if (status != JXL_DEC_SUCCESS) return status;
|
if (status != JXL_DEC_SUCCESS) return status;
|
||||||
return JXL_DEC_SUCCESS;
|
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)) {
|
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");
|
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;
|
size_t min_size;
|
||||||
// This also checks whether the format is valid and supported and basic info
|
// This also checks whether the format is valid and supported and basic info
|
||||||
// is available.
|
// is available.
|
||||||
|
@ -2027,14 +2069,23 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
|
||||||
dec->image_out_size = size;
|
dec->image_out_size = size;
|
||||||
dec->image_out_format = *format;
|
dec->image_out_format = *format;
|
||||||
|
|
||||||
if (format->data_type == JXL_TYPE_UINT8 && format->num_channels >= 3 &&
|
return JXL_DEC_SUCCESS;
|
||||||
dec->frame_dec_in_progress) {
|
}
|
||||||
bool is_rgba = format->num_channels == 4;
|
|
||||||
dec->frame_dec->MaybeSetRGB8OutputBuffer(reinterpret_cast<uint8_t*>(buffer),
|
JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec,
|
||||||
jxl::GetStride(dec, *format),
|
const JxlPixelFormat* format,
|
||||||
is_rgba);
|
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;
|
return JXL_DEC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "lib/jxl/base/file_io.h"
|
#include "lib/jxl/base/file_io.h"
|
||||||
#include "lib/jxl/base/span.h"
|
#include "lib/jxl/base/span.h"
|
||||||
#include "lib/jxl/base/status.h"
|
#include "lib/jxl/base/status.h"
|
||||||
|
#include "lib/jxl/common.h"
|
||||||
#include "lib/jxl/dec_file.h"
|
#include "lib/jxl/dec_file.h"
|
||||||
#include "lib/jxl/enc_butteraugli_comparator.h"
|
#include "lib/jxl/enc_butteraugli_comparator.h"
|
||||||
#include "lib/jxl/enc_external_image.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);
|
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
|
// What type of codestream format in the boxes to use for testing
|
||||||
enum CodeStreamBoxFormat {
|
enum CodeStreamBoxFormat {
|
||||||
// Do not use box format at all, only pure codestream
|
// 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.
|
// Decodes one-shot with the API for non-streaming decoding tests.
|
||||||
std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
|
std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
|
||||||
Span<const uint8_t> compressed,
|
Span<const uint8_t> compressed,
|
||||||
const JxlPixelFormat& format) {
|
const JxlPixelFormat& format,
|
||||||
|
bool use_callback, bool set_buffer_early) {
|
||||||
void* runner = JxlThreadParallelRunnerCreate(
|
void* runner = JxlThreadParallelRunnerCreate(
|
||||||
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
||||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||||
JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner));
|
JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner));
|
||||||
|
|
||||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(
|
EXPECT_EQ(
|
||||||
dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE));
|
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,
|
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||||
JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
|
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));
|
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
|
||||||
std::vector<uint8_t> pixels(buffer_size);
|
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(
|
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
|
||||||
dec, &format, pixels.data(), pixels.size()));
|
|
||||||
|
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));
|
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.
|
// Decodes one-shot with the API for non-streaming decoding tests.
|
||||||
std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
|
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);
|
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);
|
JxlDecoderDestroy(dec);
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
@ -371,95 +512,6 @@ std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
|
||||||
} // namespace jxl
|
} // namespace jxl
|
||||||
|
|
||||||
namespace {
|
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
|
// Procedure to convert pixels to double precision, not efficient, but
|
||||||
// well-controlled for testing. It uses double, to be able to represent all
|
// well-controlled for testing. It uses double, to be able to represent all
|
||||||
|
@ -1303,109 +1355,189 @@ TEST(DecodeTest, ICCPartialTest) {
|
||||||
JxlDecoderDestroy(dec);
|
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);
|
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||||
|
|
||||||
for (int include_alpha = 0; include_alpha <= 1; include_alpha++) {
|
size_t num_pixels = config.xsize * config.ysize;
|
||||||
uint32_t orig_channels = include_alpha ? 4 : 3;
|
uint32_t orig_channels =
|
||||||
for (size_t box = 0; box < kCSBF_NUM_ENTRIES; ++box) {
|
(config.grayscale ? 1 : 3) + (config.include_alpha ? 1 : 0);
|
||||||
CodeStreamBoxFormat add_container = (CodeStreamBoxFormat)box;
|
std::vector<uint8_t> pixels =
|
||||||
size_t xsize = 123, ysize = 77;
|
jxl::test::GetSomeTestImage(config.xsize, config.ysize, orig_channels, 0);
|
||||||
size_t num_pixels = xsize * ysize;
|
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
|
||||||
std::vector<uint8_t> pixels =
|
0};
|
||||||
jxl::test::GetSomeTestImage(xsize, ysize, orig_channels, 0);
|
jxl::CompressParams cparams;
|
||||||
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16,
|
// Lossless to verify pixels exactly after roundtrip.
|
||||||
JXL_BIG_ENDIAN, 0};
|
cparams.SetLossless();
|
||||||
jxl::CompressParams cparams;
|
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
|
||||||
// Lossless to verify pixels exactly after roundtrip.
|
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), config.xsize,
|
||||||
cparams.SetLossless();
|
config.ysize, orig_channels, cparams, config.add_container,
|
||||||
// For variation: some have container and no preview, others have preview
|
config.add_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);
|
|
||||||
|
|
||||||
const JxlEndianness endiannesses[] = {JXL_NATIVE_ENDIAN,
|
JxlPixelFormat format = {config.output_channels, config.data_type,
|
||||||
JXL_LITTLE_ENDIAN, JXL_BIG_ENDIAN};
|
config.endianness, 0};
|
||||||
for (JxlEndianness endianness : endiannesses) {
|
|
||||||
for (uint32_t channels = 3; channels <= orig_channels; ++channels) {
|
|
||||||
{
|
|
||||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, endianness, 0};
|
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec,
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
format, config.use_callback, config.set_buffer_early);
|
||||||
format);
|
JxlDecoderReset(dec);
|
||||||
JxlDecoderReset(dec);
|
EXPECT_EQ(num_pixels * config.output_channels *
|
||||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
GetDataBits(config.data_type) / jxl::kBitsPerByte,
|
||||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
|
pixels2.size());
|
||||||
ysize, format_orig, format));
|
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), config.xsize,
|
||||||
}
|
config.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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JxlDecoderDestroy(dec);
|
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) {
|
TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
||||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||||
|
|
||||||
|
@ -1428,7 +1560,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/false, /*set_buffer_early=*/false);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
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.
|
// Test with the container for one of the pixel formats.
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/true, /*set_buffer_early=*/true);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||||
|
@ -1452,7 +1584,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/false, /*set_buffer_early=*/false);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||||
|
@ -1480,7 +1612,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/false, /*set_buffer_early=*/true);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||||
|
|
||||||
|
@ -1534,7 +1666,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/true, /*set_buffer_early=*/false);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||||
|
|
||||||
|
@ -1596,7 +1728,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||||
format);
|
format, /*use_callback=*/false, /*set_buffer_early=*/true);
|
||||||
JxlDecoderReset(dec);
|
JxlDecoderReset(dec);
|
||||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
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) {
|
void TestPartialStream(bool reconstructible_jpeg) {
|
||||||
size_t xsize = 123, ysize = 77;
|
size_t xsize = 123, ysize = 77;
|
||||||
uint32_t channels = 4;
|
uint32_t channels = 4;
|
||||||
|
@ -2134,7 +2188,7 @@ TEST(DecodeTest, PreviewTest) {
|
||||||
// ButteraugliComparator::Diffmap in butteraugli.cc.
|
// ButteraugliComparator::Diffmap in butteraugli.cc.
|
||||||
EXPECT_LE(ButteraugliDistance(io0, io1, ba,
|
EXPECT_LE(ButteraugliDistance(io0, io1, ba,
|
||||||
/*distmap=*/nullptr, nullptr),
|
/*distmap=*/nullptr, nullptr),
|
||||||
0.9f);
|
1.4f);
|
||||||
|
|
||||||
JxlDecoderDestroy(dec);
|
JxlDecoderDestroy(dec);
|
||||||
}
|
}
|
||||||
|
@ -2155,11 +2209,14 @@ TEST(DecodeTest, AlignTest) {
|
||||||
// On purpose not using jxl::RoundUpTo to test it independently.
|
// On purpose not using jxl::RoundUpTo to test it independently.
|
||||||
size_t expected_line_bytes = (1 * 3 * xsize + align - 1) / align * align;
|
size_t expected_line_bytes = (1 * 3 * xsize + align - 1) / align * align;
|
||||||
|
|
||||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
for (int use_callback = 0; use_callback <= 1; ++use_callback) {
|
||||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format);
|
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||||
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
|
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format,
|
||||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
use_callback, /*set_buffer_early=*/false);
|
||||||
format_orig, format));
|
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
|
||||||
|
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||||
|
format_orig, format));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DecodeTest, AnimationTest) {
|
TEST(DecodeTest, AnimationTest) {
|
||||||
|
|
|
@ -157,6 +157,40 @@ HWY_NOINLINE void TestFastPQEFD() {
|
||||||
printf("max abs err %e\n", static_cast<double>(max_abs_err));
|
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() {
|
HWY_NOINLINE void TestFastPQDFE() {
|
||||||
constexpr size_t kNumTrials = 1 << 23;
|
constexpr size_t kNumTrials = 1 << 23;
|
||||||
std::mt19937 rng(1);
|
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, TestFastSRGB);
|
||||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQDFE);
|
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQDFE);
|
||||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQEFD);
|
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);
|
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastXYB);
|
||||||
|
|
||||||
} // namespace jxl
|
} // namespace jxl
|
||||||
|
|
|
@ -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->inter_marker_data[i]));
|
||||||
}
|
}
|
||||||
JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data));
|
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");
|
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) {
|
if (available_in != 0) {
|
||||||
return JXL_FAILURE("Unused data after brotli stream");
|
return JXL_FAILURE("Unused data after brotli stream");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
// Macros and functions useful for tests.
|
// Macros and functions useful for tests.
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "jxl/codestream_header.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,
|
std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
|
||||||
size_t num_channels, uint16_t seed) {
|
size_t num_channels, uint16_t seed) {
|
||||||
// Cause more significant image difference for successive seeds.
|
// 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;
|
size_t num_pixels = xsize * ysize;
|
||||||
// 16 bits per channel, big endian, 4 channels
|
// 16 bits per channel, big endian, 4 channels
|
||||||
std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
|
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.
|
// can be compared after roundtrip.
|
||||||
for (size_t y = 0; y < ysize; y++) {
|
for (size_t y = 0; y < ysize; y++) {
|
||||||
for (size_t x = 0; x < xsize; x++) {
|
for (size_t x = 0; x < xsize; x++) {
|
||||||
uint16_t r = (65535 - x * y) ^ seed;
|
uint16_t r = dark(rng);
|
||||||
uint16_t g = (x << 8) + y + seed;
|
uint16_t g = dark(rng);
|
||||||
uint16_t b = (y << 8) + x * seed;
|
uint16_t b = dark(rng);
|
||||||
uint16_t a = 32768 + x * 256 - y;
|
uint16_t a = dark(rng);
|
||||||
// put some shape in there for visual debugging
|
// put some shape in there for visual debugging
|
||||||
if (x * x + y * y < 1000) {
|
if (x * x + y * y < 1000) {
|
||||||
std::swap(r, g);
|
r = (65535 - x * y) ^ seed;
|
||||||
b = 0;
|
g = (x << 8) + y + seed;
|
||||||
|
b = (y << 8) + x * seed;
|
||||||
|
a = 32768 + x * 256 - y;
|
||||||
}
|
}
|
||||||
size_t i = (y * xsize + x) * 2 * num_channels;
|
size_t i = (y * xsize + x) * 2 * num_channels;
|
||||||
pixels[i + 0] = (r >> 8);
|
pixels[i + 0] = (r >> 8);
|
||||||
|
|
|
@ -61,6 +61,24 @@ class TF_HLG {
|
||||||
return e;
|
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:
|
private:
|
||||||
// OETF (defines the HLG approach). s = scene, returns encoded.
|
// OETF (defines the HLG approach). s = scene, returns encoded.
|
||||||
JXL_INLINE double OETF(double s) const {
|
JXL_INLINE double OETF(double s) const {
|
||||||
|
@ -113,6 +131,30 @@ class TF_HLG {
|
||||||
static constexpr double kDiv12 = 1.0 / 12;
|
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
|
// Perceptual Quantization
|
||||||
class TF_PQ {
|
class TF_PQ {
|
||||||
public:
|
public:
|
||||||
|
|
Загрузка…
Ссылка в новой задаче