streaming_fuzzer.cc (10636B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <jxl/decode.h> 7 #include <jxl/decode_cxx.h> 8 #include <jxl/encode.h> 9 #include <jxl/encode_cxx.h> 10 #include <jxl/thread_parallel_runner.h> 11 #include <jxl/thread_parallel_runner_cxx.h> 12 #include <limits.h> 13 #include <stdint.h> 14 #include <stdlib.h> 15 #include <string.h> 16 17 #include <algorithm> 18 #include <cstdint> 19 #include <functional> 20 #include <hwy/targets.h> 21 #include <random> 22 #include <vector> 23 24 #include "lib/jxl/base/status.h" 25 #include "lib/jxl/test_image.h" 26 27 namespace { 28 29 struct FuzzSpec { 30 uint32_t xsize; 31 uint32_t ysize; 32 bool grayscale; 33 bool alpha; 34 uint8_t bit_depth; // 1 - 16 35 36 struct IntOptionSpec { 37 JxlEncoderFrameSettingId flag; 38 int min; 39 int max; 40 int value; 41 }; 42 43 std::vector<IntOptionSpec> int_options = { 44 IntOptionSpec{JXL_ENC_FRAME_SETTING_EFFORT, 1, 9, 0}, 45 IntOptionSpec{JXL_ENC_FRAME_SETTING_DECODING_SPEED, 0, 4, 0}, 46 IntOptionSpec{JXL_ENC_FRAME_SETTING_NOISE, -1, 1, 0}, 47 IntOptionSpec{JXL_ENC_FRAME_SETTING_DOTS, -1, 1, 0}, 48 IntOptionSpec{JXL_ENC_FRAME_SETTING_PATCHES, -1, 1, 0}, 49 IntOptionSpec{JXL_ENC_FRAME_SETTING_EPF, -1, 3, 0}, 50 IntOptionSpec{JXL_ENC_FRAME_SETTING_GABORISH, -1, 1, 0}, 51 IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR, -1, 1, 0}, 52 IntOptionSpec{JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE, -1, 1, 0}, 53 IntOptionSpec{JXL_ENC_FRAME_SETTING_RESPONSIVE, -1, 1, 0}, 54 IntOptionSpec{JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, -1, 1, 0}, 55 IntOptionSpec{JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, -1, 1, 0}, 56 IntOptionSpec{JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, -1, 1, 0}, 57 IntOptionSpec{JXL_ENC_FRAME_SETTING_PALETTE_COLORS, -1, 255, 0}, 58 IntOptionSpec{JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, -1, 1, 0}, 59 IntOptionSpec{JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, -1, 2, 0}, 60 IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, -1, 41, 0}, 61 IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, -1, 3, 0}, 62 IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, -1, 15, 0}, 63 IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, -1, 11, 0}, 64 }; 65 66 struct FloatOptionSpec { 67 JxlEncoderFrameSettingId flag; 68 float possible_values[4]; 69 float value; 70 }; 71 72 std::vector<FloatOptionSpec> float_options = { 73 FloatOptionSpec{JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 74 {-1, 0, 50, 100}, 75 -1}, 76 FloatOptionSpec{JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 77 {-1, 0, 50, 100}, 78 -1}, 79 FloatOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 80 {-1, 0, 50, 100}, 81 -1}, 82 FloatOptionSpec{ 83 JXL_ENC_FRAME_SETTING_PHOTON_NOISE, {-1, 200, 1600, 10000}, -1}, 84 }; 85 86 uint8_t num_threads; 87 88 float distance; // 0.01 - 25 89 90 // Tiled to cover the entire image area. 91 uint16_t pixel_data[4][64][64]; 92 93 static FuzzSpec FromData(const uint8_t* data, size_t len) { 94 size_t pos = 0; 95 auto u8 = [&]() -> uint8_t { 96 if (pos == len) return 0; 97 return data[pos++]; 98 }; 99 auto b1 = [&]() -> bool { return static_cast<bool>(u8() % 2); }; 100 auto u16 = [&]() -> uint16_t { return (uint16_t{u8()} << 8) | u8(); }; 101 FuzzSpec spec; 102 spec.xsize = uint32_t{u16()} + 1; 103 spec.ysize = uint32_t{u16()} + 1; 104 constexpr uint64_t kMaxSize = 1 << 24; 105 if (spec.xsize * uint64_t{spec.ysize} > kMaxSize) { 106 spec.ysize = kMaxSize / spec.xsize; 107 } 108 spec.grayscale = b1(); 109 spec.alpha = b1(); 110 spec.bit_depth = u8() % 16 + 1; 111 // constants chosen so to cover the entire 0.01 - 25 range. 112 spec.distance = u8() % 2 ? 0.0 : 0.01 + 0.00038132 * u16(); 113 114 JXL_CHECK(spec.float_options[2].flag == 115 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT); 116 JXL_CHECK(spec.int_options[15].flag == 117 JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM); 118 if (spec.distance != 0 || spec.int_options[15].value == 0) { 119 spec.float_options[2].possible_values[1] = 1; 120 } 121 122 spec.num_threads = u8(); 123 124 for (auto& int_opt : spec.int_options) { 125 int_opt.value = u8() % (int_opt.max - int_opt.min + 1) + int_opt.min; 126 } 127 for (auto& float_opt : spec.float_options) { 128 float_opt.value = float_opt.possible_values[u8() % 4]; 129 } 130 131 for (auto& x : spec.pixel_data) { 132 for (auto& y : x) { 133 for (auto& p : y) { 134 p = u16(); 135 } 136 } 137 } 138 139 return spec; 140 } 141 }; 142 143 std::vector<uint8_t> Encode(const FuzzSpec& spec, bool streaming) { 144 auto runner = JxlThreadParallelRunnerMake(nullptr, spec.num_threads); 145 JxlEncoderPtr enc_ptr = JxlEncoderMake(/*memory_manager=*/nullptr); 146 JxlEncoder* enc = enc_ptr.get(); 147 148 JXL_CHECK(JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner, 149 runner.get()) == JXL_ENC_SUCCESS); 150 JxlEncoderFrameSettings* frame_settings = 151 JxlEncoderFrameSettingsCreate(enc, nullptr); 152 153 JXL_CHECK(JxlEncoderSetFrameDistance(frame_settings, spec.distance) == 154 JXL_ENC_SUCCESS); 155 156 for (const auto& opt : spec.int_options) { 157 JXL_CHECK(JxlEncoderFrameSettingsSetOption(frame_settings, opt.flag, 158 opt.value) == JXL_ENC_SUCCESS); 159 } 160 for (const auto& opt : spec.float_options) { 161 if (opt.value != -1) { 162 JXL_CHECK(JxlEncoderFrameSettingsSetFloatOption( 163 frame_settings, opt.flag, opt.value) == JXL_ENC_SUCCESS); 164 } 165 } 166 167 JXL_CHECK(JxlEncoderFrameSettingsSetOption( 168 frame_settings, JXL_ENC_FRAME_SETTING_BUFFERING, 169 streaming ? 3 : 0) == JXL_ENC_SUCCESS); 170 171 JxlBasicInfo basic_info; 172 JxlEncoderInitBasicInfo(&basic_info); 173 basic_info.num_color_channels = spec.grayscale ? 1 : 3; 174 basic_info.xsize = spec.xsize; 175 basic_info.ysize = spec.ysize; 176 basic_info.bits_per_sample = spec.bit_depth; 177 basic_info.uses_original_profile = JXL_FALSE; 178 uint32_t nchan = basic_info.num_color_channels; 179 if (spec.alpha) { 180 nchan += 1; 181 basic_info.alpha_bits = spec.bit_depth; 182 basic_info.num_extra_channels = 1; 183 } 184 JXL_CHECK(JxlEncoderSetBasicInfo(enc, &basic_info) == JXL_ENC_SUCCESS); 185 if (spec.alpha) { 186 JxlExtraChannelInfo info; 187 memset(&info, 0, sizeof(info)); 188 info.type = JxlExtraChannelType::JXL_CHANNEL_ALPHA; 189 info.bits_per_sample = spec.bit_depth; 190 JxlEncoderSetExtraChannelInfo(enc, 0, &info); 191 } 192 JxlColorEncoding color_encoding; 193 memset(&color_encoding, 0, sizeof(color_encoding)); 194 color_encoding.color_space = spec.grayscale 195 ? JxlColorSpace::JXL_COLOR_SPACE_GRAY 196 : JxlColorSpace::JXL_COLOR_SPACE_RGB; 197 color_encoding.transfer_function = 198 JxlTransferFunction::JXL_TRANSFER_FUNCTION_SRGB; 199 color_encoding.primaries = JxlPrimaries::JXL_PRIMARIES_2100; 200 color_encoding.white_point = JxlWhitePoint::JXL_WHITE_POINT_D65; 201 color_encoding.rendering_intent = 202 JxlRenderingIntent::JXL_RENDERING_INTENT_RELATIVE; 203 JXL_CHECK(JxlEncoderSetColorEncoding(enc, &color_encoding) == 204 JXL_ENC_SUCCESS); 205 206 JxlFrameHeader frame_header; 207 JxlEncoderInitFrameHeader(&frame_header); 208 // TODO(szabadka) Add more frame header options. 209 JXL_CHECK(JxlEncoderSetFrameHeader(frame_settings, &frame_header) == 210 JXL_ENC_SUCCESS); 211 JxlPixelFormat pixelformat = {nchan, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}; 212 std::vector<uint16_t> pixels(spec.xsize * static_cast<uint64_t>(spec.ysize) * 213 nchan); 214 for (size_t y = 0; y < spec.ysize; y++) { 215 for (size_t x = 0; x < spec.xsize; x++) { 216 for (size_t c = 0; c < nchan; c++) { 217 pixels[(y * spec.xsize + x) * nchan + c] = 218 spec.pixel_data[c][y % 64][x % 64]; 219 } 220 } 221 } 222 JXL_CHECK(JxlEncoderAddImageFrame(frame_settings, &pixelformat, pixels.data(), 223 pixels.size() * sizeof(uint16_t)) == 224 JXL_ENC_SUCCESS); 225 JxlEncoderCloseInput(enc); 226 // Reading compressed output 227 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 228 std::vector<uint8_t> buf(1024); 229 size_t written = 0; 230 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 231 buf.resize(buf.size() * 2); 232 uint8_t* next_out = buf.data() + written; 233 size_t avail_out = buf.size() - written; 234 process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); 235 written = next_out - buf.data(); 236 } 237 JXL_CHECK(process_result == JXL_ENC_SUCCESS); 238 buf.resize(written); 239 240 return buf; 241 } 242 243 std::vector<float> Decode(const std::vector<uint8_t>& data) { 244 // Multi-threaded parallel runner. 245 auto dec = JxlDecoderMake(nullptr); 246 JXL_CHECK(JxlDecoderSubscribeEvents( 247 dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) == 248 JXL_DEC_SUCCESS); 249 250 JxlBasicInfo info; 251 JxlPixelFormat format = {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; 252 253 std::vector<float> pixels; 254 255 JxlDecoderSetInput(dec.get(), data.data(), data.size()); 256 JxlDecoderCloseInput(dec.get()); 257 258 for (;;) { 259 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 260 261 if (status == JXL_DEC_BASIC_INFO) { 262 JXL_CHECK(JxlDecoderGetBasicInfo(dec.get(), &info) == JXL_DEC_SUCCESS); 263 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 264 size_t buffer_size; 265 JXL_CHECK(JxlDecoderImageOutBufferSize(dec.get(), &format, 266 &buffer_size) == JXL_DEC_SUCCESS); 267 pixels.resize(buffer_size / sizeof(float)); 268 void* pixels_buffer = static_cast<void*>(pixels.data()); 269 size_t pixels_buffer_size = pixels.size() * sizeof(float); 270 JXL_CHECK(JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, 271 pixels_buffer_size) == 272 JXL_DEC_SUCCESS); 273 } else if (status == JXL_DEC_FULL_IMAGE || status == JXL_DEC_SUCCESS) { 274 return pixels; 275 } else { 276 // Unexpected status 277 JXL_CHECK(false); 278 } 279 } 280 } 281 282 int TestOneInput(const uint8_t* data, size_t size) { 283 auto spec = FuzzSpec::FromData(data, size); 284 auto enc_default = Encode(spec, false); 285 auto enc_streaming = Encode(spec, true); 286 auto dec_default = Decode(enc_default); 287 auto dec_streaming = Decode(enc_streaming); 288 if (dec_default != dec_streaming) { 289 return 1; 290 } 291 return 0; 292 } 293 294 } // namespace 295 296 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 297 return TestOneInput(data, size); 298 }