| // 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. |
| // ----------------------------------------------------------------------------- |
| // |
| // Intratile directional filter. |
| // |
| // Author: Yannis Guyon (yguyon@google.com) |
| |
| #include "src/dec/filters/directional_filter.h" |
| |
| #include "src/common/vdebug.h" |
| #include "src/dec/wp2_dec_i.h" |
| #include "src/dsp/dsp.h" |
| #include "src/dsp/math.h" |
| #include "src/utils/utils.h" |
| #include "src/wp2/format_constants.h" |
| |
| namespace WP2 { |
| |
| //------------------------------------------------------------------------------ |
| |
| constexpr uint32_t DirectionalFilter::kMaxPriStr; |
| constexpr uint32_t DirectionalFilter::kMaxSecStr; |
| |
| static uint32_t GetStrengthFromMagnitude(uint32_t filter_magnitude, |
| uint32_t max_strength) { |
| // Full filtering at low/medium qualities, no filtering at high qualities. |
| return std::min(DivRound(filter_magnitude * max_strength * 3u, |
| kMaxYuvFilterMagnitude), |
| max_strength); |
| } |
| |
| bool IsDirectionalFilterEnabled(const DecoderConfig& config, |
| const GlobalParams& gparams) { |
| const uint32_t strength = GetStrengthFromMagnitude( |
| gparams.yuv_filter_magnitude_, |
| std::max(DirectionalFilter::kMaxPriStr, DirectionalFilter::kMaxSecStr)); |
| |
| return ((config.enable_directional_filter && strength > 0) || |
| VDMatch(config, "directional-filter")); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| DirectionalFilter::DirectionalFilter(const DecoderConfig& config, |
| const GlobalParams& gparams, |
| const FilterBlockMap& blocks) |
| : config_(config), |
| gparams_(gparams), |
| blocks_(blocks), |
| enabled_(IsDirectionalFilterEnabled(config, gparams)) { |
| assert(blocks_.bit_depth_.num_bits >= 8u); // To match AV1 algorithms. |
| } |
| |
| WP2Status DirectionalFilter::Allocate() { |
| if (!enabled_) return WP2_STATUS_OK; |
| |
| WP2_CHECK_STATUS(top_taps_.Resize(blocks_.tile_rect_.width, kDrctFltTapDist)); |
| WP2_CHECK_STATUS(bottom_backup_.Resize(top_taps_.Y.w_, top_taps_.Y.h_)); |
| |
| WP2_CHECK_STATUS(left_taps_.Resize(kDrctFltTapDist, kDrctFltSize)); |
| WP2_CHECK_STATUS(right_backup_.Resize(left_taps_.Y.w_, left_taps_.Y.h_)); |
| |
| WP2MathInit(); |
| DrctFilterInit(); |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Reduces the filter primary strength for low 'variance' values (when there is |
| // uncertainty about the detected direction), only for the luma channel. |
| static uint32_t AdjustPriStr(Channel channel, uint32_t pri_str, |
| uint32_t variance) { |
| if (channel == kYChannel) { |
| if (variance == 0u) return 0u; |
| const uint32_t variance_strength = |
| std::min((uint32_t)WP2Log2Floor(variance >> 6), 12u); |
| return (pri_str * (4u + variance_strength) + 8u) >> 4; |
| } |
| return pri_str; |
| } |
| |
| // The difference between the original value of the pixel to filter and the taps |
| // will be right-shifted by this damping. Higher values mean weaker filtering. |
| static uint32_t GetDamping(Channel channel, uint32_t precision_shift) { |
| return 3u + precision_shift - ((channel == kYChannel) ? 0u : 1u); |
| } |
| |
| // Converts available stats to an AV1 CDEF primary or secondary strength. |
| static uint32_t GetCdefStr(uint32_t filter_magnitude, uint32_t min_bpp, |
| uint32_t res_den, uint32_t max_cdef_str) { |
| assert(min_bpp <= 255 && res_den <= 255); |
| min_bpp = (min_bpp * min_bpp) / 255; |
| res_den = (res_den * res_den) / 255; |
| // More details (bpp, res) need less filtering. |
| const uint32_t strength = |
| GetStrengthFromMagnitude(filter_magnitude, max_cdef_str); |
| return ((255 - min_bpp) * (255 - res_den) * strength + 127 * 127) / |
| (255 * 255); |
| } |
| |
| uint32_t DirectionalFilter::Smooth(uint32_t num_rows) { |
| if (!enabled_) return num_rows; |
| |
| YUVPlane* const pixels = blocks_.pixels_; |
| assert(num_rows <= blocks_.tile_rect_.height); |
| assert(num_filtered_rows_ <= num_rows); |
| |
| uint32_t num_filterable_rows; |
| if (num_rows == blocks_.tile_rect_.height) { |
| // All pixels are available: filter everything left. |
| num_filterable_rows = num_rows; |
| } else { |
| // Wait for bottom taps to be available. |
| num_filterable_rows = SafeSub(num_rows, kDrctFltTapDist); |
| } |
| |
| #if !defined(WP2_REDUCE_BINARY_SIZE) |
| if (VDMatch(config_, "directional-filter")) SavePixelsForVDebug(num_rows); |
| #endif // WP2_REDUCE_BINARY_SIZE |
| |
| if (num_filterable_rows <= num_filtered_rows_) return num_filtered_rows_; |
| |
| const uint32_t block_step = blocks_.max_num_blocks_x_; |
| const uint32_t precision_shift = (blocks_.bit_depth_.num_bits - 8u); |
| |
| // Temporary padded buffer for CDEF computation. |
| // 'kDrctFltSize x kDrctFltSize' pixels + margins of 'kDrctFltTapDist' pixels. |
| static constexpr int32_t kTmpStep = |
| kDrctFltTapDist + kDrctFltSize + kDrctFltTapDist; |
| int16_t tmp_buf[kTmpStep * kTmpStep]; |
| int16_t* const tmp = tmp_buf + kDrctFltTapDist * kTmpStep + kDrctFltTapDist; |
| |
| uint32_t y = num_filtered_rows_; |
| while (y < blocks_.tile_rect_.height) { |
| const uint32_t height = |
| std::min(kDrctFltSize, blocks_.tile_rect_.height - y); |
| if (y + height > num_filterable_rows) break; |
| |
| // Backup bottom pixels for the top taps of the next row of blocks, if any. |
| const bool backup_bottom = (y + height < blocks_.tile_rect_.height); |
| if (backup_bottom) { |
| assert(height >= bottom_backup_.Y.h_); |
| YUVPlane bottom; |
| WP2_ASSERT_STATUS( |
| bottom.SetView(*pixels, {0, y + height - bottom_backup_.Y.h_, |
| bottom_backup_.Y.w_, bottom_backup_.Y.h_})); |
| bottom.A.Clear(); // Alpha is not filtered here. |
| WP2_ASSERT_STATUS( |
| bottom_backup_.Copy(bottom, /*resize_if_needed=*/false)); |
| } |
| |
| for (uint32_t x = 0; x < blocks_.tile_rect_.width; x += kDrctFltSize) { |
| const uint32_t width = |
| std::min(kDrctFltSize, blocks_.tile_rect_.width - x); |
| |
| // TODO(yguyon): Filter partial blocks (bottom- or right-most in a tile) |
| if (height != kDrctFltSize || width != kDrctFltSize) break; |
| |
| const uint32_t block_x = x / kMinBlockSizePix; |
| const uint32_t block_y = y / kMinBlockSizePix; |
| const uint32_t num_blocks_x = SizeBlocks(width); |
| const uint32_t num_blocks_y = SizeBlocks(height); |
| |
| // Backup right pixels for the left taps of the next block. |
| const bool backup_right = (x + width < blocks_.tile_rect_.width); |
| if (backup_right) { |
| assert(width >= right_backup_.Y.w_); |
| YUVPlane right; |
| WP2_ASSERT_STATUS( |
| right.SetView(*pixels, {x + width - right_backup_.Y.w_, y, |
| right_backup_.Y.w_, right_backup_.Y.h_})); |
| right.A.Clear(); // Alpha is not filtered here. |
| WP2_ASSERT_STATUS( |
| right_backup_.Copy(right, /*resize_if_needed=*/false)); |
| } |
| |
| // Get average num-bits-per-pixel and residual density. |
| uint32_t min_bpp = 0, res_den[3]{0}; |
| const FilterBlockMap::BlockFeatures* block_row = |
| &blocks_.features_[block_y * block_step + block_x]; |
| for (uint32_t offset_y = 0; offset_y < num_blocks_y; ++offset_y) { |
| for (uint32_t offset_x = 0; offset_x < num_blocks_x; ++offset_x) { |
| min_bpp += block_row[offset_x].min_bpp; |
| for (Channel c : {kYChannel, kUChannel, kVChannel}) { |
| res_den[c] += block_row[offset_x].res_den[c]; |
| } |
| } |
| block_row += block_step; |
| } |
| const uint32_t num_blocks = num_blocks_x * num_blocks_y; |
| min_bpp = (min_bpp + num_blocks / 2) / num_blocks; |
| for (Channel c : {kYChannel, kUChannel, kVChannel}) { |
| res_den[c] = (res_den[c] + num_blocks / 2) / num_blocks; |
| } |
| |
| // Get overall direction and its variance. |
| uint32_t direction, variance; |
| CdefDirection8x8(&pixels->Y.At(x, y), (int32_t)pixels->Y.Step(), |
| blocks_.bit_depth_.num_bits, &direction, &variance); |
| |
| // Margins for taps. Top-left is the origin. |
| const uint32_t n_left = x; |
| const uint32_t n_right = blocks_.tile_rect_.width - (x + width); |
| const uint32_t n_top = y; |
| const uint32_t n_bottom = blocks_.tile_rect_.height - (y + height); |
| |
| const bool margin_left = (n_left >= left_taps_.Y.w_); |
| const bool margin_top = (n_top >= top_taps_.Y.h_); |
| const uint32_t backup_x = margin_left ? (x - left_taps_.Y.w_) : x; |
| |
| for (Channel c : {kYChannel, kUChannel, kVChannel}) { |
| Plane16* const pixels_plane = &pixels->GetChannel(c); |
| const Plane16& left_plane = left_taps_.GetChannel(c); |
| const Plane16& top_plane = top_taps_.GetChannel(c); |
| |
| const int16_t* const left = |
| margin_left ? &left_plane.At(0, 0) : nullptr; |
| const int16_t* const top = |
| margin_top ? &top_plane.At(backup_x, 0) : nullptr; |
| |
| const uint32_t pri_str = GetCdefStr(gparams_.yuv_filter_magnitude_, |
| min_bpp, res_den[c], kMaxPriStr); |
| const uint32_t sec_str = GetCdefStr(gparams_.yuv_filter_magnitude_, |
| min_bpp, res_den[c], kMaxSecStr); |
| const uint32_t shifted_pri_str = pri_str << precision_shift; |
| const uint32_t shifted_sec_str = sec_str << precision_shift; |
| |
| // Copy pixels from source plane and backups to 'tmp'. |
| CdefPad(&pixels_plane->At(x, y), (int32_t)pixels_plane->Step(), |
| left, (int32_t)left_plane.Step(), |
| top, (int32_t)top_plane.Step(), |
| (int32_t)width, (int32_t)height, (int32_t)n_left, |
| (int32_t)n_right, (int32_t)n_top, (int32_t)n_bottom, |
| tmp, kTmpStep); |
| |
| CdefFiltering(tmp, kTmpStep, blocks_.bit_depth_.num_bits, width, height, |
| AdjustPriStr(c, shifted_pri_str, variance), |
| shifted_sec_str, GetDamping(c, precision_shift), |
| direction, &pixels_plane->At(x, y), |
| (int32_t)pixels_plane->Step()); |
| } |
| |
| #if !defined(WP2_REDUCE_BINARY_SIZE) |
| if (VDMatch(config_, "directional-filter")) { |
| RegisterPixelsForVDebug(x, x + width - 1u, y, y + height - 1u, |
| GetCdefStr(gparams_.yuv_filter_magnitude_, |
| min_bpp, res_den[0], kMaxPriStr), |
| GetCdefStr(gparams_.yuv_filter_magnitude_, |
| min_bpp, res_den[0], kMaxSecStr), |
| direction, variance); |
| } |
| #endif // WP2_REDUCE_BINARY_SIZE |
| |
| // One's right column is another one's left taps. |
| if (backup_right) { |
| using std::swap; |
| swap(left_taps_, right_backup_); |
| } |
| } |
| |
| if (backup_bottom) { |
| using std::swap; |
| swap(top_taps_, bottom_backup_); |
| } |
| |
| y += height; |
| } |
| num_filtered_rows_ = y; |
| |
| |
| #if !defined(WP2_REDUCE_BINARY_SIZE) |
| if (VDMatch(config_, "directional-filter")) ApplyVDebug(num_rows); |
| #endif // WP2_REDUCE_BINARY_SIZE |
| |
| return num_filtered_rows_; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| } // namespace WP2 |