blob: 7eb7074b9870835e2417a351c30cbf98db1534d4 [file] [log] [blame]
// Copyright 2019 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.
// -----------------------------------------------------------------------------
//
// Fuzzing tools.
//
// Author: Yannis Guyon (yguyon@google.com)
#include "./fuzz_utils.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iterator>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "imageio/image_enc.h"
#include "imageio/imageio_util.h"
#include "src/common/constants.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/decode.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
#include "fuzzing/fuzztest.h"
namespace WP2 {
namespace testutil {
//------------------------------------------------------------------------------
void LimitConfigForBigImages(DataView bitstream, DecoderConfig* config) {
// Try to keep the peak allocated memory below 2 GB by reducing the number of
// concurrently decoded tiles for big images.
BitstreamFeatures features;
if (features.Read(bitstream.bytes, bitstream.size) == WP2_STATUS_OK) {
if (features.width * features.height <= (1u << 20)) {
// Small image, leave as is.
} else {
constexpr uint32_t kMaxPeakMem = (1u << 28); // 256 MB
// Estimation of the peak memory allocation within each tile
// (roughly 16 times the 16-bit 4-channel tile canvas).
const uint32_t tile_width = TileWidth(
features.tile_shape, features.raw_width, features.raw_height);
const uint32_t tile_height = TileHeight(
features.tile_shape, features.raw_width, features.raw_height);
const uint32_t peak_mem_divided_per_byte_size =
kMaxPeakMem / tile_width / tile_height / (sizeof(int16_t) * 4) / 16;
config->thread_level =
std::min(config->thread_level,
std::max(peak_mem_divided_per_byte_size, 1u) - 1u);
}
}
}
//------------------------------------------------------------------------------
std::string GetTestAssetPath(std::string_view name) {
return "tests/" + std::string(name);
}
// Returns a vector of strings instead of const char* to avoid
// fuzztest::ElementOf()-related errors.
std::vector<std::string> GetTestImageNames() {
// Valid files only.
return {"alpha_ramp.bmp",
"alpha_ramp.lossy.webp",
"alpha_ramp.pam",
"alpha_ramp.png",
"alpha_ramp.tiff",
"alpha_ramp.webp",
"animation0.gif",
"animation0.webp",
"animation1.webp",
"background.png",
"composited.png",
"large.png",
"logo.png",
"source0.pgm",
"source0.ppm",
"source1_1x1.png",
"source1_1x48.png",
"source1_32x32.png",
"source1_4x4.png",
"source1_64x1.png",
"source1_64x48.png",
"source1.itl.png",
"source1.png",
"source2.tiff",
"source3_222x167.jpg",
"source3.jpg",
"source4.ll.webp",
"source4.webp",
"source5.bmp",
"taiwan.png",
"test_exif_xmp.webp",
"verylarge.png"};
}
Data ReadTestImage(const std::string& name) {
Data data;
if (IoUtilReadFile(GetTestAssetPath("testdata/" + name).c_str(), &data) !=
WP2_STATUS_OK) {
std::abort();
}
return data;
}
std::vector<std::string> GetTestImagesContents(size_t max_file_size) {
const char* test_data_dirs[] = {"testdata"};
std::vector<std::string> seeds;
for (const char* test_data_dir : test_data_dirs) {
std::cout << "Reading seeds from " << test_data_dir << " (non recursively)"
<< std::endl;
std::vector<std::tuple<std::string>> tuple_vector =
fuzztest::ReadFilesFromDirectory(test_data_dir);
seeds.reserve(seeds.size() + tuple_vector.size());
for (auto& [file_content] : tuple_vector) {
if (file_content.size() > max_file_size) continue;
seeds.push_back(std::move(file_content));
}
}
if (seeds.empty()) {
std::cerr << "ERROR: no files found that match the given file size"
<< std::endl;
std::abort();
}
std::cout << "Returning " << seeds.size() << " seed images" << std::endl;
return seeds;
}
std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
std::string_view dir_path, size_t max_file_size) {
std::vector<std::tuple<std::string>> files =
fuzztest::ReadFilesFromDirectory(dir_path);
if (files.empty()) {
std::cerr << "fuzztest::ReadFilesFromDirectory() failed on " << dir_path
<< std::endl;
abort();
}
for (std::tuple<std::string>& file : files) {
if (std::get<0>(file).size() > max_file_size) {
std::get<0>(file).resize(max_file_size);
}
}
return files;
}
std::vector<std::string> ReadDictionaryFromFiles(
const std::vector<std::string_view>& file_paths) {
std::vector<std::string> merged_dictionary;
for (std::string_view file_path : file_paths) {
std::vector<std::string> dictionary =
fuzztest::ReadDictionaryFromFile(file_path);
if (dictionary.empty()) {
std::cerr << "fuzztest::ReadDictionaryFromFile() failed on " << file_path
<< std::endl;
abort();
}
merged_dictionary.insert(merged_dictionary.end(),
std::make_move_iterator(dictionary.begin()),
std::make_move_iterator(dictionary.end()));
}
return merged_dictionary;
}
//------------------------------------------------------------------------------
EncoderConfig CreateEncoderConfig(
float quality, size_t target_size, float target_psnr, float alpha_quality,
int effort, bool exact, Orientation decoding_orientation,
bool create_preview, TransferFunction transfer_function, int pass,
float sns, int error_diffusion, const std::vector<float>& segments,
EncoderConfig::SegmentIdMode segment_id_mode, bool enable_alt_tuning,
TileShape tile_shape, PartitionMethod partition_method,
PartitionSet partition_set, bool partition_snapping, Csp csp_type,
EncoderConfig::UVMode uv_mode, int preprocessing,
int preprocessing_strength, bool use_random_matrix, bool store_grain,
bool tune_perceptual, int thread_level, bool low_memory) {
EncoderConfig config;
config.quality = quality;
config.target_size = target_size;
config.target_psnr = target_psnr;
config.alpha_quality = alpha_quality;
config.effort = effort;
config.use_av1 = false;
config.decoding_orientation = decoding_orientation;
config.create_preview = create_preview;
// 'preview' and 'preview_size' are not fuzzed.
config.transfer_function = transfer_function;
config.pass = pass;
config.sns = sns;
config.error_diffusion = error_diffusion;
config.segments = static_cast<int>(segments.size());
config.segment_id_mode = segment_id_mode;
std::copy(segments.data(), segments.data() + segments.size(),
config.segment_factors);
config.tile_shape = tile_shape;
config.partition_method = partition_method;
config.partition_set = partition_set;
config.partition_snapping =
(partition_method == AREA_ENCODE_PARTITIONING ||
partition_method == SUB_AREA_ENCODE_PARTITIONING ||
partition_method == SPLIT_PARTITIONING ||
partition_method == SPLIT_RECURSE_PARTITIONING)
? true
: partition_snapping;
config.csp_type = csp_type;
config.uv_mode = uv_mode;
config.preprocessing = preprocessing;
config.preprocessing_strength = preprocessing_strength;
config.use_random_matrix = use_random_matrix;
config.store_grain = store_grain;
config.tune_perceptual = tune_perceptual;
config.thread_level = thread_level;
config.low_memory = low_memory;
// 'use_neural_compression' is not fuzzed.
return config;
}
DecoderConfig CreateDecoderConfig(
uint32_t thread_level, bool enable_deblocking_filter,
bool enable_directional_filter, bool enable_restoration_filter,
bool enable_alpha_filter, uint8_t grain_amplitude,
DecoderConfig::IncrementalMode incremental_mode) {
DecoderConfig config;
config.thread_level = thread_level;
config.enable_deblocking_filter = enable_deblocking_filter;
config.enable_directional_filter = enable_directional_filter;
config.enable_restoration_filter = enable_restoration_filter;
config.enable_alpha_filter = enable_alpha_filter;
config.grain_amplitude = grain_amplitude;
config.incremental_mode = incremental_mode;
return config;
}
ArgbBuffer CreateArgbBuffer8b(size_t width, size_t height,
WP2SampleFormat format,
const std::vector<uint8_t>& samples) {
assert(samples.size() == GetNumSamples(width, height, format));
ArgbBuffer image(format);
// Converting samples is simpler than generating alpha-premultiplied ones.
const WP2SampleFormat fuzzed_format = format == WP2_Argb_32 ? WP2_ARGB_32
: format == WP2_rgbA_32 ? WP2_RGBA_32
: format == WP2_bgrA_32 ? WP2_BGRA_32
: format;
assert(!WP2IsPremultiplied(fuzzed_format));
if (image.Import(fuzzed_format, width, height, samples.data(),
WP2FormatBpp(format) * width) != WP2_STATUS_OK) {
std::abort();
}
return image;
}
size_t GetNumSamples(size_t width, size_t height, WP2SampleFormat format) {
return width * height * WP2FormatBpp(format) /
(WP2Formatbpc(format) == 8 ? 1 : 2);
}
//------------------------------------------------------------------------------
void SaveBeforeAfter(const ArgbBuffer& original, const ArgbBuffer& decompressed,
const std::string& path) {
ArgbBuffer original_buffer(
WP2IsPremultiplied(original.format()) ? WP2_Argb_32 : WP2_ARGB_32);
WP2_ASSERT_STATUS(original.format() == original_buffer.format()
? original_buffer.SetView(original)
: original_buffer.ConvertFrom(original));
ArgbBuffer decompressed_buffer(
WP2IsPremultiplied(decompressed.format()) ? WP2_Argb_32 : WP2_ARGB_32);
WP2_ASSERT_STATUS(decompressed.format() == decompressed_buffer.format()
? decompressed_buffer.SetView(decompressed)
: decompressed_buffer.ConvertFrom(decompressed));
WP2_ASSERT_STATUS(SaveImage(original_buffer, (path + "_original.png").c_str(),
/*overwrite=*/true));
WP2_ASSERT_STATUS(SaveImage(decompressed_buffer,
(path + "_decompressed.png").c_str(),
/*overwrite=*/true));
}
//------------------------------------------------------------------------------
} // namespace testutil
} // namespace WP2