| // 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. |
| |
| // Tests for histograms and segments. |
| |
| #include <cstdint> |
| |
| #include "include/helpers.h" |
| #include "src/common/constants.h" |
| #include "src/common/lossy/block.h" |
| #include "src/enc/analysis.h" |
| #include "src/enc/wp2_enc_i.h" |
| #include "src/utils/plane.h" |
| #include "src/wp2/encode.h" |
| |
| namespace WP2 { |
| namespace { |
| |
| using testing::ElementsAre; |
| |
| TEST(ClusterHistogramTest, Simple) { |
| const uint32_t histogram[10] = {1, 10, 2, 0, 1, 5, 6, 30, 3, 10}; |
| uint32_t result[10]; |
| |
| uint32_t max_clusters = 1; |
| uint32_t num_clusters; |
| uint32_t center_positions[kMaxNumSegments]; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); |
| EXPECT_EQ(max_clusters, num_clusters); |
| EXPECT_EQ(5u, center_positions[0]); |
| |
| max_clusters = 2; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 0, 1, 1, 1, 1, 1)); |
| EXPECT_EQ(max_clusters, num_clusters); |
| EXPECT_EQ(2u, center_positions[0]); |
| EXPECT_EQ(7u, center_positions[1]); |
| |
| max_clusters = 3; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 1, 1, 1, 1, 2, 2)); |
| EXPECT_EQ(max_clusters, num_clusters); |
| EXPECT_EQ(1u, center_positions[0]); |
| EXPECT_EQ(6u, center_positions[1]); |
| EXPECT_EQ(8u, center_positions[2]); |
| |
| max_clusters = 4; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 1, 1, 1, 1, 2, 2)); |
| EXPECT_EQ(max_clusters - 1, num_clusters); // can't really split in 4! |
| EXPECT_EQ(1u, center_positions[0]); |
| EXPECT_EQ(6u, center_positions[1]); |
| EXPECT_EQ(8u, center_positions[2]); |
| } |
| |
| TEST(ClusterHistogramTest, FewNonZeroValues) { |
| const uint32_t histogram[10] = {1, 0, 0, 0, 0, 0, 0, 2, 5, 6}; |
| uint32_t result[10]; |
| |
| uint32_t max_clusters = 2; |
| uint32_t num_clusters; |
| uint32_t center_positions[kMaxNumSegments]; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 0, 1, 1, 1, 1, 1)); |
| EXPECT_EQ(max_clusters, num_clusters); |
| EXPECT_EQ(0u, center_positions[0]); |
| EXPECT_EQ(8u, center_positions[1]); |
| |
| max_clusters = 3; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 0, 1, 1, 1, 1, 1)); |
| EXPECT_EQ(max_clusters - 1, num_clusters); // can't really split in 3 |
| EXPECT_EQ(0u, center_positions[0]); |
| EXPECT_EQ(8u, center_positions[1]); |
| |
| max_clusters = 4; |
| num_clusters = |
| ClusterHistogram(histogram, 10, max_clusters, result, center_positions); |
| EXPECT_THAT(result, ElementsAre(0, 0, 0, 0, 0, 1, 1, 1, 1, 1)); |
| EXPECT_EQ(max_clusters - 2, num_clusters); // one less cluster than expected |
| EXPECT_EQ(0u, center_positions[0]); |
| EXPECT_EQ(8u, center_positions[1]); |
| } |
| |
| Vector<CodedBlock> SplitInBlocks(const YUVPlane& yuv) { |
| Vector<CodedBlock> cblocks; |
| const uint32_t bw = SizeBlocks(yuv.Y.w_); |
| const uint32_t bh = SizeBlocks(yuv.Y.h_); |
| EXPECT_TRUE(cblocks.resize(bw * bh)); |
| uint32_t n = 0; |
| for (uint32_t y = 0; y < bh; ++y) { |
| for (uint32_t x = 0; x < bw; ++x) { |
| if (x + 4 <= bw && y + 4 <= bh) { |
| CodedBlock* const cb = &cblocks[n++]; |
| cb->SetDimDefault({x, y, BLK_4x4}); |
| } |
| } |
| } |
| EXPECT_TRUE(cblocks.resize(n)); |
| return cblocks; |
| } |
| |
| TEST(TestSegment, Simple) { |
| // A uniform 10x10 image with a square of a different color in the bottom |
| // right quarter. |
| YUVPlane yuv; |
| ASSERT_WP2_OK(yuv.Resize(10, 10)); |
| yuv.Fill(Ayuv38b{kAlphaMax, 0, 0, 0}); |
| yuv.Y.Fill({5, 5, 5, 5}, 100); |
| |
| EncoderConfig config; |
| config.segments = 3; |
| Vector<CodedBlock> cblocks = SplitInBlocks(yuv); |
| GlobalParams gparams; |
| FeatureMap map; |
| gparams.features_ = ↦ |
| ASSERT_WP2_OK(FindSegments(yuv, config, &gparams)); |
| EXPECT_LE(gparams.segments_.size(), 3u); |
| } |
| |
| TEST(TestSegment, OneSegment) { |
| YUVPlane yuv; |
| ASSERT_WP2_OK(yuv.Resize(10, 10)); |
| yuv.Fill(Ayuv38b{kAlphaMax, 0, 0, 0}); |
| |
| EncoderConfig config; |
| config.segments = 1; |
| GlobalParams gparams; |
| FeatureMap map; |
| gparams.features_ = ↦ |
| ASSERT_WP2_OK(FindSegments(yuv, config, &gparams)); |
| EXPECT_EQ(1u, gparams.segments_.size()); |
| } |
| |
| TEST(TestSegment, TinyImage) { |
| YUVPlane yuv; |
| ASSERT_WP2_OK(yuv.Resize(5, 2)); |
| yuv.Fill(Ayuv38b{kAlphaMax, 0, 0, 0}); |
| |
| EncoderConfig config; |
| config.segments = 2; |
| GlobalParams gparams; |
| FeatureMap map; |
| gparams.features_ = ↦ |
| ASSERT_WP2_OK(FindSegments(yuv, config, &gparams)); |
| // Number of segments was reduced to 1. |
| EXPECT_EQ(1u, gparams.segments_.size()); |
| } |
| |
| TEST(TestSegment, Quantization) { |
| Segment segment; |
| segment.SetYUVBounds(-500, 500); |
| segment.SetQuality(kQualityToQFactor(60.f), kNeutralQuantOffset / 2, |
| kNeutralQuantOffset * 2); |
| segment.FinalizeQuant(); |
| const uint32_t quantized_range1 = segment.quant_y_.GetMaxAbsResDC(TRF_16x16); |
| const uint32_t u_quantized_range1 = |
| segment.quant_u_.GetMaxAbsResDC(TRF_16x16); |
| const uint32_t v_quantized_range1 = |
| segment.quant_v_.GetMaxAbsResDC(TRF_16x16); |
| segment.SetYUVBounds(-200, 200); |
| segment.FinalizeQuant(); |
| const uint32_t quantized_range2 = segment.quant_y_.GetMaxAbsResDC(TRF_16x16); |
| segment.SetYUVBounds(-1000, 1000); |
| segment.FinalizeQuant(); |
| const uint32_t quantized_range3 = segment.quant_y_.GetMaxAbsResDC(TRF_16x16); |
| // V is more quantized than U. |
| EXPECT_LT(v_quantized_range1, u_quantized_range1); |
| // Check the range of quantized values is more or less the same (modulo |
| // rounding errors) regardless of the yuv bounds, as long as the yuv range |
| // is centered. |
| EXPECT_NEAR(quantized_range1, quantized_range2, 1); |
| EXPECT_NEAR(quantized_range2, quantized_range3, 1); |
| // Here the range of YUV values is not centered so the max quantized value. |
| // Still the range is eventually the same. |
| segment.SetYUVBounds(0, 1000); |
| segment.FinalizeQuant(); |
| const uint32_t uncentered_range = segment.quant_y_.GetMaxAbsResDC(TRF_16x16); |
| EXPECT_NEAR(uncentered_range, quantized_range3, 1); |
| |
| segment.SetQuality(kQualityToQFactor(20.f), kDefaultQuantOffset, |
| kDefaultQuantOffset); |
| segment.FinalizeQuant(); |
| const uint32_t lower_quality_factor_range = |
| segment.quant_y_.GetMaxAbsResDC(TRF_16x16); |
| // Counter intuitively, a lower "quality" setting actually means we quantize |
| // less (better actual quality), and therefore the range gets larger. |
| EXPECT_GT(lower_quality_factor_range, quantized_range3); |
| } |
| |
| TEST(TestSegment, UVQuantOffset) { |
| Segment segment; |
| segment.SetYUVBounds(-500, 500); |
| segment.SetQuality(kQualityToQFactor(50.f), |
| /*u_quant_offset=*/kNeutralQuantOffset, |
| /*v_quant_offset=*/kNeutralQuantOffset); |
| segment.FinalizeQuant(); |
| EXPECT_EQ(segment.quant_steps_[kUChannel][3], |
| segment.quant_steps_[kVChannel][3]); |
| |
| segment.SetQuality(kQualityToQFactor(50.f), |
| /*u_quant_offset=*/kMinQuantOffset, |
| /*v_quant_offset=*/kMaxQuantOffset); |
| segment.FinalizeQuant(); |
| EXPECT_LE(segment.quant_steps_[kUChannel][3], |
| segment.quant_steps_[kVChannel][3]); |
| } |
| |
| TEST(TestSegment, IsMergeableWith) { |
| Segment segment1; |
| segment1.SetYUVBounds(-512, 512); |
| segment1.SetQuality(kQualityToQFactor(42.f), kDefaultQuantOffset, |
| kDefaultQuantOffset); |
| segment1.FinalizeQuant(); |
| |
| Segment segment2; |
| segment2.SetYUVBounds(-512, 512); |
| segment2.SetQuality(kQualityToQFactor(42.f), kDefaultQuantOffset, |
| kDefaultQuantOffset); |
| segment2.FinalizeQuant(); |
| |
| // Segments are the same. |
| EXPECT_TRUE(segment1.IsMergeableWith(segment2)); |
| EXPECT_TRUE(segment2.IsMergeableWith(segment1)); |
| |
| // Set a different quality. Segments are now different. |
| segment2.SetQuality(kQualityToQFactor(43.f), kDefaultQuantOffset, |
| kDefaultQuantOffset); |
| EXPECT_FALSE(segment1.IsMergeableWith(segment2)); |
| EXPECT_FALSE(segment2.IsMergeableWith(segment1)); |
| |
| // Revert quality and change the bounds. Segments are also different. |
| segment2.SetQuality(kQualityToQFactor(42.f), kDefaultQuantOffset, |
| kDefaultQuantOffset); |
| segment2.SetYUVBounds(-1024, 1024); |
| EXPECT_FALSE(segment1.IsMergeableWith(segment2)); |
| } |
| |
| TEST(TestSegment, MinQuant) { |
| Segment segment; |
| // Largest possible input range. |
| segment.SetYUVBounds(-(1 << kYuvMaxPrec), (1 << kYuvMaxPrec) - 1); |
| // Lowest possible quantization. |
| segment.SetQuality(/*quality_factor=*/0, /*u_quant_offset=*/0, |
| /*v_quant_offset=*/0); |
| segment.FinalizeQuant(); |
| // Make sure the max dc value is STRICTLY lower than kMaxDcValue which means |
| // it wasn't capped. If this test fails, it probably means kMaxDcValue is too |
| // small. |
| EXPECT_LT(segment.GetMaxAbsDC(kYChannel), kMaxDcValue); |
| EXPECT_LT(segment.GetMaxAbsDC(kUChannel), kMaxDcValue); |
| EXPECT_LT(segment.GetMaxAbsDC(kVChannel), kMaxDcValue); |
| |
| segment.SetAlphaQuality(/*quality_factor=*/0); |
| segment.FinalizeAlphaQuant(); |
| EXPECT_LT(segment.GetMaxAbsDC(kAChannel), kMaxDcValue); |
| } |
| |
| } // namespace |
| } // namespace WP2 |