| // 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. |
| |
| // Compare YUVPlane and ArgbBuffer distortion computation. |
| |
| #include <array> |
| #include <string> |
| #include <tuple> |
| |
| #include "examples/example_utils.h" |
| #include "extras/ccsp_imageio.h" |
| #include "extras/extras.h" |
| #include "imageio/image_dec.h" |
| #include "include/helpers.h" |
| #include "include/helpers_filter.h" |
| #include "src/wp2/decode.h" |
| #include "src/wp2/encode.h" |
| |
| namespace WP2 { |
| namespace { |
| |
| //------------------------------------------------------------------------------ |
| |
| // Returns true if 'ccsp' and 'rgb' contain the same samples. |
| testing::AssertionResult AreEqual(const YUVPlane& ccsp, const ArgbBuffer& rgb) { |
| if (ccsp.GetWidth() != rgb.width() || ccsp.GetHeight() != rgb.height() || |
| ccsp.IsDownsampled()) { |
| return testing::AssertionFailure(); |
| } |
| |
| for (uint32_t y = 0; y < ccsp.GetHeight(); ++y) { |
| const uint8_t* const rgb_row = rgb.GetRow8(y); |
| for (uint32_t x = 0; x < ccsp.GetWidth(); ++x) { |
| const int32_t ccsp_a = ccsp.HasAlpha() ? ccsp.A.At(x, y) : kAlphaMax; |
| const int32_t rgb_a = rgb_row[x * 4 + 0]; |
| const int32_t ccsp_r = ccsp.Y.At(x, y), rgb_r = rgb_row[x * 4 + 1]; |
| const int32_t ccsp_g = ccsp.U.At(x, y), rgb_g = rgb_row[x * 4 + 2]; |
| const int32_t ccsp_b = ccsp.V.At(x, y), rgb_b = rgb_row[x * 4 + 3]; |
| if (ccsp_a != rgb_a || ccsp_r != rgb_r || ccsp_g != rgb_g || |
| ccsp_b != rgb_b) { |
| return testing::AssertionFailure() |
| << " diff at " << x << ", " << y << ": " << ccsp_a << ", " |
| << ccsp_r << ", " << ccsp_g << ", " << ccsp_b << " vs " << rgb_a |
| << ", " << rgb_r << ", " << rgb_g << ", " << rgb_b; |
| } |
| } |
| } |
| return testing::AssertionSuccess(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Tests GetDistortion for all format/metric combinations. |
| TEST(GetDistortion, HandlesAllFormats) { |
| for (uint32_t f = 0; f < WP2_Argb_38; ++f) { |
| SCOPED_TRACE(SPrintf("format %d", f)); |
| const WP2SampleFormat format = (WP2SampleFormat)f; |
| for (uint32_t metric = 0; metric < NUM_METRIC_TYPES; ++metric) { |
| SCOPED_TRACE(SPrintf("metric %d", metric)); |
| ArgbBuffer orig(format); |
| const uint32_t kWidth = 10, kHeight = 10; // Big enough for PSNRHVS |
| const uint8_t kOrigValue = 100; |
| std::vector<uint8_t> data_orig( |
| kWidth * kHeight * WP2FormatBpp(format) + 1, kOrigValue); |
| const uint32_t stride = kWidth * WP2FormatBpp(format); |
| ASSERT_WP2_OK(orig.SetExternal(kWidth, kHeight, &data_orig[0], stride)); |
| |
| std::vector<uint8_t> data_copy = data_orig; |
| // Make some changes that should have no impact. |
| const uint8_t kChangedValue = kOrigValue / 2; |
| // Change outside of the image. |
| data_copy[data_copy.size() - 1] = kChangedValue; |
| // Change nonexistent alpha channel. |
| if (format == WP2_XRGB_32) data_copy[0] = kChangedValue; |
| if (format == WP2_RGBX_32 || format == WP2_BGRX_32) { |
| data_copy[3] = kChangedValue; |
| } |
| |
| ArgbBuffer copy(orig.format()); |
| ASSERT_WP2_OK(copy.SetExternal(kWidth, kHeight, &data_copy[0], stride)); |
| |
| float disto[5]; |
| const WP2Status status = |
| copy.GetDistortion(orig, (MetricType)metric, disto); |
| // Some format/metric combinations are not supported. |
| if (status == WP2_STATUS_UNSUPPORTED_FEATURE || |
| status == WP2_STATUS_INVALID_PARAMETER) { |
| continue; |
| } |
| |
| ASSERT_WP2_OK(status); |
| uint32_t alpha_channel_index; |
| const bool has_alpha = WP2FormatHasAlpha(format, &alpha_channel_index); |
| for (uint32_t c = 0; c < 4; ++c) { |
| if (!has_alpha && c == alpha_channel_index) continue; |
| EXPECT_EQ(disto[c], 99); |
| } |
| EXPECT_EQ(disto[4], 99); // total disto == 99 |
| |
| // Make a change that SHOULD cause some distortion. |
| const uint32_t kChangedChannelIdx = 1; |
| data_copy[kChangedChannelIdx] = kChangedValue; |
| ASSERT_WP2_OK(copy.GetDistortion(orig, (MetricType)metric, disto)); |
| if (metric == LSIM) { |
| // LSIM should still find 99 if only one pixel is changed (because it |
| // matches with surrounding pixels). |
| EXPECT_EQ(disto[4], 99); |
| continue; |
| } |
| // Other metrics should find a difference. |
| EXPECT_LT(disto[4], 99); // total disto < 99 |
| |
| // For argb metrics, only the changed channel should have a disto < 99. |
| const bool is_yuv_metric = |
| metric == PSNRHVS || metric == PSNR_YUV || metric == SSIM_YUV; |
| if (is_yuv_metric) continue; |
| |
| for (uint32_t c = 0; c < 4; ++c) { |
| if (!has_alpha && c == alpha_channel_index) continue; |
| if (c == kChangedChannelIdx) { |
| EXPECT_LT(disto[c], 99); |
| } else { |
| EXPECT_EQ(disto[c], 99); |
| } |
| } |
| } |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Verify that distortion results are not bitdepth-dependent. |
| TEST(DistoTest, BitDepth) { |
| YUVPlane original_8b, distorted_8b; |
| for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| Plane16& original_plane = original_8b.GetChannel(c); |
| Plane16& distorted_plane = distorted_8b.GetChannel(c); |
| ASSERT_WP2_OK(original_plane.Resize(512, 512)); |
| original_plane.Fill((c == kAChannel) ? 128 : 0); |
| const int32_t min = (c == kAChannel) ? -128 : 0; |
| const int32_t max = (c == kAChannel) ? 127 : 0; |
| testutil::Noise(min, max, /*seed=*/c, /*strength=*/128, &original_plane); |
| ASSERT_WP2_OK( |
| distorted_plane.Copy(original_plane, /*resize_if_needed=*/true)); |
| testutil::Noise(min, max, /*seed=*/c, /*strength=*/5, &distorted_plane); |
| } |
| |
| for (uint32_t bit_depth : {9, 10, 11, 12}) { // Sign included. |
| YUVPlane original, distorted; |
| ASSERT_WP2_OK(original.Copy(original_8b, /*resize_if_needed=*/true)); |
| ASSERT_WP2_OK(distorted.Copy(distorted_8b, /*resize_if_needed=*/true)); |
| const int32_t m = 1 << (bit_depth - 8); |
| const int32_t k8bTo12bMatrix[] = {m, 0, 0, 0, m, 0, 0, 0, m}; |
| ASSERT_WP2_OK(original.Apply(k8bTo12bMatrix, /*shift=*/0)); |
| ASSERT_WP2_OK(distorted.Apply(k8bTo12bMatrix, /*shift=*/0)); |
| |
| for (MetricType metric : {PSNR, SSIM}) { |
| float disto_8b[5], disto[5]; |
| ASSERT_WP2_OK(distorted_8b.GetDistortion( |
| original_8b, /*bit_depth=*/{8, /*is_signed=*/true}, metric, |
| disto_8b)); |
| ASSERT_WP2_OK(distorted.GetDistortion( |
| original, {bit_depth, /*is_signed=*/true}, metric, disto)); |
| for (uint32_t i = 0; i < 5; ++i) { |
| EXPECT_NEAR(disto_8b[i], disto[i], 1e-6) << "Channel " << i; |
| } |
| } |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| class DistoTest |
| : public testing::TestWithParam<std::tuple<std::string, MetricType>> {}; |
| |
| // Compare RGB distortion for YUVPlane and ArgbBuffer structs. It should be |
| // equal for opaque images and might slightly differ otherwise (because some |
| // random color noise might be clamped during YUVPlane::Extract() as the |
| // samples are alpha-premultiplied). |
| TEST_P(DistoTest, ComparedToRGB) { |
| const std::string file_path = |
| testutil::GetTestDataPath(std::get<0>(GetParam())); |
| const MetricType metric = std::get<1>(GetParam()); |
| |
| // Load image into 'original_ccsp' and apply noise to get 'distorted_ccsp'. |
| YUVPlane original_ccsp, distorted_ccsp; |
| CSPMtx ccsp_to_rgb = {}; |
| ASSERT_WP2_OK(ReadImage(file_path.c_str(), &original_ccsp, &ccsp_to_rgb)); |
| ASSERT_WP2_OK(distorted_ccsp.Copy(original_ccsp, /*resize_if_needed=*/true)); |
| // Distort alpha first then clamp distorted RGB samples. |
| testutil::Noise(0, 255, /*seed=*/kAChannel, /*strength=*/5, |
| &distorted_ccsp.A); |
| for (Channel channel : {kYChannel, kUChannel, kVChannel}) { |
| Plane16* const plane = &distorted_ccsp.GetChannel(channel); |
| testutil::Noise(0, 255, /*seed=*/channel, /*strength=*/5, plane); |
| if (distorted_ccsp.HasAlpha()) { |
| assert(!distorted_ccsp.IsDownsampled()); |
| for (uint32_t y = 0; y < plane->h_; ++y) { |
| for (uint32_t x = 0; x < plane->w_; ++x) { |
| plane->At(x, y) = |
| std::min(plane->At(x, y), distorted_ccsp.A.At(x, y)); |
| } |
| } |
| } |
| } |
| |
| // Copy both to ArgbBuffer (identity matrix, no shift). |
| ASSERT_EQ(ccsp_to_rgb, CSPMtx({1, 0, 0, 0, 1, 0, 0, 0, 1}, 0)); |
| ArgbBuffer original_rgb, distorted_rgb; |
| ASSERT_WP2_OK(original_ccsp.Export(ccsp_to_rgb, /*resize_if_needed=*/true, |
| &original_rgb, &SamplingTaps::kUpSmooth)); |
| ASSERT_WP2_OK(distorted_ccsp.Export(ccsp_to_rgb, /*resize_if_needed=*/true, |
| &distorted_rgb, |
| &SamplingTaps::kUpSmooth)); |
| |
| // Make sure data did not change during the copy. |
| ASSERT_TRUE(AreEqual(original_ccsp, original_rgb)); |
| ASSERT_TRUE(AreEqual(distorted_ccsp, distorted_rgb)); |
| |
| // Compute distortion with YUVPlane and ArgbBuffer. |
| float disto_ccsp[5], disto_rgb[5]; |
| ASSERT_WP2_OK(distorted_ccsp.GetDistortion( |
| original_ccsp, /*bit_depth=*/{8, /*is_signed=*/false}, metric, |
| disto_ccsp)); |
| ASSERT_WP2_OK(distorted_rgb.GetDistortion(original_rgb, metric, disto_rgb)); |
| |
| // Same metric for each channel. |
| for (uint32_t i = 0; i < 5; ++i) { |
| // The number of channels impacts the number of samples, hence a different |
| // total distortion. |
| const float error_margin = (original_ccsp.HasAlpha() && i == 4) ? 1.f : 0.f; |
| EXPECT_NEAR(disto_ccsp[i], disto_rgb[i], error_margin) << i; |
| } |
| } |
| |
| // Compare distortion for down/upsampled chroma. |
| TEST_P(DistoTest, ChromaSubsampling) { |
| const std::string file_path = |
| testutil::GetTestDataPath(std::get<0>(GetParam())); |
| const MetricType metric = std::get<1>(GetParam()); |
| |
| // Load image into 'original_ccsp' and apply noise to get 'distorted_ccsp'. |
| YUVPlane original_420, distorted_420; |
| CSPMtx ccsp_to_rgb = {}; |
| ASSERT_WP2_OK(ReadImage(file_path.c_str(), &original_420, &ccsp_to_rgb)); |
| if (!original_420.IsDownsampled()) { |
| ASSERT_WP2_OK(original_420.Downsample()); |
| } |
| |
| ASSERT_WP2_OK(distorted_420.Copy(original_420, /*resize_if_needed=*/true)); |
| for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| testutil::Noise(0, 255, /*seed=*/channel, /*strength=*/5, |
| &distorted_420.GetChannel(channel)); |
| } |
| |
| // 420 -> 444 with no interpolation for closer distortion. |
| YUVPlane original_444, distorted_444; |
| const bool upsamplable = |
| (original_420.GetWidth() > 1 || original_420.GetHeight() > 1); |
| ASSERT_WP2_OK( |
| original_444.UpsampleFrom(original_420, SamplingTaps::kUpNearest)); |
| ASSERT_WP2_OK( |
| distorted_444.UpsampleFrom(distorted_420, SamplingTaps::kUpNearest)); |
| |
| // Compare 420 and 444 distortions. |
| BitDepth bit_depth; |
| ASSERT_WP2_OK(ReadBitDepth(file_path.c_str(), &bit_depth)); |
| float disto_420[5], disto_444[5]; |
| ASSERT_WP2_OK( |
| distorted_420.GetDistortion(original_420, bit_depth, metric, disto_420)); |
| ASSERT_WP2_OK( |
| distorted_444.GetDistortion(original_444, bit_depth, metric, disto_444)); |
| for (uint32_t i = 0; i < 5; ++i) { |
| // SSIM uses some padding so the distortion differs when dimensions change. |
| // TODO(yguyon): Modify SSIM to take that into account? |
| const float error_margin = |
| (metric == SSIM && upsamplable && i >= 2) ? 4.5f : 0.f; |
| EXPECT_NEAR(disto_420[i], disto_444[i], error_margin) << i; |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| DistoTestInstantiation, DistoTest, |
| testing::Combine(testing::Values("source0.pgm", "source0.ppm", |
| "source3.jpg", "source1_1x48.png", |
| "source1_64x1.png", "source1_1x1.png"), |
| testing::Values(PSNR, SSIM))); |
| |
| //------------------------------------------------------------------------------ |
| // Encode 'original_ccsp', either by converting it to RGB first or not. |
| |
| WP2Status EncodeInRGB(const YUVPlane& original_ccsp, const CSPMtx& ccsp_to_rgb, |
| const EncoderConfig& config, Writer* const writer) { |
| ArgbBuffer original_rgb; |
| WP2_CHECK_STATUS(original_ccsp.Export(ccsp_to_rgb, /*resize_if_needed=*/true, |
| &original_rgb, |
| &SamplingTaps::kUpSmooth)); |
| WP2_CHECK_STATUS(Encode(original_rgb, writer, config)); |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status EncodeInCCSP(const YUVPlane& original_ccsp, const CSPMtx& ccsp_to_rgb, |
| const EncoderConfig& config, Writer* const writer) { |
| YUVPlane upsampled; |
| if (original_ccsp.IsDownsampled()) { |
| WP2_CHECK_STATUS(upsampled.UpsampleFrom(original_ccsp)); |
| } else { |
| WP2_CHECK_STATUS(upsampled.SetView( |
| original_ccsp, |
| {0, 0, original_ccsp.GetWidth(), original_ccsp.GetHeight()})); |
| } |
| WP2_CHECK_STATUS(Encode( |
| upsampled.GetWidth(), upsampled.GetHeight(), |
| upsampled.Y.Row(0), upsampled.Y.Step(), |
| upsampled.U.Row(0), upsampled.U.Step(), |
| upsampled.V.Row(0), upsampled.V.Step(), |
| upsampled.HasAlpha() ? upsampled.A.Row(0) : nullptr, upsampled.A.Step(), |
| ccsp_to_rgb.mtx(), ccsp_to_rgb.shift, writer, config)); |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Decode to RGB then convert or decode directly to 'decoded_ccsp'. |
| |
| WP2Status DecodeInRGB(const uint8_t* const data, size_t data_size, |
| FileFormat format, BitDepth bit_depth, bool has_alpha, |
| bool is_downsampled, YUVPlane* const decoded_ccsp) { |
| ArgbBuffer decoded_rgb; |
| WP2_CHECK_STATUS(Decode(data, data_size, &decoded_rgb)); |
| |
| if (IsCustomColorSpace(format)) { |
| WP2_CHECK_STATUS(ToYCbCr( |
| decoded_rgb, bit_depth, |
| is_downsampled ? &SamplingTaps::kDownSharp : nullptr, decoded_ccsp)); |
| } else { |
| const CSPMtx identity({1, 0, 0, 0, 1, 0, 0, 0, 1}, bit_depth.num_bits - 8); |
| WP2_CHECK_STATUS(decoded_ccsp->Import(decoded_rgb, has_alpha, identity, |
| /*resize_if_needed=*/true)); |
| if (is_downsampled) WP2_CHECK_ALLOC_OK(decoded_ccsp->Downsample()); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status DecodeInCCSP(const uint8_t* const data, size_t data_size, |
| FileFormat format, BitDepth bit_depth, bool has_alpha, |
| bool is_downsampled, YUVPlane* const decoded_ccsp) { |
| // Select output color space and bit depth. |
| int16_t rgb_to_ccsp_matrix[9] = {}; |
| uint32_t rgb_to_ccsp_shift; |
| if (IsCustomColorSpace(format)) { |
| std::copy(kRGBToYCbCrMatrix, kRGBToYCbCrMatrix + 9, rgb_to_ccsp_matrix); |
| rgb_to_ccsp_shift = kRGBToYCbCrShift - (bit_depth.num_bits - 8); |
| } else { |
| const int16_t v = 1 << (bit_depth.num_bits - 8); |
| SetArray<int16_t>(rgb_to_ccsp_matrix, {v, 0, 0, 0, v, 0, 0, 0, v}); |
| rgb_to_ccsp_shift = 0; |
| } |
| |
| BitstreamFeatures bitstream; |
| WP2_CHECK_STATUS(bitstream.Read(data, data_size)); |
| |
| // Allocate output and decode. |
| WP2_CHECK_STATUS(decoded_ccsp->Resize(bitstream.width, bitstream.height, |
| /*pad=*/1, has_alpha)); |
| WP2_CHECK_STATUS(Decode( |
| data, data_size, rgb_to_ccsp_matrix, rgb_to_ccsp_shift, |
| decoded_ccsp->Y.Row(0), decoded_ccsp->Y.Step(), decoded_ccsp->Y.Size(), |
| decoded_ccsp->U.Row(0), decoded_ccsp->U.Step(), decoded_ccsp->U.Size(), |
| decoded_ccsp->V.Row(0), decoded_ccsp->V.Step(), decoded_ccsp->V.Size(), |
| decoded_ccsp->HasAlpha() ? decoded_ccsp->A.Row(0) : nullptr, |
| decoded_ccsp->A.Step(), decoded_ccsp->A.Size())); |
| if (is_downsampled) WP2_CHECK_ALLOC_OK(decoded_ccsp->Downsample()); |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| enum class WhatToDo { |
| kEncodeDecodeSameWay, |
| kEncodeBothAsRGB, |
| kEncodeBothAsCCSP, |
| kDecodeBothAsRGB, |
| kDecodeBothAsCCSP |
| }; |
| |
| class MorePreciseCCSPTest |
| : public testing::TestWithParam< |
| std::tuple<const char*, float, WhatToDo, MetricType>> {}; |
| |
| // Make sure encoding then decoding from and to custom color space samples does |
| // not bring more distortion than doing it in RGB. |
| TEST_P(MorePreciseCCSPTest, EncodeDecode) { |
| const char* const file_name = std::get<0>(GetParam()); |
| const float quality = std::get<1>(GetParam()); |
| const WhatToDo what_to_do = std::get<2>(GetParam()); |
| const MetricType metric = std::get<3>(GetParam()); |
| |
| Data data; |
| ASSERT_WP2_OK( |
| IoUtilReadFile(testutil::GetTestDataPath(file_name).c_str(), &data)); |
| const FileFormat format = GuessImageFormat(data.bytes, data.size); |
| BitDepth bit_depth; |
| ASSERT_WP2_OK(ReadBitDepth(data.bytes, data.size, &bit_depth)); |
| |
| YUVPlane original_ccsp; |
| CSPMtx ccsp_to_rgb = {}; |
| ASSERT_WP2_OK(ReadImage(data.bytes, data.size, &original_ccsp, &ccsp_to_rgb)); |
| |
| EncoderConfig config = EncoderConfig::kDefault; |
| config.quality = quality; |
| float disto_rgb[5], disto_ccsp[5]; |
| |
| // RGB |
| { |
| MemoryWriter writer; |
| if (what_to_do == WhatToDo::kEncodeBothAsCCSP) { |
| ASSERT_WP2_OK(EncodeInCCSP(original_ccsp, ccsp_to_rgb, config, &writer)); |
| } else { |
| ASSERT_WP2_OK(EncodeInRGB(original_ccsp, ccsp_to_rgb, config, &writer)); |
| } |
| YUVPlane decoded_ccsp; |
| if (what_to_do == WhatToDo::kDecodeBothAsCCSP) { |
| ASSERT_WP2_OK(DecodeInCCSP(writer.mem_, writer.size_, format, bit_depth, |
| original_ccsp.HasAlpha(), |
| original_ccsp.IsDownsampled(), &decoded_ccsp)); |
| } else { |
| ASSERT_WP2_OK(DecodeInRGB(writer.mem_, writer.size_, format, bit_depth, |
| original_ccsp.HasAlpha(), |
| original_ccsp.IsDownsampled(), &decoded_ccsp)); |
| } |
| ASSERT_WP2_OK(decoded_ccsp.GetDistortion(original_ccsp, bit_depth, metric, |
| disto_rgb)); |
| } |
| |
| // Custom Color Space |
| { |
| MemoryWriter writer; |
| if (what_to_do == WhatToDo::kEncodeBothAsRGB) { |
| ASSERT_WP2_OK(EncodeInRGB(original_ccsp, ccsp_to_rgb, config, &writer)); |
| } else { |
| ASSERT_WP2_OK(EncodeInCCSP(original_ccsp, ccsp_to_rgb, config, &writer)); |
| } |
| YUVPlane decoded_ccsp; |
| if (what_to_do == WhatToDo::kDecodeBothAsRGB) { |
| ASSERT_WP2_OK(DecodeInRGB(writer.mem_, writer.size_, format, bit_depth, |
| original_ccsp.HasAlpha(), |
| original_ccsp.IsDownsampled(), &decoded_ccsp)); |
| } else { |
| ASSERT_WP2_OK(DecodeInCCSP(writer.mem_, writer.size_, format, bit_depth, |
| original_ccsp.HasAlpha(), |
| original_ccsp.IsDownsampled(), &decoded_ccsp)); |
| } |
| ASSERT_WP2_OK(decoded_ccsp.GetDistortion(original_ccsp, bit_depth, metric, |
| disto_ccsp)); |
| } |
| |
| // Custom color space should not be (way) more distorted than RGB. |
| // Some loss is expected because of the lack of clamping by alpha for the CCSP |
| // pipeline (even if actually handling RGB samples). |
| const float error_margin = |
| (IsCustomColorSpace(format) || original_ccsp.HasAlpha()) ? 1.5f : 0.01f; |
| for (uint32_t i = 0; i < 5; ++i) { |
| EXPECT_NEAR(disto_rgb[i], disto_ccsp[i], error_margin) |
| << i << " in " << std::endl |
| << disto_rgb[0] << ", " << disto_rgb[1] << ", " << disto_rgb[2] << ", " |
| << disto_rgb[3] << ", " << disto_rgb[4] << " vs " << std::endl |
| << disto_ccsp[0] << ", " << disto_ccsp[1] << ", " << disto_ccsp[2] |
| << ", " << disto_ccsp[3] << ", " << disto_ccsp[4]; |
| } |
| |
| if (quality > (float)kMaxLossyQuality) { |
| for (uint32_t i = 0; i < 5; ++i) { |
| if (IsCustomColorSpace(format)) { |
| // Lossless custom color space should never be more distorted than RGB. |
| EXPECT_LE(disto_rgb[i], disto_ccsp[i]) << i; |
| } else { |
| // Lossless RGB or alpha input should give the exact same distortion. |
| EXPECT_EQ(disto_rgb[i], disto_ccsp[i]) << i; |
| } |
| } |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| MorePreciseCCSPTestInstantiationSmall, MorePreciseCCSPTest, |
| testing::Combine(testing::Values("source1_1x1.png", "source1_64x48.png"), |
| testing::Values(0.f, 95.f, 100.f), |
| testing::Values(WhatToDo::kEncodeBothAsCCSP, |
| WhatToDo::kDecodeBothAsCCSP), |
| testing::Values(PSNR, SSIM))); |
| |
| // This one might take ages so it is disabled. |
| // Can still be run with flag --test_arg=--gunit_also_run_disabled_tests |
| INSTANTIATE_TEST_SUITE_P( |
| DISABLED_MorePreciseCCSPTestInstantiation, MorePreciseCCSPTest, |
| testing::Combine( |
| testing::Values("source1_1x1.png", "source1_64x48.png", "source1.png", |
| "source3.jpg", "ccsp/source3_C444p8.y4m", |
| "ccsp/source3_C444p12.y4m", "ccsp/source3_C420p8.y4m", |
| "ccsp/source3_C420p12.y4m"), |
| testing::Values(0.f, 75.f, 95.f, 100.f), |
| testing::Values(WhatToDo::kEncodeDecodeSameWay, |
| WhatToDo::kEncodeBothAsCCSP, WhatToDo::kEncodeBothAsRGB, |
| WhatToDo::kDecodeBothAsCCSP, |
| WhatToDo::kDecodeBothAsRGB), |
| testing::Values(PSNR, SSIM))); |
| |
| //------------------------------------------------------------------------------ |
| |
| } // namespace |
| } // namespace WP2 |