blob: 87579c70fc287049fd70cab38d74e6f60dc29bf0 [file]
// Copyright (c) the JPEG XL Project
// Copyright 2019 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
//
// https://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.
// -----------------------------------------------------------------------------
//
// Forked from https://github.com/google/pik/blob/master/pik/lossless8.cc
// at 16268ef512a65b541c7b5e485468a7ed33bc13d8
#ifndef WP2_COMMON_LOSSLESS_SCP_H_
#define WP2_COMMON_LOSSLESS_SCP_H_
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include "src/common/lossless/plane.h"
#include "src/dsp/math.h"
#include "src/utils/vector.h"
#include "src/wp2/base.h"
namespace WP2L {
namespace scp {
////////////////////////////////////////////////////////////////////////////////
// Constants for the classical Self Correcting Predictor.
// Multiple prediction modes, each has its own 4 predictors and heuristics.
// Auto is for encoding only.
enum class PredictionMode { Regular, West, North, Auto, Num = Auto };
inline constexpr int kMmulWeights0and1_R[] = {
34, 36, // when errors are small,
31, 37, // we assume they are random noise,
33, 37, // and penalize predictors 0 and 1
36, 40, //
39, 44, //
42, 46, //
43, 47, //
43, 42, //
};
// Weights for the different predictors "te" stands for the true error weights.
// The notation is R for regular, W for west, N for north, E for east, EE for
// east-east, etc.
inline constexpr int kMulWeights3teNE_R[] = {
28, 0, 24, 15, 24, 19, 24, 16, 23, 12, 23, 12, 25, 11, 32, 11,
};
inline constexpr int kMulWeights0and1_W[] = {
27, 31, // when errors are small,
33, 31, // we assume they are random noise,
40, 34, // and penalize predictors 0 and 1
43, 36, 52, 43, 59, 45, 63, 43, 65, 28,
};
inline constexpr int kMulWeights3teNE_W[] = {
31, 0, 31, 21, 29, 19, 28, 13, 26, 14, 28, 24, 32, 26, 43, 35,
};
inline constexpr int kMulWeights0and1_N[] = {
43, 23, // when errors are small,
38, 21, // we assume they are random noise,
35, 24, // and penalize predictors 0 and 1
34, 27, 35, 29, 33, 31, 28, 31, 23, 31,
};
inline constexpr int kMulWeights3teNE_N[] = {
27, 0, 23, 29, 26, 34, 29, 29, 30, 13, 35, 13, 40, 11, 51, 9,
};
inline constexpr int kWithSign = 7;
inline constexpr int kNumContexts = 8 + kWithSign + 2;
inline constexpr int kMaxError = 101;
inline constexpr int kMaxSumErrors = kMaxError * 7 + 1;
////////////////////////////////////////////////////////////////////////////////
// Constants for the JXL Self Correcting Predictor.
struct JxlHeader {
int16_t p1c = 0, p2c = 0, p3ca = 0, p3cb = 0, p3cc = 0, p3cd = 0, p3ce = 0;
std::array<uint32_t, 4> w;
};
inline constexpr std::array<JxlHeader, 4> kJxlHeaders = {{
// default lossless8 predictor
{.p1c = 8,
.p2c = 8,
.p3ca = 4,
.p3cb = 0,
.p3cc = 3,
.p3cd = 23,
.p3ce = 2,
.w = {0xd, 0xc, 0xc, 0xb}},
// west lossless8 predictor
{.p1c = 10,
.p2c = 9,
.p3ca = 7,
.p3cb = 0,
.p3cc = 0,
.p3cd = 16,
.p3ce = 9,
.w = {0xd, 0xc, 0xd, 0xc}},
// north lossless8 predictor
{.p1c = 16,
.p2c = 8,
.p3ca = 0,
.p3cb = 16,
.p3cc = 0,
.p3cd = 23,
.p3ce = 0,
.w = {0xd, 0xd, 0xc, 0xc}},
// ~ north lossless8 predictor
{.p1c = 10,
.p2c = 10,
.p3ca = 5,
.p3cb = 5,
.p3cc = 5,
.p3cd = 12,
.p3ce = 4,
.w = {0xd, 0xc, 0xc, 0xc}},
}};
////////////////////////////////////////////////////////////////////////////////
// Class that stores the state of the Self Correcting Predictor and is able to
// do predictions.
class State : public PlaneCodec {
public:
WP2Status Init(uint32_t width, uint32_t height) override;
WP2Status Reset() override;
public: // Only for testing.
// p = prediction, v = true pixel value, m = inclusive maximum true pixel
// value
static int16_t CalcDistanceFromPredictionAndTPV(int16_t p, int16_t v,
int16_t min, int16_t max);
// d =distance
static int16_t CalcTPVFromPredictionAndDistance(int16_t p, int16_t d,
int16_t min, int16_t max);
protected:
inline constexpr static size_t kNumPredictors = 4;
// Multiplier (by shift) of pixel, prediction and error values, to perform
// prediction computations in higher precision.
static inline constexpr int kPredExtraBits = 3;
static inline constexpr int kPredictionRound =
((1 << kPredExtraBits) >> 1) - 1;
// Common API. Kept here for now as that function could be the same for both
// methods.
template <bool USE_JXL>
inline void UpdateErrors(size_t x, int16_t val_no_added_bits, int q) {
const size_t xc = cur_row_ + x, xp = prev_row_ + x;
const int16_t val = WP2::LeftShift(val_no_added_bits, kPredExtraBits);
error_[xc] = pred_ - val;
q = Quantized(q);
quantized_error_[xc] = q;
for (size_t i = 0; i < kNumPredictors; i++) {
if (USE_JXL) {
const uint16_t err =
(std::abs(prediction_[i] - val) + kPredictionRound) >>
kPredExtraBits;
// For predicting in the next row.
pred_errors_[i][xc] = err;
} else {
pred_errors_[i][xc] = DiffToError(val_no_added_bits, prediction_[i]);
}
// Add the error on this pixel to the error on the NE pixel. This has the
// effect of adding the error on this pixel to the E and EE pixels.
pred_errors_[i][1 + xp] += pred_errors_[i][xc];
}
}
inline static constexpr int16_t WeightedAverage(
const std::array<int16_t, kNumPredictors>& p,
const std::array<int32_t, kNumPredictors>& w) {
int64_t weight_sum = 0, sum = 0;
for (size_t i = 0; i < kNumPredictors; i++) {
weight_sum += w[i];
sum += (int64_t)p[i] * w[i];
}
return WP2::DivRound(sum, weight_sum);
}
//////////////////////////////////////////////////////////////////////////////
inline void StartProcessingLine(uint32_t y, const int16_t* row) override {
WP2L::PlaneCodec::StartProcessingLine(y, row);
std::swap(cur_row_, prev_row_);
}
inline int Quantized(int x) const {
return static_cast<size_t>(x) >= quantized_table_.size()
? 14
: quantized_table_[x];
}
// Their range is -255...510 rather than 0...255!
// And -510..510 after subtracting truePixelValue
std::array<int16_t, kNumPredictors> prediction_;
int16_t pred_; // prediction with added bits
// Errors of the 4 predictors for current and previous scanline.
// Range 0..kMaxError
WP2::Vector_u16 pred_errors_[kNumPredictors];
WP2::Vector_s16 error_; // true errors, with added bits
WP2::Vector_u8 quantized_error_; // The range is 0...14, all are
// even due to quantizedInit()
int16_t min_tpv_, max_tpv_; // Tpv is truePixelValue, left shifted by PBits
// Beginning of the current and previous row in the temporary buffers.
size_t cur_row_, prev_row_;
private:
static inline constexpr size_t kBufferPadding = 2;
// Classical specific. Kept here for now.
uint8_t DiffToError(int16_t tpv, int16_t prediction) const;
uint8_t diff_to_error_[512 * 2];
// Heuristics table for prediction.
std::array<uint8_t, 29> quantized_table_; // const
};
////////////////////////////////////////////////////////////////////////////////
// State for the classical Self Correcting Predictor.
class ClassicalState : public State {
public:
static int GetNumContexts(int maxerr_shift);
WP2Status Init(uint32_t, uint32_t height) override;
protected:
int16_t PredictY0(size_t x, uint8_t* ctxt);
int16_t PredictX0(uint8_t* ctxt);
int16_t PredictRegular(size_t x, uint8_t* ctxt);
int16_t PredictWest(size_t x, uint8_t* ctxt);
int16_t PredictNorth(size_t x, uint8_t* ctxt);
private:
inline uint16_t ErrorToWeight(uint16_t error) const {
if (error < std::size(error_to_weight_)) return error_to_weight_[error];
return 150 * 512 / (58 + error * std::sqrt(error + 50));
}
uint16_t error_to_weight_[kMaxSumErrors];
};
////////////////////////////////////////////////////////////////////////////////
// State for the JXL Self Correcting Predictor.
class JXLState : public State {
public:
void SetHeaderIndex(size_t index);
void SetHeader(const JxlHeader& header);
static int GetNumContexts();
protected:
int16_t Predict(size_t x, uint8_t* ctxt);
private:
// Allows to approximate division by a number from 1 to 64.
// for (int i = 0; i < 64; i++) divlookup[i] = (1 << 24) / (i + 1);
static inline constexpr uint32_t kDivLookup[64] = {
16777216, 8388608, 5592405, 4194304, 3355443, 2796202, 2396745, 2097152,
1864135, 1677721, 1525201, 1398101, 1290555, 1198372, 1118481, 1048576,
986895, 932067, 883011, 838860, 798915, 762600, 729444, 699050,
671088, 645277, 621378, 599186, 578524, 559240, 541200, 524288,
508400, 493447, 479349, 466033, 453438, 441505, 430185, 419430,
409200, 399457, 390167, 381300, 372827, 364722, 356962, 349525,
342392, 335544, 328965, 322638, 316551, 310689, 305040, 299593,
294337, 289262, 284359, 279620, 275036, 270600, 266305, 262144};
inline constexpr static int32_t kCutoffs[] = {
-500, -392, -255, -191, -127, -95, -63, -47, -31, -23, -15,
-11, -7, -4, -3, -1, 0, 1, 3, 5, 7, 11,
15, 23, 31, 47, 63, 95, 127, 191, 255, 392, 500};
// Approximates 4+(maxweight<<24)/(x+1), avoiding division
inline static uint32_t ErrorWeight(uint64_t x, uint32_t maxweight) {
int shift = static_cast<int>(std::floor(std::log2(x + 1))) - 5;
if (shift < 0) shift = 0;
return 4 + ((maxweight * kDivLookup[x >> shift]) >> shift);
}
JxlHeader header_;
};
} // namespace scp
} // namespace WP2L
#endif // WP2_COMMON_LOSSLESS_SCP_H_