blob: 5381a18709cd546e00a9c036e1767ba4c9606b6d [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.
// 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_ = &map;
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_ = &map;
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_ = &map;
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