libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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 }