blob: dd02fe2c7b90f7f3165fe1bd572867d7765c833a [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit tests for predictors.
#include "examples/example_utils.h"
#include "imageio/image_enc.h"
#include "include/helpers.h"
#include "src/common/lossy/block.h"
#include "src/common/lossy/predictor.h"
#include "src/enc/block_enc.h"
#include "src/dsp/math.h"
#include "src/utils/random.h"
namespace WP2 {
namespace {
using testing::ElementsAre;
using testing::Ge;
class ContextCacheForTest : public ContextCache {
public:
ContextCacheForTest() { check_cache_ = false; }
void SetContext(Channel channel, const std::vector<int16_t> context) {
for (uint32_t i = 0; i < kNumContextType; ++i) {
std::copy(&context[0], &context[kContextSize], large_[channel][i]);
is_computed_large_[channel][i] = true;
}
}
};
WP2Status init(int16_t min, int16_t max, YPredictors* const predictors) {
return predictors->Fill(min, max);
}
template <class V>
void TestConstantContext(const std::vector<BlockSize>& block_sizes,
bool split_tf) {
ContextCache context_cache;
for (BlockSize size : block_sizes) {
CodedBlock cb;
cb.SetDimDefault({/*x=*/1, /*y=*/1, size});
cb.GetCodingParams(kYChannel)->split_tf = split_tf;
const BlockSize dim = GetSplitSize(size, split_tf);
const uint32_t w = BlockWidthPix(dim);
const uint32_t h = BlockHeightPix(dim);
const int16_t kValue = 42;
YUVPlane plane;
ASSERT_WP2_OK(
plane.Resize(cb.x_pix() + cb.w_pix(), cb.y_pix() + cb.h_pix()));
plane.Y.Fill(kValue);
cb.SetContextInput(plane, &context_cache);
V predictors;
ASSERT_WP2_OK(init(-512, 512, &predictors));
for (const Predictor* p : predictors) {
const uint32_t num_tfs = cb.GetNumTransforms(kYChannel);
if (split_tf) {
ASSERT_GT(num_tfs, 1u);
}
for (uint32_t tf_i = 0; tf_i < num_tfs; ++tf_i) {
int16_t prediction[kMaxBlockSizePix2];
p->Predict(cb, kYChannel, split_tf, tf_i, prediction, w);
if (p->GetPredStr(cb, kYChannel, split_tf, tf_i) == "zero predictor") {
for (uint32_t i = 0; i < w * h; ++i) {
ASSERT_EQ(prediction[i], 0);
}
} else {
for (uint32_t i = 0; i < w * h; ++i) {
ASSERT_NEAR(kValue, prediction[i], 1);
}
}
}
}
}
}
// Test that given a context where all pixels have the same value, all
// regular predictors predict that value for all pixels (except for rounding
// errors). The only predictors that don't have this property are the zero
// predictor (always predicts zero) and CflPredictor. They are not tested here.
TEST(AllPredictors, ConstantContext) {
WP2MathInit();
WP2::PredictionInit();
TestConstantContext<YPredictors>({BLK_8x8, BLK_16x4, BLK_16x32},
/*split_tf=*/false);
}
TEST(AllPredictors, ConstantContextSplitTf) {
WP2MathInit();
WP2::PredictionInit();
TestConstantContext<YPredictors>({BLK_8x8, BLK_16x4, BLK_16x32},
/*split_tf=*/true);
}
template <class V>
void TestPredictionRange(const std::vector<BlockSize>& block_sizes,
bool split_tf) {
UniformIntDistribution gen(53251);
// Since this is a random test, we run it multiple times.
ContextCache context_cache;
constexpr uint32_t kNumIter = 5;
for (uint32_t iter = 0; iter < kNumIter; ++iter) {
for (BlockSize size : block_sizes) {
CodedBlock cb;
cb.SetDimDefault({/*x=*/1, /*y=*/1, size});
cb.GetCodingParams(kYChannel)->split_tf = split_tf;
const BlockSize dim = GetSplitSize(size, split_tf);
const uint32_t w = BlockWidthPix(dim);
const uint32_t h = BlockHeightPix(dim);
const int16_t max = 1 << gen.Get(2, 10);
const int16_t min = -(1 << gen.Get(2, 10));
V predictors;
ASSERT_WP2_OK(init(min, max, &predictors));
YUVPlane plane;
ASSERT_WP2_OK(
plane.Resize(cb.x_pix() + cb.w_pix(), cb.y_pix() + cb.h_pix()));
for (uint32_t j = 0; j < plane.Y.h_; ++j) {
for (uint32_t i = 0; i < plane.Y.w_; ++i) {
plane.Y.At(i, j) = gen.Get(min, max);
}
}
cb.SetContextInput(plane, &context_cache);
for (const Predictor* p : predictors) {
int16_t prediction[kMaxBlockSizePix2];
const uint32_t num_tfs = cb.GetNumTransforms(kYChannel);
if (split_tf) {
ASSERT_GT(num_tfs, 1u);
}
for (uint32_t tf_i = 0; tf_i < num_tfs; ++tf_i) {
p->Predict(cb, kYChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
w);
for (uint32_t i = 0; i < w * h; ++i) {
const int16_t v = prediction[i];
EXPECT_GE(v, min) << "Failed for predictor " << p->GetName()
<< " block size " << size;
EXPECT_LE(v, max) << "Failed for predictor " << p->GetName()
<< " block size " << size;
}
}
}
}
}
}
// Tests that predicted values are within the given min/max bounds.
TEST(AllPredictors, ValueRange) {
WP2MathInit();
WP2::PredictionInit();
TestPredictionRange<YPredictors>({BLK_8x8, BLK_16x4, BLK_16x32},
/*split_tf=*/false);
}
TEST(AllPredictors, ValueRangeSplitTf) {
WP2MathInit();
WP2::PredictionInit();
TestPredictionRange<YPredictors>({BLK_8x8, BLK_16x4, BLK_16x32},
/*split_tf=*/true);
}
// Simple test with split_tf true.
TEST(DCPredictor, SplitTf) {
WP2MathInit();
WP2::PredictionInit();
CodedBlock cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, BLK_8x8});
YUVPlane plane;
ASSERT_WP2_OK(plane.Resize(8, 8));
plane.Y.Fill({0, 0, 4, 4}, 4);
plane.Y.Fill({4, 0, 4, 4}, 8);
plane.Y.Fill({0, 4, 4, 4}, 16);
plane.Y.Fill({4, 4, 4, 4}, 32);
ContextCache context_cache;
cb.SetContextInput(plane, &context_cache);
const uint32_t step = 4;
cb.GetCodingParams(kYChannel)->split_tf = true;
YPredictors predictors;
ASSERT_WP2_OK(init(-512, 512, &predictors));
// We assume the first predictor is DC.
const Predictor* p = predictors[0];
int16_t prediction[16];
p->Predict(cb, kYChannel, /*split_tf=*/true, /*tf_i=*/0, prediction, step);
for (uint32_t i = 0; i < 16; ++i) ASSERT_EQ(prediction[i], 0);
p->Predict(cb, kYChannel, /*split_tf=*/true, /*tf_i=*/1, prediction, step);
// Predicted from sub tf 0.
for (uint32_t i = 0; i < 16; ++i) ASSERT_EQ(prediction[i], 4);
p->Predict(cb, kYChannel, /*split_tf=*/true, /*tf_i=*/2, prediction, step);
// Predicted from sub tf 0.
for (uint32_t i = 0; i < 16; ++i) ASSERT_EQ(prediction[i], 4);
p->Predict(cb, kYChannel, /*split_tf=*/true, /*tf_i=*/3, prediction, step);
// Predicted from sub tf 2 on the left (value 16), tf 0 on the top-left (value
// 4) and tf 1 on top (value 8).
for (uint32_t i = 0; i < 16; ++i) ASSERT_EQ(prediction[i], 11);
}
//------------------------------------------------------------------------------
// Cfl
TEST(ImplicitCflPredictor, SimpleLinearRelationship) {
WP2MathInit();
WP2::PredictionInit();
CflPredictor p(/*min_value=*/-512, /*max_value=*/511);
CodedBlockBase cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, /*dim=*/BLK_4x4});
ContextCacheForTest cache;
YUVPlane plane;
ASSERT_WP2_OK(plane.Resize(4, 4));
plane.Y.Fill(50);
cb.SetContextInput(plane, &cache);
// Set up context so that U = 2 * Y + 50
// Missing values should be ignored.
const int16_t missing = CodedBlock::kMissing;
cache.SetContext(kYChannel, {100, 100, 100, 100, 100, // Left + top left
200, 200, 200, 200, 200, // Top + top right
100, 100, missing, missing}); // Right
cache.SetContext(kUChannel, {250, 250, 250, 250, 250, // Left + top left
450, 450, 450, 450, 450, // Top + top right
250, 250, missing, missing}); // Right
int16_t prediction[kPredSize];
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
for (uint32_t i = 0; i < kPredSize; ++i) {
// 2 * 50 + 50 = 150
EXPECT_EQ(150, prediction[i]);
}
plane.Y.Fill(300);
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
EXPECT_EQ(511, prediction[0]); // Value is clipped.
plane.Y.Fill(4);
cb.SetContextInput(plane);
// Try with more extreme values to test accuracy of the prediction.
cache.SetContext(kYChannel, {2, 2, 2, 2, 2, // Left + top left
4, 4, 4, 4, 4, // Top + top right
2, 2, missing, missing}); // Right
cache.SetContext(kUChannel, {250, 250, 250, 250, 250, // Left + top left
450, 450, 450, 450, 450, // Top + top right
250, 250, missing, missing}); // Right
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
printf("Info:\n%s\n",
p.GetPredStr(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0).c_str());
for (uint32_t i = 0; i < kPredSize; ++i) {
// 4 * 4 + 50 = 66 ('a' is saturated)
EXPECT_EQ(66, prediction[i]);
}
}
TEST(ImplicitCflPredictor, AllMissing) {
WP2MathInit();
WP2::PredictionInit();
CflPredictor p(/*min_value=*/-512, /*max_value=*/511);
CodedBlockBase cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, /*dim=*/BLK_4x4});
ContextCacheForTest cache;
YUVPlane plane;
ASSERT_WP2_OK(plane.Resize(4, 4));
plane.Y.Fill(50);
cb.SetContextInput(plane, &cache);
// No context available.
const int16_t missing = CodedBlock::kMissing;
cache.SetContext(
kYChannel,
{missing, missing, missing, missing, missing, // Left + top left
missing, missing, missing, missing, missing, // Top + top right
missing, missing, missing, missing}); // Right
cache.SetContext(
kUChannel,
{missing, missing, missing, missing, missing, // Left + top left
missing, missing, missing, missing, missing, // Top + top right
missing, missing, missing, missing}); // Right
// Without context, the prediction should be 0.
int16_t prediction[kPredSize];
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
for (uint32_t i = 0; i < kPredSize; ++i) {
EXPECT_EQ(0, prediction[i]);
}
}
TEST(ImplicitCflPredictor, WithErrors) {
WP2MathInit();
WP2::PredictionInit();
CflPredictor p(/*min_value=*/-512, /*max_value=*/511);
CodedBlock cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, /*dim=*/BLK_4x4});
ContextCacheForTest cache;
YUVPlane plane;
ASSERT_WP2_OK(plane.Resize(4, 4));
plane.Y.Fill(150);
cb.SetContextInput(plane, &cache);
// Set up context so that U is more or less equal to 2 * Y + 50, with some
// errors.
cache.SetContext(kYChannel, {100, 100, 100, 100, 100, // Left + top left
200, 200, 200, 200, 200, // Top + top right
100, 100, 100, 100}); // Right
cache.SetContext(kUChannel,
{250 + 10, 250 - 5, 250, 250 + 5, 250, // Left + top left
450 + 11, 450 + 15, 450 - 10, 450 - 2, 450, // Top + top right
250, 250 + 5, 250 - 3, 250 + 4}); // Right
int16_t prediction[kPredSize];
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
printf("Info:\n%s\n",
p.GetPredStr(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0).c_str());
for (uint32_t i = 0; i < kPredSize; ++i) {
// 2 * 150 + 51 = 351, with some error.
EXPECT_NEAR(351, prediction[i], 2);
}
}
TEST(SignalingCflPredictor, Test) {
WP2MathInit();
WP2::PredictionInit();
SignalingCflPredictor p(/*min_value=*/-512, /*max_value=*/511);
CodedBlock cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, /*dim=*/BLK_4x4});
ContextCacheForTest cache;
YUVPlane plane;
ASSERT_WP2_OK(plane.Resize(4, 4));
plane.Y.Fill(150);
cb.SetContextInput(plane, &cache);
// Set up context so that U is more or less equal to 3 * Y + 50, with some
// errors.
cache.SetContext(kYChannel, {100, 100, 100, 100, 100, // Left + top left
200, 200, 200, 200, 200, // Top + top right
100, 100, 100, 100}); // Right
cache.SetContext(kUChannel,
{350 + 40, 350 - 25, 350, 350 + 35, 350, // Left + top left
550 + 51, 550 + 35, 550 - 30, 550 - 20, 550, // Top + top right
350, 350 + 35, 350 - 33, 350 + 4}); // Right
cb.cfl_[0] = -27;
int16_t prediction[kPredSize];
p.Predict(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
kPredWidth);
printf("Info:\n%s\n",
p.GetPredStr(cb, kUChannel, /*split_tf=*/false, /*tf_i=*/0).c_str());
printf("cfl_[0] = %d\n", cb.cfl_[0]);
for (uint32_t i = 0; i < kPredSize; ++i) {
// 1.59 * 150 + 219 ~= 457, with some error.
EXPECT_NEAR(457, prediction[i], 2);
}
}
//------------------------------------------------------------------------------
// Tests that if the context is constant, the prediction will be constant.
TEST(LargePredictors, UniformTest) {
WP2::PredictionInit();
UniformIntDistribution gen(53251);
const uint32_t step = kMaxBlockSizePix + 5;
const uint32_t kNumTests = 5;
for (BlockSize dim : kAllBlockSizes) {
const uint32_t w = BlockWidthPix(dim), h = BlockHeightPix(dim);
const uint32_t ctx_size = ContextSize(kContextSmall, w, h);
int16_t ctx[kMaxContextSize];
const int16_t max = 1 << WP2::kYuvMaxPrec;
const int16_t min = -max;
for (uint32_t n = 0; n < kNumTests; ++n) {
const int16_t r = gen.Get(min, max);
for (uint32_t i = 0; i < ctx_size; ++i) ctx[i] = r;
for (BasePredictor d :
{BPRED_DC, BPRED_DC_L, BPRED_DC_T, BPRED_SMOOTH, BPRED_SMOOTH_H,
BPRED_SMOOTH_V, BPRED_TM}) {
int16_t prediction[kMaxBlockSizePix * step];
BasePredictors[d](ctx, w, h, min, max, prediction, step);
for (uint32_t j = 0; j < h; ++j) {
for (uint32_t i = 0; i < w; ++i) {
EXPECT_EQ(prediction[i + j * step], r);
}
}
}
}
for (float strength : {0.0f, 0.1f, 0.5f, 1.0f}) {
const int16_t r = gen.Get(min, max);
for (uint32_t i = 0; i < ctx_size; ++i) ctx[i] = r;
int16_t prediction[kMaxBlockSizePix * step];
LargeWeightTable table;
PrecomputeLargeWeightTable(strength, table);
BaseFusePredictor(table, ctx, w, h, prediction, step, min, max);
for (uint32_t j = 0; j < h; ++j) {
for (uint32_t i = 0; i < w; ++i) {
ASSERT_NEAR(prediction[i + j * step], r, 1)
<< " at i=" << i << " j=" << j;
}
}
}
}
}
TEST(LargePredictors, TestSimpleAngle) {
WP2::PredictionInit();
UniformIntDistribution gen(53251);
const uint32_t step = kMaxBlockSizePix + 5;
constexpr int16_t kMagic = 0x7324;
int16_t prediction[kMaxBlockSizePix * step];
for (uint32_t ind = 0; ind < kNumDirectionalAngles; ++ind) {
for (BlockSize dim : kAllBlockSizes) {
const uint32_t w = BlockWidthPix(dim), h = BlockHeightPix(dim);
const uint32_t w_idx = TrfLog2[w];
const uint32_t h_idx = TrfLog2[h];
const ContextType context_type = GetContextType(ind);
const uint32_t ctx_size = ContextSize(context_type, w, h);
int16_t ctx[kMaxContextSize];
const int16_t max = 1 << WP2::kYuvMaxPrec;
const int16_t min = -max;
const int16_t r = gen.Get(min, max);
for (uint32_t i = 0; i < ctx_size; ++i) ctx[i] = r;
for (auto& p : prediction) p = kMagic;
SimpleAnglePredictor(ind, ctx, w_idx, h_idx, prediction, step);
for (uint32_t j = 0; j < h; ++j) {
for (uint32_t i = 0; i < w; ++i) {
EXPECT_NEAR(prediction[i + j * step], r, 1)
<< " at i=" << i << " j=" << j << " index=" << ind;
}
for (uint32_t i = w; i < step; ++i) {
EXPECT_EQ(prediction[i + j * step], kMagic);
}
}
}
}
}
WP2Status SaveOutput(BlockSize dim, uint32_t angle_idx,
const Plane16& prediction) {
#ifdef WP2_TEST_ANGLE_PREDICTOR_SAVE_OUTPUT
ArgbBuffer debug_image;
const uint32_t w = BlockWidthPix(dim), h = BlockHeightPix(dim);
WP2_CHECK_STATUS(debug_image.Resize(w, h));
WP2_CHECK_STATUS(prediction.ToGray(
&debug_image, /*bit_depth=*/{WP2::kYuvMaxPrec + 1, /*is_signed=*/true}));
WP2_CHECK_STATUS(
SaveImage(debug_image,
SPrintf("/tmp/pred-%d-%d.png", angle_idx, dim).c_str(), true));
#else
(void)dim;
(void)angle_idx;
(void)prediction;
#endif
return WP2_STATUS_OK;
}
TEST(LargePredictors, TestSimpleAnglePredictorSpeed) {
WP2::PredictionInit();
UniformIntDistribution gen(76234);
constexpr int16_t max = 1 << WP2::kYuvMaxPrec;
constexpr int16_t min = -max;
int16_t ctx[kMaxContextSize];
for (auto& v : ctx) v = gen.Get(min, max);
Plane16 prediction;
constexpr uint32_t kNumTests = 1000;
for (uint32_t ind = 0; ind < kNumDirectionalAngles; ++ind) {
uint32_t CRC = 0;
for (BlockSize dim : kAllBlockSizes) {
const uint32_t w = BlockWidthPix(dim), h = BlockHeightPix(dim);
const uint32_t w_idx = TrfLog2[w];
const uint32_t h_idx = TrfLog2[h];
ASSERT_WP2_OK(prediction.Resize(w, h));
for (uint32_t N = 0; N < kNumTests; ++N) {
SimpleAnglePredictor(ind, ctx, w_idx, h_idx, &prediction.At(0, 0),
prediction.step_);
}
for (uint32_t j = 0; j < h; ++j) {
for (uint32_t i = 0; i < w; ++i) {
CRC = testutil::SillyCRC(CRC, prediction.At(i, j));
}
}
ASSERT_WP2_OK(SaveOutput(dim, ind, prediction));
}
// It's a bit extreme to hardcode a CRC for each angle_idx, but it
// will hopefully make the debugging easier for some particular angles
// when writing SIMD code.
constexpr uint32_t kCRCs[] = {
0x545A998F, 0x78CB056D, 0x95E501B9, 0x5AB08CCC, 0x00679C3D, 0x183E57BF,
0x58A62EAA, 0xfc0766af, 0x24076af3, 0x7fa2a433, 0xf7faebf3, 0xe41835a5,
0x08683a98, 0x63676ed7, 0xba68a9ac, 0x56daf1bd, 0x40b882b4, 0x592d6725,
0x6b74c1fe, 0x2dee5147, 0x579abdb2, 0xcb1606a4, 0x407c836f, 0xb10777c1,
0x1564dfd5, 0x62c17ea6, 0x1f50d632, 0xeeb93e3c, 0x42bf14c0, 0x80f43826,
0x4b67e1d2, 0xa8d8ef27, 0xc067f6a2, 0x6b81ad22, 0xad969dc5, 0x43cfa60c,
0x28809de1, 0xc272024e, 0xef4d3d45, 0xee4f256b, 0xe8d34800, 0xfc31e532,
0x9566491e, 0x5e130153, 0x9006678f, 0x203b30c0, 0x9100f6ca, 0xd378e42f,
0x0f6dc9a5, 0x0377ddd9, 0xebbe7dfb, 0x1dde713c, 0x210c9655, 0xdec6539a,
0x28458c89, 0xda383a24, 0x8c1fb4d1, 0xa5562033, 0x14347471, 0xb8913681,
0x85263518, 0xc2e90c17, 0x4628fca5, 0x83056ce9, 0xaf7f1bd5, 0xf1585dc4,
0x5ef83869, 0xbcf4a9c6, 0xf45c6506, 0x5d7a7888};
STATIC_ASSERT_ARRAY_SIZE(kCRCs, kNumDirectionalAngles);
EXPECT_EQ(CRC, kCRCs[ind]) << "Angle idx : " << ind;
}
}
TEST(LargePredictors, AlphaPredictors) {
WP2::PredictionInit();
APredictors preds;
CSPTransform csp;
csp.InitYCoCg();
ASSERT_WP2_OK(preds.Fill(/*min_value=*/0, /*max_value=*/kAlphaMax, &csp));
CodedBlock cb;
cb.SetDimDefault({/*x=*/0, /*y=*/0, /*dim=*/BLK_4x4});
ArgbBuffer rgb;
ASSERT_WP2_OK(rgb.Resize(4, 4));
rgb.Fill(/*window=*/{0, 0, 2, 2}, Argb32b{128, 32, 100, 54});
rgb.Fill(/*window=*/{2, 0, 2, 2}, Argb32b{254, 230, 50, 0});
rgb.Fill(/*window=*/{0, 2, 2, 2}, Argb32b{255, 255, 0, 0});
rgb.Fill(/*window=*/{2, 2, 2, 2}, Argb32b{0, 0, 0, 0});
YUVPlane yuv;
ASSERT_WP2_OK(
yuv.Import(rgb, /*import_alpha=*/true, csp, /*resize_if_needed=*/true));
ContextCache context_cache;
cb.SetContextInput(yuv, &context_cache);
for (const Predictor* const p : preds) {
SCOPED_TRACE(SPrintf("predictor %s", p->GetName().c_str()));
int16_t prediction[16];
p->Predict(cb, kAChannel, /*split_tf=*/false, /*tf_i=*/0, prediction,
cb.w_pix());
EXPECT_THAT(prediction,
ElementsAre(Ge(100), Ge(100), Ge(230), Ge(230), // Row 1.
Ge(100), Ge(100), Ge(230), Ge(230), // Row 2.
Ge(255), Ge(255), Ge(0), Ge(0), // Row 3.
Ge(255), Ge(255), Ge(0), Ge(0))); // Row 4.
}
}
// not exactly a test, but it's good to have a way to easily generate the tables
TEST(LargePredictors, PrintSimpleAngleTable) {
constexpr uint32_t kAnglePredPrecision = 15;
printf("static constexpr int32_t kCoTanTable[] = { /* %dbit precision */\n ",
kAnglePredPrecision);
for (uint32_t i = 0; i < kNumDirectionalAngles; ++i) {
const double alpha = M_PI / 180. * (45.f + 22.5f * ((int)i - 3) / 7.);
printf("%d,", (i == 45) ? 0 :
(int32_t)(1. / std::tan(alpha) * (1u << kAnglePredPrecision)));
printf("%s", ((i & 7) == 7) ? "\n " : " ");
}
printf("};\n");
printf("static constexpr int32_t kTanTable[] = { /* %dbit precision */\n ",
kAnglePredPrecision);
for (uint32_t i = 0; i < kNumDirectionalAngles; ++i) {
const double alpha = M_PI / 180. * (45.f + 22.5f * ((int)i - 3) / 7.);
printf("%d,", (i == 17) ? 0 :
(int32_t)(std::tan(alpha) * (1u << kAnglePredPrecision)));
printf("%s", ((i & 7) == 7) ? "\n " : " ");
}
printf("};\n");
}
} // namespace
} // namespace WP2