зеркало из 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
|
||||
schema: 1
|
||||
|
||||
bugzilla:
|
||||
# Bugzilla product and component for this directory and subdirectories
|
||||
product: Core
|
||||
component: "ImageLib"
|
||||
|
||||
# Document the source of externally hosted code
|
||||
origin:
|
||||
|
||||
# Short name of the package/library
|
||||
name: jpeg-xl
|
||||
|
||||
description: JPEG XL image format reference implementation
|
||||
|
||||
# Full URL for the package's homepage/etc
|
||||
# Usually different from repository url
|
||||
url: https://gitlab.com/wg1/jpeg-xl
|
||||
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
release: commit 9a8f5195e4d1c45112fd65f184ebe115f4163ba2 (2021-05-04T13:15:00.000+02:00).
|
||||
|
||||
# Revision to pull in
|
||||
# Must be a long or short commit SHA (long preferred)
|
||||
# NOTE(krosylight): Update highway together when updating this!
|
||||
revision: 9a8f5195e4d1c45112fd65f184ebe115f4163ba2
|
||||
|
||||
# The package's license, where possible using the mnemonic from
|
||||
# https://spdx.org/licenses/
|
||||
# Multiple licenses can be specified (as a YAML list)
|
||||
# A "LICENSE" file must exist containing the full license text
|
||||
license: Apache-2.0
|
||||
|
||||
license-file: LICENSE
|
||||
|
||||
updatebot:
|
||||
maintainer-phab: saschanaz
|
||||
maintainer-bz: krosylight@mozilla.com
|
||||
tasks:
|
||||
- type: vendoring
|
||||
enabled: True
|
||||
|
||||
vendoring:
|
||||
url: https://gitlab.com/wg1/jpeg-xl.git
|
||||
source-hosting: gitlab
|
||||
vendor-directory: third_party/jpeg-xl
|
||||
|
||||
exclude:
|
||||
- doc/
|
||||
- third_party/testdata/
|
||||
- tools/
|
||||
# Version of this schema
|
||||
schema: 1
|
||||
|
||||
bugzilla:
|
||||
# Bugzilla product and component for this directory and subdirectories
|
||||
product: Core
|
||||
component: "ImageLib"
|
||||
|
||||
# Document the source of externally hosted code
|
||||
origin:
|
||||
|
||||
# Short name of the package/library
|
||||
name: jpeg-xl
|
||||
|
||||
description: JPEG XL image format reference implementation
|
||||
|
||||
# Full URL for the package's homepage/etc
|
||||
# Usually different from repository url
|
||||
url: https://gitlab.com/wg1/jpeg-xl
|
||||
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
release: commit 040eae8105b61b312a67791213091103f4c0d034 (2021-05-06T14:11:52.000+02:00).
|
||||
|
||||
# Revision to pull in
|
||||
# Must be a long or short commit SHA (long preferred)
|
||||
# NOTE(krosylight): Update highway together when updating this!
|
||||
revision: 040eae8105b61b312a67791213091103f4c0d034
|
||||
|
||||
# The package's license, where possible using the mnemonic from
|
||||
# https://spdx.org/licenses/
|
||||
# Multiple licenses can be specified (as a YAML list)
|
||||
# A "LICENSE" file must exist containing the full license text
|
||||
license: Apache-2.0
|
||||
|
||||
license-file: LICENSE
|
||||
|
||||
updatebot:
|
||||
maintainer-phab: saschanaz
|
||||
maintainer-bz: krosylight@mozilla.com
|
||||
tasks:
|
||||
- type: vendoring
|
||||
enabled: True
|
||||
|
||||
vendoring:
|
||||
url: https://gitlab.com/wg1/jpeg-xl.git
|
||||
source-hosting: gitlab
|
||||
vendor-directory: third_party/jpeg-xl
|
||||
|
||||
exclude:
|
||||
- doc/
|
||||
- third_party/testdata/
|
||||
- tools/
|
||||
|
|
|
@ -293,11 +293,12 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
PaddedBytes pixels(ib.xsize() * ib.ysize() *
|
||||
(bits_per_sample / kBitsPerByte));
|
||||
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample,
|
||||
/*float_out=*/false,
|
||||
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool, pixels.data(),
|
||||
pixels.size(), metadata.GetOrientation()));
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ConvertToExternal(*transformed, bits_per_sample,
|
||||
/*float_out=*/false,
|
||||
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
|
||||
pixels.data(), pixels.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
int header_size = 0;
|
||||
|
|
|
@ -826,7 +826,8 @@ Status EncodeImagePNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, /*float_out=*/false,
|
||||
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
|
||||
pool, raw_bytes.data(), raw_bytes.size(), metadata.GetOrientation()));
|
||||
pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
PNGState state;
|
||||
// For maximum compatibility, still store 8-bit even if pixels are all zero.
|
||||
|
|
|
@ -407,6 +407,7 @@ Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
||||
endianness, stride, pool, pixels.data(), pixels.size(),
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||
metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
|
|
|
@ -729,14 +729,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
|
|||
* @param opaque optional user data, as given to JxlDecoderSetImageOutCallback.
|
||||
* @param x horizontal position of leftmost pixel of the pixel data.
|
||||
* @param y vertical position of the pixel data.
|
||||
* @param xsize amount of pixels included in the pixel data, horizontally.
|
||||
* @param num_pixels amount of pixels included in the pixel data, horizontally.
|
||||
* This is not the same as xsize of the full image, it may be smaller.
|
||||
* @param pixels pixel data as a horizontal stripe, in the format passed to
|
||||
* JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is
|
||||
* only valid during the time the callback is running.
|
||||
*/
|
||||
typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
|
||||
size_t xsize, const void* pixels);
|
||||
size_t num_pixels, const void* pixels);
|
||||
|
||||
/**
|
||||
* Sets pixel output callback. This is an alternative to
|
||||
|
|
|
@ -77,16 +77,6 @@ void PerformAlphaWeightedAdd(const float* bg, const float* fg, const float* fga,
|
|||
}
|
||||
}
|
||||
|
||||
void PerformMulBlending(const AlphaBlendingInputLayer& bg,
|
||||
const AlphaBlendingInputLayer& fg,
|
||||
const AlphaBlendingOutput& out, size_t num_pixels) {
|
||||
for (size_t x = 0; x < num_pixels; ++x) {
|
||||
out.r[x] = bg.r[x] * fg.r[x];
|
||||
out.g[x] = bg.g[x] * fg.g[x];
|
||||
out.b[x] = bg.b[x] * fg.b[x];
|
||||
out.a[x] = bg.a[x] * fg.a[x];
|
||||
}
|
||||
}
|
||||
void PerformMulBlending(const float* bg, const float* fg, float* out,
|
||||
size_t num_pixels) {
|
||||
for (size_t x = 0; x < num_pixels; ++x) {
|
||||
|
|
|
@ -78,20 +78,11 @@ TEST(AlphaTest, AlphaWeightedAdd) {
|
|||
}
|
||||
|
||||
TEST(AlphaTest, Mul) {
|
||||
const float bg_rgb[3] = {100, 110, 120};
|
||||
const float bg_a = 180.f / 255;
|
||||
const float fg_rgb[3] = {25, 21, 23};
|
||||
const float fg_a = 1.f / 4;
|
||||
float out_rgb[3];
|
||||
float out_a;
|
||||
PerformMulBlending(
|
||||
/*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
|
||||
/*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a},
|
||||
/*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1);
|
||||
EXPECT_THAT(out_rgb, ElementsAre(FloatNear(100.f * 25.f, .05f),
|
||||
FloatNear(110.f * 21.f, .05f),
|
||||
FloatNear(120.f * 23.f, .05f)));
|
||||
EXPECT_NEAR(out_a, bg_a * fg_a, 1e-5);
|
||||
const float bg = 100;
|
||||
const float fg = 25;
|
||||
float out;
|
||||
PerformMulBlending(&bg, &fg, &out, 1);
|
||||
EXPECT_THAT(out, FloatNear(fg * bg, .05f));
|
||||
}
|
||||
|
||||
TEST(AlphaTest, PremultiplyAndUnpremultiply) {
|
||||
|
|
|
@ -302,7 +302,8 @@ Status ImageBlender::RectBlender::DoBlending(size_t y) const {
|
|||
}
|
||||
}
|
||||
|
||||
if (info_.mode == BlendMode::kAdd) {
|
||||
if (info_.mode == BlendMode::kAdd ||
|
||||
(info_.mode == BlendMode::kAlphaWeightedAdd && !foreground_.HasAlpha())) {
|
||||
for (int p = 0; p < 3; p++) {
|
||||
AddTo(overlap_row, foreground_.color().Plane(p), cropbox_row,
|
||||
&dest_->color()->Plane(p));
|
||||
|
@ -347,21 +348,20 @@ Status ImageBlender::RectBlender::DoBlending(size_t y) const {
|
|||
PerformAlphaWeightedAdd(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
|
||||
/*out=*/{r, g, b, a}, cropbox_row.xsize());
|
||||
} else if (info_.mode == BlendMode::kMul) {
|
||||
// Foreground.
|
||||
const float* JXL_RESTRICT a1 = overlap_row.ConstRow(foreground_.alpha(), 0);
|
||||
const float* JXL_RESTRICT r1 =
|
||||
overlap_row.ConstRow(foreground_.color().Plane(0), 0);
|
||||
const float* JXL_RESTRICT g1 =
|
||||
overlap_row.ConstRow(foreground_.color().Plane(1), 0);
|
||||
const float* JXL_RESTRICT b1 =
|
||||
overlap_row.ConstRow(foreground_.color().Plane(2), 0);
|
||||
// Background & destination.
|
||||
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
|
||||
float* JXL_RESTRICT r = cropbox_row.Row(&dest_->color()->Plane(0), 0);
|
||||
float* JXL_RESTRICT g = cropbox_row.Row(&dest_->color()->Plane(1), 0);
|
||||
float* JXL_RESTRICT b = cropbox_row.Row(&dest_->color()->Plane(2), 0);
|
||||
PerformMulBlending(/*bg=*/{r, g, b, a}, /*fg=*/{r1, g1, b1, a1},
|
||||
/*out=*/{r, g, b, a}, cropbox_row.xsize());
|
||||
for (int p = 0; p < 3; p++) {
|
||||
// Foreground.
|
||||
const float* JXL_RESTRICT c1 =
|
||||
overlap_row.ConstRow(foreground_.color().Plane(p), 0);
|
||||
// Background & destination.
|
||||
float* JXL_RESTRICT c = cropbox_row.Row(&dest_->color()->Plane(p), 0);
|
||||
PerformMulBlending(c, c1, c, cropbox_row.xsize());
|
||||
}
|
||||
if (foreground_.HasAlpha()) {
|
||||
const float* JXL_RESTRICT a1 =
|
||||
overlap_row.ConstRow(foreground_.alpha(), 0);
|
||||
float* JXL_RESTRICT a = cropbox_row.Row(dest_->alpha(), 0);
|
||||
PerformMulBlending(a, a1, a, cropbox_row.xsize());
|
||||
}
|
||||
} else { // kReplace
|
||||
CopyImageTo(overlap_row, foreground_.color(), cropbox_row, dest_->color());
|
||||
if (foreground_.HasAlpha()) {
|
||||
|
|
|
@ -201,6 +201,12 @@ struct CustomTransferFunction : public Fields {
|
|||
bool IsHLG() const {
|
||||
return !have_gamma_ && (transfer_function_ == TransferFunction::kHLG);
|
||||
}
|
||||
bool Is709() const {
|
||||
return !have_gamma_ && (transfer_function_ == TransferFunction::k709);
|
||||
}
|
||||
bool IsDCI() const {
|
||||
return !have_gamma_ && (transfer_function_ == TransferFunction::kDCI);
|
||||
}
|
||||
bool IsSame(const CustomTransferFunction& other) const {
|
||||
if (have_gamma_ != other.have_gamma_) return false;
|
||||
if (have_gamma_) {
|
||||
|
|
|
@ -86,9 +86,17 @@ struct PassesDecoderState {
|
|||
// Whether to use int16 float-XYB-to-uint8-srgb conversion.
|
||||
bool fast_xyb_srgb8_conversion;
|
||||
|
||||
// If true, rgb_output is RGBA using 4 instead of 3 bytes per pixel.
|
||||
// If true, rgb_output or callback output is RGBA using 4 instead of 3 bytes
|
||||
// per pixel.
|
||||
bool rgb_output_is_rgba;
|
||||
|
||||
// Callback for line-by-line output.
|
||||
std::function<void(const float*, size_t, size_t, size_t)> pixel_callback;
|
||||
// Buffer of upsampling * kApplyImageFeaturesTileDim ones.
|
||||
std::vector<float> opaque_alpha;
|
||||
// One row per thread
|
||||
std::vector<std::vector<float>> pixel_callback_rows;
|
||||
|
||||
// Seed for noise, to have different noise per-frame.
|
||||
size_t noise_seed = 0;
|
||||
|
||||
|
@ -180,7 +188,7 @@ struct PassesDecoderState {
|
|||
ZeroFillImage(&group_data.back());
|
||||
#endif
|
||||
}
|
||||
if (rgb_output) {
|
||||
if (rgb_output || pixel_callback) {
|
||||
size_t log2_upsampling = CeilLog2Nonzero(shared->frame_header.upsampling);
|
||||
for (size_t _ = output_pixel_data_storage[log2_upsampling].size();
|
||||
_ < num_threads; _++) {
|
||||
|
@ -188,6 +196,16 @@ struct PassesDecoderState {
|
|||
kApplyImageFeaturesTileDim << log2_upsampling,
|
||||
kApplyImageFeaturesTileDim << log2_upsampling);
|
||||
}
|
||||
opaque_alpha.resize(
|
||||
kApplyImageFeaturesTileDim * shared->frame_header.upsampling, 1.0f);
|
||||
if (pixel_callback) {
|
||||
pixel_callback_rows.resize(num_threads);
|
||||
for (size_t i = 0; i < pixel_callback_rows.size(); ++i) {
|
||||
pixel_callback_rows[i].resize(kApplyImageFeaturesTileDim *
|
||||
shared->frame_header.upsampling *
|
||||
(rgb_output_is_rgba ? 4 : 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,6 +220,7 @@ struct PassesDecoderState {
|
|||
std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f);
|
||||
|
||||
rgb_output = nullptr;
|
||||
pixel_callback = nullptr;
|
||||
rgb_output_is_rgba = false;
|
||||
fast_xyb_srgb8_conversion = false;
|
||||
used_acs = 0;
|
||||
|
|
|
@ -258,10 +258,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
bool float_out, size_t num_channels,
|
||||
JxlEndianness endianness, size_t stride,
|
||||
jxl::ThreadPool* pool, void* out_image,
|
||||
size_t out_size, jxl::Orientation undo_orientation) {
|
||||
size_t out_size, JxlImageOutCallback out_callback,
|
||||
void* out_opaque, jxl::Orientation undo_orientation) {
|
||||
if (bits_per_sample < 1 || bits_per_sample > 32) {
|
||||
return JXL_FAILURE("Invalid bits_per_sample value.");
|
||||
}
|
||||
if (!!out_image == !!out_callback) {
|
||||
return JXL_FAILURE(
|
||||
"Must provide either an out_image or an out_callback, but not both.");
|
||||
}
|
||||
// TODO(deymo): Implement 1-bit per pixel packed in 8 samples per byte.
|
||||
if (bits_per_sample == 1) {
|
||||
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
|
||||
|
@ -269,8 +274,6 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
size_t xsize = ib.xsize();
|
||||
size_t ysize = ib.ysize();
|
||||
|
||||
uint8_t* out = reinterpret_cast<uint8_t*>(out_image);
|
||||
|
||||
bool want_alpha = num_channels == 2 || num_channels == 4;
|
||||
size_t color_channels = num_channels <= 2 ? 1 : 3;
|
||||
|
||||
|
@ -283,6 +286,16 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
const ImageF* alpha = ib.HasAlpha() ? &ib.alpha() : nullptr;
|
||||
ImageF temp_alpha;
|
||||
|
||||
std::vector<std::vector<uint8_t>> row_out_callback;
|
||||
auto InitOutCallback = [&](size_t num_threads) {
|
||||
if (out_callback) {
|
||||
row_out_callback.resize(num_threads);
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
row_out_callback[i].resize(stride);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (undo_orientation != Orientation::kIdentity) {
|
||||
Image3F transformed;
|
||||
for (size_t c = 0; c < color_channels; ++c) {
|
||||
|
@ -325,6 +338,7 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
[&](size_t num_threads) {
|
||||
f16_cache =
|
||||
Plane<hwy::float16_t>(xsize, num_channels * num_threads);
|
||||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
|
@ -344,30 +358,42 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
HWY_DYNAMIC_DISPATCH(FloatToF16)
|
||||
(row_in[r], row_f16[r], xsize);
|
||||
}
|
||||
uint8_t* row_out =
|
||||
out_callback
|
||||
? row_out_callback[thread].data()
|
||||
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
|
||||
// interleave the one scanline
|
||||
hwy::float16_t* f16_out = &(reinterpret_cast<hwy::float16_t*>(
|
||||
out_image))[y * xsize * num_channels];
|
||||
hwy::float16_t* row_f16_out =
|
||||
reinterpret_cast<hwy::float16_t*>(row_out);
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
for (size_t r = 0; r < c; r++) {
|
||||
f16_out[x * num_channels + r] = row_f16[r][x];
|
||||
row_f16_out[x * num_channels + r] = row_f16[r][x];
|
||||
}
|
||||
}
|
||||
if (swap_endianness) {
|
||||
uint8_t* u8_out = &(reinterpret_cast<uint8_t*>(
|
||||
out_image))[y * xsize * num_channels * 2];
|
||||
size_t size = xsize * num_channels * 2;
|
||||
for (size_t i = 0; i < size; i += 2) {
|
||||
std::swap(u8_out[i + 0], u8_out[i + 1]);
|
||||
std::swap(row_out[i + 0], row_out[i + 1]);
|
||||
}
|
||||
}
|
||||
if (out_callback) {
|
||||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertF16");
|
||||
} else if (bits_per_sample == 32) {
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
pool, 0, static_cast<uint32_t>(ysize),
|
||||
[&](size_t num_threads) {
|
||||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
const int64_t y = task;
|
||||
size_t i = stride * y;
|
||||
uint8_t* row_out =
|
||||
out_callback
|
||||
? row_out_callback[thread].data()
|
||||
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
|
||||
const float* JXL_RESTRICT row_in[4];
|
||||
size_t c = 0;
|
||||
for (; c < color_channels; c++) {
|
||||
|
@ -378,9 +404,12 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
}
|
||||
JXL_ASSERT(c == num_channels);
|
||||
if (little_endian) {
|
||||
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, out + i);
|
||||
StoreFloatRow<StoreLEFloat>(row_in, c, xsize, row_out);
|
||||
} else {
|
||||
StoreFloatRow<StoreBEFloat>(row_in, c, xsize, out + i);
|
||||
StoreFloatRow<StoreBEFloat>(row_in, c, xsize, row_out);
|
||||
}
|
||||
if (out_callback) {
|
||||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertFloat");
|
||||
|
@ -396,11 +425,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
pool, 0, static_cast<uint32_t>(ysize),
|
||||
[&](size_t num_threads) {
|
||||
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
|
||||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
const int64_t y = task;
|
||||
size_t i = stride * y;
|
||||
uint8_t* row_out =
|
||||
out_callback
|
||||
? row_out_callback[thread].data()
|
||||
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
|
||||
const float* JXL_RESTRICT row_in[4];
|
||||
size_t c = 0;
|
||||
for (; c < color_channels; c++) {
|
||||
|
@ -418,26 +451,29 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
}
|
||||
// TODO(deymo): add bits_per_sample == 1 case here.
|
||||
if (bits_per_sample <= 8) {
|
||||
StoreUintRow<Store8>(row_u32, c, xsize, 1, out + i);
|
||||
StoreUintRow<Store8>(row_u32, c, xsize, 1, row_out);
|
||||
} else if (bits_per_sample <= 16) {
|
||||
if (little_endian) {
|
||||
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, out + i);
|
||||
StoreUintRow<StoreLE16>(row_u32, c, xsize, 2, row_out);
|
||||
} else {
|
||||
StoreUintRow<StoreBE16>(row_u32, c, xsize, 2, out + i);
|
||||
StoreUintRow<StoreBE16>(row_u32, c, xsize, 2, row_out);
|
||||
}
|
||||
} else if (bits_per_sample <= 24) {
|
||||
if (little_endian) {
|
||||
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, out + i);
|
||||
StoreUintRow<StoreLE24>(row_u32, c, xsize, 3, row_out);
|
||||
} else {
|
||||
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, out + i);
|
||||
StoreUintRow<StoreBE24>(row_u32, c, xsize, 3, row_out);
|
||||
}
|
||||
} else {
|
||||
if (little_endian) {
|
||||
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, out + i);
|
||||
StoreUintRow<StoreLE32>(row_u32, c, xsize, 4, row_out);
|
||||
} else {
|
||||
StoreUintRow<StoreBE32>(row_u32, c, xsize, 4, out + i);
|
||||
StoreUintRow<StoreBE32>(row_u32, c, xsize, 4, row_out);
|
||||
}
|
||||
}
|
||||
if (out_callback) {
|
||||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertUint");
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
@ -37,7 +38,7 @@ namespace jxl {
|
|||
// TODO(lode): support 1-bit output (bits_per_sample == 1)
|
||||
// TODO(lode): support rectangle crop.
|
||||
// stride_out is output scanline size in bytes, must be >=
|
||||
// output_xsize * bytes_per_pixel.
|
||||
// output_xsize * output_bytes_per_pixel.
|
||||
// undo_orientation is an EXIF orientation to undo. Depending on the
|
||||
// orientation, the output xsize and ysize are swapped compared to input
|
||||
// xsize and ysize.
|
||||
|
@ -45,7 +46,8 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
|
|||
bool float_out, size_t num_channels,
|
||||
JxlEndianness endianness, size_t stride_out,
|
||||
jxl::ThreadPool* thread_pool, void* out_image,
|
||||
size_t out_size, jxl::Orientation undo_orientation);
|
||||
size_t out_size, JxlImageOutCallback out_callback,
|
||||
void* out_opaque, jxl::Orientation undo_orientation);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ void BM_DecExternalImage_ConvertImageRGBA(benchmark::State& state) {
|
|||
/*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN,
|
||||
/*stride*/ bytes_per_row,
|
||||
/*thread_pool=*/nullptr, interleaved.data(), interleaved.size(),
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||
/*undo_orientation=*/jxl::Orientation::kIdentity));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -429,11 +429,12 @@ void FrameDecoder::FinalizeDC() {
|
|||
|
||||
void FrameDecoder::AllocateOutput() {
|
||||
const CodecMetadata& metadata = *frame_header_.nonserialized_metadata;
|
||||
if (dec_state_->rgb_output == nullptr) {
|
||||
if (dec_state_->rgb_output == nullptr && !dec_state_->pixel_callback) {
|
||||
decoded_->SetFromImage(Image3F(frame_dim_.xsize_upsampled_padded,
|
||||
frame_dim_.ysize_upsampled_padded),
|
||||
dec_state_->output_encoding_info.color_encoding);
|
||||
}
|
||||
dec_state_->extra_channels.clear();
|
||||
if (metadata.m.num_extra_channels > 0) {
|
||||
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
|
||||
const auto eci = metadata.m.extra_channel_info[i];
|
||||
|
@ -456,7 +457,8 @@ void FrameDecoder::AllocateOutput() {
|
|||
|
||||
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
|
||||
JXL_CHECK(finalized_dc_);
|
||||
JXL_CHECK(decoded_->HasColor() || dec_state_->rgb_output != nullptr);
|
||||
JXL_CHECK(decoded_->HasColor() || dec_state_->rgb_output != nullptr ||
|
||||
!!dec_state_->pixel_callback);
|
||||
|
||||
// Decode AC group.
|
||||
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
||||
|
@ -765,6 +767,12 @@ Status FrameDecoder::Flush() {
|
|||
if (has_blending && !is_finalized_) {
|
||||
return false;
|
||||
}
|
||||
// No early Flush() - nothing to do - if the frame is a kSkipProgressive
|
||||
// frame.
|
||||
if (frame_header_.frame_type == FrameType::kSkipProgressive &&
|
||||
!is_finalized_) {
|
||||
return true;
|
||||
}
|
||||
if (decoded_->IsJPEG()) {
|
||||
// Nothing to do.
|
||||
return true;
|
||||
|
|
|
@ -133,6 +133,7 @@ class FrameDecoder {
|
|||
// blending, the current frame cannot be referenced by future frames, sets the
|
||||
// buffer to which uint8 sRGB pixels will be decoded to.
|
||||
// TODO(veluca): reduce this set of restrictions.
|
||||
// If an output callback is set, this function *must not* be called.
|
||||
void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride,
|
||||
bool is_rgba) const {
|
||||
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
|
||||
|
@ -148,6 +149,7 @@ class FrameDecoder {
|
|||
dec_state_->rgb_output = rgb_output;
|
||||
dec_state_->rgb_output_is_rgba = is_rgba;
|
||||
dec_state_->rgb_stride = stride;
|
||||
JXL_ASSERT(dec_state_->pixel_callback == nullptr);
|
||||
#if !JXL_HIGH_PRECISION
|
||||
if (!is_rgba && decoded_->metadata()->xyb_encoded &&
|
||||
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
|
||||
|
@ -158,9 +160,34 @@ class FrameDecoder {
|
|||
#endif
|
||||
}
|
||||
|
||||
// Same as MaybeSetRGB8OutputBuffer, but with a float callback.
|
||||
// If a RGB8 output buffer is set, this function *must not* be called.
|
||||
void MaybeSetFloatCallback(
|
||||
const std::function<void(const float* pixels, size_t x, size_t y,
|
||||
size_t num_pixels)>& cb,
|
||||
bool is_rgba) const {
|
||||
if (decoded_->metadata()->GetOrientation() != Orientation::kIdentity) {
|
||||
return;
|
||||
}
|
||||
if (frame_header_.blending_info.mode != BlendMode::kReplace ||
|
||||
frame_header_.custom_size_or_origin) {
|
||||
return;
|
||||
}
|
||||
if (frame_header_.CanBeReferenced()) {
|
||||
return;
|
||||
}
|
||||
dec_state_->pixel_callback = cb;
|
||||
dec_state_->rgb_output_is_rgba = is_rgba;
|
||||
JXL_ASSERT(dec_state_->rgb_output == nullptr);
|
||||
}
|
||||
|
||||
// Returns true if the rgb output buffer passed by MaybeSetRGB8OutputBuffer
|
||||
// has been/will be populated by Flush() / FinalizeFrame().
|
||||
bool HasRGBBuffer() const { return dec_state_->rgb_output != nullptr; }
|
||||
// has been/will be populated by Flush() / FinalizeFrame(), or if a pixel
|
||||
// callback has been used.
|
||||
bool HasRGBBuffer() const {
|
||||
return dec_state_->rgb_output != nullptr ||
|
||||
dec_state_->pixel_callback != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Status ProcessDCGlobal(BitReader* br);
|
||||
|
|
|
@ -114,7 +114,40 @@ Status UndoXYBInPlace(Image3F* idct, const Rect& rect,
|
|||
Store(TF_PQ().EncodedFromDisplay(d, linear_g), d, row1 + x);
|
||||
Store(TF_PQ().EncodedFromDisplay(d, linear_b), d, row2 + x);
|
||||
}
|
||||
} else if (output_encoding_info.color_encoding.tf.IsGamma()) {
|
||||
} else if (output_encoding_info.color_encoding.tf.IsHLG()) {
|
||||
for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
|
||||
const auto in_opsin_x = Load(d, row0 + x);
|
||||
const auto in_opsin_y = Load(d, row1 + x);
|
||||
const auto in_opsin_b = Load(d, row2 + x);
|
||||
JXL_COMPILER_FENCE;
|
||||
auto linear_r = Undefined(d);
|
||||
auto linear_g = Undefined(d);
|
||||
auto linear_b = Undefined(d);
|
||||
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b,
|
||||
output_encoding_info.opsin_params, &linear_r, &linear_g,
|
||||
&linear_b);
|
||||
Store(TF_HLG().EncodedFromDisplay(d, linear_r), d, row0 + x);
|
||||
Store(TF_HLG().EncodedFromDisplay(d, linear_g), d, row1 + x);
|
||||
Store(TF_HLG().EncodedFromDisplay(d, linear_b), d, row2 + x);
|
||||
}
|
||||
} else if (output_encoding_info.color_encoding.tf.Is709()) {
|
||||
for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
|
||||
const auto in_opsin_x = Load(d, row0 + x);
|
||||
const auto in_opsin_y = Load(d, row1 + x);
|
||||
const auto in_opsin_b = Load(d, row2 + x);
|
||||
JXL_COMPILER_FENCE;
|
||||
auto linear_r = Undefined(d);
|
||||
auto linear_g = Undefined(d);
|
||||
auto linear_b = Undefined(d);
|
||||
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b,
|
||||
output_encoding_info.opsin_params, &linear_r, &linear_g,
|
||||
&linear_b);
|
||||
Store(TF_709().EncodedFromDisplay(d, linear_r), d, row0 + x);
|
||||
Store(TF_709().EncodedFromDisplay(d, linear_g), d, row1 + x);
|
||||
Store(TF_709().EncodedFromDisplay(d, linear_b), d, row2 + x);
|
||||
}
|
||||
} else if (output_encoding_info.color_encoding.tf.IsGamma() ||
|
||||
output_encoding_info.color_encoding.tf.IsDCI()) {
|
||||
auto gamma_tf = [&](hwy::HWY_NAMESPACE::Vec<decltype(d)> v) {
|
||||
return IfThenZeroElse(
|
||||
v <= Set(d, 1e-5f),
|
||||
|
@ -402,7 +435,9 @@ Status FinalizeImageRect(
|
|||
// enough border available. (rect_for_if_input)
|
||||
|
||||
Image3F* output_color =
|
||||
dec_state->rgb_output == nullptr ? output_image->color() : nullptr;
|
||||
dec_state->rgb_output == nullptr && dec_state->pixel_callback == nullptr
|
||||
? output_image->color()
|
||||
: nullptr;
|
||||
|
||||
Image3F* storage_for_if = output_color;
|
||||
Rect rect_for_if = output_rect;
|
||||
|
@ -490,7 +525,7 @@ Status FinalizeImageRect(
|
|||
// +-------------------------------------------------------------------+
|
||||
Image3F* output_pixel_data_storage = output_color;
|
||||
Rect upsampled_output_rect_for_storage = upsampled_output_rect;
|
||||
if (dec_state->rgb_output) {
|
||||
if (dec_state->rgb_output || dec_state->pixel_callback) {
|
||||
size_t log2_upsampling = CeilLog2Nonzero(frame_header.upsampling);
|
||||
if (storage_for_if == output_color) {
|
||||
storage_for_if =
|
||||
|
@ -520,12 +555,7 @@ Status FinalizeImageRect(
|
|||
if (ec < metadata.extra_channel_info.size()) {
|
||||
JXL_ASSERT(ec < extra_channels.size());
|
||||
alpha = extra_channels[ec].first;
|
||||
JXL_ASSERT(upsampled_output_rect.x0() >= extra_channels[ec].second.x0());
|
||||
JXL_ASSERT(upsampled_output_rect.y0() >= extra_channels[ec].second.y0());
|
||||
alpha_rect =
|
||||
Rect(upsampled_output_rect.x0() - extra_channels[ec].second.x0(),
|
||||
upsampled_output_rect.y0() - extra_channels[ec].second.y0(),
|
||||
upsampled_output_rect.xsize(), upsampled_output_rect.ysize());
|
||||
alpha_rect = extra_channels[ec].second;
|
||||
}
|
||||
|
||||
// +----------------------------- STEP 3 ------------------------------+
|
||||
|
@ -712,6 +742,40 @@ Status FinalizeImageRect(
|
|||
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize)),
|
||||
dec_state->rgb_output, dec_state->rgb_stride);
|
||||
}
|
||||
if (dec_state->pixel_callback != nullptr) {
|
||||
Rect alpha_line_rect = alpha_rect.Lines(available_y, num_ys);
|
||||
Rect color_input_line_rect =
|
||||
upsampled_output_rect_for_storage.Lines(available_y, num_ys);
|
||||
Rect image_line_rect =
|
||||
upsampled_output_rect.Lines(available_y, num_ys)
|
||||
.Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize));
|
||||
const float* line_buffers[4];
|
||||
for (size_t iy = 0; iy < image_line_rect.ysize(); iy++) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
line_buffers[c] = color_input_line_rect.ConstPlaneRow(
|
||||
*output_pixel_data_storage, c, iy);
|
||||
}
|
||||
if (alpha) {
|
||||
line_buffers[3] = alpha_line_rect.ConstRow(*alpha, iy);
|
||||
} else {
|
||||
line_buffers[3] = dec_state->opaque_alpha.data();
|
||||
}
|
||||
std::vector<float>& interleaved =
|
||||
dec_state->pixel_callback_rows[thread];
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < image_line_rect.xsize(); i++) {
|
||||
interleaved[j++] = line_buffers[0][i];
|
||||
interleaved[j++] = line_buffers[1][i];
|
||||
interleaved[j++] = line_buffers[2][i];
|
||||
if (dec_state->rgb_output_is_rgba) {
|
||||
interleaved[j++] = line_buffers[3][i];
|
||||
}
|
||||
}
|
||||
dec_state->pixel_callback(interleaved.data(), image_line_rect.x0(),
|
||||
image_line_rect.y0() + iy,
|
||||
image_line_rect.xsize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -346,11 +346,16 @@ Status OutputEncodingInfo::Set(const ImageMetadata& metadata) {
|
|||
// TODO(veluca): keep in sync with dec_reconstruct.cc
|
||||
if (!orig_color_encoding.tf.IsPQ() && !orig_color_encoding.tf.IsSRGB() &&
|
||||
!orig_color_encoding.tf.IsGamma() &&
|
||||
!orig_color_encoding.tf.IsLinear()) {
|
||||
!orig_color_encoding.tf.IsLinear() &&
|
||||
!orig_color_encoding.tf.IsHLG() && !orig_color_encoding.tf.IsDCI() &&
|
||||
!orig_color_encoding.tf.Is709()) {
|
||||
break;
|
||||
}
|
||||
if (orig_color_encoding.tf.IsGamma()) {
|
||||
inverse_gamma = 1.0f / orig_color_encoding.tf.GetGamma();
|
||||
inverse_gamma = orig_color_encoding.tf.GetGamma();
|
||||
}
|
||||
if (orig_color_encoding.tf.IsDCI()) {
|
||||
inverse_gamma = 1.0f / 2.6f;
|
||||
}
|
||||
if (orig_color_encoding.IsGray() &&
|
||||
orig_color_encoding.white_point != WhitePoint::kD65) {
|
||||
|
|
|
@ -39,6 +39,7 @@ struct OpsinParams {
|
|||
|
||||
struct OutputEncodingInfo {
|
||||
ColorEncoding color_encoding;
|
||||
// Used for Gamma and DCI transfer functions.
|
||||
float inverse_gamma;
|
||||
// Contains an opsin matrix that converts to the primaries of the output
|
||||
// encoding.
|
||||
|
|
|
@ -360,6 +360,8 @@ struct JxlDecoderStruct {
|
|||
void* preview_out_buffer;
|
||||
void* dc_out_buffer;
|
||||
void* image_out_buffer;
|
||||
JxlImageOutCallback image_out_callback;
|
||||
void* image_out_opaque;
|
||||
|
||||
size_t preview_out_size;
|
||||
size_t dc_out_size;
|
||||
|
@ -467,6 +469,8 @@ void JxlDecoderReset(JxlDecoder* dec) {
|
|||
dec->preview_out_buffer = nullptr;
|
||||
dec->dc_out_buffer = nullptr;
|
||||
dec->image_out_buffer = nullptr;
|
||||
dec->image_out_callback = nullptr;
|
||||
dec->image_out_opaque = nullptr;
|
||||
dec->preview_out_size = 0;
|
||||
dec->dc_out_size = 0;
|
||||
dec->image_out_size = 0;
|
||||
|
@ -719,7 +723,9 @@ static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format,
|
|||
static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
|
||||
const jxl::ImageBundle& frame,
|
||||
const JxlPixelFormat& format,
|
||||
void* out_image, size_t out_size) {
|
||||
void* out_image, size_t out_size,
|
||||
JxlImageOutCallback out_callback,
|
||||
void* out_opaque) {
|
||||
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
|
||||
// color/grayscale format
|
||||
const auto& metadata = dec->metadata.m;
|
||||
|
@ -736,7 +742,8 @@ static JxlDecoderStatus ConvertImageInternal(const JxlDecoder* dec,
|
|||
jxl::Status status = jxl::ConvertToExternal(
|
||||
frame, BitsPerChannel(format.data_type), float_format,
|
||||
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
|
||||
out_image, out_size, undo_orientation);
|
||||
out_image, out_size, /*out_callback=*/out_callback,
|
||||
/*out_opaque=*/out_opaque, undo_orientation);
|
||||
|
||||
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
|
||||
}
|
||||
|
@ -928,7 +935,8 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
|||
if (dec->preview_out_buffer) {
|
||||
JxlDecoderStatus status = ConvertImageInternal(
|
||||
dec, ib, dec->preview_out_format, dec->preview_out_buffer,
|
||||
dec->preview_out_size);
|
||||
dec->preview_out_size, /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr);
|
||||
if (status != JXL_DEC_SUCCESS) return status;
|
||||
}
|
||||
return JXL_DEC_PREVIEW_IMAGE;
|
||||
|
@ -1005,13 +1013,6 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
|||
/*allow_partial_frames=*/false, /*allow_partial_dc_global=*/false);
|
||||
if (!status) JXL_API_RETURN_IF_ERROR(status);
|
||||
|
||||
if (dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
|
||||
dec->image_out_format.num_channels >= 3) {
|
||||
bool is_rgba = dec->image_out_format.num_channels == 4;
|
||||
dec->frame_dec->MaybeSetRGB8OutputBuffer(
|
||||
reinterpret_cast<uint8_t*>(dec->image_out_buffer),
|
||||
GetStride(dec, dec->image_out_format), is_rgba);
|
||||
}
|
||||
size_t sections_begin =
|
||||
DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
|
||||
|
||||
|
@ -1038,9 +1039,43 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
|||
(dec->next_jpeg_reconstruction_out == nullptr ||
|
||||
dec->ib->jpeg_data == nullptr) &&
|
||||
dec->is_last_of_still) {
|
||||
// TODO(lode): remove the dec->is_last_of_still condition if the
|
||||
// frame decoder needs the image buffer as working space for decoding
|
||||
// non-visible or blending frames too
|
||||
return JXL_DEC_NEED_IMAGE_OUT_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
if (dec->image_out_buffer_set && !!dec->image_out_buffer &&
|
||||
dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
|
||||
dec->image_out_format.num_channels >= 3) {
|
||||
bool is_rgba = dec->image_out_format.num_channels == 4;
|
||||
dec->frame_dec->MaybeSetRGB8OutputBuffer(
|
||||
reinterpret_cast<uint8_t*>(dec->image_out_buffer),
|
||||
GetStride(dec, dec->image_out_format), is_rgba);
|
||||
}
|
||||
|
||||
const bool little_endian =
|
||||
dec->image_out_format.endianness == JXL_LITTLE_ENDIAN ||
|
||||
(dec->image_out_format.endianness == JXL_NATIVE_ENDIAN &&
|
||||
IsLittleEndian());
|
||||
bool swap_endianness = little_endian != IsLittleEndian();
|
||||
|
||||
// TODO(lode): Support more formats than just native endian float32 for
|
||||
// the low-memory callback path
|
||||
if (dec->image_out_buffer_set && !!dec->image_out_callback &&
|
||||
dec->image_out_format.data_type == JXL_TYPE_FLOAT &&
|
||||
dec->image_out_format.num_channels >= 3 && !swap_endianness &&
|
||||
dec->frame_dec_in_progress) {
|
||||
bool is_rgba = dec->image_out_format.num_channels == 4;
|
||||
dec->frame_dec->MaybeSetFloatCallback(
|
||||
[dec](const float* pixels, size_t x, size_t y, size_t num_pixels) {
|
||||
dec->image_out_callback(dec->image_out_opaque, x, y, num_pixels,
|
||||
pixels);
|
||||
},
|
||||
is_rgba);
|
||||
}
|
||||
|
||||
size_t pos = dec->frame_start - dec->codestream_pos;
|
||||
|
||||
bool get_dc = dec->is_last_of_still &&
|
||||
|
@ -1118,9 +1153,10 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
|||
dc_bundle.SetFromImage(
|
||||
std::move(dc),
|
||||
dec->passes_state->output_encoding_info.color_encoding);
|
||||
JXL_API_RETURN_IF_ERROR(
|
||||
ConvertImageInternal(dec, dc_bundle, dec->dc_out_format,
|
||||
dec->dc_out_buffer, dec->dc_out_size));
|
||||
JXL_API_RETURN_IF_ERROR(ConvertImageInternal(
|
||||
dec, dc_bundle, dec->dc_out_format, dec->dc_out_buffer,
|
||||
dec->dc_out_size,
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr));
|
||||
dec->frame_stage = FrameStage::kFull;
|
||||
return JXL_DEC_DC_IMAGE;
|
||||
}
|
||||
|
@ -1168,7 +1204,8 @@ JxlDecoderStatus JxlDecoderProcessInternal(JxlDecoder* dec, const uint8_t* in,
|
|||
// Copy pixels if desired.
|
||||
JxlDecoderStatus status = ConvertImageInternal(
|
||||
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
|
||||
dec->image_out_size);
|
||||
dec->image_out_size, dec->image_out_callback,
|
||||
dec->image_out_opaque);
|
||||
if (status != JXL_DEC_SUCCESS) return status;
|
||||
}
|
||||
dec->image_out_buffer_set = false;
|
||||
|
@ -1901,9 +1938,10 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
|
|||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
JxlDecoderStatus status =
|
||||
jxl::ConvertImageInternal(dec, *dec->ib, dec->image_out_format,
|
||||
dec->image_out_buffer, dec->image_out_size);
|
||||
JxlDecoderStatus status = jxl::ConvertImageInternal(
|
||||
dec, *dec->ib, dec->image_out_format, dec->image_out_buffer,
|
||||
dec->image_out_size,
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr);
|
||||
if (status != JXL_DEC_SUCCESS) return status;
|
||||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
@ -2013,6 +2051,10 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
|
|||
if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) {
|
||||
return JXL_API_ERROR("No image out buffer needed at this time");
|
||||
}
|
||||
if (dec->image_out_buffer_set && !!dec->image_out_callback) {
|
||||
return JXL_API_ERROR(
|
||||
"Cannot change from image out callback to image out buffer");
|
||||
}
|
||||
size_t min_size;
|
||||
// This also checks whether the format is valid and supported and basic info
|
||||
// is available.
|
||||
|
@ -2027,14 +2069,23 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
|
|||
dec->image_out_size = size;
|
||||
dec->image_out_format = *format;
|
||||
|
||||
if (format->data_type == JXL_TYPE_UINT8 && format->num_channels >= 3 &&
|
||||
dec->frame_dec_in_progress) {
|
||||
bool is_rgba = format->num_channels == 4;
|
||||
dec->frame_dec->MaybeSetRGB8OutputBuffer(reinterpret_cast<uint8_t*>(buffer),
|
||||
jxl::GetStride(dec, *format),
|
||||
is_rgba);
|
||||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec,
|
||||
const JxlPixelFormat* format,
|
||||
JxlImageOutCallback callback,
|
||||
void* opaque) {
|
||||
if (dec->image_out_buffer_set && !!dec->image_out_buffer) {
|
||||
return JXL_API_ERROR(
|
||||
"Cannot change from image out buffer to image out callback");
|
||||
}
|
||||
|
||||
dec->image_out_buffer_set = true;
|
||||
dec->image_out_callback = callback;
|
||||
dec->image_out_opaque = opaque;
|
||||
dec->image_out_format = *format;
|
||||
|
||||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "lib/jxl/base/file_io.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/dec_file.h"
|
||||
#include "lib/jxl/enc_butteraugli_comparator.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
|
@ -55,6 +56,96 @@ void AppendU32BE(uint32_t u32, jxl::PaddedBytes* bytes) {
|
|||
bytes->push_back(u32 >> 0);
|
||||
}
|
||||
|
||||
bool Near(double expected, double value, double max_dist) {
|
||||
double dist = expected > value ? expected - value : value - expected;
|
||||
return dist <= max_dist;
|
||||
}
|
||||
|
||||
// Loads a Big-Endian float
|
||||
float LoadBEFloat(const uint8_t* p) {
|
||||
uint32_t u = LoadBE32(p);
|
||||
float result;
|
||||
memcpy(&result, &u, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loads a Little-Endian float
|
||||
float LoadLEFloat(const uint8_t* p) {
|
||||
uint32_t u = LoadLE32(p);
|
||||
float result;
|
||||
memcpy(&result, &u, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Based on highway scalar implementation, for testing
|
||||
float LoadFloat16(uint16_t bits16) {
|
||||
const uint32_t sign = bits16 >> 15;
|
||||
const uint32_t biased_exp = (bits16 >> 10) & 0x1F;
|
||||
const uint32_t mantissa = bits16 & 0x3FF;
|
||||
|
||||
// Subnormal or zero
|
||||
if (biased_exp == 0) {
|
||||
const float subnormal = (1.0f / 16384) * (mantissa * (1.0f / 1024));
|
||||
return sign ? -subnormal : subnormal;
|
||||
}
|
||||
|
||||
// Normalized: convert the representation directly (faster than ldexp/tables).
|
||||
const uint32_t biased_exp32 = biased_exp + (127 - 15);
|
||||
const uint32_t mantissa32 = mantissa << (23 - 10);
|
||||
const uint32_t bits32 = (sign << 31) | (biased_exp32 << 23) | mantissa32;
|
||||
|
||||
float result;
|
||||
memcpy(&result, &bits32, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
float LoadLEFloat16(const uint8_t* p) {
|
||||
uint16_t bits16 = LoadLE16(p);
|
||||
return LoadFloat16(bits16);
|
||||
}
|
||||
|
||||
float LoadBEFloat16(const uint8_t* p) {
|
||||
uint16_t bits16 = LoadBE16(p);
|
||||
return LoadFloat16(bits16);
|
||||
}
|
||||
|
||||
size_t GetPrecision(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
return 1;
|
||||
case JXL_TYPE_UINT8:
|
||||
return 8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return 16;
|
||||
case JXL_TYPE_UINT32:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT:
|
||||
// Floating point mantissa precision
|
||||
return 24;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
return 11;
|
||||
}
|
||||
JXL_ASSERT(false); // unknown type
|
||||
}
|
||||
|
||||
size_t GetDataBits(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
return 1;
|
||||
case JXL_TYPE_UINT8:
|
||||
return 8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return 16;
|
||||
case JXL_TYPE_UINT32:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
return 16;
|
||||
}
|
||||
JXL_ASSERT(false); // unknown type
|
||||
}
|
||||
|
||||
// What type of codestream format in the boxes to use for testing
|
||||
enum CodeStreamBoxFormat {
|
||||
// Do not use box format at all, only pure codestream
|
||||
|
@ -323,14 +414,18 @@ PaddedBytes CreateTestJXLCodestream(Span<const uint8_t> pixels, size_t xsize,
|
|||
// Decodes one-shot with the API for non-streaming decoding tests.
|
||||
std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
|
||||
Span<const uint8_t> compressed,
|
||||
const JxlPixelFormat& format) {
|
||||
const JxlPixelFormat& format,
|
||||
bool use_callback, bool set_buffer_early) {
|
||||
void* runner = JxlThreadParallelRunnerCreate(
|
||||
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner));
|
||||
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(
|
||||
dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE));
|
||||
EXPECT_EQ(
|
||||
JXL_DEC_SUCCESS,
|
||||
JxlDecoderSubscribeEvents(
|
||||
dec, JXL_DEC_BASIC_INFO | (set_buffer_early ? JXL_DEC_FRAME : 0) |
|
||||
JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FULL_IMAGE));
|
||||
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
|
||||
|
@ -342,10 +437,54 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
|
|||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
|
||||
std::vector<uint8_t> pixels(buffer_size);
|
||||
|
||||
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
|
||||
size_t bytes_per_pixel =
|
||||
format.num_channels * GetDataBits(format.data_type) / jxl::kBitsPerByte;
|
||||
size_t stride = bytes_per_pixel * info.xsize;
|
||||
if (format.align > 1) {
|
||||
stride = jxl::DivCeil(stride, format.align) * format.align;
|
||||
}
|
||||
auto callback = [&](size_t x, size_t y, size_t num_pixels,
|
||||
const void* pixels_row) {
|
||||
memcpy(pixels.data() + stride * y + bytes_per_pixel * x, pixels_row,
|
||||
num_pixels * bytes_per_pixel);
|
||||
};
|
||||
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
|
||||
dec, &format, pixels.data(), pixels.size()));
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
|
||||
|
||||
std::vector<uint8_t> preview;
|
||||
if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
|
||||
size_t buffer_size;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size));
|
||||
preview.resize(buffer_size);
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetPreviewOutBuffer(dec, &format, preview.data(),
|
||||
preview.size()));
|
||||
EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec));
|
||||
|
||||
status = JxlDecoderProcessInput(dec);
|
||||
}
|
||||
|
||||
if (set_buffer_early) {
|
||||
EXPECT_EQ(JXL_DEC_FRAME, status);
|
||||
} else {
|
||||
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, status);
|
||||
}
|
||||
|
||||
if (use_callback) {
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetImageOutCallback(
|
||||
dec, &format,
|
||||
[](void* opaque, size_t x, size_t y, size_t xsize,
|
||||
const void* pixels_row) {
|
||||
auto cb = static_cast<decltype(&callback)>(opaque);
|
||||
(*cb)(x, y, xsize, pixels_row);
|
||||
},
|
||||
/*opaque=*/&callback));
|
||||
} else {
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
|
||||
dec, &format, pixels.data(), pixels.size()));
|
||||
}
|
||||
|
||||
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
|
||||
|
||||
|
@ -360,9 +499,11 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
|
|||
|
||||
// Decodes one-shot with the API for non-streaming decoding tests.
|
||||
std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
|
||||
const JxlPixelFormat& format) {
|
||||
const JxlPixelFormat& format,
|
||||
bool use_callback, bool set_buffer_early) {
|
||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||
std::vector<uint8_t> pixels = DecodeWithAPI(dec, compressed, format);
|
||||
std::vector<uint8_t> pixels =
|
||||
DecodeWithAPI(dec, compressed, format, use_callback, set_buffer_early);
|
||||
JxlDecoderDestroy(dec);
|
||||
return pixels;
|
||||
}
|
||||
|
@ -371,95 +512,6 @@ std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
|
|||
} // namespace jxl
|
||||
|
||||
namespace {
|
||||
bool Near(double expected, double value, double max_dist) {
|
||||
double dist = expected > value ? expected - value : value - expected;
|
||||
return dist <= max_dist;
|
||||
}
|
||||
|
||||
// Loads a Big-Endian float
|
||||
float LoadBEFloat(const uint8_t* p) {
|
||||
uint32_t u = LoadBE32(p);
|
||||
float result;
|
||||
memcpy(&result, &u, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loads a Little-Endian float
|
||||
float LoadLEFloat(const uint8_t* p) {
|
||||
uint32_t u = LoadLE32(p);
|
||||
float result;
|
||||
memcpy(&result, &u, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Based on highway scalar implementation, for testing
|
||||
float LoadFloat16(uint16_t bits16) {
|
||||
const uint32_t sign = bits16 >> 15;
|
||||
const uint32_t biased_exp = (bits16 >> 10) & 0x1F;
|
||||
const uint32_t mantissa = bits16 & 0x3FF;
|
||||
|
||||
// Subnormal or zero
|
||||
if (biased_exp == 0) {
|
||||
const float subnormal = (1.0f / 16384) * (mantissa * (1.0f / 1024));
|
||||
return sign ? -subnormal : subnormal;
|
||||
}
|
||||
|
||||
// Normalized: convert the representation directly (faster than ldexp/tables).
|
||||
const uint32_t biased_exp32 = biased_exp + (127 - 15);
|
||||
const uint32_t mantissa32 = mantissa << (23 - 10);
|
||||
const uint32_t bits32 = (sign << 31) | (biased_exp32 << 23) | mantissa32;
|
||||
|
||||
float result;
|
||||
memcpy(&result, &bits32, 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
float LoadLEFloat16(const uint8_t* p) {
|
||||
uint16_t bits16 = LoadLE16(p);
|
||||
return LoadFloat16(bits16);
|
||||
}
|
||||
|
||||
float LoadBEFloat16(const uint8_t* p) {
|
||||
uint16_t bits16 = LoadBE16(p);
|
||||
return LoadFloat16(bits16);
|
||||
}
|
||||
|
||||
size_t GetPrecision(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
return 1;
|
||||
case JXL_TYPE_UINT8:
|
||||
return 8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return 16;
|
||||
case JXL_TYPE_UINT32:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT:
|
||||
// Floating point mantissa precision
|
||||
return 24;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
return 11;
|
||||
}
|
||||
JXL_ASSERT(false); // unknown type
|
||||
}
|
||||
|
||||
size_t GetDataBits(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
return 1;
|
||||
case JXL_TYPE_UINT8:
|
||||
return 8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return 16;
|
||||
case JXL_TYPE_UINT32:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
return 16;
|
||||
}
|
||||
JXL_ASSERT(false); // unknown type
|
||||
}
|
||||
|
||||
// Procedure to convert pixels to double precision, not efficient, but
|
||||
// well-controlled for testing. It uses double, to be able to represent all
|
||||
|
@ -1303,109 +1355,189 @@ TEST(DecodeTest, ICCPartialTest) {
|
|||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
|
||||
TEST(DecodeTest, PixelTest) {
|
||||
struct PixelTestConfig {
|
||||
// Input image definition.
|
||||
bool grayscale;
|
||||
bool include_alpha;
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
bool add_preview;
|
||||
// Output format.
|
||||
JxlEndianness endianness;
|
||||
JxlDataType data_type;
|
||||
uint32_t output_channels;
|
||||
// Container options.
|
||||
CodeStreamBoxFormat add_container;
|
||||
// Decoding mode.
|
||||
bool use_callback;
|
||||
bool set_buffer_early;
|
||||
};
|
||||
|
||||
class DecodeTestParam : public ::testing::TestWithParam<PixelTestConfig> {};
|
||||
|
||||
TEST_P(DecodeTestParam, PixelTest) {
|
||||
PixelTestConfig config = GetParam();
|
||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||
|
||||
for (int include_alpha = 0; include_alpha <= 1; include_alpha++) {
|
||||
uint32_t orig_channels = include_alpha ? 4 : 3;
|
||||
for (size_t box = 0; box < kCSBF_NUM_ENTRIES; ++box) {
|
||||
CodeStreamBoxFormat add_container = (CodeStreamBoxFormat)box;
|
||||
size_t xsize = 123, ysize = 77;
|
||||
size_t num_pixels = xsize * ysize;
|
||||
std::vector<uint8_t> pixels =
|
||||
jxl::test::GetSomeTestImage(xsize, ysize, orig_channels, 0);
|
||||
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16,
|
||||
JXL_BIG_ENDIAN, 0};
|
||||
jxl::CompressParams cparams;
|
||||
// Lossless to verify pixels exactly after roundtrip.
|
||||
cparams.SetLossless();
|
||||
// For variation: some have container and no preview, others have preview
|
||||
// and no container.
|
||||
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
|
||||
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
|
||||
orig_channels, cparams, add_container, false);
|
||||
jxl::PaddedBytes compressed_with_preview = jxl::CreateTestJXLCodestream(
|
||||
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
|
||||
orig_channels, cparams, add_container, true);
|
||||
size_t num_pixels = config.xsize * config.ysize;
|
||||
uint32_t orig_channels =
|
||||
(config.grayscale ? 1 : 3) + (config.include_alpha ? 1 : 0);
|
||||
std::vector<uint8_t> pixels =
|
||||
jxl::test::GetSomeTestImage(config.xsize, config.ysize, orig_channels, 0);
|
||||
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
|
||||
0};
|
||||
jxl::CompressParams cparams;
|
||||
// Lossless to verify pixels exactly after roundtrip.
|
||||
cparams.SetLossless();
|
||||
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
|
||||
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), config.xsize,
|
||||
config.ysize, orig_channels, cparams, config.add_container,
|
||||
config.add_preview);
|
||||
|
||||
const JxlEndianness endiannesses[] = {JXL_NATIVE_ENDIAN,
|
||||
JXL_LITTLE_ENDIAN, JXL_BIG_ENDIAN};
|
||||
for (JxlEndianness endianness : endiannesses) {
|
||||
for (uint32_t channels = 3; channels <= orig_channels; ++channels) {
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, endianness, 0};
|
||||
JxlPixelFormat format = {config.output_channels, config.data_type,
|
||||
config.endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec,
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
|
||||
ysize, format_orig, format));
|
||||
}
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT16, endianness, 0};
|
||||
|
||||
// Test with the container for one of the pixel formats.
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec,
|
||||
jxl::Span<const uint8_t>(compressed_with_preview.data(),
|
||||
compressed_with_preview.size()),
|
||||
format);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
|
||||
ysize, format_orig, format));
|
||||
}
|
||||
|
||||
#if 0 // Disabled since external_image doesn't currently support uint32_t
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT32, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(dec,
|
||||
jxl::Span<const uint8_t>(compressed.data(),
|
||||
compressed.size()), format);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(),
|
||||
xsize, ysize, format_orig, format));
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec,
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
|
||||
ysize, format_orig, format));
|
||||
}
|
||||
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT16, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec,
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize,
|
||||
ysize, format_orig, format));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format, config.use_callback, config.set_buffer_early);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * config.output_channels *
|
||||
GetDataBits(config.data_type) / jxl::kBitsPerByte,
|
||||
pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), config.xsize,
|
||||
config.ysize, format_orig, format));
|
||||
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
|
||||
std::vector<PixelTestConfig> GeneratePixelTests() {
|
||||
std::vector<PixelTestConfig> all_tests;
|
||||
struct ChannelInfo {
|
||||
bool grayscale;
|
||||
bool include_alpha;
|
||||
size_t output_channels;
|
||||
};
|
||||
ChannelInfo ch_info[] = {
|
||||
{false, true, 4}, // RGBA -> RGBA
|
||||
{true, false, 1}, // G -> G
|
||||
{true, true, 1}, // GA -> G
|
||||
{true, true, 2}, // GA -> GA
|
||||
{false, false, 3}, // RGB -> RGB
|
||||
{false, true, 3}, // RGBA -> RGB
|
||||
{false, false, 4}, // RGB -> RGBA
|
||||
};
|
||||
|
||||
struct OutputFormat {
|
||||
JxlEndianness endianness;
|
||||
JxlDataType data_type;
|
||||
};
|
||||
OutputFormat out_formats[] = {
|
||||
{JXL_NATIVE_ENDIAN, JXL_TYPE_UINT8},
|
||||
{JXL_LITTLE_ENDIAN, JXL_TYPE_UINT16},
|
||||
{JXL_BIG_ENDIAN, JXL_TYPE_UINT16},
|
||||
{JXL_NATIVE_ENDIAN, JXL_TYPE_FLOAT16},
|
||||
{JXL_LITTLE_ENDIAN, JXL_TYPE_FLOAT},
|
||||
{JXL_BIG_ENDIAN, JXL_TYPE_FLOAT},
|
||||
};
|
||||
|
||||
auto make_test = [&](ChannelInfo ch, size_t xsize, size_t ysize, bool preview,
|
||||
CodeStreamBoxFormat box, OutputFormat format,
|
||||
bool use_callback, bool set_buffer_early) {
|
||||
PixelTestConfig c;
|
||||
c.grayscale = ch.grayscale;
|
||||
c.include_alpha = ch.include_alpha;
|
||||
c.add_preview = preview;
|
||||
c.xsize = xsize;
|
||||
c.ysize = ysize;
|
||||
c.add_container = (CodeStreamBoxFormat)box;
|
||||
c.output_channels = ch.output_channels;
|
||||
c.data_type = format.data_type;
|
||||
c.endianness = format.endianness;
|
||||
c.use_callback = use_callback;
|
||||
c.set_buffer_early = set_buffer_early;
|
||||
all_tests.push_back(c);
|
||||
};
|
||||
|
||||
// Test output formats and methods.
|
||||
for (ChannelInfo ch : ch_info) {
|
||||
for (int use_callback = 0; use_callback <= 1; use_callback++) {
|
||||
for (OutputFormat fmt : out_formats) {
|
||||
make_test(ch, 301, 33, /*add_preview=*/false,
|
||||
CodeStreamBoxFormat::kCSBF_None, fmt, use_callback,
|
||||
/*set_buffer_early=*/false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Test codestream formats.
|
||||
for (size_t box = 1; box < kCSBF_NUM_ENTRIES; ++box) {
|
||||
make_test(ch_info[0], 77, 33, /*add_preview=*/false,
|
||||
(CodeStreamBoxFormat)box, out_formats[0], /*use_callback=*/false,
|
||||
/*set_buffer_early=*/false);
|
||||
}
|
||||
// Test previews.
|
||||
for (int add_preview = 0; add_preview <= 1; add_preview++) {
|
||||
make_test(ch_info[0], 77, 33, add_preview, CodeStreamBoxFormat::kCSBF_None,
|
||||
out_formats[0],
|
||||
/*use_callback=*/false, /*set_buffer_early=*/false);
|
||||
}
|
||||
// Test setting buffers early.
|
||||
make_test(ch_info[0], 300, 33, /*add_preview=*/false,
|
||||
CodeStreamBoxFormat::kCSBF_None, out_formats[0],
|
||||
/*use_callback=*/false, /*set_buffer_early=*/true);
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::string PixelTestDescription(
|
||||
const testing::TestParamInfo<DecodeTestParam::ParamType>& info) {
|
||||
PixelTestConfig c = info.param;
|
||||
std::string name;
|
||||
name += std::to_string(c.xsize) + "x" + std::to_string(c.ysize);
|
||||
const char* colors[] = {"", "G", "GA", "RGB", "RGBA"};
|
||||
name += colors[(c.grayscale ? 1 : 3) + (c.include_alpha ? 1 : 0)];
|
||||
name += "to";
|
||||
name += colors[c.output_channels];
|
||||
switch (c.data_type) {
|
||||
case JXL_TYPE_UINT8:
|
||||
name += "u8";
|
||||
break;
|
||||
case JXL_TYPE_UINT16:
|
||||
name += "u16";
|
||||
break;
|
||||
case JXL_TYPE_FLOAT:
|
||||
name += "f32";
|
||||
break;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
name += "f16";
|
||||
break;
|
||||
case JXL_TYPE_UINT32:
|
||||
name += "u32";
|
||||
break;
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
name += "b";
|
||||
break;
|
||||
};
|
||||
if (GetDataBits(c.data_type) > jxl::kBitsPerByte) {
|
||||
if (c.endianness == JXL_NATIVE_ENDIAN) {
|
||||
// add nothing
|
||||
} else if (c.endianness == JXL_BIG_ENDIAN) {
|
||||
name += "BE";
|
||||
} else if (c.endianness == JXL_LITTLE_ENDIAN) {
|
||||
name += "LE";
|
||||
}
|
||||
}
|
||||
if (c.add_container != CodeStreamBoxFormat::kCSBF_None) {
|
||||
name += "Box" + std::to_string((size_t)c.add_container);
|
||||
}
|
||||
if (c.add_preview) name += "Preview";
|
||||
if (c.use_callback) name += "Callback";
|
||||
if (c.set_buffer_early) name += "EarlyBuffer";
|
||||
return name;
|
||||
}
|
||||
|
||||
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(DecodeTest, DecodeTestParam,
|
||||
testing::ValuesIn(GeneratePixelTests()),
|
||||
PixelTestDescription);
|
||||
|
||||
TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||
|
||||
|
@ -1428,7 +1560,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
|||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/false, /*set_buffer_early=*/false);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
|
@ -1440,7 +1572,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
|||
// Test with the container for one of the pixel formats.
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/true, /*set_buffer_early=*/true);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
|
@ -1452,7 +1584,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
|
|||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/false, /*set_buffer_early=*/false);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
|
@ -1480,7 +1612,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
|
|||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/false, /*set_buffer_early=*/true);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
|
||||
|
@ -1534,7 +1666,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
|
|||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/true, /*set_buffer_early=*/false);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||
|
||||
|
@ -1596,7 +1728,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
|
|||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
format, /*use_callback=*/false, /*set_buffer_early=*/true);
|
||||
JxlDecoderReset(dec);
|
||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||
|
||||
|
@ -1638,84 +1770,6 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
|
|||
}
|
||||
}
|
||||
|
||||
TEST(DecodeTest, GrayscaleTest) {
|
||||
size_t xsize = 123, ysize = 77;
|
||||
size_t num_pixels = xsize * ysize;
|
||||
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 2, 0);
|
||||
JxlPixelFormat format_orig = {2, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
|
||||
|
||||
jxl::CompressParams cparams;
|
||||
cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
|
||||
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
|
||||
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 2,
|
||||
cparams, kCSBF_None, true);
|
||||
|
||||
const JxlEndianness endiannesses[] = {JXL_NATIVE_ENDIAN, JXL_LITTLE_ENDIAN,
|
||||
JXL_BIG_ENDIAN};
|
||||
for (JxlEndianness endianness : endiannesses) {
|
||||
// The compressed image is grayscale, but the output can be tested with
|
||||
// up to 4 channels (RGBA)
|
||||
for (uint32_t channels = 1; channels <= 4; ++channels) {
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
EXPECT_EQ(num_pixels * channels, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT16, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
|
||||
#if 0 // Disabled since external_image doesn't currently support uint32_t
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_UINT32, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(),
|
||||
compressed.size()), format);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
|
||||
{
|
||||
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT16, endianness, 0};
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
|
||||
format);
|
||||
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestPartialStream(bool reconstructible_jpeg) {
|
||||
size_t xsize = 123, ysize = 77;
|
||||
uint32_t channels = 4;
|
||||
|
@ -2134,7 +2188,7 @@ TEST(DecodeTest, PreviewTest) {
|
|||
// ButteraugliComparator::Diffmap in butteraugli.cc.
|
||||
EXPECT_LE(ButteraugliDistance(io0, io1, ba,
|
||||
/*distmap=*/nullptr, nullptr),
|
||||
0.9f);
|
||||
1.4f);
|
||||
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
|
@ -2155,11 +2209,14 @@ TEST(DecodeTest, AlignTest) {
|
|||
// On purpose not using jxl::RoundUpTo to test it independently.
|
||||
size_t expected_line_bytes = (1 * 3 * xsize + align - 1) / align * align;
|
||||
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format);
|
||||
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
for (int use_callback = 0; use_callback <= 1; ++use_callback) {
|
||||
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
|
||||
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format,
|
||||
use_callback, /*set_buffer_early=*/false);
|
||||
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
|
||||
EXPECT_EQ(0, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize,
|
||||
format_orig, format));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DecodeTest, AnimationTest) {
|
||||
|
|
|
@ -157,6 +157,40 @@ HWY_NOINLINE void TestFastPQEFD() {
|
|||
printf("max abs err %e\n", static_cast<double>(max_abs_err));
|
||||
}
|
||||
|
||||
HWY_NOINLINE void TestFastHLGEFD() {
|
||||
constexpr size_t kNumTrials = 1 << 23;
|
||||
std::mt19937 rng(1);
|
||||
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
|
||||
float max_abs_err = 0;
|
||||
HWY_FULL(float) d;
|
||||
for (size_t i = 0; i < kNumTrials; i++) {
|
||||
const float f = dist(rng);
|
||||
const float actual = GetLane(TF_HLG().EncodedFromDisplay(d, Set(d, f)));
|
||||
const float expected = TF_HLG().EncodedFromDisplay(f);
|
||||
const float abs_err = std::abs(expected - actual);
|
||||
EXPECT_LT(abs_err, 5e-7) << "f = " << f;
|
||||
max_abs_err = std::max(max_abs_err, abs_err);
|
||||
}
|
||||
printf("max abs err %e\n", static_cast<double>(max_abs_err));
|
||||
}
|
||||
|
||||
HWY_NOINLINE void TestFast709EFD() {
|
||||
constexpr size_t kNumTrials = 1 << 23;
|
||||
std::mt19937 rng(1);
|
||||
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
|
||||
float max_abs_err = 0;
|
||||
HWY_FULL(float) d;
|
||||
for (size_t i = 0; i < kNumTrials; i++) {
|
||||
const float f = dist(rng);
|
||||
const float actual = GetLane(TF_709().EncodedFromDisplay(d, Set(d, f)));
|
||||
const float expected = TF_709().EncodedFromDisplay(f);
|
||||
const float abs_err = std::abs(expected - actual);
|
||||
EXPECT_LT(abs_err, 2e-6) << "f = " << f;
|
||||
max_abs_err = std::max(max_abs_err, abs_err);
|
||||
}
|
||||
printf("max abs err %e\n", static_cast<double>(max_abs_err));
|
||||
}
|
||||
|
||||
HWY_NOINLINE void TestFastPQDFE() {
|
||||
constexpr size_t kNumTrials = 1 << 23;
|
||||
std::mt19937 rng(1);
|
||||
|
@ -246,6 +280,8 @@ HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastErf);
|
|||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastSRGB);
|
||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQDFE);
|
||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQEFD);
|
||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastHLGEFD);
|
||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFast709EFD);
|
||||
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastXYB);
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -121,13 +121,24 @@ Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data) {
|
|||
JXL_RETURN_IF_ERROR(br_read(jpeg_data->inter_marker_data[i]));
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data));
|
||||
if (result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
|
||||
return JXL_FAILURE("Invalid brotli-compressed data");
|
||||
}
|
||||
|
||||
if (!BrotliDecoderIsFinished(brotli_dec)) {
|
||||
// Check if there is more decompressed output.
|
||||
size_t available_out = 1;
|
||||
uint64_t dummy;
|
||||
uint8_t* next_out = reinterpret_cast<uint8_t*>(&dummy);
|
||||
result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in,
|
||||
&available_out, &next_out, nullptr);
|
||||
if (available_out == 0 ||
|
||||
result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
|
||||
return JXL_FAILURE("Excess data in compressed stream");
|
||||
}
|
||||
if (result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
|
||||
return JXL_FAILURE("Incomplete brotli-stream");
|
||||
}
|
||||
if (!BrotliDecoderIsFinished(brotli_dec) ||
|
||||
result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
|
||||
return JXL_FAILURE("Corrupted brotli-stream");
|
||||
}
|
||||
if (available_in != 0) {
|
||||
return JXL_FAILURE("Unused data after brotli stream");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
// Macros and functions useful for tests.
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/codestream_header.h"
|
||||
|
@ -273,7 +275,8 @@ std::vector<ColorEncodingDescriptor> AllEncodings() {
|
|||
std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
|
||||
size_t num_channels, uint16_t seed) {
|
||||
// Cause more significant image difference for successive seeds.
|
||||
seed = static_cast<uint16_t>(seed * 77);
|
||||
std::mt19937 rng(seed);
|
||||
std::uniform_int_distribution<uint16_t> dark(0, 32767);
|
||||
size_t num_pixels = xsize * ysize;
|
||||
// 16 bits per channel, big endian, 4 channels
|
||||
std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
|
||||
|
@ -281,14 +284,16 @@ std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
|
|||
// can be compared after roundtrip.
|
||||
for (size_t y = 0; y < ysize; y++) {
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
uint16_t r = (65535 - x * y) ^ seed;
|
||||
uint16_t g = (x << 8) + y + seed;
|
||||
uint16_t b = (y << 8) + x * seed;
|
||||
uint16_t a = 32768 + x * 256 - y;
|
||||
uint16_t r = dark(rng);
|
||||
uint16_t g = dark(rng);
|
||||
uint16_t b = dark(rng);
|
||||
uint16_t a = dark(rng);
|
||||
// put some shape in there for visual debugging
|
||||
if (x * x + y * y < 1000) {
|
||||
std::swap(r, g);
|
||||
b = 0;
|
||||
r = (65535 - x * y) ^ seed;
|
||||
g = (x << 8) + y + seed;
|
||||
b = (y << 8) + x * seed;
|
||||
a = 32768 + x * 256 - y;
|
||||
}
|
||||
size_t i = (y * xsize + x) * 2 * num_channels;
|
||||
pixels[i + 0] = (r >> 8);
|
||||
|
|
|
@ -61,6 +61,24 @@ class TF_HLG {
|
|||
return e;
|
||||
}
|
||||
|
||||
// Maximum error 5e-7.
|
||||
template <class D, class V>
|
||||
JXL_INLINE V EncodedFromDisplay(D d, V x) const {
|
||||
const hwy::HWY_NAMESPACE::Rebind<uint32_t, D> du;
|
||||
const V kSign = BitCast(d, Set(du, 0x80000000u));
|
||||
const V original_sign = And(x, kSign);
|
||||
x = AndNot(kSign, x); // abs
|
||||
const V below_div12 = Sqrt(Set(d, 3.0f) * x);
|
||||
const V e =
|
||||
MulAdd(Set(d, kA * 0.693147181f),
|
||||
FastLog2f(d, MulAdd(Set(d, 12), x, Set(d, -kB))), Set(d, kC));
|
||||
const V magnitude = IfThenElse(x <= Set(d, kDiv12), below_div12, e);
|
||||
const V lifted = Or(AndNot(kSign, magnitude), original_sign);
|
||||
const V kMul = Set(d, 1.0f / (1.0f - kBeta));
|
||||
const V kAdd = Set(d, -kBeta / (1.0f - kBeta));
|
||||
return MulAdd(kMul, lifted, kAdd);
|
||||
}
|
||||
|
||||
private:
|
||||
// OETF (defines the HLG approach). s = scene, returns encoded.
|
||||
JXL_INLINE double OETF(double s) const {
|
||||
|
@ -113,6 +131,30 @@ class TF_HLG {
|
|||
static constexpr double kDiv12 = 1.0 / 12;
|
||||
};
|
||||
|
||||
class TF_709 {
|
||||
public:
|
||||
JXL_INLINE double EncodedFromDisplay(const double d) const {
|
||||
if (d < kThresh) return kMulLow * d;
|
||||
return kMulHi * std::pow(d, kPowHi) + kSub;
|
||||
}
|
||||
|
||||
// Maximum error 1e-6.
|
||||
template <class D, class V>
|
||||
JXL_INLINE V EncodedFromDisplay(D d, V x) const {
|
||||
auto low = Set(d, kMulLow) * x;
|
||||
auto hi =
|
||||
MulAdd(Set(d, kMulHi), FastPowf(d, x, Set(d, kPowHi)), Set(d, kSub));
|
||||
return IfThenElse(x <= Set(d, kThresh), low, hi);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double kThresh = 0.018;
|
||||
static constexpr double kMulLow = 4.5;
|
||||
static constexpr double kMulHi = 1.099;
|
||||
static constexpr double kPowHi = 0.45;
|
||||
static constexpr double kSub = -0.099;
|
||||
};
|
||||
|
||||
// Perceptual Quantization
|
||||
class TF_PQ {
|
||||
public:
|
||||
|
|
Загрузка…
Ссылка в новой задаче