blob: ec3c8abed520889cc6c184d735e26f244fed0682 [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.
// -----------------------------------------------------------------------------
// Incremental animation decoding test.
#include <string>
#include <tuple>
#include <vector>
#include "imageio/image_dec.h"
#include "include/helpers.h"
#include "include/helpers_incr.h"
#include "src/wp2/decode.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
typedef std::tuple<std::vector<const char*>, std::vector<uint32_t>, float, int,
uint32_t, testutil::IncrementalDecodingTestSetup>
Param;
class AnimTestIncr : public testing::TestWithParam<Param> {};
TEST_P(AnimTestIncr, Simple) {
const std::vector<const char*>& file_names = std::get<0>(GetParam());
const std::vector<uint32_t>& durations_ms = std::get<1>(GetParam());
const float quality = std::get<2>(GetParam());
const int effort = std::get<3>(GetParam());
const uint32_t thread_level = std::get<4>(GetParam());
testutil::IncrementalDecodingTestSetup setup = std::get<5>(GetParam());
std::vector<ArgbBuffer> frames;
MemoryWriter encoded_data;
ASSERT_WP2_OK(testutil::CompressAnimation(file_names, durations_ms,
&encoded_data, &frames, quality,
effort, thread_level));
DecoderConfig config = DecoderConfig::kDefault;
config.thread_level = thread_level;
// Try incremental decoding with different steps.
const DataView input = {encoded_data.mem_, encoded_data.size_};
for (const size_t step : {(size_t)1, input.size / 20, input.size}) {
setup.incr_size_step = step;
std::vector<ArgbBuffer> decoded_frames;
std::vector<uint32_t> decoded_durations_ms;
ASSERT_WP2_OK(testutil::DecodeIncremental(
config, input, setup, &decoded_frames, &decoded_durations_ms));
// Hard to know which frames to compare if some were skipped.
if (setup.actions.empty() ||
setup.actions.back().type != testutil::DecoderAction::Type::kSkip) {
ASSERT_EQ(decoded_frames.size(), frames.size());
for (size_t i = 0; i < frames.size(); ++i) {
EXPECT_TRUE(
testutil::Compare(decoded_frames[i], frames[i], file_names[i],
testutil::GetExpectedDistortion(quality)));
EXPECT_EQ(decoded_durations_ms[i], durations_ms[i]);
}
}
}
}
constexpr bool kDoNotCompare = false;
constexpr bool kCompareWithSingleChunkDec = true;
// Input images are expected to be of equal sizes inside a Param.
INSTANTIATE_TEST_SUITE_P(
AnimTestIncrInstantiation, AnimTestIncr,
testing::Values(
Param(
{"source1_64x48.png"},
/*frame durations (ms):*/ {1000},
/*quality=*/0.0f, /*effort=*/4, /*thread_level=*/2,
{1, testutil::DecoderType::kArray, WP2_Argb_32, kDoNotCompare, {}}),
Param({"alpha_ramp.png", "alpha_ramp.webp"},
/*frame durations (ms):*/ {50, 1},
/*quality=*/100.0f, /*effort=*/0, /*thread_level=*/4,
{1,
testutil::DecoderType::kUnstableCustom,
WP2_ARGB_32,
kDoNotCompare,
{}}),
Param({"source1_64x48.png"},
/*frame durations (ms):*/ {1000},
/*quality=*/40.0f, /*effort=*/0, /*thread_level=*/0,
{1,
testutil::DecoderType::kSwappingCustom,
WP2_rgbA_32,
kDoNotCompare,
{}})));
// 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_AnimTestIncrInstantiation, AnimTestIncr,
testing::Values(
Param({"alpha_ramp.png", "alpha_ramp.lossy.webp", "alpha_ramp.webp"},
/*frame durations (ms):*/ {50, 70, 1},
/*quality=*/100.0f, /*effort=*/2, /*thread_level=*/0,
{1,
testutil::DecoderType::kUnstableCustom,
WP2_RGBA_32,
kDoNotCompare,
{}}),
Param({"source0.pgm", "source1.png", "source2.tiff", "source3.jpg"},
/*frame durations (ms):*/ {kMaxFrameDurationMs, 1, 20, 3},
/*quality=*/75.0f, /*effort=*/4, /*thread_level=*/8,
{1,
testutil::DecoderType::kStream,
WP2_XRGB_32,
kDoNotCompare,
{}}),
Param({"source3.jpg", "source2.tiff", "source3.jpg"},
/*frame durations (ms):*/ {123, 456, 789},
/*quality=*/25.0f, /*effort=*/3, /*thread_level=*/5,
{1,
testutil::DecoderType::kSwappingCustom,
WP2_RGBX_32,
kDoNotCompare,
{}}),
Param({"source0.pgm", "source3.jpg"},
/*frame durations (ms):*/ {kMaxFrameDurationMs, 3},
/*quality=*/75.0f, /*effort=*/2, /*thread_level=*/8,
{1,
testutil::DecoderType::kStream,
WP2_bgrA_32,
kDoNotCompare,
{}})));
INSTANTIATE_TEST_SUITE_P(
AnimTestIncrRewind, AnimTestIncr,
testing::Values(Param({"source1_64x48.png"},
/*frame durations (ms):*/ {1000},
/*quality=*/0.0f, /*effort=*/4, /*thread_level=*/2,
{1,
testutil::DecoderType::kArray,
WP2_BGRA_32,
kDoNotCompare,
{{testutil::DecoderAction::Type::kRewind, 0, 0},
{testutil::DecoderAction::Type::kRewind, 100,
0}}})));
// 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_AnimTestIncrRewind, AnimTestIncr,
testing::Values(
Param({"source1_64x48.png"},
/*frame durations (ms):*/ {1},
/*quality=*/100.0f, /*effort=*/7, /*thread_level=*/0,
{1,
testutil::DecoderType::kArray,
WP2_BGRX_32,
kCompareWithSingleChunkDec,
{{testutil::DecoderAction::Type::kRewind, 100000000, 0}}}),
Param({"test_exif_xmp.webp"},
/*frame durations (ms):*/ {1},
/*quality=*/33.0f, /*effort=*/0, /*thread_level=*/1,
{1,
testutil::DecoderType::kArray,
WP2_Argb_32,
kDoNotCompare,
{{testutil::DecoderAction::Type::kSkip, 35, 1}}}),
Param({"alpha_ramp.png", "alpha_ramp.lossy.webp", "alpha_ramp.webp"},
/*frame durations (ms):*/ {50, 70, 1},
/*quality=*/100.0f, /*effort=*/2, /*thread_level=*/0,
{1,
testutil::DecoderType::kUnstableCustom,
WP2_RGB_24,
kDoNotCompare,
{{testutil::DecoderAction::Type::kSkip, 355, 1},
{testutil::DecoderAction::Type::kRewindKeepBytes, 356, 0},
{testutil::DecoderAction::Type::kSkip, 357, 0}}}),
Param({"source0.pgm", "source1.png", "source2.tiff", "source3.jpg"},
/*frame durations (ms):*/ {kMaxFrameDurationMs, 1, 20, 3},
/*quality=*/75.0f, /*effort=*/4, /*thread_level=*/8,
{1,
testutil::DecoderType::kStream,
WP2_BGR_24,
kDoNotCompare,
{{testutil::DecoderAction::Type::kSkip, 15, kMaxNumFrames + 1},
{testutil::DecoderAction::Type::kRewindKeepBytes, 12000, 1}}}),
Param({"source0.pgm", "source1.png", "source2.tiff", "source3.jpg"},
/*frame durations (ms):*/ {5689, 21121, 52, 3},
/*quality=*/50.0f, /*effort=*/5, /*thread_level=*/4,
{1,
testutil::DecoderType::kArray,
WP2_Argb_32,
kCompareWithSingleChunkDec,
{{testutil::DecoderAction::Type::kSkip, 0, kMaxNumFrames},
{testutil::DecoderAction::Type::kRewind, 100000000, 0},
{testutil::DecoderAction::Type::kSkip, 0, 1},
{testutil::DecoderAction::Type::kRewind,
kHeaderMaxSize + kANMFMaxHeaderSize + 1, 0},
{testutil::DecoderAction::Type::kSkip, 0, kMaxNumFrames},
{testutil::DecoderAction::Type::kRewind, 100000000, 0}}}),
Param({"source3.jpg", "source2.tiff", "source3.jpg"},
/*frame durations (ms):*/ {123, 456, 789},
/*quality=*/25.0f, /*effort=*/3, /*thread_level=*/5,
{1,
testutil::DecoderType::kSwappingCustom,
WP2_Argb_38,
kDoNotCompare,
{{testutil::DecoderAction::Type::kSkip, 0, 2},
{testutil::DecoderAction::Type::kRewind, 100000000, 1}}})));
//------------------------------------------------------------------------------
// Tests that skipping frames returns the same frame features as decoding all
// pixels.
TEST(AnimTestIncr, SkipAndCompareFeatures) {
MemoryWriter writer;
ASSERT_WP2_OK(testutil::CompressAnimation(
{"source0.pgm", "source1.png", "source3.jpg", "source4.webp"},
{50, 50, 50, 50}, &writer, nullptr, EncoderConfig::kDefault.quality,
EncoderConfig::kDefault.effort, EncoderConfig::kDefault.thread_level,
/*num_downsamplings=*/2));
ArrayDecoder feature_decoder, frame_decoder;
feature_decoder.SkipNumNextFrames(kMaxNumFrames);
for (size_t size = 1; size <= writer.size_; ++size) {
feature_decoder.SetInput(writer.mem_, size);
frame_decoder.SetInput(writer.mem_, size);
ASSERT_FALSE(feature_decoder.ReadFrame());
frame_decoder.ReadFrame();
ASSERT_FALSE(feature_decoder.Failed());
ASSERT_FALSE(frame_decoder.Failed());
// Byte-by-byte incremental decoding should give the same values.
// Otherwise the 'frame_decoder' might halt after finishing a frame.
ASSERT_EQ(feature_decoder.GetNumFrameDecodedFeatures(),
frame_decoder.GetNumFrameDecodedFeatures());
for (uint32_t i = 0; i < frame_decoder.GetNumFrameDecodedFeatures(); ++i) {
const FrameFeatures* const frame_a =
feature_decoder.TryGetFrameDecodedFeatures(i);
const FrameFeatures* const frame_b =
frame_decoder.TryGetFrameDecodedFeatures(i);
ASSERT_NE(frame_a, nullptr);
ASSERT_NE(frame_b, nullptr);
ASSERT_EQ(frame_a->duration_ms, frame_b->duration_ms);
ASSERT_EQ(frame_a->window, frame_b->window);
ASSERT_EQ(frame_a->is_last, frame_b->is_last);
ASSERT_EQ(frame_a->last_dispose_frame_index,
frame_b->last_dispose_frame_index);
size_t offset_a = 0, offset_b = 0, length_a = 0, length_b = 0;
ASSERT_EQ(feature_decoder.TryGetFrameLocation(i, &offset_a),
frame_decoder.TryGetFrameLocation(i, &offset_b));
ASSERT_EQ(offset_a, offset_b);
ASSERT_EQ(feature_decoder.TryGetFrameLocation(i, nullptr, &length_a),
frame_decoder.TryGetFrameLocation(i, nullptr, &length_b));
ASSERT_EQ(length_a, length_b);
}
}
}
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2