blob: 04f3b8e14bd13f7cf8dfae55235079f4b1b6120e [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.
// Image analysis/preprocessing tests.
#include <cstdint>
#include <unordered_set>
#include "examples/example_utils.h"
#include "imageio/image_dec.h"
#include "include/helpers.h"
#include "src/enc/analysis.h"
#include "src/wp2/base.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
namespace {
uint32_t CountColors(const ArgbBuffer& buffer) {
std::unordered_set<uint32_t> colors;
for (uint32_t y = 0; y < buffer.height(); ++y) {
const uint32_t* const row = (uint32_t*)buffer.GetRow(y);
for (uint32_t x = 0; x < buffer.width(); ++x) {
colors.insert(row[x]);
}
}
return colors.size();
}
TEST(PreprocessNearLosslessTest, Photo) {
ArgbBuffer original;
ASSERT_WP2_OK(ReadImage(testutil::GetTestDataPath("background.png").c_str(),
&original));
original.SimpleHalfDownsample(); // Speed up the test.
// Encode losslessly.
EncoderConfig encoder_config;
encoder_config.quality = 100; // lossless
MemoryWriter memory_writer;
ASSERT_WP2_OK(Encode(original, &memory_writer, encoder_config));
uint32_t previous_size = memory_writer.size_;
uint32_t previous_num_colors = CountColors(original);
// Encode at various near lossless levels.
for (uint32_t q : {99, 98, 97, 96}) {
SCOPED_TRACE(SPrintf("Quality: %d", q));
encoder_config.quality = q;
ArgbBuffer preprocessed;
ASSERT_WP2_OK(PreprocessNearLossless(original, encoder_config,
/*is_alpha=*/false, &preprocessed));
// Check we have fewer colors and the encoded image is smaller than at
// higher quality levels. While this is not strictly guaranteed by the
// algorithm, it's generally the case for typical images and is a desirable
// property (kind of the whole point).
const uint32_t num_colors = CountColors(preprocessed);
EXPECT_LT(num_colors, previous_num_colors);
previous_num_colors = num_colors;
memory_writer.Reset();
ASSERT_WP2_OK(Encode(preprocessed, &memory_writer, encoder_config));
const uint32_t size = memory_writer.size_;
EXPECT_LT(size, previous_size);
previous_size = size;
}
}
TEST(PreprocessNearLosslessTest, Mask) {
ArgbBuffer original;
ASSERT_WP2_OK(
ReadImage(testutil::GetTestDataPath("logo.png").c_str(), &original));
original.SimpleHalfDownsample(); // Speed up the test.
// Convert image to an opaque grayscale mask.
ArgbBuffer mask;
ASSERT_WP2_OK(mask.Resize(original.width(), original.height()));
for (uint32_t y = 0; y < original.height(); ++y) {
const uint8_t* const src_row = original.GetRow8(y);
uint8_t* const dst = mask.GetRow8(y);
for (uint32_t x = 0; x < original.width(); ++x) {
dst[4 * x + 0] = kAlphaMax;
dst[4 * x + 1] = dst[4 * x + 2] = dst[4 * x + 3] = src_row[4 * x + 0];
}
}
// Encode losslessly.
EncoderConfig encoder_config;
encoder_config.quality = encoder_config.alpha_quality = kMaxQuality;
MemoryWriter memory_writer;
ASSERT_WP2_OK(Encode(mask, &memory_writer, encoder_config));
uint32_t previous_size = memory_writer.size_;
uint32_t previous_num_colors = CountColors(mask);
// Encode at various near lossless levels.
for (uint32_t q = kMaxQuality - 1; q > kMaxLossyQuality; --q) {
SCOPED_TRACE(SPrintf("Quality: %d", q));
encoder_config.alpha_quality = q;
ArgbBuffer preprocessed;
ASSERT_WP2_OK(PreprocessNearLossless(mask, encoder_config,
/*is_alpha=*/true, &preprocessed));
// Check we have fewer colors and the encoded image is smaller than at
// higher quality levels. While this is not strictly guaranteed by the
// algorithm, it's generally the case for typical images and is a desirable
// property (kind of the whole point).
const uint32_t num_colors = CountColors(preprocessed);
// Check there are fewer colors (or equal, a bit less strict here as there
// are not many colors to begin with).
EXPECT_LT(num_colors, previous_num_colors);
previous_num_colors = num_colors;
// Encode the 'mask'. Because it is opaque, only the 'quality' setting
// (which is set to "lossless") is taken into account. 'alpha_quality' has
// no effect here.
memory_writer.Reset();
ASSERT_WP2_OK(Encode(preprocessed, &memory_writer, encoder_config));
EXPECT_LT(memory_writer.size_, previous_size);
previous_size = memory_writer.size_;
// Check that fully white and fully black pixels are unchanged.
for (uint32_t y = 0; y < original.height(); ++y) {
const uint8_t* const mask_row = mask.GetRow8(y);
const uint8_t* const nll_row = preprocessed.GetRow8(y);
for (uint32_t x = 0; x < original.width(); ++x) {
if (mask_row[4 * x + 1] == 0 || mask_row[4 * x + 1] == 255) {
EXPECT_EQ(nll_row[4 * x + 1], mask_row[4 * x + 1]);
}
}
}
}
}
} // namespace
} // namespace WP2