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 <stdint.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include "jxl/decode.h"
15 
PrintBasicInfo(FILE * file)16 int PrintBasicInfo(FILE* file) {
17   uint8_t* data = NULL;
18   size_t data_size = 0;
19   // In how large chunks to read from the file and try decoding the basic info.
20   const size_t chunk_size = 64;
21 
22   JxlDecoder* dec = JxlDecoderCreate(NULL);
23   if (!dec) {
24     fprintf(stderr, "JxlDecoderCreate failed\n");
25     return 0;
26   }
27 
28   JxlDecoderSetKeepOrientation(dec, 1);
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 
42   for (;;) {
43     // The first time, this will output JXL_DEC_NEED_MORE_INPUT because no
44     // input is set yet, this is ok since the input is set when handling this
45     // event.
46     JxlDecoderStatus status = JxlDecoderProcessInput(dec);
47 
48     if (status == JXL_DEC_ERROR) {
49       fprintf(stderr, "Decoder error\n");
50       break;
51     } else if (status == JXL_DEC_NEED_MORE_INPUT) {
52       // The first time there is nothing to release and it returns 0, but that
53       // is ok.
54       size_t remaining = JxlDecoderReleaseInput(dec);
55       // move any remaining bytes to the front if necessary
56       if (remaining != 0) {
57         memmove(data, data + data_size - remaining, remaining);
58       }
59       // resize the buffer to append one more chunk of data
60       // TODO(lode): avoid unnecessary reallocations
61       data = (uint8_t*)realloc(data, remaining + chunk_size);
62       // append bytes read from the file behind the remaining bytes
63       size_t read_size = fread(data + remaining, 1, chunk_size, file);
64       if (read_size == 0 && feof(file)) {
65         fprintf(stderr, "Unexpected EOF\n");
66         break;
67       }
68       data_size = remaining + read_size;
69       JxlDecoderSetInput(dec, data, data_size);
70       if (feof(file)) JxlDecoderCloseInput(dec);
71     } else if (status == JXL_DEC_SUCCESS) {
72       // Finished all processing.
73       break;
74     } else if (status == JXL_DEC_BASIC_INFO) {
75       if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) {
76         fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
77         break;
78       }
79 
80       seen_basic_info = 1;
81 
82       printf("dimensions: %ux%u\n", info.xsize, info.ysize);
83       printf("have_container: %d\n", info.have_container);
84       printf("uses_original_profile: %d\n", info.uses_original_profile);
85       printf("bits_per_sample: %d\n", info.bits_per_sample);
86       if (info.exponent_bits_per_sample)
87         printf("float, with exponent_bits_per_sample: %d\n",
88                info.exponent_bits_per_sample);
89       if (info.intensity_target != 255.f || info.min_nits != 0.f ||
90           info.relative_to_max_display != 0 ||
91           info.relative_to_max_display != 0.f) {
92         printf("intensity_target: %f\n", info.intensity_target);
93         printf("min_nits: %f\n", info.min_nits);
94         printf("relative_to_max_display: %d\n", info.relative_to_max_display);
95         printf("linear_below: %f\n", info.linear_below);
96       }
97       printf("have_preview: %d\n", info.have_preview);
98       if (info.have_preview) {
99         printf("preview xsize: %u\n", info.preview.xsize);
100         printf("preview ysize: %u\n", info.preview.ysize);
101       }
102       printf("have_animation: %d\n", info.have_animation);
103       if (info.have_animation) {
104         printf("ticks per second (numerator / denominator): %u / %u\n",
105                info.animation.tps_numerator, info.animation.tps_denominator);
106         printf("num_loops: %u\n", info.animation.num_loops);
107         printf("have_timecodes: %d\n", info.animation.have_timecodes);
108       }
109       printf("intrinsic xsize: %u\n", info.intrinsic_xsize);
110       printf("intrinsic ysize: %u\n", info.intrinsic_ysize);
111       const char* const orientation_string[8] = {
112           "Normal",          "Flipped horizontally",
113           "Upside down",     "Flipped vertically",
114           "Transposed",      "90 degrees clockwise",
115           "Anti-Transposed", "90 degrees counter-clockwise"};
116       if (info.orientation > 0 && info.orientation < 9) {
117         printf("orientation: %d (%s)\n", info.orientation,
118                orientation_string[info.orientation - 1]);
119       } else {
120         fprintf(stderr, "Invalid orientation\n");
121       }
122       printf("num_color_channels: %d\n", info.num_color_channels);
123       printf("num_extra_channels: %d\n", info.num_extra_channels);
124 
125       const char* const ec_type_names[7] = {"Alpha",       "Depth",
126                                             "Spot color",  "Selection mask",
127                                             "K (of CMYK)", "CFA (Bayer data)",
128                                             "Thermal"};
129       for (uint32_t i = 0; i < info.num_extra_channels; i++) {
130         JxlExtraChannelInfo extra;
131         if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
132           fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
133           break;
134         }
135         printf("extra channel %u:\n", i);
136         printf("  type: %s\n",
137                (extra.type < 7 ? ec_type_names[extra.type]
138                                : (extra.type == JXL_CHANNEL_OPTIONAL
139                                       ? "Unknown but can be ignored"
140                                       : "Unknown, please update your libjxl")));
141         printf("  bits_per_sample: %u\n", extra.bits_per_sample);
142         if (extra.exponent_bits_per_sample > 0) {
143           printf("  float, with exponent_bits_per_sample: %u\n",
144                  extra.exponent_bits_per_sample);
145         }
146         if (extra.dim_shift > 0) {
147           printf("  dim_shift: %u (upsampled %ux)\n", extra.dim_shift,
148                  1 << extra.dim_shift);
149         }
150         if (extra.name_length) {
151           char* name = malloc(extra.name_length + 1);
152           if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName(
153                                      dec, i, name, extra.name_length + 1)) {
154             fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
155             free(name);
156             break;
157           }
158           printf("  name: %s\n", name);
159           free(name);
160         }
161         if (extra.type == JXL_CHANNEL_ALPHA)
162           printf("  alpha_premultiplied: %d (%s)\n", extra.alpha_premultiplied,
163                  extra.alpha_premultiplied ? "Premultiplied"
164                                            : "Non-premultiplied");
165         if (extra.type == JXL_CHANNEL_SPOT_COLOR) {
166           printf("  spot_color: (%f, %f, %f) with opacity %f\n",
167                  extra.spot_color[0], extra.spot_color[1], extra.spot_color[2],
168                  extra.spot_color[3]);
169         }
170         if (extra.type == JXL_CHANNEL_CFA)
171           printf("  cfa_channel: %u\n", extra.cfa_channel);
172       }
173     } else if (status == JXL_DEC_COLOR_ENCODING) {
174       JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
175       printf("color profile:\n");
176 
177       JxlColorEncoding color_encoding;
178       if (JXL_DEC_SUCCESS ==
179           JxlDecoderGetColorAsEncodedProfile(dec, &format,
180                                              JXL_COLOR_PROFILE_TARGET_ORIGINAL,
181                                              &color_encoding)) {
182         printf("  format: JPEG XL encoded color profile\n");
183         const char* const cs_string[4] = {"RGB color", "Grayscale", "XYB",
184                                           "Unknown"};
185         const char* const wp_string[12] = {"", "D65", "Custom", "", "",  "",
186                                            "", "",    "",       "", "E", "P3"};
187         const char* const pr_string[12] = {
188             "", "sRGB", "Custom", "", "", "", "", "", "", "Rec.2100", "", "P3"};
189         const char* const tf_string[19] = {
190             "", "709", "Unknown", "",     "", "", "",   "",    "Linear", "",
191             "", "",    "",        "sRGB", "", "", "PQ", "DCI", "HLG"};
192         const char* const ri_string[4] = {"Perceptual", "Relative",
193                                           "Saturation", "Absolute"};
194         printf("  color_space: %d (%s)\n", color_encoding.color_space,
195                cs_string[color_encoding.color_space]);
196         printf("  white_point: %d (%s)\n", color_encoding.white_point,
197                wp_string[color_encoding.white_point]);
198         if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM) {
199           printf("  white_point XY: %f %f\n", color_encoding.white_point_xy[0],
200                  color_encoding.white_point_xy[1]);
201         }
202         if (color_encoding.color_space == JXL_COLOR_SPACE_RGB ||
203             color_encoding.color_space == JXL_COLOR_SPACE_UNKNOWN) {
204           printf("  primaries: %d (%s)\n", color_encoding.primaries,
205                  pr_string[color_encoding.primaries]);
206           if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM) {
207             printf("  red primaries XY: %f %f\n",
208                    color_encoding.primaries_red_xy[0],
209                    color_encoding.primaries_red_xy[1]);
210             printf("  green primaries XY: %f %f\n",
211                    color_encoding.primaries_green_xy[0],
212                    color_encoding.primaries_green_xy[1]);
213             printf("  blue primaries XY: %f %f\n",
214                    color_encoding.primaries_blue_xy[0],
215                    color_encoding.primaries_blue_xy[1]);
216           }
217         }
218         if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
219           printf("  transfer_function: gamma: %f\n", color_encoding.gamma);
220         } else {
221           printf("  transfer_function: %d (%s)\n",
222                  color_encoding.transfer_function,
223                  tf_string[color_encoding.transfer_function]);
224         }
225         printf("  rendering_intent: %d (%s)\n", color_encoding.rendering_intent,
226                ri_string[color_encoding.rendering_intent]);
227 
228       } else {
229         // The profile is not in JPEG XL encoded form, get as ICC profile
230         // instead.
231         printf("  format: ICC profile\n");
232         size_t profile_size;
233         if (JXL_DEC_SUCCESS !=
234             JxlDecoderGetICCProfileSize(dec, &format,
235                                         JXL_COLOR_PROFILE_TARGET_ORIGINAL,
236                                         &profile_size)) {
237           fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
238           continue;
239         }
240         printf("  ICC profile size: %" PRIu64 "\n", (uint64_t)profile_size);
241         if (profile_size < 132) {
242           fprintf(stderr, "ICC profile too small\n");
243           continue;
244         }
245         uint8_t* profile = (uint8_t*)malloc(profile_size);
246         if (JXL_DEC_SUCCESS !=
247             JxlDecoderGetColorAsICCProfile(dec, &format,
248                                            JXL_COLOR_PROFILE_TARGET_ORIGINAL,
249                                            profile, profile_size)) {
250           fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
251           free(profile);
252           continue;
253         }
254         printf("  CMM type: \"%.4s\"\n", profile + 4);
255         printf("  color space: \"%.4s\"\n", profile + 16);
256         printf("  rendering intent: %d\n", (int)profile[67]);
257         free(profile);
258       }
259     } else if (status == JXL_DEC_FRAME) {
260       if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame_header)) {
261         fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
262         break;
263       }
264       printf("frame:\n");
265       if (frame_header.name_length) {
266         char* name = malloc(frame_header.name_length + 1);
267         if (JXL_DEC_SUCCESS !=
268             JxlDecoderGetFrameName(dec, name, frame_header.name_length + 1)) {
269           fprintf(stderr, "JxlDecoderGetFrameName failed\n");
270           free(name);
271           break;
272         }
273         printf("  name: %s\n", name);
274         free(name);
275       }
276       float ms = frame_header.duration * 1000.f *
277                  info.animation.tps_denominator / info.animation.tps_numerator;
278       if (info.have_animation) {
279         printf("  duration: %u ticks (%f ms)\n", frame_header.duration, ms);
280         if (info.animation.have_timecodes) {
281           printf("  time code: %X\n", frame_header.timecode);
282         }
283       }
284       if (!frame_header.name_length && !info.have_animation) {
285         printf("  still frame, unnamed\n");
286       }
287     } else if (status == JXL_DEC_BOX) {
288       JxlBoxType type;
289       uint64_t size;
290       JxlDecoderGetBoxType(dec, type, JXL_FALSE);
291       JxlDecoderGetBoxSizeRaw(dec, &size);
292       printf("box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0], type[1],
293              type[2], type[3], (uint64_t)size);
294     } else {
295       fprintf(stderr, "Unexpected decoder status\n");
296       break;
297     }
298   }
299 
300   JxlDecoderDestroy(dec);
301   free(data);
302 
303   return seen_basic_info;
304 }
305 
main(int argc,char * argv[])306 int main(int argc, char* argv[]) {
307   if (argc != 2) {
308     fprintf(stderr,
309             "Usage: %s <jxl>\n"
310             "Where:\n"
311             "  jxl = input JPEG XL image filename\n",
312             argv[0]);
313     return 1;
314   }
315 
316   const char* jxl_filename = argv[1];
317 
318   FILE* file = fopen(jxl_filename, "rb");
319   if (!file) {
320     fprintf(stderr, "Failed to read file %s\n", jxl_filename);
321     return 1;
322   }
323 
324   if (!PrintBasicInfo(file)) {
325     fclose(file);
326     fprintf(stderr, "Couldn't print basic info\n");
327     return 1;
328   }
329 
330   fclose(file);
331   return 0;
332 }
333