blob: 9cabbeca6fe2e6f9aa10195630a54d4672c3f3f2 [file]
// 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.
// -----------------------------------------------------------------------------
//
// Block position/size scoring functions.
//
// Author: Yannis Guyon (yguyon@google.com)
#include "src/enc/partitioning/partition_score_func_multi.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include "src/common/global_params.h"
#include "src/common/integral.h"
#include "src/common/lossy/block_size.h"
#include "src/common/progress_watcher.h"
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
#include "src/enc/partitioning/partition_score_func.h"
#include "src/utils/plane.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
struct Slope {
float from, to; // Value at 0 (min quality) and value at 1 (max quality).
};
//------------------------------------------------------------------------------
static constexpr uint32_t kMaxCertainty = 3;
static void GetMinMax(const Plane16& src, Plane16& min, Plane16& max) {
assert(min.w_ == max.w_ && min.h_ == max.h_);
const uint32_t step = src.Step();
const uint32_t num_blocks = min.w_;
const int16_t* row = (const int16_t*)src.Row(0);
for (uint32_t y = 0; y < min.h_; ++y) {
int16_t* const min_row = (int16_t*)min.Row(y);
int16_t* const max_row = (int16_t*)max.Row(y);
WP2::GetBlockMinMax(row, step, num_blocks, min_row, max_row);
row += kMinBlockSizePix * step;
}
}
constexpr float MultiScoreFunc::kMinScore;
WP2Status MultiScoreFunc::Init(const EncoderConfig& config,
const Rectangle& tile_rect, const YUVPlane& yuv,
const GlobalParams& gparams,
const ProgressRange& progress) {
DrctFilterInit();
ScoreDspInit();
WP2_CHECK_STATUS(PartitionScoreFunc::Init(config, tile_rect, yuv, gparams,
ProgressRange(progress, 0.5)));
yuv_range_ =
(float)(gparams.transf_.GetYUVMax() - gparams.transf_.GetYUVMin());
a_range_ratio_ = yuv_range_ / kAlphaMax;
// Cache the 'min_' and 'max_' luma/chroma values per kMinBlockSize square.
WP2_CHECK_STATUS(min_.Resize(SizeBlocks(src_->Y.w_), SizeBlocks(src_->Y.h_)));
WP2_CHECK_STATUS(max_.Resize(min_.Y.w_, min_.Y.h_));
for (Channel channel : {kYChannel, kUChannel, kVChannel}) {
const Plane16& src_plane = src_->GetChannel(channel);
assert(src_plane.w_ % kMinBlockSizePix == 0 &&
src_plane.h_ % kMinBlockSizePix == 0);
GetMinMax(src_plane, min_.GetChannel(channel), max_.GetChannel(channel));
}
// Per-block standard deviation.
WP2_CHECK_STATUS(
stddev_.Allocate(num_block_cols_, num_block_rows_, kMinBlockSizePix));
stddev_.AddValues(*src_);
if (with_alpha_) {
WP2_CHECK_STATUS(
a_stddev_.Allocate(num_block_cols_, num_block_rows_, kMinBlockSizePix));
a_stddev_.AddValues(src_->A);
}
// Per-block luma general direction.
// In might give better results to compute that for each NxN block instead of
// aggregating pre-computed 4x4 ones but it is probably too expensive.
WP2_CHECK_ALLOC_OK(direction_.resize(num_block_cols_ * num_block_rows_));
WP2_CHECK_ALLOC_OK(
direction_certainty_.resize(num_block_cols_ * num_block_rows_));
const uint32_t bitdepth = gparams.transf_.GetYuvDepth().num_bits;
for (uint32_t y = 0; y < num_block_rows_; ++y) {
const int16_t* x_ptr = &src_->Y.At(/*x=*/0, y * kMinBlockSizePix);
for (uint32_t i = y * num_block_cols_; i < (y + 1) * num_block_cols_;
++i, x_ptr += kMinBlockSizePix) {
uint32_t variance;
CdefDirection4x4(x_ptr, src_->Y.Step(), bitdepth, &direction_[i],
&variance);
direction_certainty_[i] = Clamp(variance >> 4, 0u, kMaxCertainty);
// TODO(yguyon): Also compute, store, use 8x8 direction for bigger blocks
}
}
WP2_CHECK_REDUCED_STATUS(DrawVDebug());
WP2_CHECK_STATUS(progress.AdvanceBy(0.5));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status MultiScoreFunc::ComputeScore(const Block& block,
const ProgressRange& progress,
float* const score) {
float value = 0.f, threshold = 1.f; // Passing if 'value <= threshold'.
switch (pass_) {
case Pass::LumaAlphaGradient:
value = GetLumaAlphaGradient(block);
threshold = GetLumaAlphaGradientThreshold(block);
break;
case Pass::NarrowStdDev:
value = GetStdDevRange(block);
threshold = GetStdDevRangeThreshold(block);
break;
case Pass::Direction:
value = GetDirection(block);
threshold = GetDirectionThreshold(block);
break;
case Pass::Any:
value = 0.f;
threshold = 1.f;
break;
default:
assert(false);
}
// Convert to "higher score is better" in [0:1], 0.5 being the threshold.
if (value <= threshold) {
*score = 1.001f - 0.5f * value / threshold; // Will pass.
} else {
*score = 0.499f * threshold / value; // Will not pass.
}
WP2_CHECK_REDUCED_STATUS(RegisterScoreForVDebug(block, *score));
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
// Returns the maximum difference between 'src' and its predicted gradient.
// 'step' goes from a 'src' line to the next.
static int32_t GetGradientDiff(const int16_t* src, int32_t step, int32_t w,
int32_t h) {
assert(w >= 4 && h >= 4);
const int32_t max_x = w - 1, max_y = h - 1;
// Average the corners (division by 3 is done at the very end).
const int32_t top_left = src[0] + src[1] + src[step];
const int32_t bottom_left =
src[(max_y - 1) * step] + src[max_y * step] + src[max_y * step + 1];
const int32_t top_right = src[max_x - 1] + src[max_x] + src[step + max_x];
const int32_t bottom_right = src[(max_y - 1) * step + max_x] +
src[max_y * step + max_x - 1] +
src[max_y * step + max_x];
// Create a gradient by bidimensional interpolation and compare with 'src'.
int32_t max_diff = 0;
for (int32_t y = 0; y <= max_y; ++y) {
const int32_t left = top_left * (max_y - y) + bottom_left * y;
const int32_t right = top_right * (max_y - y) + bottom_right * y;
for (int32_t x = 0; x <= max_x; ++x) {
const int32_t gradient_pixel =
DivRound(left * (max_x - x) + right * x, 3 * max_x * max_y);
max_diff = std::max(max_diff, std::abs(src[x] - gradient_pixel));
}
src += step;
}
return max_diff;
}
float MultiScoreFunc::GetLumaAlphaGradient(const Block& block) const {
// Empirically chosen values.
const float kDiffScale[] = {1.f, 1.f, 1.f, 0.25f / kAlphaMax * yuv_range_};
float max_diff = 0.f;
// kUChannel and kVChannel do not bring valuable partition decision-making
// here so skip them for speed.
for (Channel c : {kYChannel, kAChannel}) {
if (c == kAChannel && !with_alpha_) continue;
const int32_t diff = GetGradientDiff(
&src_->GetChannel(c).At(block.x_pix(), block.y_pix()),
src_->GetChannel(c).Step(), block.w_pix(), block.h_pix());
max_diff = std::max(max_diff, diff * kDiffScale[c]);
}
return max_diff;
}
float MultiScoreFunc::GetLumaAlphaGradientThreshold(const Block& block) const {
// The threshold is tighter for bigger blocks at higher qualities.
// Medium blocks are ignored except at high qualities.
constexpr Slope kSlope[] = {
{0.f, 0.f}, // 4x4, unused
{0.f, 0.f}, // 8x4, unused
{-0.6f, 0.1f}, // 8x8 16x4
{-0.06f, 0.02f}, // 16x8
{-0.03f, 0.015f}, // 16x16 32x8
{0.04f, 0.005f}, // 32x16
{0.05f, 0.005f}, // 32x32
};
STATIC_ASSERT_ARRAY_SIZE(kSlope, WP2Log2Ceil_k(kMaxBlockSize2) + 1);
const uint32_t index = (uint32_t)WP2Log2Floor(block.rect().GetArea());
assert((1u << index) == block.rect().GetArea());
return yuv_range_ *
MapQuality(*config_, kSlope[index].from, kSlope[index].to);
}
//------------------------------------------------------------------------------
static float StdDevRange(const Block& block, const Integral& variance) {
// Consider the standard deviation of the whole block as sub-blocks could
// be coherent within themselves but not with other sub-blocks.
const uint8_t overall_variance = variance.StdDevUint8(
block.x(), block.y(), block.x() + block.w(), block.y() + block.h());
uint8_t min = overall_variance, max = overall_variance;
// Now check the sub-blocks.
for (uint32_t sub_y = block.y(); sub_y < block.y() + block.h(); ++sub_y) {
for (uint32_t sub_x = block.x(); sub_x < block.x() + block.w(); ++sub_x) {
const uint8_t variance_tmp =
variance.StdDevUint8(sub_x, sub_y, sub_x + 1, sub_y + 1);
if (variance_tmp < min) {
min = variance_tmp;
} else if (variance_tmp > max) {
max = variance_tmp;
}
}
}
return (max - min) / 255.f;
}
float MultiScoreFunc::GetStdDevRange(const Block& block) const {
float range = StdDevRange(block, stddev_);
if (!a_stddev_.empty()) {
const float a_range =
StdDevRange(block, a_stddev_) * a_range_ratio_ * a_range_ratio_;
range = std::max(range, a_range);
}
return range;
}
float MultiScoreFunc::GetStdDevRangeThreshold(const Block& block) const {
// The higher the quality, the narrower the standard deviation range needs to
// be for a block to be accepted. Accept bigger blocks during partitioning at
// low qualities, and seek smaller blocks at high qualities.
return MapQuality(*config_, 0.50f, 0.12f);
}
//------------------------------------------------------------------------------
// Returns a low score for a 'block' that appears to have an obvious and uniform
// orientation.
float MultiScoreFunc::GetDirection(const Block& block) const {
const uint32_t stride = num_block_cols_;
const uint32_t* direction =
direction_.data() + block.y() * stride + block.x();
const uint32_t* certainty =
direction_certainty_.data() + block.y() * stride + block.x();
uint32_t weight[kDrctFltNumDirs] = {0};
for (uint32_t sub_y = 0; sub_y < block.h(); ++sub_y) {
for (uint32_t sub_x = 0; sub_x < block.w(); ++sub_x) {
assert(direction[sub_x] < kDrctFltNumDirs);
weight[direction[sub_x]] += std::min(certainty[sub_x], kMaxCertainty);
}
direction += stride;
certainty += stride;
}
const uint32_t heaviest =
std::max_element(weight, weight + kDrctFltNumDirs) - weight;
const uint32_t max_weight = block.w() * block.h() * kMaxCertainty;
assert(weight[heaviest] <= max_weight);
const uint32_t previous_direction =
(heaviest - 1 + kDrctFltNumDirs) % kDrctFltNumDirs;
const uint32_t next_direction = (heaviest + 1) % kDrctFltNumDirs;
const uint32_t weight_with_close_directions =
weight[heaviest] +
(weight[previous_direction] + weight[next_direction]) / 4;
const float direction_score =
1.f - Clamp(weight_with_close_directions / (float)max_weight, 0.f, 1.f);
return direction_score;
}
float MultiScoreFunc::GetDirectionThreshold(const Block& block) const {
return MapQuality(*config_, 0.15f, 0.1025f);
}
//------------------------------------------------------------------------------
} // namespace WP2