// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/jxl/ac_strategy.h"
#include <jxl/memory_manager.h>
#include <algorithm>
#include <cstring>
#include <hwy/base.h> // HWY_ALIGN_MAX
#include <hwy/tests/hwy_gtest.h>
#include "lib/jxl/base/random.h"
#include "lib/jxl/coeff_order_fwd.h"
#include "lib/jxl/dec_transforms_testonly.h"
#include "lib/jxl/enc_transforms.h"
#include "lib/jxl/memory_manager_internal.h"
#include "lib/jxl/simd_util.h"
#include "lib/jxl/test_memory_manager.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"
namespace jxl {
namespace {
// Test that DCT -> IDCT is a noop.
class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT<int > {
protected :
void Run() {
JxlMemoryManager* memory_manager = test::MemoryManager();
const AcStrategyType type = static_cast <AcStrategyType>(GetParam());
const AcStrategy acs = AcStrategy::FromRawStrategy(type);
const size_t dct_scratch_size =
3 * (MaxVectorSize() / sizeof (float )) * AcStrategy::kMaxBlockDim;
size_t mem_bytes =
(4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof (float );
JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem,
AlignedMemory::Create(memory_manager, mem_bytes));
float * coeffs = mem.address<float >();
float * idct = coeffs + AcStrategy::kMaxCoeffArea;
float * input = idct + AcStrategy::kMaxCoeffArea;
float * scratch_space = input + AcStrategy::kMaxCoeffArea;
Rng rng(static_cast <uint64_t>(type) * 65537 + 13 );
for (size_t j = 0 ; j < 64 ; j++) {
size_t i = (acs.log2_covered_blocks()
? rng.UniformU(0 , 64 u << acs.log2_covered_blocks())
: j);
std::fill_n(input, AcStrategy::kMaxCoeffArea, 0 );
input[i] = 0 .2 f;
TransformFromPixels(type, input, acs.covered_blocks_x() * 8 , coeffs,
scratch_space);
ASSERT_NEAR(coeffs[0 ], 0 .2 / (64 << acs.log2_covered_blocks()), 1 e-6 )
<< " i = " << i;
TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8 ,
scratch_space);
for (size_t j = 0 ; j < 64 u << acs.log2_covered_blocks(); j++) {
ASSERT_NEAR(idct[j], j == i ? 0 .2 f : 0 , 2 e-6 )
<< "j = " << j << " i = " << i << " acs " << static_cast <int >(type);
}
}
// Test DC.
std::fill_n(idct, AcStrategy::kMaxCoeffArea, 0 );
for (size_t y = 0 ; y < acs.covered_blocks_y(); y++) {
for (size_t x = 0 ; x < acs.covered_blocks_x(); x++) {
float * dc = idct + AcStrategy::kMaxCoeffArea;
std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0 );
dc[y * acs.covered_blocks_x() * 8 + x] = 0 .2 ;
LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8 , coeffs,
scratch_space);
DCFromLowestFrequencies(type, coeffs, idct, acs.covered_blocks_x() * 8 );
std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0 );
dc[y * acs.covered_blocks_x() * 8 + x] = 0 .2 ;
for (size_t j = 0 ; j < 64 u << acs.log2_covered_blocks(); j++) {
ASSERT_NEAR(idct[j], dc[j], 1 e-6 )
<< "j = " << j << " x = " << x << " y = " << y << " acs "
<< static_cast <int >(type);
}
}
}
}
};
HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(
AcStrategyRoundtrip,
::testing::Range(0 , static_cast <int >(AcStrategy::kNumValidStrategies)));
TEST_P(AcStrategyRoundtrip, Test) { Run(); }
// Test that DC(2x2) -> DCT coefficients -> IDCT -> downsampled IDCT is a noop.
class AcStrategyRoundtripDownsample
: public ::hwy::TestWithParamTargetAndT<int > {
protected :
void Run() {
JxlMemoryManager* memory_manager = test::MemoryManager();
const AcStrategyType type = static_cast <AcStrategyType>(GetParam());
const AcStrategy acs = AcStrategy::FromRawStrategy(type);
const size_t dct_scratch_size =
3 * (MaxVectorSize() / sizeof (float )) * AcStrategy::kMaxBlockDim;
size_t mem_bytes =
(4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof (float );
JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem,
AlignedMemory::Create(memory_manager, mem_bytes));
float * coeffs = mem.address<float >();
float * idct = coeffs + AcStrategy::kMaxCoeffArea;
float * dc = idct + AcStrategy::kMaxCoeffArea;
float * scratch_space = dc + AcStrategy::kMaxCoeffArea;
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0 .0 f);
Rng rng(static_cast <uint64_t>(type) * 65537 + 13 );
for (size_t y = 0 ; y < acs.covered_blocks_y(); y++) {
for (size_t x = 0 ; x < acs.covered_blocks_x(); x++) {
if (x > 4 || y > 4 ) {
if (rng.Bernoulli(0 .9 f)) continue ;
}
std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0 );
dc[y * acs.covered_blocks_x() * 8 + x] = 0 .2 f;
LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8 , coeffs,
scratch_space);
TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8 ,
scratch_space);
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0 .0 f);
std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0 );
dc[y * acs.covered_blocks_x() * 8 + x] = 0 .2 f;
// Downsample
for (size_t dy = 0 ; dy < acs.covered_blocks_y(); dy++) {
for (size_t dx = 0 ; dx < acs.covered_blocks_x(); dx++) {
float sum = 0 ;
for (size_t iy = 0 ; iy < 8 ; iy++) {
for (size_t ix = 0 ; ix < 8 ; ix++) {
sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() +
dx * 8 + ix];
}
}
sum /= 64 .0 f;
ASSERT_NEAR(sum, dc[dy * 8 * acs.covered_blocks_x() + dx], 1 e-6 )
<< "acs " << static_cast <int >(type);
}
}
}
}
}
};
HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(
AcStrategyRoundtripDownsample,
::testing::Range(0 , static_cast <int >(AcStrategy::kNumValidStrategies)));
TEST_P(AcStrategyRoundtripDownsample, Test) { Run(); }
// Test that IDCT(block with zeros in the non-topleft corner) -> downsampled
// IDCT is the same as IDCT -> DC(2x2) of the same block.
class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT<int > {
protected :
void Run() {
JxlMemoryManager* memory_manager = test::MemoryManager();
const AcStrategyType type = static_cast <AcStrategyType>(GetParam());
const AcStrategy acs = AcStrategy::FromRawStrategy(type);
const size_t dct_scratch_size =
3 * (MaxVectorSize() / sizeof (float )) * AcStrategy::kMaxBlockDim;
size_t cx = acs.covered_blocks_y();
size_t cy = acs.covered_blocks_x();
CoefficientLayout(&cy, &cx);
size_t mem_bytes =
(4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof (float );
JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem,
AlignedMemory::Create(memory_manager, mem_bytes));
float * idct = mem.address<float >();
float * idct_acs_downsampled = idct + AcStrategy::kMaxCoeffArea;
float * coeffs = idct + AcStrategy::kMaxCoeffArea;
float * scratch_space = coeffs + AcStrategy::kMaxCoeffArea;
Rng rng(static_cast <uint64_t>(type) * 65537 + 13 );
for (size_t y = 0 ; y < cy; y++) {
for (size_t x = 0 ; x < cx; x++) {
if (x > 4 || y > 4 ) {
if (rng.Bernoulli(0 .9 f)) continue ;
}
float * coeffs = idct + AcStrategy::kMaxCoeffArea;
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0 );
coeffs[y * cx * 8 + x] = 0 .2 f;
TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8 ,
scratch_space);
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0 );
coeffs[y * cx * 8 + x] = 0 .2 f;
DCFromLowestFrequencies(type, coeffs, idct_acs_downsampled,
acs.covered_blocks_x() * 8 );
// Downsample
for (size_t dy = 0 ; dy < acs.covered_blocks_y(); dy++) {
for (size_t dx = 0 ; dx < acs.covered_blocks_x(); dx++) {
float sum = 0 ;
for (size_t iy = 0 ; iy < 8 ; iy++) {
for (size_t ix = 0 ; ix < 8 ; ix++) {
sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() +
dx * 8 + ix];
}
}
sum /= 64 ;
ASSERT_NEAR(
sum, idct_acs_downsampled[dy * 8 * acs.covered_blocks_x() + dx],
1 e-6 )
<< " acs " << static_cast <int >(type);
}
}
}
}
}
};
HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(
AcStrategyDownsample,
::testing::Range(0 , static_cast <int >(AcStrategy::kNumValidStrategies)));
TEST_P(AcStrategyDownsample, Test) { Run(); }
class AcStrategyTargetTest : public ::hwy::TestWithParamTarget {};
HWY_TARGET_INSTANTIATE_TEST_SUITE_P(AcStrategyTargetTest);
TEST_P(AcStrategyTargetTest, RoundtripAFVDCT) {
HWY_ALIGN_MAX float idct[16 ];
for (size_t i = 0 ; i < 16 ; i++) {
HWY_ALIGN_MAX float pixels[16 ] = {};
pixels[i] = 1 ;
HWY_ALIGN_MAX float coeffs[16 ] = {};
AFVDCT4x4(pixels, coeffs);
AFVIDCT4x4(coeffs, idct);
for (size_t j = 0 ; j < 16 ; j++) {
EXPECT_NEAR(idct[j], pixels[j], 1 e-6 );
}
}
}
TEST_P(AcStrategyTargetTest, BenchmarkAFV) {
JxlMemoryManager* memory_manager = test::MemoryManager();
const AcStrategyType type = AcStrategyType::AFV0;
HWY_ALIGN_MAX float pixels[64 ] = {1 };
HWY_ALIGN_MAX float coeffs[64 ] = {};
const size_t dct_scratch_size =
3 * (MaxVectorSize() / sizeof (float )) * AcStrategy::kMaxBlockDim;
size_t mem_bytes = (64 + dct_scratch_size) * sizeof (float );
JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem,
AlignedMemory::Create(memory_manager, mem_bytes));
float * scratch_space = mem.address<float >();
for (size_t i = 0 ; i < 1 << 14 ; i++) {
TransformToPixels(type, coeffs, pixels, 8 , scratch_space);
TransformFromPixels(type, pixels, 8 , coeffs, scratch_space);
}
EXPECT_NEAR(pixels[0 ], 0 .0 , 1 E-6 );
}
TEST_P(AcStrategyTargetTest, BenchmarkAFVDCT) {
HWY_ALIGN_MAX float pixels[64 ] = {1 };
HWY_ALIGN_MAX float coeffs[64 ] = {};
for (size_t i = 0 ; i < 1 << 14 ; i++) {
AFVDCT4x4(pixels, coeffs);
AFVIDCT4x4(coeffs, pixels);
}
EXPECT_NEAR(pixels[0 ], 1 .0 , 1 E-6 );
}
} // namespace
} // namespace jxl
Messung V0.5 in Prozent C=89 H=87 G=87
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland