blob: 7f2b2a81113b3f4a2dd6ad967dd5709ab4f074b3 [file] [log] [blame]
// Copyright 2025 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.
// -----------------------------------------------------------------------------
//
// Common CALIC code from "Context-Based, Adaptive, Lossless Image Coding"
// IEEE TRANSACTIONS ON COMMUNICATIONS, VOL. 45, NO. 4, APRIL 1997
// by Xiaolin Wu and Nasir Memon
// https://ieeexplore.ieee.org/document/585919
//
// Author: Vincent Rabaud (vrabaud@google.com)
#include "src/common/lossless/calic.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include "src/common/lossless/plane.h"
#include "src/dsp/math.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
namespace WP2L {
namespace calic {
WP2Status CalicState::Init(uint32_t width, uint32_t height) {
WP2_CHECK_STATUS(WP2L::PlaneCodec::Init(width, height));
WP2_CHECK_ALLOC_OK(curr_diff_col_.resize(width));
WP2_CHECK_ALLOC_OK(curr_diff_row_.resize(width));
WP2_CHECK_ALLOC_OK(prev_diff_col_.resize(width));
WP2_CHECK_ALLOC_OK(prev_diff_row_.resize(width));
WP2_CHECK_STATUS(Reset());
return WP2_STATUS_OK;
}
WP2Status CalicState::SetParameters(Quantization quantization, int16_t min_tpv,
int16_t max_tpv) {
// Initialize the quantized delta table.
quantization_ = quantization;
[[maybe_unused]] const uint8_t max_context =
GetNumContextsFromQuantization(quantization) - 1;
int size;
switch (quantization) {
case Quantization::kOriginal:
WP2_CHECK_ALLOC_OK(quantized_delta_.resize(141));
std::fill(&quantized_delta_[0], &quantized_delta_[5], 0);
std::fill(&quantized_delta_[5], &quantized_delta_[15], 1);
std::fill(&quantized_delta_[15], &quantized_delta_[25], 2);
std::fill(&quantized_delta_[25], &quantized_delta_[42], 3);
std::fill(&quantized_delta_[42], &quantized_delta_[60], 4);
std::fill(&quantized_delta_[60], &quantized_delta_[85], 5);
std::fill(&quantized_delta_[85], &quantized_delta_[140], 6);
quantized_delta_[140] = 7;
size = ((quantized_delta_.back() + 1) / 2) * (1 << 8);
break;
#ifndef ORIGINAL_CALIC
case Quantization::k18:
case Quantization::k20: {
// The scale and log function are totally empirical.
size_t i = 1;
const float scale = quantization == Quantization::k18 ? 1.8 : 2;
while (static_cast<int>(1 + std::log(i) * scale + .5) < max_context) ++i;
WP2_CHECK_ALLOC_OK(quantized_delta_.resize(i + 1));
quantized_delta_[0] = 0;
for (i = 1; i < quantized_delta_.size(); ++i) {
quantized_delta_[i] = static_cast<int>(1 + std::log(i) * scale + .5);
}
size = (max_context + 1) * (1 << 8);
break;
}
#endif
case Quantization::kNum:
default:
assert(false);
return WP2_STATUS_INVALID_PARAMETER;
}
assert(quantized_delta_.back() == max_context);
WP2_CHECK_ALLOC_OK(mean_errors_.resize(size));
WP2_CHECK_ALLOC_OK(s_errors_.resize(size));
WP2_CHECK_ALLOC_OK(n_errors_.resize(size));
min_tpv_ = min_tpv;
max_tpv_ = max_tpv;
default_value_ = (min_tpv + max_tpv + 1) / 2;
WP2_CHECK_STATUS(Reset());
return WP2_STATUS_OK;
}
uint8_t CalicState::GetNumContextsFromQuantization(Quantization quantization) {
switch (quantization) {
case Quantization::kOriginal:
return 8;
#ifndef ORIGINAL_CALIC
case Quantization::k18:
case Quantization::k20:
return 12;
#endif
case Quantization::kNum:
default:
assert(false);
return WP2_STATUS_INVALID_PARAMETER;
}
}
WP2Status CalicState::Reset() {
std::fill(mean_errors_.begin(), mean_errors_.end(), 0);
std::fill(s_errors_.begin(), s_errors_.end(), 0);
std::fill(n_errors_.begin(), n_errors_.end(), 0);
prev_pred_error_ = 0;
return WP2_STATUS_OK;
}
void CalicState::StartProcessingLine(uint32_t y, const int16_t* row) {
PlaneCodec::StartProcessingLine(y, row);
curr_diff_row_.swap(prev_diff_row_);
curr_diff_col_.swap(prev_diff_col_);
}
int16_t CalicState::PredictGAP(uint16_t dh, uint16_t dv, int16_t w, int16_t n,
int16_t ne, int16_t nw) {
int16_t pred;
if (dv - dh > 80) {
// Sharp horizontal edge.
pred = w;
} else if (dv - dh < -80) {
// Sharp vertical edge.
pred = n;
} else {
int16_t p = (w + n) / 2 + (ne - nw) / 4;
if (dv - dh > 32) {
// Horizontal edge.
p = (p + w) / 2;
} else if (dv - dh > 8) {
// Weak horizontal edge.
p = (3 * p + w) / 4;
} else if (dv - dh < -32) {
// Vertical edge.
p = (p + n) / 2;
} else if (dv - dh < -8) {
// Weak vertical edge.
p = (3 * p + n) / 4;
}
pred = p;
}
return pred;
}
void CalicState::Predict(uint32_t x, int16_t& prediction, uint8_t& ctxt,
bool& is_mean_error_negative) {
// Section 3: GAP, Gradient-Adjusted Prediction.
uint16_t dh = 0, dv = 0;
// Edges are not properly defined in the paper.
if (x > 0) {
dh = curr_diff_col_[x - 1];
dv = curr_diff_row_[x - 1];
} else {
dh = dv = 0;
}
if (y_ > 0) {
dh += prev_diff_col_[x];
dv += prev_diff_row_[x];
if (x + 1 < width_) {
dh += prev_diff_col_[x + 1];
dv += prev_diff_row_[x + 1];
}
}
// Edge conditions are not specified by the paper so we take the closest
// physical value, or the default one if none.
const int16_t w = x > 0 ? row_[x - 1] : y_ > 0 ? row_p_[0] : default_value_;
const int16_t n = y_ > 0 ? row_p_[x] : w;
const int16_t ne = x + 1 < width_ && y_ > 0 ? row_p_[x + 1] : n;
const int16_t nw = x > 0 && y_ > 0 ? row_p_[x - 1] : n;
pred_ = PredictGAP(dh, dv, w, n, ne, nw);
pred_ = std::clamp(pred_, min_tpv_, max_tpv_);
// Section 4: Coding Context Selection and Quantization.
static constexpr int kA = 1, kB = 1, kC = 2;
const size_t delta = kA * dh + kB * dv + kC * std::abs(prev_pred_error_);
const uint8_t quantized_delta = delta >= quantized_delta_.size()
? quantized_delta_.back()
: quantized_delta_[delta];
// Section 5.A: Formation and Quantization of Contexts.
const int16_t nn = y_ > 1 ? row_pp_[x] : n;
const int16_t ww = x > 1 ? row_[x - 2] : w;
uint8_t b = 0;
b |= (n < pred_) << 0;
b |= (w < pred_) << 1;
b |= (nw < pred_) << 2;
b |= (ne < pred_) << 3;
b |= (nn < pred_) << 4;
b |= (ww < pred_) << 5;
b |= ((2 * n - nn) < pred_) << 6;
b |= ((2 * w - ww) < pred_) << 7;
switch (quantization_) {
case Quantization::kOriginal:
compound_context_ = ((quantized_delta / 2) << 8) | b;
break;
#ifndef ORIGINAL_CALIC
case Quantization::k18:
case Quantization::k20:
compound_context_ = (quantized_delta << 8) | b;
break;
#endif
case Quantization::kNum:
assert(false);
return;
}
// Section 5.D: Error Feedback.
const auto mean_error = mean_errors_[compound_context_];
const int16_t rounded_abs_residual =
WP2::RightShiftRound(std::abs(mean_error), kPrecisionBits);
prediction =
pred_ + (mean_error >= 0 ? rounded_abs_residual : -rounded_abs_residual);
prediction = std::clamp(prediction, min_tpv_, max_tpv_);
#ifdef ORIGINAL_CALIC
is_mean_error_negative = mean_error < 0;
#else
// Group on whether to round away from 0 or not.
is_mean_error_negative = (mean_error >> (kPrecisionBits - 1)) & 1;
#endif
ctxt = quantized_delta;
}
void CalicState::Update(uint32_t x, int16_t tpv) {
if (x > 0) {
curr_diff_col_[x] = std::abs(tpv - row_[x - 1]);
} else {
curr_diff_col_[0] = 0;
}
if (y_ > 0) {
curr_diff_row_[x] = std::abs(row_[x] - row_p_[x]);
} else {
curr_diff_row_[x] = 0;
}
prev_pred_error_ = tpv - pred_;
// Section 5.C: Estimation of Error Magnitude Within a Context.
// Use the final prediction and not the original one (cf section 5.D).
s_errors_[compound_context_] += tpv - pred_;
++n_errors_[compound_context_];
if (n_errors_[compound_context_] >= 128) {
s_errors_[compound_context_] /= 2;
n_errors_[compound_context_] = 64;
}
mean_errors_[compound_context_] = WP2::DivRound(
static_cast<int32_t>(s_errors_[compound_context_]) << kPrecisionBits,
static_cast<int32_t>(n_errors_[compound_context_]));
}
} // namespace calic
} // namespace WP2L