test_image.cc (16333B)
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 "lib/jxl/test_image.h" 7 8 #include <jxl/encode.h> 9 10 #include <algorithm> 11 #include <cstring> 12 #include <utility> 13 14 #include "lib/extras/dec/color_description.h" 15 #include "lib/extras/dec/color_hints.h" 16 #include "lib/extras/dec/decode.h" 17 #include "lib/jxl/base/byte_order.h" 18 #include "lib/jxl/base/random.h" 19 #include "lib/jxl/base/span.h" 20 #include "lib/jxl/base/status.h" 21 #include "lib/jxl/color_encoding_internal.h" 22 23 namespace jxl { 24 namespace test { 25 26 namespace { 27 28 void StoreValue(float val, size_t bits_per_sample, JxlPixelFormat format, 29 uint8_t** out) { 30 const float mul = (1u << bits_per_sample) - 1; 31 if (format.data_type == JXL_TYPE_UINT8) { 32 **out = val * mul; 33 } else if (format.data_type == JXL_TYPE_UINT16) { 34 uint16_t uval = val * mul; 35 if (SwapEndianness(format.endianness)) { 36 uval = JXL_BSWAP16(uval); 37 } 38 memcpy(*out, &uval, 2); 39 } else if (format.data_type == JXL_TYPE_FLOAT) { 40 // TODO(szabadka) Add support for custom bits / exponent bits floats. 41 if (SwapEndianness(format.endianness)) { 42 val = BSwapFloat(val); 43 } 44 memcpy(*out, &val, 4); 45 } else { 46 // TODO(szabadka) Add support for FLOAT16. 47 } 48 *out += extras::PackedImage::BitsPerChannel(format.data_type) / 8; 49 } 50 51 void FillPackedImage(size_t bits_per_sample, uint16_t seed, 52 extras::PackedImage* image) { 53 const size_t xsize = image->xsize; 54 const size_t ysize = image->ysize; 55 const JxlPixelFormat format = image->format; 56 57 // Cause more significant image difference for successive seeds. 58 Rng generator(seed); 59 60 // Returns random integer in interval [0, max_value) 61 auto rngu = [&generator](size_t max_value) -> size_t { 62 return generator.UniformU(0, max_value); 63 }; 64 65 // Returns random float in interval [0.0, max_value) 66 auto rngf = [&generator](float max_value) { 67 return generator.UniformF(0.0f, max_value); 68 }; 69 70 // Dark background gradient color 71 float r0 = rngf(0.5f); 72 float g0 = rngf(0.5f); 73 float b0 = rngf(0.5f); 74 float a0 = rngf(0.5f); 75 float r1 = rngf(0.5f); 76 float g1 = rngf(0.5f); 77 float b1 = rngf(0.5f); 78 float a1 = rngf(0.5f); 79 80 // Circle with different color 81 size_t circle_x = rngu(xsize); 82 size_t circle_y = rngu(ysize); 83 size_t circle_r = rngu(std::min(xsize, ysize)); 84 85 // Rectangle with random noise 86 size_t rect_x0 = rngu(xsize); 87 size_t rect_y0 = rngu(ysize); 88 size_t rect_x1 = rngu(xsize); 89 size_t rect_y1 = rngu(ysize); 90 if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1); 91 if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1); 92 93 // Create pixel content to test, actual content does not matter as long as it 94 // can be compared after roundtrip. 95 const float imul16 = 1.0f / 65536.0f; 96 for (size_t y = 0; y < ysize; y++) { 97 uint8_t* out = 98 reinterpret_cast<uint8_t*>(image->pixels()) + y * image->stride; 99 for (size_t x = 0; x < xsize; x++) { 100 float r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize; 101 float g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize; 102 float b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize; 103 float a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize; 104 // put some shape in there for visual debugging 105 if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) < 106 circle_r * circle_r) { 107 r = std::min(1.0f, ((65535 - x * y) ^ seed) * imul16); 108 g = std::min(1.0f, ((x << 8) + y + seed) * imul16); 109 b = std::min(1.0f, ((y << 8) + x * seed) * imul16); 110 a = std::min(1.0f, (32768 + x * 256 - y) * imul16); 111 } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) { 112 r = rngf(1.0f); 113 g = rngf(1.0f); 114 b = rngf(1.0f); 115 a = rngf(1.0f); 116 } 117 if (format.num_channels == 1) { 118 StoreValue(g, bits_per_sample, format, &out); 119 } else if (format.num_channels == 2) { 120 StoreValue(g, bits_per_sample, format, &out); 121 StoreValue(a, bits_per_sample, format, &out); 122 } else if (format.num_channels == 3) { 123 StoreValue(r, bits_per_sample, format, &out); 124 StoreValue(g, bits_per_sample, format, &out); 125 StoreValue(b, bits_per_sample, format, &out); 126 } else if (format.num_channels == 4) { 127 StoreValue(r, bits_per_sample, format, &out); 128 StoreValue(g, bits_per_sample, format, &out); 129 StoreValue(b, bits_per_sample, format, &out); 130 StoreValue(a, bits_per_sample, format, &out); 131 } 132 } 133 } 134 } 135 136 } // namespace 137 138 std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize, 139 size_t num_channels, uint16_t seed) { 140 // Cause more significant image difference for successive seeds. 141 Rng generator(seed); 142 143 // Returns random integer in interval [0, max_value) 144 auto rng = [&generator](size_t max_value) -> size_t { 145 return generator.UniformU(0, max_value); 146 }; 147 148 // Dark background gradient color 149 uint16_t r0 = rng(32768); 150 uint16_t g0 = rng(32768); 151 uint16_t b0 = rng(32768); 152 uint16_t a0 = rng(32768); 153 uint16_t r1 = rng(32768); 154 uint16_t g1 = rng(32768); 155 uint16_t b1 = rng(32768); 156 uint16_t a1 = rng(32768); 157 158 // Circle with different color 159 size_t circle_x = rng(xsize); 160 size_t circle_y = rng(ysize); 161 size_t circle_r = rng(std::min(xsize, ysize)); 162 163 // Rectangle with random noise 164 size_t rect_x0 = rng(xsize); 165 size_t rect_y0 = rng(ysize); 166 size_t rect_x1 = rng(xsize); 167 size_t rect_y1 = rng(ysize); 168 if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1); 169 if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1); 170 171 size_t num_pixels = xsize * ysize; 172 // 16 bits per channel, big endian, 4 channels 173 std::vector<uint8_t> pixels(num_pixels * num_channels * 2); 174 // Create pixel content to test, actual content does not matter as long as it 175 // can be compared after roundtrip. 176 for (size_t y = 0; y < ysize; y++) { 177 for (size_t x = 0; x < xsize; x++) { 178 uint16_t r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize; 179 uint16_t g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize; 180 uint16_t b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize; 181 uint16_t a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize; 182 // put some shape in there for visual debugging 183 if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) < 184 circle_r * circle_r) { 185 r = (65535 - x * y) ^ seed; 186 g = (x << 8) + y + seed; 187 b = (y << 8) + x * seed; 188 a = 32768 + x * 256 - y; 189 } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) { 190 r = rng(65536); 191 g = rng(65536); 192 b = rng(65536); 193 a = rng(65536); 194 } 195 size_t i = (y * xsize + x) * 2 * num_channels; 196 pixels[i + 0] = (r >> 8); 197 pixels[i + 1] = (r & 255); 198 if (num_channels >= 2) { 199 // This may store what is called 'g' in the alpha channel of a 2-channel 200 // image, but that's ok since the content is arbitrary 201 pixels[i + 2] = (g >> 8); 202 pixels[i + 3] = (g & 255); 203 } 204 if (num_channels >= 3) { 205 pixels[i + 4] = (b >> 8); 206 pixels[i + 5] = (b & 255); 207 } 208 if (num_channels >= 4) { 209 pixels[i + 6] = (a >> 8); 210 pixels[i + 7] = (a & 255); 211 } 212 } 213 } 214 return pixels; 215 } 216 217 TestImage::TestImage() { 218 SetChannels(3); 219 SetAllBitDepths(8); 220 SetColorEncoding("RGB_D65_SRG_Rel_SRG"); 221 } 222 223 TestImage& TestImage::DecodeFromBytes(const std::vector<uint8_t>& bytes) { 224 ColorEncoding c_enc; 225 JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding)); 226 extras::ColorHints color_hints; 227 color_hints.Add("color_space", Description(c_enc)); 228 JXL_CHECK(extras::DecodeBytes(Bytes(bytes), color_hints, &ppf_)); 229 return *this; 230 } 231 232 TestImage& TestImage::ClearMetadata() { 233 ppf_.metadata = extras::PackedMetadata(); 234 return *this; 235 } 236 237 TestImage& TestImage::SetDimensions(size_t xsize, size_t ysize) { 238 if (xsize <= ppf_.info.xsize && ysize <= ppf_.info.ysize) { 239 for (auto& frame : ppf_.frames) { 240 CropLayerInfo(xsize, ysize, &frame.frame_info.layer_info); 241 CropImage(xsize, ysize, &frame.color); 242 for (auto& ec : frame.extra_channels) { 243 CropImage(xsize, ysize, &ec); 244 } 245 } 246 } else { 247 JXL_CHECK(ppf_.info.xsize == 0 && ppf_.info.ysize == 0); 248 } 249 ppf_.info.xsize = xsize; 250 ppf_.info.ysize = ysize; 251 return *this; 252 } 253 254 TestImage& TestImage::SetChannels(size_t num_channels) { 255 JXL_CHECK(ppf_.frames.empty()); 256 JXL_CHECK(!ppf_.preview_frame); 257 ppf_.info.num_color_channels = num_channels < 3 ? 1 : 3; 258 ppf_.info.num_extra_channels = num_channels - ppf_.info.num_color_channels; 259 if (ppf_.info.num_extra_channels > 0 && ppf_.info.alpha_bits == 0) { 260 ppf_.info.alpha_bits = ppf_.info.bits_per_sample; 261 ppf_.info.alpha_exponent_bits = ppf_.info.exponent_bits_per_sample; 262 } 263 ppf_.extra_channels_info.clear(); 264 for (size_t i = 1; i < ppf_.info.num_extra_channels; ++i) { 265 extras::PackedExtraChannel ec; 266 ec.index = i; 267 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &ec.ec_info); 268 if (ec.ec_info.bits_per_sample == 0) { 269 ec.ec_info.bits_per_sample = ppf_.info.bits_per_sample; 270 ec.ec_info.exponent_bits_per_sample = ppf_.info.exponent_bits_per_sample; 271 } 272 ppf_.extra_channels_info.emplace_back(std::move(ec)); 273 } 274 format_.num_channels = std::min(static_cast<size_t>(4), num_channels); 275 if (ppf_.info.num_color_channels == 1 && 276 ppf_.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) { 277 SetColorEncoding("Gra_D65_Rel_SRG"); 278 } 279 return *this; 280 } 281 282 // Sets the same bit depth on color, alpha and all extra channels. 283 TestImage& TestImage::SetAllBitDepths(uint32_t bits_per_sample, 284 uint32_t exponent_bits_per_sample) { 285 ppf_.info.bits_per_sample = bits_per_sample; 286 ppf_.info.exponent_bits_per_sample = exponent_bits_per_sample; 287 if (ppf_.info.num_extra_channels > 0) { 288 ppf_.info.alpha_bits = bits_per_sample; 289 ppf_.info.alpha_exponent_bits = exponent_bits_per_sample; 290 } 291 for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) { 292 extras::PackedExtraChannel& ec = ppf_.extra_channels_info[i]; 293 ec.ec_info.bits_per_sample = bits_per_sample; 294 ec.ec_info.exponent_bits_per_sample = exponent_bits_per_sample; 295 } 296 format_.data_type = DefaultDataType(ppf_.info); 297 return *this; 298 } 299 300 TestImage& TestImage::SetDataType(JxlDataType data_type) { 301 format_.data_type = data_type; 302 return *this; 303 } 304 305 TestImage& TestImage::SetEndianness(JxlEndianness endianness) { 306 format_.endianness = endianness; 307 return *this; 308 } 309 310 TestImage& TestImage::SetRowAlignment(size_t align) { 311 format_.align = align; 312 return *this; 313 } 314 315 TestImage& TestImage::SetColorEncoding(const std::string& description) { 316 JXL_CHECK(ParseDescription(description, &ppf_.color_encoding)); 317 ColorEncoding c_enc; 318 JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding)); 319 IccBytes icc = c_enc.ICC(); 320 JXL_CHECK(!icc.empty()); 321 ppf_.icc.assign(icc.begin(), icc.end()); 322 return *this; 323 } 324 325 TestImage& TestImage::CoalesceGIFAnimationWithAlpha() { 326 extras::PackedFrame canvas = ppf_.frames[0].Copy(); 327 JXL_CHECK(canvas.color.format.num_channels == 3); 328 JXL_CHECK(canvas.color.format.data_type == JXL_TYPE_UINT8); 329 JXL_CHECK(canvas.extra_channels.size() == 1); 330 for (size_t i = 1; i < ppf_.frames.size(); i++) { 331 const extras::PackedFrame& frame = ppf_.frames[i]; 332 JXL_CHECK(frame.extra_channels.size() == 1); 333 const JxlLayerInfo& layer_info = frame.frame_info.layer_info; 334 extras::PackedFrame rendered = canvas.Copy(); 335 uint8_t* pixels_rendered = 336 reinterpret_cast<uint8_t*>(rendered.color.pixels()); 337 const uint8_t* pixels_frame = 338 reinterpret_cast<const uint8_t*>(frame.color.pixels()); 339 uint8_t* alpha_rendered = 340 reinterpret_cast<uint8_t*>(rendered.extra_channels[0].pixels()); 341 const uint8_t* alpha_frame = 342 reinterpret_cast<const uint8_t*>(frame.extra_channels[0].pixels()); 343 for (size_t y = 0; y < frame.color.ysize; y++) { 344 for (size_t x = 0; x < frame.color.xsize; x++) { 345 size_t idx_frame = y * frame.color.xsize + x; 346 size_t idx_rendered = ((layer_info.crop_y0 + y) * rendered.color.xsize + 347 (layer_info.crop_x0 + x)); 348 if (alpha_frame[idx_frame] != 0) { 349 memcpy(&pixels_rendered[idx_rendered * 3], 350 &pixels_frame[idx_frame * 3], 3); 351 alpha_rendered[idx_rendered] = alpha_frame[idx_frame]; 352 } 353 } 354 } 355 if (layer_info.save_as_reference != 0) { 356 canvas = rendered.Copy(); 357 } 358 ppf_.frames[i] = std::move(rendered); 359 } 360 return *this; 361 } 362 363 TestImage::Frame::Frame(TestImage* parent, bool is_preview, size_t index) 364 : parent_(parent), is_preview_(is_preview), index_(index) {} 365 366 void TestImage::Frame::ZeroFill() { 367 memset(frame().color.pixels(), 0, frame().color.pixels_size); 368 for (auto& ec : frame().extra_channels) { 369 memset(ec.pixels(), 0, ec.pixels_size); 370 } 371 } 372 373 void TestImage::Frame::RandomFill(uint16_t seed) { 374 FillPackedImage(ppf().info.bits_per_sample, seed, &frame().color); 375 for (size_t i = 0; i < ppf().extra_channels_info.size(); ++i) { 376 FillPackedImage(ppf().extra_channels_info[i].ec_info.bits_per_sample, 377 seed + 1 + i, &frame().extra_channels[i]); 378 } 379 } 380 381 void TestImage::Frame::SetValue(size_t y, size_t x, size_t c, float val) { 382 const extras::PackedImage& color = frame().color; 383 JxlPixelFormat format = color.format; 384 JXL_CHECK(y < ppf().info.ysize); 385 JXL_CHECK(x < ppf().info.xsize); 386 JXL_CHECK(c < format.num_channels); 387 size_t pwidth = extras::PackedImage::BitsPerChannel(format.data_type) / 8; 388 size_t idx = ((y * color.xsize + x) * format.num_channels + c) * pwidth; 389 uint8_t* pixels = reinterpret_cast<uint8_t*>(frame().color.pixels()); 390 uint8_t* p = pixels + idx; 391 StoreValue(val, ppf().info.bits_per_sample, frame().color.format, &p); 392 } 393 394 TestImage::Frame TestImage::AddFrame() { 395 size_t index = ppf_.frames.size(); 396 extras::PackedFrame frame(ppf_.info.xsize, ppf_.info.ysize, format_); 397 for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) { 398 JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0}; 399 extras::PackedImage image(ppf_.info.xsize, ppf_.info.ysize, ec_format); 400 frame.extra_channels.emplace_back(std::move(image)); 401 } 402 ppf_.frames.emplace_back(std::move(frame)); 403 return Frame(this, false, index); 404 } 405 406 TestImage::Frame TestImage::AddPreview(size_t xsize, size_t ysize) { 407 extras::PackedFrame frame(xsize, ysize, format_); 408 for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) { 409 JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0}; 410 extras::PackedImage image(xsize, ysize, ec_format); 411 frame.extra_channels.emplace_back(std::move(image)); 412 } 413 ppf_.preview_frame = make_unique<extras::PackedFrame>(std::move(frame)); 414 return Frame(this, true, 0); 415 } 416 417 void TestImage::CropLayerInfo(size_t xsize, size_t ysize, JxlLayerInfo* info) { 418 if (info->crop_x0 < static_cast<ssize_t>(xsize)) { 419 info->xsize = std::min<size_t>(info->xsize, xsize - info->crop_x0); 420 } else { 421 info->xsize = 0; 422 } 423 if (info->crop_y0 < static_cast<ssize_t>(ysize)) { 424 info->ysize = std::min<size_t>(info->ysize, ysize - info->crop_y0); 425 } else { 426 info->ysize = 0; 427 } 428 } 429 430 void TestImage::CropImage(size_t xsize, size_t ysize, 431 extras::PackedImage* image) { 432 size_t new_stride = (image->stride / image->xsize) * xsize; 433 uint8_t* buf = reinterpret_cast<uint8_t*>(image->pixels()); 434 for (size_t y = 0; y < ysize; ++y) { 435 memmove(&buf[y * new_stride], &buf[y * image->stride], new_stride); 436 } 437 image->xsize = xsize; 438 image->ysize = ysize; 439 image->stride = new_stride; 440 image->pixels_size = ysize * new_stride; 441 } 442 443 JxlDataType TestImage::DefaultDataType(const JxlBasicInfo& info) { 444 if (info.bits_per_sample == 16 && info.exponent_bits_per_sample == 5) { 445 return JXL_TYPE_FLOAT16; 446 } else if (info.exponent_bits_per_sample > 0 || info.bits_per_sample > 16) { 447 return JXL_TYPE_FLOAT; 448 } else if (info.bits_per_sample > 8) { 449 return JXL_TYPE_UINT16; 450 } else { 451 return JXL_TYPE_UINT8; 452 } 453 } 454 455 } // namespace test 456 } // namespace jxl