blob: 50057c56cf03bc92ef9c3080aed4b4466e35bb36 [file] [log] [blame]
// Copyright 2020 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
//
// http://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.
// Test the directional filter.
#include <cmath>
#include <tuple>
#include "include/helpers.h"
#include "include/helpers_filter.h"
#include "src/dec/filters/directional_filter.h"
#include "src/dsp/math.h"
#include "src/utils/utils.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
const double kPi = std::acos(-1);
// Fills the 'plane' with a sinusoidal pattern oriented at 'angle'.
void Wave(int16_t min_value, int16_t max_value, double angle, double wavelength,
Plane16* const plane) {
angle += kPi * 0.5; // 'angle' is for pattern orientation, not gradient.
const double cosinus = std::cos(angle), sinus = std::sin(angle);
wavelength *= kPi * 2.; // For cos(x/wavelength) step.
for (uint32_t y = 0; y < plane->h_; ++y) {
for (uint32_t x = 0; x < plane->w_; ++x) {
const double oriented_x = x * cosinus + y * sinus;
const double amplitude =
std::cos(oriented_x / wavelength) * 0.5 * (max_value - min_value);
plane->At(x, y) =
(int16_t)std::lround(amplitude + (min_value + max_value) * 0.5);
assert(plane->At(x, y) >= min_value && plane->At(x, y) <= max_value);
}
}
}
YUVPlane CreateNoisyWavyPixels(uint32_t width, uint32_t height,
BitDepth bit_depth) {
const int16_t min = bit_depth.min(), max = bit_depth.max();
YUVPlane pixels;
WP2_ASSERT_STATUS(pixels.Resize(width, height));
Wave(min, max, kPi * 0.0, pixels.Y.w_, &pixels.Y); // Horizontal.
Wave(min, max, kPi * 0.5, pixels.U.h_, &pixels.U); // Vertical.
Wave(min, max, kPi * 2.1, 10., &pixels.V); // Something.
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
testutil::Noise(min, max, /*seed=*/channel, /*strength=*/5,
&pixels.GetChannel(channel));
}
return pixels;
}
// Applies the directional filter on the 'pixels'.
void Filter(BitDepth bit_depth, float quality, YUVPlane* const pixels) {
const Vector<Segment> segments = testutil::CreateSegments(quality, bit_depth);
const double residual_density = quality / kMaxLossyQuality;
const double min_bpp = 4. * quality / kMaxLossyQuality;
FilterBlockMap blocks;
testutil::CreateBlocks(/*tile_x=*/0, /*tile_y=*/0, segments, residual_density,
min_bpp, bit_depth, pixels, &blocks);
DecoderConfig config = DecoderConfig::kDefault;
config.enable_directional_filter = true;
GlobalParams gparams;
gparams.yuv_filter_magnitude_ =
std::lround((1.f - quality / kMaxLossyQuality) * kMaxYuvFilterMagnitude);
DirectionalFilter directional_filter(config, gparams, blocks);
ASSERT_WP2_OK(directional_filter.Allocate());
uint32_t num_deblocked_rows = 0;
for (uint32_t num_decoded_yuv_rows = 0; num_decoded_yuv_rows <= pixels->Y.h_;
++num_decoded_yuv_rows) {
const uint32_t n = directional_filter.Smooth(num_decoded_yuv_rows);
ASSERT_GE(n, num_deblocked_rows);
num_deblocked_rows = n;
}
ASSERT_EQ(num_deblocked_rows, pixels->Y.h_);
}
//------------------------------------------------------------------------------
class DirectionalFilterTest
: public testing::TestWithParam<std::tuple<BitDepth, uint32_t, uint32_t>> {
};
// Tests that no pixel is modified if each plane has one unique color.
TEST_P(DirectionalFilterTest, FilteringPlain) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = testutil::CreatePlainPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(Filter(bit_depth, /*quality=*/0.f, &pixels));
EXPECT_TRUE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_TRUE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_TRUE(testutil::AreEqual(pixels.V, reference.V));
}
// Tests that pixels are filtered.
TEST_P(DirectionalFilterTest, MaxFilteringBlocked) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = CreateNoisyWavyPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(Filter(bit_depth, /*quality=*/0.f, &pixels));
EXPECT_FALSE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_FALSE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_FALSE(testutil::AreEqual(pixels.V, reference.V));
}
// Tests that no pixel is modified if it's the highest quality.
TEST_P(DirectionalFilterTest, NoFiltering) {
const BitDepth bit_depth = std::get<0>(GetParam());
const uint32_t width = std::get<1>(GetParam());
const uint32_t height = std::get<2>(GetParam());
YUVPlane pixels = CreateNoisyWavyPixels(width, height, bit_depth);
YUVPlane reference;
ASSERT_WP2_OK(reference.Copy(pixels, /*resize_if_needed=*/true));
ASSERT_NO_FATAL_FAILURE(Filter(bit_depth, kMaxLossyQuality, &pixels));
EXPECT_TRUE(testutil::AreEqual(pixels.Y, reference.Y));
EXPECT_TRUE(testutil::AreEqual(pixels.U, reference.U));
EXPECT_TRUE(testutil::AreEqual(pixels.V, reference.V));
}
INSTANTIATE_TEST_SUITE_P(
DirectionalFilterTestInstantiation, DirectionalFilterTest,
testing::Combine(
testing::Values(BitDepth{8, /*is_signed=*/true},
BitDepth{10, /*is_signed=*/true},
BitDepth{12, /*is_signed=*/true}),
testing::Values(kDrctFltSize, 3 * kDrctFltSize - 1u), // width
testing::Values(kDrctFltSize, 3 * kDrctFltSize - 1u) // height
));
//------------------------------------------------------------------------------
// Tests that PSNR is good even if there is only directional filtering.
TEST(DirectionalFilterTest, Simple) {
EncoderConfig encoder_config = EncoderConfig::kDefault;
encoder_config.quality = 40; // Expect compression artifacts.
DecoderConfig decoder_config = DecoderConfig::kDefault;
decoder_config.enable_deblocking_filter = false;
decoder_config.enable_directional_filter = true;
decoder_config.enable_restoration_filter = false;
ASSERT_TRUE(testutil::EncodeDecodeCompare("source4.webp", encoder_config,
decoder_config));
}
// Tests that PSNR is better with the directional filter than without.
TEST(DirectionalFilterTest, BetterPSNR) {
EncoderConfig encoder_config = EncoderConfig::kDefault;
encoder_config.quality = 50; // Expect compression artifacts.
DecoderConfig decoder_config_no_filter = DecoderConfig::kDefault;
decoder_config_no_filter.enable_directional_filter = false;
DecoderConfig decoder_config_filter = decoder_config_no_filter;
decoder_config_filter.enable_directional_filter = true;
float disto_diff[5];
ASSERT_WP2_OK(testutil::GetDistortionDiff(
"source1.png", {1, 42, 300, 66}, encoder_config, decoder_config_no_filter,
decoder_config_filter, disto_diff));
EXPECT_GT(disto_diff[4], 0.f);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2