blob: 7dcdeaf85fd0dd452fb431c218b404b03fbe9891 [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 ImageIo.
#include <string>
#include "extras/ccsp_imageio.h"
#include "imageio/anim_image_dec.h"
#include "imageio/image_dec.h"
#include "imageio/image_enc.h"
#include "include/helpers.h"
namespace WP2 {
namespace {
//------------------------------------------------------------------------------
TEST(ReadImageTest, Metadata) {
ArgbBuffer buffer;
// Read image with metadata.
ASSERT_WP2_OK(ReadImage(
testutil::GetTestDataPath("test_exif_xmp.webp").c_str(), &buffer));
EXPECT_TRUE(!buffer.metadata_.IsEmpty());
// Read image without metadata.
ASSERT_WP2_OK(ReadImage(
testutil::GetTestDataPath("source1_64x48.png").c_str(), &buffer));
// Expect nothing left from first image.
EXPECT_TRUE(buffer.metadata_.IsEmpty());
}
TEST(ReadImageTest, NoStdoutFileSize) {
ArgbBuffer buffer;
ASSERT_WP2_OK(buffer.Resize(1, 1));
buffer.Fill(Argb32b{0, 0, 0, 0});
size_t file_size;
ASSERT_EQ(
SaveImage(buffer, "-", /*overwrite=*/true, FileFormat::AUTO, &file_size),
WP2_STATUS_INVALID_PARAMETER);
}
//------------------------------------------------------------------------------
// Test all formats that support read and lossless save.
class WriteImageTest : public testing::TestWithParam<const char*> {};
TEST_P(WriteImageTest, Argb) {
const char* const file_name = GetParam();
Data data;
ASSERT_WP2_OK(
IoUtilReadFile(testutil::GetTestDataPath(file_name).c_str(), &data));
const FileFormat format = GuessImageFormat(data.bytes, data.size);
ArgbBuffer original;
if (IsCustomColorSpace(format)) {
EXPECT_EQ(ReadImage(data.bytes, data.size, &original),
WP2_STATUS_UNSUPPORTED_FEATURE);
return;
}
ASSERT_WP2_OK(ReadImage(data.bytes, data.size, &original));
const std::string temp_file_path = testutil::GetTempDataPath(file_name);
std::remove(temp_file_path.c_str()); // Make sure it doesn't exist.
size_t written_file_size, read_file_size;
ASSERT_WP2_OK(SaveImage(original, temp_file_path.c_str(), /*overwrite=*/true,
format, &written_file_size));
ArgbBuffer comparison;
ASSERT_WP2_OK(
ReadImage(temp_file_path.c_str(), &comparison, &read_file_size));
EXPECT_EQ(written_file_size, read_file_size);
// Reading a custom color space into an ArgbBuffer will generate loss.
const bool is_ccsp = IsCustomColorSpace(GetFormatFromExtension(file_name));
const float expected_distortion = is_ccsp ? 40.f : 99.f;
EXPECT_TRUE(
testutil::Compare(original, comparison, file_name, expected_distortion));
}
TEST_P(WriteImageTest, Ccsp) {
const char* const file_name = GetParam();
Data data;
ASSERT_WP2_OK(
IoUtilReadFile(testutil::GetTestDataPath(file_name).c_str(), &data));
const FileFormat format = GuessImageFormat(data.bytes, data.size);
YUVPlane original;
CSPMtx ccsp_to_rgb = {};
ASSERT_WP2_OK(ReadImage(data.bytes, data.size, &original, &ccsp_to_rgb));
BitDepth file_num_bits;
if (IsCustomColorSpace(format)) {
ASSERT_GE(ccsp_to_rgb.shift, CSPTransform::kMtxShift);
file_num_bits = {CCSPImageReader::kMinBitDepth +
(ccsp_to_rgb.shift - CSPTransform::kMtxShift),
/*is_signed=*/false};
} else {
file_num_bits = {8, /*is_signed=*/false};
}
BitDepth bit_depth;
ASSERT_WP2_OK(ReadBitDepth(data.bytes, data.size, &bit_depth));
ASSERT_EQ(file_num_bits, bit_depth);
ASSERT_EQ(IsChromaSubsampled(format), original.IsDownsampled());
YUVPlane upsampled;
if (original.IsDownsampled()) {
// Upscale with no interpolation to keep the original samples.
ASSERT_WP2_OK(upsampled.UpsampleFrom(original, SamplingTaps::kUpNearest));
} else {
ASSERT_WP2_OK(upsampled.SetView(
original, {0, 0, original.GetWidth(), original.GetHeight()}));
}
const std::string temp_file_path = testutil::GetTempDataPath(file_name);
std::remove(temp_file_path.c_str()); // Make sure it doesn't exist.
ASSERT_WP2_OK(SaveImage(upsampled, ccsp_to_rgb,
file_num_bits, temp_file_path.c_str(),
/*overwrite=*/true, format, Metadata(),
SamplingTaps::kDownAvg));
YUVPlane comparison;
ASSERT_WP2_OK(ReadImage(temp_file_path.c_str(), &comparison, &ccsp_to_rgb));
// Reading an Argb color space into a YUVPlane will not generate loss.
EXPECT_TRUE(
testutil::Compare(original, comparison, file_num_bits, file_name));
}
INSTANTIATE_TEST_SUITE_P(WriteImageTestInstantiation, WriteImageTest,
testing::Values("ccsp/source3_C420p8.y4m"));
//------------------------------------------------------------------------------
// Simple test to exercise a corner case (1px wide, non-multiple-of-2 tall).
TEST(Y4MTest, Nearest) {
const char* const file_name = "ccsp/source3_C420p8.y4m";
YUVPlane original;
CSPMtx ccsp_to_rgb = {};
ASSERT_WP2_OK(ReadImage(testutil::GetTestDataPath(file_name).c_str(),
&original, &ccsp_to_rgb));
ASSERT_TRUE(original.IsDownsampled());
// Crop.
ASSERT_WP2_OK(original.Y.SetView(original.Y, {0, 0, 1, 41}));
ASSERT_WP2_OK(original.U.SetView(original.U, {0, 0, 1, 21}));
ASSERT_WP2_OK(original.V.SetView(original.V, {0, 0, 1, 21}));
original.A.Clear();
YUVPlane upsampled;
ASSERT_WP2_OK(upsampled.UpsampleFrom(original, SamplingTaps::kUpNearest));
ASSERT_WP2_OK(upsampled.Downsample(SamplingTaps::kDownAvg));
BitDepth bit_depth;
ASSERT_WP2_OK(
ReadBitDepth(testutil::GetTestDataPath(file_name).c_str(), &bit_depth));
EXPECT_TRUE(testutil::Compare(original, upsampled, bit_depth, file_name));
}
//------------------------------------------------------------------------------
TEST(WriteImageTest, NegativeArgbBuffer) {
ArgbBuffer buffer;
const std::string dst = testutil::GetTempDataPath("some_path.some_extension");
ASSERT_TRUE(buffer.IsEmpty());
EXPECT_EQ(SaveImage(buffer, dst.c_str()), WP2_STATUS_INVALID_PARAMETER);
ASSERT_WP2_OK(buffer.Resize(1, 1));
buffer.Fill({0, 0, 1, 1}, Argb32b({0x00u, 0x00u, 0x00u, 0x00u}));
ASSERT_FALSE(buffer.IsEmpty());
ASSERT_EQ(SaveImage(buffer, nullptr), WP2_STATUS_NULL_PARAMETER);
}
TEST(WriteImageTest, NegativeYUVPlane) {
YUVPlane buffer;
const CSPMtx good_ccsp_to_rgb({1, 0, 0, 0, 1, 0, 0, 0, 1}, 0);
const CSPMtx bad_ccsp_to_rgb({1, 0, 0, 0, 1, 0, 0, 0, 1}, 1000);
const BitDepth file_num_bits = {8, /*is_signed=*/false};
const BitDepth bad_file_num_bits = {2, /*is_signed=*/true};
const std::string dst = testutil::GetTempDataPath("some_path.some_extension");
ASSERT_TRUE(buffer.IsEmpty());
EXPECT_EQ(SaveImage(buffer, good_ccsp_to_rgb, file_num_bits, dst.c_str()),
WP2_STATUS_INVALID_PARAMETER);
ASSERT_WP2_OK(buffer.Resize(1, 1));
buffer.Fill(Rectangle{0, 0, 1, 1}, Ayuv38b{0xffu, 0x00u, 0x00u, 0x00u});
ASSERT_FALSE(buffer.IsEmpty());
ASSERT_EQ(SaveImage(buffer, good_ccsp_to_rgb, file_num_bits, nullptr),
WP2_STATUS_NULL_PARAMETER);
const FileFormat format = FileFormat::Y4M_444;
ASSERT_EQ(SaveImage(buffer, bad_ccsp_to_rgb, file_num_bits, dst.c_str(),
/*overwrite=*/true, format),
WP2_STATUS_INVALID_PARAMETER);
ASSERT_EQ(SaveImage(buffer, good_ccsp_to_rgb, bad_file_num_bits, dst.c_str(),
/*overwrite=*/true, format),
WP2_STATUS_INVALID_PARAMETER);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2