summaryrefslogtreecommitdiff
path: root/media/libjxl/src/tools/jxlinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/tools/jxlinfo.c')
-rw-r--r--media/libjxl/src/tools/jxlinfo.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/media/libjxl/src/tools/jxlinfo.c b/media/libjxl/src/tools/jxlinfo.c
new file mode 100644
index 0000000000..0cced1740c
--- /dev/null
+++ b/media/libjxl/src/tools/jxlinfo.c
@@ -0,0 +1,461 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This example prints information from the main codestream header.
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jxl/decode.h"
+
+int PrintBasicInfo(FILE* file, int verbose) {
+ uint8_t* data = NULL;
+ size_t data_size = 0;
+ // In how large chunks to read from the file and try decoding the basic info.
+ const size_t chunk_size = 2048;
+
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ if (!dec) {
+ fprintf(stderr, "JxlDecoderCreate failed\n");
+ return 0;
+ }
+
+ JxlDecoderSetKeepOrientation(dec, 1);
+ JxlDecoderSetCoalescing(dec, JXL_FALSE);
+
+ if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(
+ dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
+ JXL_DEC_FRAME | JXL_DEC_BOX)) {
+ fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
+ JxlDecoderDestroy(dec);
+ return 0;
+ }
+
+ JxlBasicInfo info;
+ int seen_basic_info = 0;
+ JxlFrameHeader frame_header;
+ int framecount = 0;
+ float total_duration = 0.f;
+
+ for (;;) {
+ // The first time, this will output JXL_DEC_NEED_MORE_INPUT because no
+ // input is set yet, this is ok since the input is set when handling this
+ // event.
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+
+ if (status == JXL_DEC_ERROR) {
+ fprintf(stderr, "Decoder error\n");
+ break;
+ } else if (status == JXL_DEC_NEED_MORE_INPUT) {
+ // The first time there is nothing to release and it returns 0, but that
+ // is ok.
+ size_t remaining = JxlDecoderReleaseInput(dec);
+ // move any remaining bytes to the front if necessary
+ if (remaining != 0) {
+ memmove(data, data + data_size - remaining, remaining);
+ }
+ // resize the buffer to append one more chunk of data
+ // TODO(lode): avoid unnecessary reallocations
+ data = (uint8_t*)realloc(data, remaining + chunk_size);
+ // append bytes read from the file behind the remaining bytes
+ size_t read_size = fread(data + remaining, 1, chunk_size, file);
+ if (read_size == 0 && feof(file)) {
+ fprintf(stderr, "Unexpected EOF\n");
+ break;
+ }
+ data_size = remaining + read_size;
+ JxlDecoderSetInput(dec, data, data_size);
+ if (feof(file)) JxlDecoderCloseInput(dec);
+ } else if (status == JXL_DEC_SUCCESS) {
+ // Finished all processing.
+ break;
+ } else if (status == JXL_DEC_BASIC_INFO) {
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) {
+ fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
+ break;
+ }
+
+ seen_basic_info = 1;
+
+ printf("JPEG XL %s, %ux%u, %s",
+ info.have_animation ? "animation" : "image", info.xsize,
+ info.ysize,
+ info.uses_original_profile ? "(possibly) lossless" : "lossy");
+ printf(", %d-bit ", info.bits_per_sample);
+ if (info.exponent_bits_per_sample) {
+ printf("float (%d exponent bits) ", info.exponent_bits_per_sample);
+ }
+ int cmyk = 0, alpha = 0;
+ const char* const ec_type_names[7] = {"Alpha", "Depth", "Spotcolor",
+ "Selection", "Black", "CFA",
+ "Thermal"};
+ for (uint32_t i = 0; i < info.num_extra_channels; i++) {
+ JxlExtraChannelInfo extra;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
+ break;
+ }
+ if (extra.type == JXL_CHANNEL_BLACK) cmyk = 1;
+ if (extra.type == JXL_CHANNEL_ALPHA) alpha = 1;
+ }
+ if (info.num_color_channels == 1)
+ printf("Grayscale");
+ else {
+ if (cmyk) {
+ printf("CMYK");
+ cmyk = 0;
+ } else if (alpha) {
+ printf("RGBA");
+ alpha = 0;
+ } else {
+ printf("RGB");
+ }
+ }
+ for (uint32_t i = 0; i < info.num_extra_channels; i++) {
+ JxlExtraChannelInfo extra;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
+ break;
+ }
+ if (extra.type == JXL_CHANNEL_BLACK && cmyk == 0) {
+ cmyk = 1;
+ continue;
+ }
+ if (extra.type == JXL_CHANNEL_ALPHA && alpha == 0) {
+ alpha = 1;
+ continue;
+ }
+
+ printf("+%s", (extra.type < 7 ? ec_type_names[extra.type]
+ : (extra.type == JXL_CHANNEL_OPTIONAL
+ ? "UnknownOptional"
+ : "Unknown(OUTDATED libjxl!)")));
+ }
+ printf("\n");
+ if (verbose) {
+ printf("num_color_channels: %d\n", info.num_color_channels);
+ printf("num_extra_channels: %d\n", info.num_extra_channels);
+
+ for (uint32_t i = 0; i < info.num_extra_channels; i++) {
+ JxlExtraChannelInfo extra;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
+ break;
+ }
+ printf("extra channel %u:\n", i);
+ printf(
+ " type: %s\n",
+ (extra.type < 7 ? ec_type_names[extra.type]
+ : (extra.type == JXL_CHANNEL_OPTIONAL
+ ? "Unknown but can be ignored"
+ : "Unknown, please update your libjxl")));
+ printf(" bits_per_sample: %u\n", extra.bits_per_sample);
+ if (extra.exponent_bits_per_sample > 0) {
+ printf(" float, with exponent_bits_per_sample: %u\n",
+ extra.exponent_bits_per_sample);
+ }
+ if (extra.dim_shift > 0) {
+ printf(" dim_shift: %u (upsampled %ux)\n", extra.dim_shift,
+ 1 << extra.dim_shift);
+ }
+ if (extra.name_length) {
+ char* name = malloc(extra.name_length + 1);
+ if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName(
+ dec, i, name, extra.name_length + 1)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
+ free(name);
+ break;
+ }
+ printf(" name: %s\n", name);
+ free(name);
+ }
+ if (extra.type == JXL_CHANNEL_ALPHA)
+ printf(" alpha_premultiplied: %d (%s)\n",
+ extra.alpha_premultiplied,
+ extra.alpha_premultiplied ? "Premultiplied"
+ : "Non-premultiplied");
+ if (extra.type == JXL_CHANNEL_SPOT_COLOR) {
+ printf(" spot_color: (%f, %f, %f) with opacity %f\n",
+ extra.spot_color[0], extra.spot_color[1],
+ extra.spot_color[2], extra.spot_color[3]);
+ }
+ if (extra.type == JXL_CHANNEL_CFA)
+ printf(" cfa_channel: %u\n", extra.cfa_channel);
+ }
+ }
+
+ if (info.intensity_target != 255.f || info.min_nits != 0.f ||
+ info.relative_to_max_display != 0 ||
+ info.relative_to_max_display != 0.f) {
+ printf("intensity_target: %f nits\n", info.intensity_target);
+ printf("min_nits: %f\n", info.min_nits);
+ printf("relative_to_max_display: %d\n", info.relative_to_max_display);
+ printf("linear_below: %f\n", info.linear_below);
+ }
+ if (verbose) printf("have_preview: %d\n", info.have_preview);
+ if (info.have_preview) {
+ printf("Preview image: %ux%u\n", info.preview.xsize,
+ info.preview.ysize);
+ }
+ if (verbose) printf("have_animation: %d\n", info.have_animation);
+ if (verbose && info.have_animation) {
+ printf("ticks per second (numerator / denominator): %u / %u\n",
+ info.animation.tps_numerator, info.animation.tps_denominator);
+ printf("num_loops: %u\n", info.animation.num_loops);
+ printf("have_timecodes: %d\n", info.animation.have_timecodes);
+ }
+ if (info.xsize != info.intrinsic_xsize ||
+ info.ysize != info.intrinsic_ysize || verbose) {
+ printf("Intrinsic dimensions: %ux%u\n", info.intrinsic_xsize,
+ info.intrinsic_ysize);
+ }
+ const char* const orientation_string[8] = {
+ "Normal", "Flipped horizontally",
+ "Upside down", "Flipped vertically",
+ "Transposed", "90 degrees clockwise",
+ "Anti-Transposed", "90 degrees counter-clockwise"};
+ if (info.orientation > 0 && info.orientation < 9) {
+ if (verbose || info.orientation > 1) {
+ printf("Orientation: %d (%s)\n", info.orientation,
+ orientation_string[info.orientation - 1]);
+ }
+ } else {
+ fprintf(stderr, "Invalid orientation\n");
+ }
+ } else if (status == JXL_DEC_COLOR_ENCODING) {
+ JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
+ printf("Color space: ");
+
+ JxlColorEncoding color_encoding;
+ if (JXL_DEC_SUCCESS ==
+ JxlDecoderGetColorAsEncodedProfile(dec, &format,
+ JXL_COLOR_PROFILE_TARGET_ORIGINAL,
+ &color_encoding)) {
+ const char* const cs_string[4] = {"RGB", "Grayscale", "XYB", "Unknown"};
+ const char* const wp_string[12] = {"", "D65", "Custom", "", "", "",
+ "", "", "", "", "E", "P3"};
+ const char* const pr_string[12] = {
+ "", "sRGB", "Custom", "", "", "", "", "", "", "Rec.2100", "", "P3"};
+ const char* const tf_string[19] = {
+ "", "709", "Unknown", "", "", "", "", "", "Linear", "",
+ "", "", "", "sRGB", "", "", "PQ", "DCI", "HLG"};
+ const char* const ri_string[4] = {"Perceptual", "Relative",
+ "Saturation", "Absolute"};
+ printf("%s, ", cs_string[color_encoding.color_space]);
+ printf("%s, ", wp_string[color_encoding.white_point]);
+ if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM) {
+ printf("white_point(x=%f,y=%f), ", color_encoding.white_point_xy[0],
+ color_encoding.white_point_xy[1]);
+ }
+ if (color_encoding.color_space == JXL_COLOR_SPACE_RGB ||
+ color_encoding.color_space == JXL_COLOR_SPACE_UNKNOWN) {
+ printf("%s primaries", pr_string[color_encoding.primaries]);
+ if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM) {
+ printf(": red(x=%f,y=%f),", color_encoding.primaries_red_xy[0],
+ color_encoding.primaries_red_xy[1]);
+ printf(" green(x=%f,y=%f),", color_encoding.primaries_green_xy[0],
+ color_encoding.primaries_green_xy[1]);
+ printf(" blue(x=%f,y=%f)", color_encoding.primaries_blue_xy[0],
+ color_encoding.primaries_blue_xy[1]);
+ } else
+ printf(", ");
+ }
+ if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
+ printf("gamma(%f) transfer function, ", color_encoding.gamma);
+ } else {
+ printf("%s transfer function, ",
+ tf_string[color_encoding.transfer_function]);
+ }
+ printf("rendering intent: %s\n",
+ ri_string[color_encoding.rendering_intent]);
+
+ } else {
+ // The profile is not in JPEG XL encoded form, get as ICC profile
+ // instead.
+ size_t profile_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetICCProfileSize(dec, &format,
+ JXL_COLOR_PROFILE_TARGET_ORIGINAL,
+ &profile_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ continue;
+ }
+ printf("%" PRIu64 "-byte ICC profile, ", (uint64_t)profile_size);
+ if (profile_size < 132) {
+ fprintf(stderr, "ICC profile too small\n");
+ continue;
+ }
+ uint8_t* profile = (uint8_t*)malloc(profile_size);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetColorAsICCProfile(dec, &format,
+ JXL_COLOR_PROFILE_TARGET_ORIGINAL,
+ profile, profile_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ free(profile);
+ continue;
+ }
+ printf("CMM type: \"%.4s\", ", profile + 4);
+ printf("color space: \"%.4s\", ", profile + 16);
+ printf("rendering intent: %d\n", (int)profile[67]);
+ free(profile);
+ }
+ } else if (status == JXL_DEC_FRAME) {
+ if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame_header)) {
+ fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
+ break;
+ }
+ if (frame_header.duration == 0) {
+ if (frame_header.is_last && framecount == 0 &&
+ frame_header.name_length == 0)
+ continue;
+ printf("layer: ");
+ } else {
+ printf("frame: ");
+ }
+ framecount++;
+ if (frame_header.layer_info.have_crop) {
+ printf("%ux%u at position (%i,%i)", frame_header.layer_info.xsize,
+ frame_header.layer_info.ysize, frame_header.layer_info.crop_x0,
+ frame_header.layer_info.crop_y0);
+ } else {
+ printf("full image size");
+ }
+
+ float ms = frame_header.duration * 1000.f *
+ info.animation.tps_denominator / info.animation.tps_numerator;
+ total_duration += ms;
+ if (info.have_animation) {
+ printf(", duration: %.1f ms", ms);
+ if (info.animation.have_timecodes) {
+ printf(", time code: %X", frame_header.timecode);
+ }
+ }
+ if (frame_header.name_length) {
+ char* name = malloc(frame_header.name_length + 1);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetFrameName(dec, name, frame_header.name_length + 1)) {
+ fprintf(stderr, "JxlDecoderGetFrameName failed\n");
+ free(name);
+ break;
+ }
+ printf(", name: \"%s\"", name);
+ free(name);
+ }
+ printf("\n");
+ } else if (status == JXL_DEC_BOX) {
+ JxlBoxType type;
+ uint64_t size;
+ JxlDecoderGetBoxType(dec, type, JXL_FALSE);
+ JxlDecoderGetBoxSizeRaw(dec, &size);
+ if (verbose) {
+ printf("box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0], type[1],
+ type[2], type[3], (uint64_t)size);
+ }
+ if (!strncmp(type, "JXL ", 4)) {
+ printf("JPEG XL file format container (ISO/IEC 18181-2)\n");
+ } else if (!strncmp(type, "ftyp", 4)) {
+ } else if (!strncmp(type, "jxlc", 4)) {
+ } else if (!strncmp(type, "jxlp", 4)) {
+ } else if (!strncmp(type, "jxll", 4)) {
+ } else if (!strncmp(type, "jxli", 4)) {
+ printf("Frame index box present\n");
+ } else if (!strncmp(type, "jbrd", 4)) {
+ printf("JPEG bitstream reconstruction data available\n");
+ } else if (!strncmp(type, "jumb", 4) || !strncmp(type, "Exif", 4) ||
+ !strncmp(type, "xml ", 4)) {
+ printf("Uncompressed %c%c%c%c metadata: %" PRIu64 " bytes\n", type[0],
+ type[1], type[2], type[3], (uint64_t)size);
+
+ } else if (!strncmp(type, "brob", 4)) {
+ JxlDecoderGetBoxType(dec, type, JXL_TRUE);
+ printf("Brotli-compressed %c%c%c%c metadata: %" PRIu64
+ " compressed bytes\n",
+ type[0], type[1], type[2], type[3], (uint64_t)size);
+ } else {
+ printf("unknown box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0],
+ type[1], type[2], type[3], (uint64_t)size);
+ }
+ } else {
+ fprintf(stderr, "Unexpected decoder status\n");
+ break;
+ }
+ }
+ if (info.animation.num_loops > 1) total_duration *= info.animation.num_loops;
+ if (info.have_animation) {
+ printf("Animation length: %.3f seconds%s\n", total_duration * 0.001f,
+ (info.animation.num_loops ? "" : " (looping)"));
+ }
+ JxlDecoderDestroy(dec);
+ free(data);
+
+ return seen_basic_info;
+}
+
+static void print_usage(const char* name) {
+ fprintf(stderr,
+ "Usage: %s [-v] INPUT\n"
+ " INPUT input JPEG XL image filename(s)\n"
+ " -v more verbose output\n",
+ name);
+}
+
+static int print_basic_info_filename(const char* jxl_filename, int verbose) {
+ FILE* file = fopen(jxl_filename, "rb");
+ if (!file) {
+ fprintf(stderr, "Failed to read file: %s\n", jxl_filename);
+ return 1;
+ }
+ int status = PrintBasicInfo(file, verbose);
+ fclose(file);
+ if (!status) {
+ fprintf(stderr, "Error reading file: %s\n", jxl_filename);
+ return status;
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ int verbose = 0, status = 0;
+ const char* const name = argv[0];
+
+ for (int i = 1; i < argc; i++) {
+ const char* const* help_opts =
+ (const char* const[]){"--help", "-h", "-?", NULL};
+ while (*help_opts) {
+ if (!strcmp(*help_opts++, argv[i])) {
+ print_usage(name);
+ return 0;
+ }
+ }
+ }
+
+ const char* const* verbose_opts =
+ (const char* const[]){"--verbose", "-v", NULL};
+ /* argc >= 2 gate prevents segfault on argc = 1 */
+ while (argc >= 2 && *verbose_opts) {
+ if (!strcmp(*verbose_opts++, argv[1])) {
+ verbose = 1;
+ argc--;
+ argv++;
+ break;
+ }
+ }
+
+ if (argc < 2) {
+ print_usage(name);
+ return 2;
+ }
+
+ while (argc-- >= 2) {
+ status |= print_basic_info_filename(*++argv, verbose);
+ }
+
+ return status;
+}