blob: 6f5d6d50c75b39ce330c1bf73ef9824051240d94 [file] [log] [blame]
// 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.
// -----------------------------------------------------------------------------
//
// Common code dealing with symbols.
//
// Author: Vincent Rabaud (vrabaud@google.com)
#include "src/common/symbols.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include "src/common/color_precision.h"
#include "src/common/constants.h"
#include "src/common/lossy/block.h"
#include "src/common/lossy/block_size.h"
#include "src/common/lossy/block_size_io.h"
#include "src/common/lossy/predictor.h"
#include "src/common/lossy/rnd_mtx.h"
#include "src/common/lossy/transforms.h"
#include "src/dsp/dsp.h"
#include "src/dsp/lossless/dspl.h"
#include "src/dsp/math.h"
#include "src/utils/ans.h"
#include "src/utils/ans_enc.h"
#include "src/utils/ans_utils.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
//------------------------------------------------------------------------------
namespace WP2 {
const SymbolsInfo::ClusterInfo SymbolsInfo::kDefaultClusterInfo = {};
WP2Status SymbolsInfo::CopyFrom(const SymbolsInfo& other) {
num_symbols_ = other.num_symbols_;
std::copy(other.symbols_info_, other.symbols_info_ + other.Size(),
symbols_info_);
WP2_CHECK_ALLOC_OK(cluster_info_.resize(other.cluster_info_.size()));
std::copy(other.cluster_info_.begin(), other.cluster_info_.end(),
cluster_info_.begin());
next_cluster_info_index_ = other.next_cluster_info_index_;
return WP2_STATUS_OK;
}
void SymbolsInfo::SetUnused(uint32_t sym, uint32_t num_clusters) {
SetInfo(sym, /*min=*/kInvalidBound, /*max=*/kInvalidBound, num_clusters,
StorageMethod::kUnused);
}
void SymbolsInfo::SetInfo(uint32_t sym, int32_t min, int32_t max,
uint32_t num_clusters, StorageMethod method,
bool probably_geometric) {
assert(sym < kSymbolNumMax);
// Used as a guarantee that Init() was called.
assert(method == StorageMethod::kUnused || num_clusters > 0);
num_symbols_ = std::max(num_symbols_, sym + 1);
SymbolInfo* const info = GetSymbolInfo(sym);
info->storage_method = method;
info->num_clusters = num_clusters;
info->probably_geometric = probably_geometric;
SetMinMax(sym, min, max);
}
bool SymbolsInfo::SymbolInfo::MinMaxIsValid(int32_t new_min,
int32_t new_max) const {
if (new_min == kInvalidBound && new_max == kInvalidBound) {
return true;
}
assert(new_min <= new_max);
const int32_t new_range = new_max - new_min + 1;
assert(new_range >= 0 && new_range < (1 << kANSMaxRangeBits));
switch (storage_method) {
case SymbolsInfo::StorageMethod::kAuto:
return ((uint32_t)new_range <= kANSMaxRange);
case SymbolsInfo::StorageMethod::kAdaptiveBit:
return (new_range == 2);
case SymbolsInfo::StorageMethod::kAdaptiveSym:
case SymbolsInfo::StorageMethod::kAdaptiveWithAutoSpeed:
return (new_range <= (int32_t)APROBA_MAX_SYMBOL);
case SymbolsInfo::StorageMethod::kUnused:
return true;
}
assert(false);
return true;
}
WP2Status SymbolsInfo::SetMinMax(uint32_t sym, uint32_t cluster, int32_t min,
int32_t max) {
assert(sym < kSymbolNumMax);
assert(cluster < GetSymbolInfo(sym)->num_clusters);
assert(GetSymbolInfo(sym)->MinMaxIsValid(min, max));
ClusterInfo* const info = GetOrMakeClusterInfo(sym, cluster);
WP2_CHECK_ALLOC_OK(info != nullptr);
info->min = min;
info->max = max;
return WP2_STATUS_OK;
}
void SymbolsInfo::SetMinMax(uint32_t sym, int32_t min, int32_t max) {
SymbolInfo* const info = GetSymbolInfo(sym);
// Check Init() was called.
assert(IsUnused(sym) || info->num_clusters > 0);
assert(info->MinMaxIsValid(min, max));
info->min = min;
info->max = max;
for (uint32_t c = 0; c < info->num_clusters; ++c) {
// Should not set a global range after setting per-cluster range.
assert(GetClusterInfo(sym, c).min == kInvalidBound &&
GetClusterInfo(sym, c).max == kInvalidBound);
}
}
uint32_t SymbolsInfo::MaxRangeSum() const {
uint32_t sum = 0;
for (uint32_t sym = 0; sym < num_symbols_; ++sym) sum += GetMaxRange(sym);
return sum;
}
uint32_t SymbolsInfo::RangeSum() const {
uint32_t sum = 0;
for (uint32_t sym = 0; sym < num_symbols_; ++sym) {
for (uint32_t c = 0; c < NumClusters(sym); ++c) sum += Range(sym, c);
}
return sum;
}
uint32_t SymbolsInfo::GetMaxRange() const {
const auto iter =
std::max_element(symbols_info_, symbols_info_ + Size(),
[](const SymbolInfo& lhs, const SymbolInfo& rhs) {
return (lhs.max - lhs.min < rhs.max - rhs.min);
});
return iter->max - iter->min + 1;
}
uint32_t SymbolsInfo::GetMaxRange(uint32_t sym) const {
uint32_t max_range = 0;
for (uint32_t c = 0; c < NumClusters(sym); ++c) {
max_range = std::max(max_range, Range(sym, c));
}
return max_range;
}
WP2Status SymbolsInfo::SetInitialCDF(uint32_t sym, uint32_t cluster,
const uint16_t* const cdf,
uint16_t max_proba) {
assert(IsAdaptiveMethod(sym));
ClusterInfo* const info = GetOrMakeClusterInfo(sym, cluster);
WP2_CHECK_ALLOC_OK(info != nullptr);
info->cdf = cdf;
info->max_proba = max_proba;
return WP2_STATUS_OK;
}
void SymbolsInfo::GetInitialCDF(uint32_t sym, uint32_t cluster,
const uint16_t** const cdf,
uint16_t* const max_proba) const {
assert(IsAdaptiveMethod(sym));
*cdf = GetClusterInfo(sym, cluster).cdf;
*max_proba = GetClusterInfo(sym, cluster).max_proba;
}
void SymbolsInfo::SetClusters(uint32_t sym, uint32_t num_clusters) {
assert(sym < kSymbolNumMax);
// Check Init() was called.
assert(IsUnused(sym) || GetSymbolInfo(sym)->num_clusters > 0);
assert(num_clusters > 0);
GetSymbolInfo(sym)->num_clusters = num_clusters;
}
WP2Status SymbolsInfo::SetStartingProba(uint32_t sym, uint32_t cluster,
uint32_t p0, uint32_t p1) {
assert(p0 > 0);
assert(p1 > 0);
assert(Method(sym) == StorageMethod::kAdaptiveBit);
ClusterInfo* const info = GetOrMakeClusterInfo(sym, cluster);
WP2_CHECK_ALLOC_OK(info != nullptr);
info->p0 = p0;
info->p1 = p1;
return WP2_STATUS_OK;
}
SymbolsInfo::ClusterInfo* SymbolsInfo::GetOrMakeClusterInfo(uint32_t sym,
uint32_t cluster) {
SymbolInfo* const info = GetSymbolInfo(sym);
assert(cluster < info->num_clusters);
if (info->cluster_info_index == SymbolInfo::kNoPerClusterInfo) {
info->cluster_info_index = next_cluster_info_index_;
next_cluster_info_index_ += info->num_clusters;
if (!cluster_info_.resize(next_cluster_info_index_)) {
return nullptr;
}
}
return &cluster_info_[info->cluster_info_index + cluster];
}
const SymbolsInfo::ClusterInfo& SymbolsInfo::GetClusterInfo(
uint32_t sym, uint32_t cluster) const {
assert(cluster < NumClusters(sym));
const SymbolInfo& info = GetSymbolInfo(sym);
if (info.cluster_info_index == SymbolInfo::kNoPerClusterInfo) {
return kDefaultClusterInfo;
}
return cluster_info_[info.cluster_info_index + cluster];
}
//------------------------------------------------------------------------------
WP2Status SymbolsInfo::InitLossy(uint32_t num_segments,
PartitionSet partition_set, bool has_alpha,
bool use_aom_coeffs, bool use_splits) {
const uint32_t num_channels = (has_alpha ? 4 : 3);
SetInfo(kSymbolModeY, /*min=*/0, /*max=*/kYPredModeNum - 1,
/*num_clusters=*/1, StorageMethod::kAdaptiveSym);
SetInfo(kSymbolModeUV, /*min=*/0, /*max=*/kUVPredModeNum - 1,
/*num_clusters=*/1, StorageMethod::kAdaptiveWithAutoSpeed);
SetInfo(kSymbolSegmentId, /*min=*/0, /*max=*/num_segments - 1,
/*num_clusters=*/1, StorageMethod::kAdaptiveWithAutoSpeed,
/*probably_geometric=*/true);
SetInfo(kSymbolResNumZeros, /*min=*/0, /*max=*/kResidualCons0Max,
/*num_clusters=*/kNumResidualStats * num_channels,
StorageMethod::kAuto);
SetInfo(kSymbolBits0, /*min=*/0, /*max=*/kResidual1Max,
/*num_clusters=*/kNumResidualStats * num_channels,
StorageMethod::kAuto);
SetInfo(kSymbolBits1, /*min=*/0, /*max=*/kMaxCoeffBits,
/*num_clusters=*/kNumResidualStats * num_channels,
StorageMethod::kAuto);
// 1 bit is added by the fact that we subtract the predicted value from
// the actual value, and 1 bit is added by the 2D transform.
// Finally, one bit is added for the sign.
SetInfo(kSymbolDC, /*min=*/0, /*max=*/2 * kMaxDcValue,
/*num_clusters=*/num_channels, StorageMethod::kAuto,
/*probably_geometric=*/true);
SetInfo(kSymbolRndMtx0, /*min=*/0, /*max=*/kMaxNumRndMtx - 1,
/*num_clusters=*/1, StorageMethod::kAuto);
SetInfo(kSymbolTransform, /*min=*/0, /*max=*/kNumTransformPairs - 1,
/*num_clusters=*/1, StorageMethod::kAdaptiveWithAutoSpeed);
SetInfo(kSymbolBlockAlphaMode, /*min=*/0, /*max=*/2,
/*num_clusters=*/1, StorageMethod::kAuto);
SetInfo(kSymbolSplitTransform, /*min=*/0, /*max=*/1, /*num_clusters=*/1,
StorageMethod::kAdaptiveWithAutoSpeed);
SetInfo(kSymbolYuv420, /*min=*/0, /*max=*/1, /*num_clusters=*/1,
StorageMethod::kAdaptiveWithAutoSpeed);
SetInfo(kSymbolUseRandomMatrix, /*min=*/0, /*max=*/1,
/*num_clusters=*/1, StorageMethod::kAdaptiveBit);
constexpr static uint32_t kMaxAbsValueDeltaA =
(1 << (SignalingCflPredictor::kIOBits - 1));
SetInfo(kSymbolCflSlope,
/*min=*/-kMaxAbsValueDeltaA, /*max=*/kMaxAbsValueDeltaA,
/*num_clusters=*/1, StorageMethod::kAuto,
/*probably_geometric=*/true);
SetInfo(kSymbolHasCoeffs, /*min=*/0, /*max=*/1, /*num_clusters=*/num_channels,
StorageMethod::kAdaptiveWithAutoSpeed);
if (has_alpha) {
SetInfo(kSymbolModeA, /*min=*/0, /*max=*/kAPredModeNum - 1,
/*num_clusters=*/1, StorageMethod::kAdaptiveSym);
SetInfo(kSymbolEncodingMethodA, /*min=*/0,
/*max=*/(int)EncodingMethod::kNumMethod - 1, /*num_clusters=*/1,
StorageMethod::kAdaptiveWithAutoSpeed);
} else {
SetUnused(kSymbolModeA, /*num_clusters=*/1);
SetUnused(kSymbolEncodingMethodA, /*num_clusters=*/1);
}
SetInfo(kSymbolEncodingMethod, /*min=*/0,
/*max=*/(int32_t)EncodingMethod::kNumMethod - 1,
/*num_clusters=*/3 * num_segments,
StorageMethod::kAdaptiveWithAutoSpeed);
if (use_splits) {
SetInfo(kSymbolBlockSize, /*min=*/0, /*max=*/kMaxSplitPatterns - 1,
/*num_clusters=*/GetNumBlockSizes(partition_set),
StorageMethod::kAdaptiveSym);
const BlockSize* const sizes = GetBlockSizes(partition_set);
uint32_t i = 0;
for (const BlockSize* size = sizes; *size != BLK_LAST; ++size, ++i) {
WP2_CHECK_STATUS(SetMinMax(
kSymbolBlockSize, /*cluster=*/i,
/*min=*/0, /*max=*/GetNumSplitPatterns(partition_set, *size) - 1));
}
} else {
const uint32_t max_num_sizes = GetNumBlockSizes(partition_set, BLK_32x32);
SetInfo(kSymbolBlockSize, /*min=*/0, /*max=*/max_num_sizes - 1,
/*num_clusters=*/GetNumUniqueBounds(partition_set),
(max_num_sizes > APROBA_MAX_SYMBOL) ? StorageMethod::kAuto
: StorageMethod::kAdaptiveSym);
for (uint32_t i = 0; i < GetNumUniqueBounds(partition_set); ++i) {
const BlockSize bounds = GetUniqueBounds(partition_set, i);
const uint32_t num_sizes = GetNumBlockSizes(partition_set, bounds);
assert(num_sizes <= max_num_sizes);
WP2_CHECK_STATUS(SetMinMax(kSymbolBlockSize, /*cluster=*/i,
/*min=*/0, /*max=*/num_sizes - 1));
}
}
SetInfo(kSymbolResidualUseBounds, /*min=*/0, /*max=*/1,
/*num_clusters=*/kResidualClusters, StorageMethod::kAdaptiveBit);
SetInfo(kSymbolResidualBound1IsX, /*min=*/0, /*max=*/1,
/*num_clusters=*/kResidualClusters, StorageMethod::kAdaptiveBit);
SetInfo(kSymbolResidualUseBound2, /*min=*/0, /*max=*/1,
/*num_clusters=*/kResidualClusters, StorageMethod::kAdaptiveBit);
// Non-AOM coeffs are always used for UV so they need to be initialized.
for (uint32_t sym : kSymbolsForCoeffs) {
// Y/UV, per residual method, per block size, per sector.
const uint32_t num_clusters = kResidualClusters * ResidualIO::kNumSectors;
SetInfo(sym, /*min=*/0, /*max=*/1, num_clusters,
StorageMethod::kAdaptiveBit);
}
// Initialize coefficient probabilities.
WP2_CHECK_STATUS(ResidualIO::InitializeInfo(has_alpha, use_aom_coeffs, this));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
void WritePrefixCode(int32_t value, int32_t min, int32_t max,
uint32_t prefix_size, ANSEncBase* const enc,
WP2_OPT_LABEL) {
ANSDebugPrefix ans_prefix(enc, label);
const PrefixCode prefix_code(value - min, prefix_size);
uint32_t range = max - min + 1;
const PrefixCode max_prefix_code(range - 1, prefix_size);
enc->PutRValue(prefix_code.prefix, max_prefix_code.prefix + 1, "prefix_code");
if (prefix_code.extra_bits_num > 0) {
// If the last bit interval is truncated, go with ranges.
if (prefix_code.prefix == max_prefix_code.prefix) {
assert(PrefixCode::Merge(prefix_code.prefix, prefix_size, 0) < range);
range -= PrefixCode::Merge(prefix_code.prefix, prefix_size, 0);
enc->PutRValue(prefix_code.extra_bits_value, range, "extra_bits_value");
} else {
enc->PutUValue(prefix_code.extra_bits_value, prefix_code.extra_bits_num,
"extra_bits_value");
}
}
}
int32_t ReadPrefixCode(int32_t min, int32_t max, uint32_t prefix_size,
ANSDec* const dec, WP2_OPT_LABEL) {
ANSDebugPrefix ans_prefix(dec, label);
uint32_t range = max - min + 1;
const PrefixCode max_prefix_code(range - 1, prefix_size);
const uint32_t prefix =
dec->ReadRValue(max_prefix_code.prefix + 1, "prefix_code");
const uint32_t extra_bits = PrefixCode::NumExtraBits(prefix, prefix_size);
uint32_t extra_bits_value;
if (extra_bits == 0) {
extra_bits_value = 0;
} else {
// Use ranges if we are at the last interval.
if (prefix == max_prefix_code.prefix) {
assert(PrefixCode::Merge(prefix, prefix_size, 0) < range);
range -= PrefixCode::Merge(prefix, prefix_size, 0);
extra_bits_value = dec->ReadRValue(range, "extra_bits_value");
} else {
extra_bits_value = dec->ReadUValue(extra_bits, "extra_bits_value");
}
}
return min + PrefixCode::Merge(prefix, prefix_size, extra_bits_value);
}
} // namespace WP2
//------------------------------------------------------------------------------
namespace WP2L {
void InitGroup4(uint32_t width, uint32_t num_colors,
WP2::SymbolsInfo* const info) {
constexpr WP2::SymbolsInfo::StorageMethod storage_method =
WP2::SymbolsInfo::StorageMethod::kAuto;
info->SetInfo(kSymbolG4Type, /*min=*/0, /*max=*/2, /*num_clusters=*/1,
storage_method);
info->SetInfo(kSymbolG4HorizontalDist, /*min=*/0, /*max=*/width,
/*num_clusters=*/2, storage_method);
info->SetInfo(kSymbolG4VerticalDist, /*min=*/0, /*max=*/2 * kGroup4Window,
/*num_clusters=*/1, storage_method);
if (num_colors > 2) {
info->SetInfo(kSymbolG4ColorChange, /*min=*/0, /*max=*/1,
/*num_clusters=*/1, storage_method);
// Minus 2 because the new color cannot be the same as the current color or
// the expected color.
info->SetInfo(kSymbolG4NewColor, /*min=*/0, /*max=*/num_colors - 3,
/*num_clusters=*/1, storage_method);
}
}
LosslessSymbolsInfo::LosslessSymbolsInfo() : sample_format_(WP2_FORMAT_NUM) {
for (uint32_t i = 0; i < kSymbolNum; ++i) SetUnused(i);
}
void LosslessSymbolsInfo::Init(uint32_t num_pixels, bool has_alpha,
WP2SampleFormat sample_format) {
num_pixels_ = num_pixels;
sample_format_ = sample_format;
constexpr StorageMethod storage_method = StorageMethod::kAuto;
constexpr uint32_t num_clusters = 1u;
SetInfo(kSymbolType, /*min=*/0, /*max=*/kSymbolTypeNum - 1, num_clusters,
storage_method);
if (has_alpha) {
SetInfo(kSymbolA, /*min=*/0, /*max=*/WP2::FormatMax(sample_format, 0),
num_clusters, storage_method);
} else {
SetUnused(kSymbolA);
}
SetInfo(kSymbolR, /*min=*/0, /*max=*/WP2::FormatMax(sample_format, 1),
num_clusters, storage_method);
SetInfo(kSymbolG, /*min=*/0, /*max=*/WP2::FormatMax(sample_format, 2),
num_clusters, storage_method);
SetInfo(kSymbolB, /*min=*/0, /*max=*/WP2::FormatMax(sample_format, 3),
num_clusters, storage_method);
SetUnused(kSymbolSubMode);
InitNonARGB(num_pixels);
}
void LosslessSymbolsInfo::InitNonARGB(uint32_t num_pixels) {
if (num_pixels == 1) {
// Disable all symbol types for 1 x 1 images.
SetInfo(kSymbolType, /*min=*/0, /*max=*/0, NumClusters(kSymbolType),
StorageMethod::kAuto);
SetUnused(kSymbolDist);
SetUnused(kSymbolLen);
} else {
constexpr StorageMethod storage_method = StorageMethod::kAuto;
constexpr uint32_t num_clusters = 1u;
// Distance is at most num_pixels - 1 but we have to add
// kCodeToPlaneCodes - 1 to convert to code.
WP2::PrefixCode prefix_code_max(num_pixels - 1 + kCodeToPlaneCodes - 1,
kLZ77PrefixCodeSize);
SetInfo(kSymbolDist, /*min=*/0, prefix_code_max.prefix, num_clusters,
storage_method);
// Length is at most num_pixels - 1 as it has to fit in the image.
// By format definition, it is also <= kLZ77LengthEscape.
const uint32_t max_length = std::min(kLZ77LengthEscape, num_pixels - 1);
// If we have a chance to escape, we might have a length of 0 as the size of
// a second chunk.
const uint32_t min_length =
(max_length < kLZ77LengthEscape) ? kLZ77LengthMin : 0u;
prefix_code_max =
WP2::PrefixCode(max_length - min_length, kLZ77PrefixCodeSize);
SetInfo(kSymbolLen, /*min=*/0, prefix_code_max.prefix, num_clusters,
storage_method);
}
SetCacheRange(0, /*has_segment_cache=*/false);
}
void LosslessSymbolsInfo::InitAsLabelImage(uint32_t num_pixels,
uint32_t num_labels) {
num_pixels_ = num_pixels;
SetNumClusters(1);
SetUnused(kSymbolA);
SetUnused(kSymbolR);
SetInfo(kSymbolG, /*min=*/0, /*max=*/num_labels - 1, /*num_clusters=*/1,
StorageMethod::kAuto);
SetUnused(kSymbolB);
InitNonARGB(num_pixels);
}
void LosslessSymbolsInfo::InitAsCrossColorImage(uint32_t num_pixels) {
num_pixels_ = num_pixels;
SetNumClusters(1);
SetUnused(kSymbolA);
SetInfo(kSymbolR, /*min=*/-(int32_t)kRedToBlueMax, /*max=*/kRedToBlueMax,
/*num_clusters=*/1, StorageMethod::kAuto);
SetInfo(kSymbolG, /*min=*/-(int32_t)kGreenToBlueMax,
/*max=*/kGreenToBlueMax, /*num_clusters=*/1, StorageMethod::kAuto);
SetInfo(kSymbolB, /*min=*/-(int32_t)kGreenToRedMax, /*max=*/kGreenToRedMax,
/*num_clusters=*/1, StorageMethod::kAuto);
InitNonARGB(num_pixels);
}
void LosslessSymbolsInfo::InitAsPredictorImage(uint32_t num_pixels) {
InitAsLabelImage(num_pixels, kNumPredictors);
}
WP2Status LosslessSymbolsInfo::InitSubMode() {
SetInfo(kSymbolSubMode, -3, 3, /*num_clusters=*/3,
LosslessSymbolsInfo::StorageMethod::kAuto);
// 45 degrees.
WP2_CHECK_STATUS(
SetMinMax(kSymbolSubMode, /*cluster=*/0, /*min=*/0, /*max=*/3));
// 180 degrees.
WP2_CHECK_STATUS(
SetMinMax(kSymbolSubMode, /*cluster=*/1, /*min=*/-3, /*max=*/0));
// Everything else.
WP2_CHECK_STATUS(
SetMinMax(kSymbolSubMode, /*cluster=*/2, /*min=*/-3, /*max=*/3));
return WP2_STATUS_OK;
}
WP2Status LosslessSymbolsInfo::CopyFrom(const LosslessSymbolsInfo& other) {
WP2_CHECK_STATUS(SymbolsInfo::CopyFrom(other));
sample_format_ = other.sample_format_;
num_pixels_ = other.num_pixels_;
return WP2_STATUS_OK;
}
void LosslessSymbolsInfo::SetNumClusters(uint32_t num_clusters) {
for (uint32_t sym = 0; sym < Size(); ++sym) SetClusters(sym, num_clusters);
}
void LosslessSymbolsInfo::SetCacheRange(uint32_t cache_range,
bool has_segment_cache) {
const uint32_t num_clusters = NumClusters(kSymbolType);
if (cache_range == 0) {
// Do not consider kSymbolColorCache or kSymbolSegmentCache if the cache is
// unused.
SetInfo(kSymbolType, /*min=*/0, /*max=*/IsUnused(kSymbolDist) ? 0 : 1,
num_clusters, StorageMethod::kAuto);
SetUnused(kSymbolColorCache);
SetUnused(kSymbolSegmentCache);
assert(!has_segment_cache);
} else {
assert(num_pixels_ > 1);
SetInfo(kSymbolColorCache, /*min=*/0, /*max=*/cache_range - 1, num_clusters,
StorageMethod::kAuto);
if (has_segment_cache) {
SetInfo(kSymbolType, /*min=*/0, /*max=*/kSymbolTypeNum - 1, num_clusters,
StorageMethod::kAuto);
SetInfo(kSymbolSegmentCache, /*min=*/0,
/*max=*/(1 << kMaxSegmentCacheBits) - 1, num_clusters,
StorageMethod::kAuto);
} else {
SetInfo(kSymbolType, /*min=*/0, /*max=*/kSymbolTypeNum - 2, num_clusters,
StorageMethod::kAuto);
SetUnused(kSymbolSegmentCache);
}
}
}
bool IsSymbolUsed(const Symbol sym, const bool is_maybe_used[kSymbolTypeNum]) {
switch (sym) {
case kSymbolType:
// We always have symbols so the type is always useful.
return true;
case kSymbolA:
case kSymbolR:
case kSymbolG:
case kSymbolB:
return is_maybe_used[kSymbolTypeLiteral];
case kSymbolColorCache:
return is_maybe_used[kSymbolTypeColorCacheIdx];
case kSymbolSegmentCache:
return is_maybe_used[kSymbolTypeSegmentCacheIdx];
case kSymbolDist:
case kSymbolLen:
return is_maybe_used[kSymbolTypeCopy];
case kSymbolSubMode:
return false;
default:
assert(false);
return true;
}
}
bool LosslessSymbolsInfo::HasAlpha(const SymbolsInfo& info) {
return !info.IsUnused(kSymbolA);
}
bool LosslessSymbolsInfo::IsLabelImage(const SymbolsInfo& info) {
const bool is_label_image = info.IsUnused(kSymbolR);
if (is_label_image) {
assert(info.IsUnused(kSymbolA) && info.IsUnused(kSymbolB));
}
return is_label_image;
}
//------------------------------------------------------------------------------
// A segment of integers, defining the min/max of a channel.
struct Segment {
Segment operator+(const Segment& other) const {
return Segment{min + other.min, max + other.max};
}
Segment operator-(const Segment& other) const {
return Segment{min - other.max, max - other.min};
}
Segment operator*(int32_t v) const {
return v >= 0 ? Segment{min * v, max * v} : Segment{max * v, min * v};
}
Segment operator/(int32_t v) const {
return v > 0 ? Segment{min / v, max / v} : Segment{max / v, min / v};
}
Segment operator|(const Segment& other) const {
return Segment{std::min(min, other.min), std::max(max, other.max)};
}
int32_t min, max;
};
} // namespace WP2L
namespace WP2 {
template <>
inline WP2L::Segment RightShiftRound<WP2L::Segment>(WP2L::Segment v,
uint32_t shift) {
return {RightShiftRound(v.min, shift), RightShiftRound(v.max, shift)};
}
} // namespace WP2
namespace WP2L {
void GetARGBRanges(const std::array<TransformHeader,
kPossibleTransformCombinationSize>& headers,
WP2SampleFormat format, int32_t minima_range[4],
int32_t maxima_range[4], uint32_t num_transforms) {
Segment segments[4];
// Define the original range according to the pixel format.
const int32_t num_bits_alpha = ::WP2::kAlphaBits;
const int32_t num_bits_non_alpha = static_cast<int32_t>(WP2Formatbpc(format));
for (uint32_t c = 0; c < 4; ++c) {
segments[c] = {0, static_cast<int32_t>(WP2::FormatMax(format, c))};
}
assert(segments[0].max == WP2::kAlphaMax); // WP2_ARGB_64 is unsupported.
bool do_clamp = true;
for (uint32_t i = 0; i < num_transforms; ++i) {
if (headers[i].type == TransformType::kNum) break;
switch (headers[i].type) {
case TransformType::kPredictor:
case TransformType::kPredictorWSub:
// The predictors are convex or clamped in the current bounds. Only the
// black predictor can give results out of those bounds.
// The ranges are updated to be the ranges of the residuals.
for (uint32_t c = 0; c < 4; ++c) {
segments[c] =
(segments[c] + segments[c]) | (segments[c] - segments[c]);
if (do_clamp) {
const uint32_t num_bits =
(c == 0) ? num_bits_alpha : num_bits_non_alpha;
const int32_t abs_value_max =
(num_bits == 8) ? kMaxAbsResidual8 : kMaxAbsResidual10;
static_assert(2 * kMaxAbsResidual8 + 1 <= ::WP2::kANSMaxRange,
"invalid kMaxAbsResidual8 value");
static_assert(2 * kMaxAbsResidual10 + 1 <= ::WP2::kANSMaxRange,
"invalid kMaxAbsResidual10 value");
segments[c] = Segment{std::max(segments[c].min, -abs_value_max),
std::min(segments[c].max, abs_value_max)};
if (segments[c].min > segments[c].max) {
// Should not happen.
assert(false);
segments[c].min = segments[c].max =
std::numeric_limits<int32_t>::max();
}
}
}
break;
case TransformType::kCrossColor: {
auto update_min_max = [&segments](uint32_t c0, uint32_t c1,
int32_t coeff) {
// The color transforms adds rounded(channel * coeff >>
// kColorTransformBits). Extend the interval with all possible deltas.
const Segment delta_pos =
WP2::RightShiftRound(segments[c1] * coeff, kColorTransformBits);
const Segment delta_neg =
WP2::RightShiftRound(segments[c1] * -coeff, kColorTransformBits);
segments[c0] =
(segments[c0] + delta_pos) | (segments[c0] - delta_pos) |
(segments[c0] + delta_neg) | (segments[c0] - delta_neg);
};
// First, red is modified by green.
update_min_max(/*c0=*/1, /*c1=*/2, kGreenToRedMax);
// Then blue by green and red.
update_min_max(/*c0=*/3, /*c1=*/2, kGreenToBlueMax);
update_min_max(/*c0=*/3, /*c1=*/1, kRedToBlueMax);
break;
}
case TransformType::kCrossColorGlobal: {
const uint32_t c1 =
static_cast<uint32_t>(headers[i].cc_global_first_channel);
const uint32_t c2 =
static_cast<uint32_t>(headers[i].cc_global_second_channel);
const Segment& segment1 = segments[c1];
Segment& segment2 = segments[c2];
if (headers[i].cc_global_third_transform !=
TransformHeader::CCTransform::kNothing) {
Segment& segment3 = segments[1 + 2 + 3 - c1 - c2];
if (headers[i].cc_global_third_transform ==
TransformHeader::CCTransform::kSubtractFirst) {
segment3 = segment3 - segment1;
} else if (headers[i].cc_global_third_transform ==
TransformHeader::CCTransform::kSubtractSecond) {
segment3 = segment3 - segment2;
} else {
segment3 = segment3 - (segment1 + segment2) / 2;
}
}
if (headers[i].cc_global_second_transform !=
TransformHeader::CCTransform::kNothing) {
segment2 = segment2 - segment1;
}
break;
}
case TransformType::kYCoCgR: {
// co: r-b.
const Segment segment_g = segments[2];
segments[2] = segments[1] - segments[3];
// tmp = (b + (co / 2)); cg = g - tmp;
const Segment segment_tmp = segments[3] + (segments[2] / 2);
segments[3] = segment_g - segment_tmp;
// y = tmp + (cg / 2);
segments[1] = segment_tmp + (segments[3] / 2);
break;
}
case TransformType::kSubtractGreen:
// green is subtracted from red and blue
segments[1] = segments[1] - segments[2];
segments[3] = segments[3] - segments[2];
break;
case TransformType::kColorIndexing:
assert(i == 0);
// With color indexing, values can go up to the number of colors so do
// not clamp anymore after that.
do_clamp = false;
// A is set to its maximum value.
segments[0] = {::WP2::kAlphaMax, ::WP2::kAlphaMax};
// R and B are set to 0.
segments[1] = segments[3] = {0, 0};
// Only the color index in [0, num_colors-1] is stored in G.
segments[2] = {
0, static_cast<int32_t>(headers[i].indexing_num_colors) - 1};
break;
default:
assert(false);
}
}
for (uint32_t c = 0; c < 4; ++c) {
minima_range[c] = segments[c].min;
maxima_range[c] = segments[c].max;
assert(minima_range[c] <= maxima_range[c]);
}
}
} // namespace WP2L