/src/libavif/tests/gtest/avif_fuzztest_helpers.cc
Line | Count | Source |
1 | | // Copyright 2022 Google LLC |
2 | | // SPDX-License-Identifier: BSD-2-Clause |
3 | | |
4 | | #include "avif_fuzztest_helpers.h" |
5 | | |
6 | | #include <algorithm> |
7 | | #include <cassert> |
8 | | #include <cstdint> |
9 | | #include <cstdlib> |
10 | | #include <iostream> |
11 | | #include <utility> |
12 | | #include <vector> |
13 | | |
14 | | #include "avif/avif.h" |
15 | | #include "avifutil.h" |
16 | | #include "fuzztest/fuzztest.h" |
17 | | #include "gtest/gtest.h" |
18 | | |
19 | | namespace avif { |
20 | | namespace testutil { |
21 | | namespace { |
22 | | |
23 | | //------------------------------------------------------------------------------ |
24 | | |
25 | | ImagePtr CreateAvifImage(size_t width, size_t height, int depth, |
26 | | avifPixelFormat pixel_format, bool has_alpha, |
27 | 91.2k | const uint8_t* samples) { |
28 | 91.2k | ImagePtr image(avifImageCreate(static_cast<uint32_t>(width), |
29 | 91.2k | static_cast<uint32_t>(height), depth, |
30 | 91.2k | pixel_format)); |
31 | 91.2k | if (image.get() == nullptr) { |
32 | 0 | return image; |
33 | 0 | } |
34 | 91.2k | if (avifImageAllocatePlanes(image.get(), |
35 | 91.2k | has_alpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV) != |
36 | 91.2k | AVIF_RESULT_OK) { |
37 | 0 | return nullptr; |
38 | 0 | } |
39 | | |
40 | 91.2k | for (avifChannelIndex c : |
41 | 365k | {AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A}) { |
42 | 365k | const size_t plane_height = avifImagePlaneHeight(image.get(), c); |
43 | 365k | uint8_t* row = avifImagePlane(image.get(), c); |
44 | 365k | const uint32_t row_bytes = avifImagePlaneRowBytes(image.get(), c); |
45 | 365k | assert(row_bytes == avifImagePlaneWidth(image.get(), c) * |
46 | 365k | (avifImageUsesU16(image.get()) ? 2 : 1)); |
47 | 14.3M | for (size_t y = 0; y < plane_height; ++y) { |
48 | 14.0M | std::copy(samples, samples + row_bytes, row); |
49 | 14.0M | row += row_bytes; |
50 | 14.0M | samples += row_bytes; |
51 | 14.0M | } |
52 | 365k | } |
53 | 91.2k | return image; |
54 | 91.2k | } |
55 | | |
56 | | } // namespace |
57 | | |
58 | | ImagePtr CreateAvifImage8b(size_t width, size_t height, |
59 | | avifPixelFormat pixel_format, bool has_alpha, |
60 | 27.9k | const std::vector<uint8_t>& samples) { |
61 | 27.9k | return CreateAvifImage(width, height, 8, pixel_format, has_alpha, |
62 | 27.9k | samples.data()); |
63 | 27.9k | } |
64 | | |
65 | | ImagePtr CreateAvifImage16b(size_t width, size_t height, int depth, |
66 | | avifPixelFormat pixel_format, bool has_alpha, |
67 | 18.7k | const std::vector<uint16_t>& samples) { |
68 | 18.7k | return CreateAvifImage(width, height, depth, pixel_format, has_alpha, |
69 | 18.7k | reinterpret_cast<const uint8_t*>(samples.data())); |
70 | 18.7k | } |
71 | | |
72 | | std::vector<ImagePtr> CreateAvifAnim8b(size_t num_frames, size_t width, |
73 | | size_t height, |
74 | | avifPixelFormat pixel_format, |
75 | | bool has_alpha, |
76 | 10.8k | const std::vector<uint8_t>& samples) { |
77 | 10.8k | std::vector<ImagePtr> frames; |
78 | 10.8k | frames.reserve(num_frames); |
79 | 10.8k | const uint8_t* frame_samples = samples.data(); |
80 | 42.9k | for (size_t i = 0; i < num_frames; ++i) { |
81 | 32.0k | frames.push_back(CreateAvifImage(width, height, 8, pixel_format, has_alpha, |
82 | 32.0k | frame_samples)); |
83 | 32.0k | frame_samples += |
84 | 32.0k | GetNumSamples(/*num_frames=*/1, width, height, pixel_format, has_alpha); |
85 | 32.0k | } |
86 | 10.8k | return frames; |
87 | 10.8k | } |
88 | | |
89 | | std::vector<ImagePtr> CreateAvifAnim16b(size_t num_frames, size_t width, |
90 | | size_t height, int depth, |
91 | | avifPixelFormat pixel_format, |
92 | | bool has_alpha, |
93 | 4.58k | const std::vector<uint16_t>& samples) { |
94 | 4.58k | std::vector<ImagePtr> frames; |
95 | 4.58k | frames.reserve(num_frames); |
96 | 4.58k | const uint16_t* frame_samples = samples.data(); |
97 | 17.0k | for (size_t i = 0; i < num_frames; ++i) { |
98 | 12.4k | frames.push_back( |
99 | 12.4k | CreateAvifImage(width, height, depth, pixel_format, has_alpha, |
100 | 12.4k | reinterpret_cast<const uint8_t*>(frame_samples))); |
101 | 12.4k | frame_samples += |
102 | 12.4k | GetNumSamples(/*num_frames=*/1, width, height, pixel_format, has_alpha); |
103 | 12.4k | } |
104 | 4.58k | return frames; |
105 | 4.58k | } |
106 | | |
107 | | EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads, |
108 | | int min_quantizer, int max_quantizer, |
109 | | int min_quantizer_alpha, int max_quantizer_alpha, |
110 | | int tile_rows_log2, int tile_cols_log2, |
111 | 54.7k | int speed) { |
112 | 54.7k | EncoderPtr encoder(avifEncoderCreate()); |
113 | 54.7k | if (encoder.get() == nullptr) { |
114 | 0 | return encoder; |
115 | 0 | } |
116 | 54.7k | encoder->codecChoice = codec_choice; |
117 | 54.7k | encoder->maxThreads = max_threads; |
118 | | // minQuantizer must be at most maxQuantizer. |
119 | 54.7k | encoder->minQuantizer = std::min(min_quantizer, max_quantizer); |
120 | 54.7k | encoder->maxQuantizer = std::max(min_quantizer, max_quantizer); |
121 | 54.7k | encoder->minQuantizerAlpha = |
122 | 54.7k | std::min(min_quantizer_alpha, max_quantizer_alpha); |
123 | 54.7k | encoder->maxQuantizerAlpha = |
124 | 54.7k | std::max(min_quantizer_alpha, max_quantizer_alpha); |
125 | 54.7k | encoder->tileRowsLog2 = tile_rows_log2; |
126 | 54.7k | encoder->tileColsLog2 = tile_cols_log2; |
127 | 54.7k | encoder->speed = speed; |
128 | 54.7k | return encoder; |
129 | 54.7k | } |
130 | | |
131 | | DecoderPtr CreateAvifDecoder(avifCodecChoice codec_choice, int max_threads, |
132 | | avifDecoderSource requested_source, |
133 | | bool allow_progressive, bool allow_incremental, |
134 | | bool ignore_exif, bool ignore_xmp, |
135 | | uint32_t image_size_limit, |
136 | | uint32_t image_dimension_limit, |
137 | | uint32_t image_count_limit, |
138 | 82.5k | avifStrictFlags strict_flags) { |
139 | 82.5k | DecoderPtr decoder(avifDecoderCreate()); |
140 | 82.5k | if (decoder.get() == nullptr) { |
141 | 0 | return decoder; |
142 | 0 | } |
143 | 82.5k | decoder->codecChoice = codec_choice; |
144 | 82.5k | decoder->maxThreads = max_threads; |
145 | 82.5k | decoder->requestedSource = requested_source; |
146 | 82.5k | decoder->allowProgressive = allow_progressive; |
147 | 82.5k | decoder->allowIncremental = allow_incremental; |
148 | 82.5k | decoder->ignoreExif = ignore_exif; |
149 | 82.5k | decoder->ignoreXMP = ignore_xmp; |
150 | 82.5k | decoder->imageSizeLimit = image_size_limit; |
151 | 82.5k | decoder->imageDimensionLimit = image_dimension_limit; |
152 | 82.5k | decoder->imageCountLimit = image_count_limit; |
153 | 82.5k | decoder->strictFlags = strict_flags; |
154 | 82.5k | return decoder; |
155 | 82.5k | } |
156 | | |
157 | 0 | ImagePtr AvifImageToUniquePtr(avifImage* image) { return ImagePtr(image); } |
158 | | |
159 | | DecoderPtr AddGainMapOptionsToDecoder( |
160 | 82.5k | DecoderPtr decoder, avifImageContentTypeFlags image_content_to_decode) { |
161 | 82.5k | decoder->imageContentToDecode = image_content_to_decode; |
162 | 82.5k | return decoder; |
163 | 82.5k | } |
164 | | |
165 | | ImagePtr AddGainMapToImage( |
166 | | ImagePtr image, ImagePtr gain_map, |
167 | | const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max0, |
168 | | const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max1, |
169 | | const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max2, |
170 | | uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1, |
171 | | uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0, |
172 | | uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2, |
173 | | int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2, |
174 | | uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2, |
175 | | int32_t alternate_offset_n0, int32_t alternate_offset_n1, |
176 | | int32_t alternate_offset_n2, uint32_t alternate_offset_d0, |
177 | | uint32_t alternate_offset_d1, uint32_t alternate_offset_d2, |
178 | | uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d, |
179 | | uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d, |
180 | 3.35k | bool use_base_color_space) { |
181 | 3.35k | image->gainMap = avifGainMapCreate(); |
182 | 3.35k | image->gainMap->image = gain_map.release(); |
183 | | |
184 | 3.35k | image->gainMap->gainMapMin[0] = gain_map_min_max0.first; |
185 | 3.35k | image->gainMap->gainMapMin[1] = gain_map_min_max1.first; |
186 | 3.35k | image->gainMap->gainMapMin[2] = gain_map_min_max2.first; |
187 | | |
188 | 3.35k | image->gainMap->gainMapMax[0] = gain_map_min_max0.second; |
189 | 3.35k | image->gainMap->gainMapMax[1] = gain_map_min_max1.second; |
190 | 3.35k | image->gainMap->gainMapMax[2] = gain_map_min_max2.second; |
191 | | |
192 | 3.35k | image->gainMap->gainMapGamma[0] = {gain_map_gamma_n0, gain_map_gamma_d0}; |
193 | 3.35k | image->gainMap->gainMapGamma[1] = {gain_map_gamma_n1, gain_map_gamma_d1}; |
194 | 3.35k | image->gainMap->gainMapGamma[2] = {gain_map_gamma_n2, gain_map_gamma_d2}; |
195 | | |
196 | 3.35k | image->gainMap->baseOffset[0] = {base_offset_n0, base_offset_d0}; |
197 | 3.35k | image->gainMap->baseOffset[1] = {base_offset_n1, base_offset_d1}; |
198 | 3.35k | image->gainMap->baseOffset[2] = {base_offset_n2, base_offset_d2}; |
199 | | |
200 | 3.35k | image->gainMap->alternateOffset[0] = {alternate_offset_n0, |
201 | 3.35k | alternate_offset_d0}; |
202 | 3.35k | image->gainMap->alternateOffset[1] = {alternate_offset_n1, |
203 | 3.35k | alternate_offset_d1}; |
204 | 3.35k | image->gainMap->alternateOffset[2] = {alternate_offset_n2, |
205 | 3.35k | alternate_offset_d2}; |
206 | | |
207 | 3.35k | image->gainMap->baseHdrHeadroom = {base_hdr_headroom_n, base_hdr_headroom_d}; |
208 | 3.35k | image->gainMap->alternateHdrHeadroom = {alternate_hdr_headroom_n, |
209 | 3.35k | alternate_hdr_headroom_d}; |
210 | 3.35k | image->gainMap->useBaseColorSpace = use_base_color_space; |
211 | | |
212 | 3.35k | return image; |
213 | 3.35k | } |
214 | | |
215 | | //------------------------------------------------------------------------------ |
216 | | |
217 | | size_t GetNumSamples(size_t num_frames, size_t width, size_t height, |
218 | 233k | avifPixelFormat pixel_format, bool has_alpha) { |
219 | 233k | const size_t num_luma_samples = width * height; |
220 | | |
221 | 233k | avifPixelFormatInfo pixel_format_info; |
222 | 233k | avifGetPixelFormatInfo(pixel_format, &pixel_format_info); |
223 | 233k | size_t num_chroma_samples = 0; |
224 | 233k | if (!pixel_format_info.monochrome) { |
225 | 122k | num_chroma_samples = 2 * |
226 | 122k | ((width + pixel_format_info.chromaShiftX) >> |
227 | 122k | pixel_format_info.chromaShiftX) * |
228 | 122k | ((height + pixel_format_info.chromaShiftY) >> |
229 | 122k | pixel_format_info.chromaShiftY); |
230 | 122k | } |
231 | | |
232 | 233k | size_t num_alpha_samples = 0; |
233 | 233k | if (has_alpha) { |
234 | 72.2k | num_alpha_samples = width * height; |
235 | 72.2k | } |
236 | | |
237 | 233k | return num_frames * |
238 | 233k | (num_luma_samples + num_chroma_samples + num_alpha_samples); |
239 | 233k | } |
240 | | |
241 | | //------------------------------------------------------------------------------ |
242 | | |
243 | 41.2k | std::vector<std::string> GetSeedDataDirs() { |
244 | 41.2k | const char* var = std::getenv("TEST_DATA_DIRS"); |
245 | 41.2k | std::vector<std::string> res; |
246 | 41.2k | if (var == nullptr || *var == 0) return res; |
247 | 41.2k | const char* var_start = var; |
248 | 1.97M | while (true) { |
249 | 1.97M | if (*var == 0 || *var == ';') { |
250 | 41.2k | res.push_back(std::string(var_start, var - var_start)); |
251 | 41.2k | if (*var == 0) break; |
252 | 0 | var_start = var + 1; |
253 | 0 | } |
254 | 1.93M | ++var; |
255 | 1.93M | } |
256 | 41.2k | return res; |
257 | 41.2k | } |
258 | | |
259 | | std::vector<std::string> GetTestImagesContents( |
260 | 11 | size_t max_file_size, const std::vector<avifAppFileFormat>& image_formats) { |
261 | | // Use an environment variable to get the test data directory because |
262 | | // fuzztest seeds are created before the main() function is called, so the |
263 | | // test has no chance to parse command line arguments. |
264 | 11 | const std::vector<std::string> test_data_dirs = GetSeedDataDirs(); |
265 | 11 | if (test_data_dirs.empty()) { |
266 | | // Only a warning because this can happen when running the binary with |
267 | | // --list_fuzz_tests (such as with gtest_discover_tests() in cmake). |
268 | 0 | std::cerr << "WARNING: TEST_DATA_DIRS env variable not set, unable to read " |
269 | 0 | "seed files\n"; |
270 | 0 | return {}; |
271 | 0 | } |
272 | | |
273 | 11 | std::vector<std::string> seeds; |
274 | 11 | for (const std::string& test_data_dir : test_data_dirs) { |
275 | 11 | std::cout << "Reading seeds from " << test_data_dir |
276 | 11 | << " (non recursively)\n"; |
277 | 11 | auto tuple_vector = fuzztest::ReadFilesFromDirectory(test_data_dir); |
278 | 11 | seeds.reserve(tuple_vector.size()); |
279 | 1.12k | for (auto& [file_content] : tuple_vector) { |
280 | 1.12k | if (file_content.size() > max_file_size) continue; |
281 | 1.08k | if (!image_formats.empty()) { |
282 | 1.08k | const avifAppFileFormat format = avifGuessBufferFileFormat( |
283 | 1.08k | reinterpret_cast<const uint8_t*>(file_content.data()), |
284 | 1.08k | file_content.size()); |
285 | 1.08k | if (std::find(image_formats.begin(), image_formats.end(), format) == |
286 | 1.08k | image_formats.end()) { |
287 | 407 | continue; |
288 | 407 | } |
289 | 1.08k | } |
290 | | |
291 | 682 | seeds.push_back(std::move(file_content)); |
292 | 682 | } |
293 | 11 | } |
294 | 11 | if (seeds.empty()) { |
295 | 0 | std::cerr << "ERROR: no files found that match the given file size and " |
296 | 0 | "format criteria\n"; |
297 | 0 | std::abort(); |
298 | 0 | } |
299 | 11 | std::cout << "Returning " << seeds.size() << " seed images\n"; |
300 | 11 | return seeds; |
301 | 11 | } |
302 | | |
303 | | //------------------------------------------------------------------------------ |
304 | | |
305 | | } // namespace testutil |
306 | | } // namespace avif |