blob: 645cb046a457b1141827a2da03db657a60a0f642 [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.
// Decoding progress hook test.
#include <array>
#include <tuple>
#include "imageio/image_dec.h"
#include "include/helpers.h"
#include "src/common/constants.h"
#include "src/common/progress_watcher.h"
#include "src/wp2/decode.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
class EncDecProgressTest
: public testing::TestWithParam<std::tuple<const char*, bool, uint32_t>> {};
constexpr double kFailurePoints[] = {
0., kOnePercent, kProgressBeforeFrames, 0.5, 1. - kProgressEnd, 0.9999, 1.};
// Encode() progression
TEST_P(EncDecProgressTest, Encode) {
const char* const file_name = std::get<0>(GetParam());
EncoderConfig encoder_config;
encoder_config.quality = std::get<1>(GetParam()) ? 100.f : 75.f;
encoder_config.thread_level = std::get<2>(GetParam());
ArgbBuffer src;
ASSERT_WP2_OK(ReadImage(testutil::GetTestDataPath(file_name).c_str(), &src));
testutil::ProgressTester progress_tester;
encoder_config.progress_hook = &progress_tester;
// Test that it fails as expected.
std::string output;
StringWriter data(&output);
for (float fail_at : kFailurePoints) {
output.clear();
progress_tester.Reset();
progress_tester.fail_at_ = fail_at;
ASSERT_EQ(Encode(src, &data, encoder_config), WP2_STATUS_USER_ABORT);
}
// Test that it ends at 1.
progress_tester.Reset();
output.clear();
ASSERT_WP2_OK(Encode(src, &data, encoder_config));
ASSERT_EQ(progress_tester.last_progress_, 1.);
EXPECT_GT(output.size(), 0u);
}
// Decode() progression
TEST_P(EncDecProgressTest, Decode) {
const char* const file_name = std::get<0>(GetParam());
const float quality = std::get<1>(GetParam()) ? 100.f : 75.f;
const uint32_t thread_level = std::get<2>(GetParam());
ArgbBuffer src;
MemoryWriter data;
ASSERT_WP2_OK(testutil::CompressImage(file_name, &data, &src, quality));
testutil::ProgressTester progress_tester;
ArgbBuffer output;
DecoderConfig config;
config.thread_level = thread_level;
config.progress_hook = &progress_tester;
ArrayDecoder decoder(config, &output);
// Test that it starts at 0.
for (size_t data_size = 0; data_size < kHeaderMinSize; ++data_size) {
progress_tester.Reset();
ASSERT_EQ(Decode(data.mem_, data_size, &output, config),
WP2_STATUS_BITSTREAM_ERROR);
ASSERT_EQ(progress_tester.last_progress_, 0.);
}
// Test that it fails as expected.
for (double fail_at : kFailurePoints) {
progress_tester.Reset();
progress_tester.fail_at_ = fail_at;
ASSERT_EQ(Decode(data.mem_, data.size_, &output, config),
WP2_STATUS_USER_ABORT);
}
// Test that it ends at 1.
progress_tester.Reset();
ASSERT_WP2_OK(Decode(data.mem_, data.size_, &output, config));
ASSERT_EQ(progress_tester.last_progress_, 1.);
ASSERT_TRUE(testutil::Compare(src, output, file_name,
testutil::GetExpectedDistortion(quality)));
}
// Decoder progression (same as above but use the class API)
TEST_P(EncDecProgressTest, Decoder) {
const char* const file_name = std::get<0>(GetParam());
const float quality = std::get<1>(GetParam()) ? 100.f : 75.f;
const uint32_t thread_level = std::get<2>(GetParam());
ArgbBuffer src;
MemoryWriter data;
ASSERT_WP2_OK(testutil::CompressImage(file_name, &data, &src, quality));
testutil::ProgressTester progress_tester;
ArgbBuffer output;
DecoderConfig config;
config.thread_level = thread_level;
config.progress_hook = &progress_tester;
ArrayDecoder decoder(config, &output);
// Test that it starts at 0.
for (size_t data_size = 0; data_size < kHeaderMinSize; ++data_size) {
progress_tester.Reset();
decoder.SetInput(data.mem_, data_size);
ASSERT_FALSE(decoder.ReadFrame());
ASSERT_EQ(decoder.GetStatus(), WP2_STATUS_NOT_ENOUGH_DATA);
ASSERT_EQ(progress_tester.last_progress_, 0.);
}
// Test that it fails as expected.
for (double fail_at : kFailurePoints) {
progress_tester.Reset();
progress_tester.fail_at_ = fail_at;
decoder.Rewind();
decoder.SetInput(data.mem_, data.size_);
ASSERT_FALSE(decoder.ReadFrame());
ASSERT_EQ(decoder.GetStatus(), WP2_STATUS_USER_ABORT);
}
// Test that it ends at 1.
progress_tester.Reset();
decoder.Rewind();
decoder.SetInput(data.mem_, data.size_);
ASSERT_TRUE(decoder.ReadFrame());
ASSERT_WP2_OK(decoder.GetStatus());
ASSERT_FALSE(decoder.ReadFrame());
ASSERT_EQ(progress_tester.last_progress_, 1.);
ASSERT_TRUE(testutil::Compare(src, output, file_name,
testutil::GetExpectedDistortion(quality)));
}
//------------------------------------------------------------------------------
INSTANTIATE_TEST_SUITE_P(
ProgressTestInstantiationSingleTile, EncDecProgressTest,
testing::Combine(
testing::Values("source1_1x1.png",
"source1_64x48.png") /* one-tile image */,
testing::Values(true) /* lossless */,
testing::Values(0) /* no multithread; just one tile anyway */));
INSTANTIATE_TEST_SUITE_P(
ProgressTestInstantiationMultiTile, EncDecProgressTest,
testing::Combine(testing::Values("source0.pgm") /* several tiles */,
testing::Values(false) /* lossy */,
testing::Values(4) /* extra threads */));
// This one takes a while to run so it is disabled.
// Can still be run with flag --test_arg=--gunit_also_run_disabled_tests
INSTANTIATE_TEST_SUITE_P(
DISABLED_ProgressTestInstantiation, EncDecProgressTest,
testing::Combine(testing::Values("source3.jpg", "alpha_ramp.png"),
testing::Values(true, false) /* lossless, lossy */,
testing::Values(0, 1, 64) /* extra threads */));
//------------------------------------------------------------------------------
// Test that WP2_STATUS_USER_ABORT is returned first, even with invalid or
// undefined pixels.
TEST(EncProgressTest, UserAbortBeforeAnyEncoding) {
ArgbBuffer src(WP2_Argb_32); // Premultiplied input.
ASSERT_WP2_OK(src.Resize(16, 16));
// First pixel is invalid (alpha < rgb), other pixels are undefined.
for (int i : {0, 1, 2, 3}) src.GetRow8(0)[i] = 100 + 25 * i;
testutil::ProgressTester progress_tester;
progress_tester.fail_at_ = 0.;
EncoderConfig encoder_config;
encoder_config.progress_hook = &progress_tester;
MemoryWriter data;
ASSERT_EQ(Encode(src, &data, encoder_config), WP2_STATUS_USER_ABORT);
}
// Test that WP2_STATUS_USER_ABORT is returned first, even with a failing
// bitstream.
TEST(DecProgressTest, UserAbortBeforeAnyDecoding) {
std::array<uint8_t, 64> encoded_data = {(uint8_t)'u'}; // Junk
DecoderConfig config = DecoderConfig::kDefault;
testutil::ProgressTester progress_stopper;
config.progress_hook = &progress_stopper;
std::array<uint8_t, encoded_data.size()> decoded_pixels{0};
const uint32_t stride = encoded_data.size() / 2;
ArgbBuffer decoded_buffer;
ArrayDecoder decoder(encoded_data.data(), encoded_data.size(), config);
progress_stopper.fail_at_ = 0.;
ASSERT_EQ(DecodeArgb_32(encoded_data.data(), encoded_data.size(),
decoded_pixels.data(), stride, decoded_pixels.size(),
config),
WP2_STATUS_USER_ABORT);
ASSERT_EQ(
Decode(encoded_data.data(), encoded_data.size(), &decoded_buffer, config),
WP2_STATUS_USER_ABORT);
ASSERT_FALSE(decoder.ReadFrame());
ASSERT_EQ(decoder.GetStatus(), WP2_STATUS_USER_ABORT);
progress_stopper.fail_at_ = 0.001;
ASSERT_EQ(DecodeArgb_32(encoded_data.data(), encoded_data.size(),
decoded_pixels.data(), stride, decoded_pixels.size(),
config),
WP2_STATUS_BITSTREAM_ERROR);
ASSERT_EQ(
Decode(encoded_data.data(), encoded_data.size(), &decoded_buffer, config),
WP2_STATUS_BITSTREAM_ERROR);
decoder.Rewind();
ASSERT_FALSE(decoder.ReadFrame());
ASSERT_EQ(decoder.GetStatus(), WP2_STATUS_BITSTREAM_ERROR);
}
//------------------------------------------------------------------------------
// Test progress precision with a lot of pixels.
class ProgressPrecisionTest : public testing::TestWithParam<
std::tuple<uint32_t, uint32_t, bool, float>> {
};
TEST_P(ProgressPrecisionTest, Encode) {
const uint32_t width = std::get<0>(GetParam());
const uint32_t height = std::get<1>(GetParam());
const uint8_t alpha = std::get<2>(GetParam()) ? 130 : 255;
const float quality = std::get<3>(GetParam());
ArgbBuffer big_source;
ASSERT_WP2_OK(big_source.Resize(width, height));
big_source.Fill({alpha, 129, 128, 127});
MemoryWriter encoded_bitstream;
EncoderConfig encoder_config = EncoderConfig::kDefault;
testutil::ProgressTester progress_tester;
encoder_config.progress_hook = &progress_tester;
encoder_config.effort = 0;
encoder_config.quality = quality;
ASSERT_WP2_OK(Encode(big_source, &encoded_bitstream, encoder_config));
DecoderConfig decoder_config = DecoderConfig::kDefault;
decoder_config.progress_hook = &progress_tester;
progress_tester.Reset();
ArgbBuffer decoded_buffer;
ASSERT_WP2_OK(Decode(encoded_bitstream.mem_, encoded_bitstream.size_,
&decoded_buffer, decoder_config));
progress_tester.Reset();
ArrayDecoder decoder(encoded_bitstream.mem_, encoded_bitstream.size_,
decoder_config);
ASSERT_TRUE(decoder.ReadFrame());
ASSERT_WP2_OK(decoder.GetStatus()); // There is no trailing metadata.
ASSERT_EQ(progress_tester.last_progress_, 1.);
}
INSTANTIATE_TEST_SUITE_P(ProgressPrecisionTestInstantiation,
ProgressPrecisionTest,
testing::Combine(testing::Values(1, 64), // width
testing::Values(1, 4096), // height
testing::Values(true), // has_alpha
testing::Values(25.f,
96.f) // quality
));
//------------------------------------------------------------------------------
TEST(ProgressionTest, GetFrameProgression) {
for (uint32_t num_frames : {1u, 2u, 5u, kMaxNumFrames}) {
double progress = kProgressBeforeFrames;
for (uint32_t i = 0; i < num_frames; ++i) {
const bool is_last = (i + 1 == num_frames);
progress += GetFrameProgression(i, is_last);
}
progress += kProgressEnd;
EXPECT_NEAR(progress, 1., 0.00001);
}
ASSERT_NEAR(kProgressBeforeFrames + kProgressFrames, 1. - kProgressEnd,
0.00001);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2