jxlinfo.c (17596B)
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 // This example prints information from the main codestream header. 7 8 #include <inttypes.h> 9 #include <jxl/decode.h> 10 #include <stdint.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 int PrintBasicInfo(FILE* file, int verbose) { 16 uint8_t* data = NULL; 17 size_t data_size = 0; 18 // In how large chunks to read from the file and try decoding the basic info. 19 const size_t chunk_size = 2048; 20 21 JxlDecoder* dec = JxlDecoderCreate(NULL); 22 if (!dec) { 23 fprintf(stderr, "JxlDecoderCreate failed\n"); 24 return 0; 25 } 26 27 JxlDecoderSetKeepOrientation(dec, 1); 28 JxlDecoderSetCoalescing(dec, JXL_FALSE); 29 30 if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents( 31 dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | 32 JXL_DEC_FRAME | JXL_DEC_BOX)) { 33 fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); 34 JxlDecoderDestroy(dec); 35 return 0; 36 } 37 38 JxlBasicInfo info; 39 int seen_basic_info = 0; 40 JxlFrameHeader frame_header; 41 int framecount = 0; 42 float total_duration = 0.f; 43 44 for (;;) { 45 // The first time, this will output JXL_DEC_NEED_MORE_INPUT because no 46 // input is set yet, this is ok since the input is set when handling this 47 // event. 48 JxlDecoderStatus status = JxlDecoderProcessInput(dec); 49 50 if (status == JXL_DEC_ERROR) { 51 fprintf(stderr, "Decoder error\n"); 52 break; 53 } else if (status == JXL_DEC_NEED_MORE_INPUT) { 54 // The first time there is nothing to release and it returns 0, but that 55 // is ok. 56 size_t remaining = JxlDecoderReleaseInput(dec); 57 // move any remaining bytes to the front if necessary 58 if (remaining != 0) { 59 memmove(data, data + data_size - remaining, remaining); 60 } 61 // resize the buffer to append one more chunk of data 62 // TODO(lode): avoid unnecessary reallocations 63 data = (uint8_t*)realloc(data, remaining + chunk_size); 64 // append bytes read from the file behind the remaining bytes 65 size_t read_size = fread(data + remaining, 1, chunk_size, file); 66 if (read_size == 0 && feof(file)) { 67 fprintf(stderr, "Unexpected EOF\n"); 68 break; 69 } 70 data_size = remaining + read_size; 71 JxlDecoderSetInput(dec, data, data_size); 72 if (feof(file)) JxlDecoderCloseInput(dec); 73 } else if (status == JXL_DEC_SUCCESS) { 74 // Finished all processing. 75 break; 76 } else if (status == JXL_DEC_BASIC_INFO) { 77 if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) { 78 fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); 79 break; 80 } 81 82 seen_basic_info = 1; 83 84 printf("JPEG XL %s, %ux%u, %s", 85 info.have_animation ? "animation" : "image", info.xsize, 86 info.ysize, 87 info.uses_original_profile ? "(possibly) lossless" : "lossy"); 88 printf(", %d-bit ", info.bits_per_sample); 89 if (info.exponent_bits_per_sample) { 90 printf("float (%d exponent bits) ", info.exponent_bits_per_sample); 91 } 92 int cmyk = 0; 93 const char* const ec_type_names[] = { 94 "Alpha", "Depth", "Spotcolor", "Selection", "Black", 95 "CFA", "Thermal", "Reserved0", "Reserved1", "Reserved2", 96 "Reserved3", "Reserved4", "Reserved5", "Reserved6", "Reserved7", 97 "Unknown", "Optional"}; 98 const size_t ec_type_names_size = 99 sizeof(ec_type_names) / sizeof(ec_type_names[0]); 100 for (uint32_t i = 0; i < info.num_extra_channels; i++) { 101 JxlExtraChannelInfo extra; 102 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) { 103 fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); 104 break; 105 } 106 if (extra.type == JXL_CHANNEL_BLACK) cmyk = 1; 107 } 108 if (info.num_color_channels == 1) { 109 printf("Grayscale"); 110 } else { 111 if (cmyk) { 112 printf("CMY"); 113 } else { 114 printf("RGB"); 115 } 116 } 117 for (uint32_t i = 0; i < info.num_extra_channels; i++) { 118 JxlExtraChannelInfo extra; 119 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) { 120 fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); 121 break; 122 } 123 printf("+%s", (extra.type < ec_type_names_size 124 ? ec_type_names[extra.type] 125 : "Unknown, please update your libjxl")); 126 } 127 printf("\n"); 128 if (verbose) { 129 printf("num_color_channels: %d\n", info.num_color_channels); 130 printf("num_extra_channels: %d\n", info.num_extra_channels); 131 132 for (uint32_t i = 0; i < info.num_extra_channels; i++) { 133 JxlExtraChannelInfo extra; 134 if (JXL_DEC_SUCCESS != 135 JxlDecoderGetExtraChannelInfo(dec, i, &extra)) { 136 fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); 137 break; 138 } 139 printf("extra channel %u:\n", i); 140 printf(" type: %s\n", (extra.type < ec_type_names_size 141 ? ec_type_names[extra.type] 142 : "Unknown, please update your libjxl")); 143 printf(" bits_per_sample: %u\n", extra.bits_per_sample); 144 if (extra.exponent_bits_per_sample > 0) { 145 printf(" float, with exponent_bits_per_sample: %u\n", 146 extra.exponent_bits_per_sample); 147 } 148 if (extra.dim_shift > 0) { 149 printf(" dim_shift: %u (upsampled %ux)\n", extra.dim_shift, 150 1 << extra.dim_shift); 151 } 152 if (extra.name_length) { 153 char* name = malloc(extra.name_length + 1); 154 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName( 155 dec, i, name, extra.name_length + 1)) { 156 fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n"); 157 free(name); 158 break; 159 } 160 printf(" name: %s\n", name); 161 free(name); 162 } 163 if (extra.type == JXL_CHANNEL_ALPHA) { 164 printf(" alpha_premultiplied: %d (%s)\n", 165 extra.alpha_premultiplied, 166 extra.alpha_premultiplied ? "Premultiplied" 167 : "Non-premultiplied"); 168 } 169 if (extra.type == JXL_CHANNEL_SPOT_COLOR) { 170 printf(" spot_color: (%f, %f, %f) with opacity %f\n", 171 extra.spot_color[0], extra.spot_color[1], 172 extra.spot_color[2], extra.spot_color[3]); 173 } 174 if (extra.type == JXL_CHANNEL_CFA) 175 printf(" cfa_channel: %u\n", extra.cfa_channel); 176 } 177 } 178 179 if (info.intensity_target != 255.f || info.min_nits != 0.f || 180 info.relative_to_max_display != 0 || 181 info.relative_to_max_display != 0.f) { 182 printf("intensity_target: %f nits\n", info.intensity_target); 183 printf("min_nits: %f\n", info.min_nits); 184 printf("relative_to_max_display: %d\n", info.relative_to_max_display); 185 printf("linear_below: %f\n", info.linear_below); 186 } 187 if (verbose) printf("have_preview: %d\n", info.have_preview); 188 if (info.have_preview) { 189 printf("Preview image: %ux%u\n", info.preview.xsize, 190 info.preview.ysize); 191 } 192 if (verbose) printf("have_animation: %d\n", info.have_animation); 193 if (verbose && info.have_animation) { 194 printf("ticks per second (numerator / denominator): %u / %u\n", 195 info.animation.tps_numerator, info.animation.tps_denominator); 196 printf("num_loops: %u\n", info.animation.num_loops); 197 printf("have_timecodes: %d\n", info.animation.have_timecodes); 198 } 199 if (info.xsize != info.intrinsic_xsize || 200 info.ysize != info.intrinsic_ysize || verbose) { 201 printf("Intrinsic dimensions: %ux%u\n", info.intrinsic_xsize, 202 info.intrinsic_ysize); 203 } 204 const char* const orientation_string[8] = { 205 "Normal", "Flipped horizontally", 206 "Upside down", "Flipped vertically", 207 "Transposed", "90 degrees clockwise", 208 "Anti-Transposed", "90 degrees counter-clockwise"}; 209 if (info.orientation > 0 && info.orientation < 9) { 210 if (verbose || info.orientation > 1) { 211 printf("Orientation: %d (%s)\n", info.orientation, 212 orientation_string[info.orientation - 1]); 213 } 214 } else { 215 fprintf(stderr, "Invalid orientation\n"); 216 } 217 } else if (status == JXL_DEC_COLOR_ENCODING) { 218 printf("Color space: "); 219 220 JxlColorEncoding color_encoding; 221 if (JXL_DEC_SUCCESS == 222 JxlDecoderGetColorAsEncodedProfile( 223 dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &color_encoding)) { 224 const char* const cs_string[4] = {"RGB", "Grayscale", "XYB", "Unknown"}; 225 const char* const wp_string[12] = {"", "D65", "Custom", "", "", "", 226 "", "", "", "", "E", "P3"}; 227 const char* const pr_string[12] = { 228 "", "sRGB", "Custom", "", "", "", "", "", "", "Rec.2100", "", "P3"}; 229 const char* const tf_string[19] = { 230 "", "709", "Unknown", "", "", "", "", "", "Linear", "", 231 "", "", "", "sRGB", "", "", "PQ", "DCI", "HLG"}; 232 const char* const ri_string[4] = {"Perceptual", "Relative", 233 "Saturation", "Absolute"}; 234 printf("%s, ", cs_string[color_encoding.color_space]); 235 printf("%s, ", wp_string[color_encoding.white_point]); 236 if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM) { 237 printf("white_point(x=%f,y=%f), ", color_encoding.white_point_xy[0], 238 color_encoding.white_point_xy[1]); 239 } 240 if (color_encoding.color_space == JXL_COLOR_SPACE_RGB || 241 color_encoding.color_space == JXL_COLOR_SPACE_UNKNOWN) { 242 printf("%s primaries", pr_string[color_encoding.primaries]); 243 if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM) { 244 printf(": red(x=%f,y=%f),", color_encoding.primaries_red_xy[0], 245 color_encoding.primaries_red_xy[1]); 246 printf(" green(x=%f,y=%f),", color_encoding.primaries_green_xy[0], 247 color_encoding.primaries_green_xy[1]); 248 printf(" blue(x=%f,y=%f)", color_encoding.primaries_blue_xy[0], 249 color_encoding.primaries_blue_xy[1]); 250 } else 251 printf(", "); 252 } 253 if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) { 254 printf("gamma(%f) transfer function, ", color_encoding.gamma); 255 } else { 256 printf("%s transfer function, ", 257 tf_string[color_encoding.transfer_function]); 258 } 259 printf("rendering intent: %s\n", 260 ri_string[color_encoding.rendering_intent]); 261 262 } else { 263 // The profile is not in JPEG XL encoded form, get as ICC profile 264 // instead. 265 size_t profile_size; 266 if (JXL_DEC_SUCCESS != 267 JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, 268 &profile_size)) { 269 fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); 270 continue; 271 } 272 printf("%" PRIu64 "-byte ICC profile, ", (uint64_t)profile_size); 273 if (profile_size < 132) { 274 fprintf(stderr, "ICC profile too small\n"); 275 continue; 276 } 277 uint8_t* profile = (uint8_t*)malloc(profile_size); 278 if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( 279 dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, 280 profile, profile_size)) { 281 fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); 282 free(profile); 283 continue; 284 } 285 printf("CMM type: \"%.4s\", ", profile + 4); 286 printf("color space: \"%.4s\", ", profile + 16); 287 printf("rendering intent: %d\n", (int)profile[67]); 288 free(profile); 289 } 290 } else if (status == JXL_DEC_FRAME) { 291 if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame_header)) { 292 fprintf(stderr, "JxlDecoderGetFrameHeader failed\n"); 293 break; 294 } 295 if (frame_header.duration == 0) { 296 if (frame_header.is_last && framecount == 0 && 297 frame_header.name_length == 0) 298 continue; 299 printf("layer: "); 300 } else { 301 printf("frame: "); 302 } 303 framecount++; 304 if (frame_header.layer_info.have_crop) { 305 printf("%ux%u at position (%i,%i)", frame_header.layer_info.xsize, 306 frame_header.layer_info.ysize, frame_header.layer_info.crop_x0, 307 frame_header.layer_info.crop_y0); 308 } else { 309 printf("full image size"); 310 } 311 if (info.have_animation) { 312 float ms = frame_header.duration * 1000.f * 313 info.animation.tps_denominator / 314 info.animation.tps_numerator; 315 total_duration += ms; 316 printf(", duration: %.1f ms", ms); 317 if (info.animation.have_timecodes) { 318 printf(", time code: %X", frame_header.timecode); 319 } 320 } 321 if (frame_header.name_length) { 322 char* name = malloc(frame_header.name_length + 1); 323 if (JXL_DEC_SUCCESS != 324 JxlDecoderGetFrameName(dec, name, frame_header.name_length + 1)) { 325 fprintf(stderr, "JxlDecoderGetFrameName failed\n"); 326 free(name); 327 break; 328 } 329 printf(", name: \"%s\"", name); 330 free(name); 331 } 332 printf("\n"); 333 } else if (status == JXL_DEC_BOX) { 334 JxlBoxType type; 335 uint64_t size; 336 uint64_t contents_size; 337 JxlDecoderGetBoxType(dec, type, JXL_FALSE); 338 JxlDecoderGetBoxSizeRaw(dec, &size); 339 JxlDecoderGetBoxSizeContents(dec, &contents_size); 340 if (verbose) { 341 printf("box: type: \"%c%c%c%c\" size: %" PRIu64 342 ", contents size: %" PRIu64 "\n", 343 type[0], type[1], type[2], type[3], size, contents_size); 344 } 345 if (!strncmp(type, "JXL ", 4)) { 346 printf("JPEG XL file format container (ISO/IEC 18181-2)\n"); 347 } else if (!strncmp(type, "ftyp", 4)) { 348 } else if (!strncmp(type, "jxlc", 4)) { 349 } else if (!strncmp(type, "jxlp", 4)) { 350 } else if (!strncmp(type, "jxll", 4)) { 351 } else if (!strncmp(type, "jxli", 4)) { 352 printf("Frame index box present\n"); 353 } else if (!strncmp(type, "jbrd", 4)) { 354 printf("JPEG bitstream reconstruction data available\n"); 355 } else if (!strncmp(type, "jumb", 4) || !strncmp(type, "Exif", 4) || 356 !strncmp(type, "xml ", 4)) { 357 printf("Uncompressed %c%c%c%c metadata: %" PRIu64 " bytes\n", type[0], 358 type[1], type[2], type[3], size); 359 360 } else if (!strncmp(type, "brob", 4)) { 361 JxlDecoderGetBoxType(dec, type, JXL_TRUE); 362 printf("Brotli-compressed %c%c%c%c metadata: %" PRIu64 363 " compressed bytes\n", 364 type[0], type[1], type[2], type[3], size); 365 } else { 366 printf("unknown box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0], 367 type[1], type[2], type[3], size); 368 } 369 } else { 370 fprintf(stderr, "Unexpected decoder status\n"); 371 break; 372 } 373 } 374 if (info.animation.num_loops > 1) total_duration *= info.animation.num_loops; 375 if (info.have_animation) { 376 printf("Animation length: %.3f seconds%s\n", total_duration * 0.001f, 377 (info.animation.num_loops ? "" : " (looping)")); 378 } 379 JxlDecoderDestroy(dec); 380 free(data); 381 382 return seen_basic_info; 383 } 384 385 static void print_usage(const char* name) { 386 fprintf(stderr, 387 "Usage: %s [-v] [-h] INPUT\n" 388 " INPUT input JPEG XL image filename(s)\n" 389 " -v (or --verbose) more verbose output\n" 390 " -h (or --help or -?) this help)\n", 391 name); 392 } 393 394 static int print_basic_info_filename(const char* jxl_filename, int verbose) { 395 FILE* file = fopen(jxl_filename, "rb"); 396 if (!file) { 397 fprintf(stderr, "Failed to read file: %s\n", jxl_filename); 398 return 1; 399 } 400 int status = PrintBasicInfo(file, verbose); 401 fclose(file); 402 if (!status) { 403 fprintf(stderr, "Error reading file: %s\n", jxl_filename); 404 return status; 405 } 406 407 return 0; 408 } 409 410 int is_flag(const char* arg, const char* const* opts) { 411 for (int i = 0; opts[i] != NULL; i++) { 412 if (!strcmp(opts[i], arg)) { 413 return 1; 414 } 415 } 416 return 0; 417 } 418 419 int main(int argc, char* argv[]) { 420 int verbose = 0; 421 int status = 0; 422 const char* const name = argv[0]; 423 const char* const* help_opts = 424 (const char* const[]){"--help", "-h", "-?", NULL}; 425 const char* const* verbose_opts = 426 (const char* const[]){"--verbose", "-v", NULL}; 427 if (argc < 2) { 428 print_usage(name); 429 return 2; 430 } 431 432 // First pass: Check for flags 433 for (int i = 1; i < argc; i++) { 434 if (!verbose && is_flag(argv[i], verbose_opts)) { 435 verbose = 1; 436 } 437 if (is_flag(argv[i], help_opts)) { 438 print_usage(name); 439 return 0; 440 } 441 } 442 443 // Second pass: print info 444 while (argc-- >= 2) { 445 if (is_flag(*(argv + 1), verbose_opts) || is_flag(*(argv + 1), help_opts)) { 446 ++argv; 447 } else { 448 status |= print_basic_info_filename(*++argv, verbose); 449 } 450 } 451 return status; 452 }