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