blob: 4b502874068010b50b24acba260f77a6770c7d78 [file] [log] [blame]
Nigel Tao3ff4bf42022-06-03 12:04:34 +10001// Copyright 2022 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// ----------------
16
17/*
18print-image-metadata prints images' metadata.
19*/
20
21#include <inttypes.h>
22#include <stdio.h>
23
24// Wuffs ships as a "single file C library" or "header file library" as per
25// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
26//
27// To use that single file as a "foo.c"-like implementation, instead of a
28// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
29// compiling it.
30#define WUFFS_IMPLEMENTATION
31
32// Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when
33// combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs'
34// functions have static storage.
35//
36// This can help the compiler ignore or discard unused code, which can produce
37// faster compiles and smaller binaries. Other motivations are discussed in the
38// "ALLOW STATIC IMPLEMENTATION" section of
39// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt
40#define WUFFS_CONFIG__STATIC_FUNCTIONS
41
42// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
43// release/c/etc.c choose which parts of Wuffs to build. That file contains the
44// entire Wuffs standard library, implementing a variety of codecs and file
45// formats. Without this macro definition, an optimizing compiler or linker may
46// very well discard Wuffs code for unused codecs, but listing the Wuffs
47// modules we use makes that process explicit. Preprocessing means that such
48// code simply isn't compiled.
49#define WUFFS_CONFIG__MODULES
50#define WUFFS_CONFIG__MODULE__ADLER32
51#define WUFFS_CONFIG__MODULE__BASE
52#define WUFFS_CONFIG__MODULE__BMP
53#define WUFFS_CONFIG__MODULE__CRC32
54#define WUFFS_CONFIG__MODULE__DEFLATE
55#define WUFFS_CONFIG__MODULE__GIF
56#define WUFFS_CONFIG__MODULE__LZW
57#define WUFFS_CONFIG__MODULE__NIE
58#define WUFFS_CONFIG__MODULE__PNG
59#define WUFFS_CONFIG__MODULE__TGA
60#define WUFFS_CONFIG__MODULE__WBMP
61#define WUFFS_CONFIG__MODULE__ZLIB
62
63// If building this program in an environment that doesn't easily accommodate
64// relative includes, you can use the script/inline-c-relative-includes.go
65// program to generate a stand-alone C file.
66#include "../release/c/wuffs-unsupported-snapshot.c"
67
68// ----
69
70#ifndef SRC_BUFFER_ARRAY_SIZE
71#define SRC_BUFFER_ARRAY_SIZE (64 * 1024)
72#endif
73
74#ifndef META_BUFFER_ARRAY_SIZE
75#define META_BUFFER_ARRAY_SIZE (64 * 1024)
76#endif
77
78#ifndef WORKBUF_ARRAY_SIZE
79#define WORKBUF_ARRAY_SIZE (256 * 1024 * 1024)
80#endif
81
82#define PRINTBUF_ARRAY_SIZE 80
83
84uint8_t g_src_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0};
85uint8_t g_meta_buffer_array[META_BUFFER_ARRAY_SIZE] = {0};
86uint8_t g_workbuf_array[WORKBUF_ARRAY_SIZE] = {0};
87
88uint8_t g_printbuf_array[PRINTBUF_ARRAY_SIZE] = {0};
89uint32_t g_printbuf_index = 0;
90
91// ----
92
93#define TRY(error_msg) \
94 do { \
95 const char* z = error_msg; \
96 if (z) { \
97 return z; \
98 } \
99 } while (false)
100
101const uint8_t //
102 hexify[16] = {
103 '0', '1', '2', '3', '4', '5', '6', '7', //
104 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', //
105};
106
107const uint8_t //
108 printable_ascii[256] = {
109 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
110 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
111 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
112 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
113 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, //
114 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, //
115 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, //
116 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, //
117
118 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, //
119 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, //
120 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, //
121 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, //
122 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, //
123 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, //
124 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, //
125 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, //
126
127 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
128 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
129 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
130 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
131 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
132 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
133 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
134 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
135
136 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
137 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
138 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
139 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
140 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
141 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
142 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
143 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, //
144};
145
146// ----
147
148const char* //
149read_buffer_from_file(wuffs_base__io_buffer* buf, FILE* f) {
150 if (buf->meta.closed) {
151 return "main: unexpected end of file";
152 }
153 buf->compact();
154 size_t n = fread(buf->writer_pointer(), 1, buf->writer_length(), f);
155 buf->meta.wi += n;
156 buf->meta.closed = feof(f);
157 return ferror(f) ? "main: error reading file" : nullptr;
158}
159
160void //
161print_fourcc(uint32_t fourcc) {
162 printf(" %c%c%c%c\n", //
163 (0xFF & (fourcc >> 24)), //
164 (0xFF & (fourcc >> 16)), //
165 (0xFF & (fourcc >> 8)), //
166 (0xFF & (fourcc >> 0)));
167}
168
169void //
170flush_hex_dump() {
171 if (g_printbuf_index == 0) {
172 return;
173 }
174 puts(static_cast<const char*>(static_cast<const void*>(g_printbuf_array)));
175 g_printbuf_index = 0;
176}
177
178void //
179print_hex_dump(const uint8_t* ptr, size_t len) {
180 while (len--) {
181 if (g_printbuf_index == 0) {
182 const char* s =
183 " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "
184 "----------------";
185 size_t n = strlen(s);
186 if ((n + 1) > PRINTBUF_ARRAY_SIZE) {
187 exit(1);
188 }
189 memcpy(&g_printbuf_array[0], s, n + 1);
190 }
191 const uint8_t c = *ptr++;
192 g_printbuf_array[(3 * g_printbuf_index) + 4] = hexify[c >> 4];
193 g_printbuf_array[(3 * g_printbuf_index) + 5] = hexify[c & 15];
194 g_printbuf_array[g_printbuf_index + 55] = printable_ascii[c];
195 g_printbuf_index++;
196 if (g_printbuf_index == 16) {
197 puts(
198 static_cast<const char*>(static_cast<const void*>(g_printbuf_array)));
199 g_printbuf_index = 0;
200 }
201 }
202}
203
204const char* //
205print_raw_passthrough(wuffs_base__io_buffer* src,
206 FILE* f,
207 wuffs_base__range_ie_u64 r) {
208 if (r.is_empty()) {
209 return nullptr;
210 }
211
212 // Advance src so that its reader_position is r.min_incl.
213 if (src->reader_position() > r.min_incl) {
214 return "main: unsupported metadata range";
215 }
216 while (src->reader_position() < r.min_incl) {
217 if (src->writer_position() >= r.min_incl) {
218 src->meta.ri = r.min_incl - src->meta.pos;
219 break;
220 }
221 src->meta.ri = src->meta.wi;
222 TRY(read_buffer_from_file(src, f));
223 }
224
225 // Print the passthrough bytes until src's reader_position is r.max_excl.
226 while (true) {
227 uint64_t n0 = r.max_excl - src->reader_position();
228 if (n0 == 0) {
229 break;
230 }
231 uint64_t n1 = src->reader_length();
232 uint64_t n = wuffs_base__u64__min(n0, n1);
233 print_hex_dump(src->reader_pointer(), n);
234 src->meta.ri += n;
235 }
236
237 return nullptr;
238}
239
240const char* //
241print_metadata(wuffs_base__image_decoder* dec,
242 wuffs_base__io_buffer* src,
243 FILE* f) {
244 bool printed_fourcc = false;
245 while (true) {
246 auto meta = wuffs_base__ptr_u8__writer(&g_meta_buffer_array[0],
247 META_BUFFER_ARRAY_SIZE);
248 auto minfo = wuffs_base__empty_more_information();
249 auto tmm_status = dec->tell_me_more(&meta, &minfo, src);
250
251 if (minfo.flavor) {
252 if (!printed_fourcc) {
253 printed_fourcc = true;
254 print_fourcc(minfo.metadata__fourcc());
255 }
256
257 switch (minfo.flavor) {
258 case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH:
259 TRY(print_raw_passthrough(src, f,
260 minfo.metadata_raw_passthrough__range()));
261 break;
262
263 case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM:
264 print_hex_dump(meta.reader_pointer(), meta.reader_length());
265 meta.meta.ri = meta.meta.wi;
266 break;
267
268 case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED:
269 switch (minfo.metadata__fourcc()) {
270 case WUFFS_BASE__FOURCC__CHRM:
271 for (uint32_t i = 0; i < 8; i++) {
272 printf(" %" PRId32 "\n", minfo.metadata_parsed__chrm(i));
273 }
274 break;
275 case WUFFS_BASE__FOURCC__GAMA:
276 printf(" %" PRIu32 "\n", minfo.metadata_parsed__gama());
277 break;
278 case WUFFS_BASE__FOURCC__SRGB:
279 printf(" %" PRIu32 "\n", minfo.metadata_parsed__srgb());
280 break;
281 default:
282 return "main: unsupported metadata FourCC";
283 }
284 break;
285
286 default:
287 return "main: unsupported metadata flavor";
288 }
289 }
290
291 if (tmm_status.is_ok()) {
292 break;
293 } else if (tmm_status.repr == wuffs_base__suspension__short_read) {
294 TRY(read_buffer_from_file(src, f));
295 continue;
296 } else if (tmm_status.repr == wuffs_base__suspension__short_write) {
297 continue;
298 } else if (tmm_status.repr !=
299 wuffs_base__suspension__even_more_information) {
300 return tmm_status.message();
301 }
302 }
303 flush_hex_dump();
304
305 return nullptr;
306}
307
308const char* //
309handle_redirect(int32_t* out_fourcc,
310 wuffs_base__image_decoder* dec,
311 wuffs_base__io_buffer* src,
312 FILE* f) {
313 auto empty = wuffs_base__empty_io_buffer();
314 auto minfo = wuffs_base__empty_more_information();
315 auto tmm_status = dec->tell_me_more(&empty, &minfo, src);
316 if (tmm_status.repr != NULL) {
317 return tmm_status.message();
318 } else if (minfo.flavor !=
319 WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT) {
320 return "main: unsupported file format";
321 }
322 *out_fourcc = (int32_t)(minfo.io_redirect__fourcc());
323 if (*out_fourcc <= 0) {
324 return "main: unsupported file format";
325 }
326
327 // Advance src so that its reader_position is r.min_incl.
328 auto r = minfo.io_redirect__range();
329 if (src->reader_position() > r.min_incl) {
330 return "main: unsupported I/O redirect range";
331 }
332 while (src->reader_position() < r.min_incl) {
333 if (src->writer_position() >= r.min_incl) {
334 src->meta.ri = r.min_incl - src->meta.pos;
335 break;
336 }
337 src->meta.ri = src->meta.wi;
338 TRY(read_buffer_from_file(src, f));
339 }
340 return nullptr;
341}
342
343const char* //
344handle(const char* filename, FILE* f) {
345 auto src =
346 wuffs_base__ptr_u8__writer(&g_src_buffer_array[0], SRC_BUFFER_ARRAY_SIZE);
347 auto work =
348 wuffs_base__ptr_u8__writer(&g_workbuf_array[0], WORKBUF_ARRAY_SIZE);
349
350 int32_t fourcc = 0;
351 while (true) {
352 fourcc = wuffs_base__magic_number_guess_fourcc(src.reader_slice(),
353 src.meta.closed);
354 if (fourcc > 0) {
355 break;
356 } else if (fourcc == 0) {
357 return "main: unrecognized file format";
358 } else {
359 TRY(read_buffer_from_file(&src, f));
360 }
361 }
362
363 bool redirected = false;
364redirect:
365 do {
366 print_fourcc(fourcc);
367 wuffs_base__image_decoder::unique_ptr dec(nullptr, &free);
368 switch (fourcc) {
369 case WUFFS_BASE__FOURCC__BMP:
370 dec = wuffs_bmp__decoder::alloc_as__wuffs_base__image_decoder();
371 break;
372 case WUFFS_BASE__FOURCC__GIF:
373 dec = wuffs_gif__decoder::alloc_as__wuffs_base__image_decoder();
374 break;
375 case WUFFS_BASE__FOURCC__NIE:
376 dec = wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder();
377 break;
378 case WUFFS_BASE__FOURCC__PNG:
379 dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder();
380 break;
381 case WUFFS_BASE__FOURCC__TGA:
382 dec = wuffs_tga__decoder::alloc_as__wuffs_base__image_decoder();
383 break;
384 case WUFFS_BASE__FOURCC__WBMP:
385 dec = wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder();
386 break;
387 default:
388 return "main: unsupported file format";
389 }
390
391 dec->set_report_metadata(WUFFS_BASE__FOURCC__CHRM, true);
392 dec->set_report_metadata(WUFFS_BASE__FOURCC__EXIF, true);
393 dec->set_report_metadata(WUFFS_BASE__FOURCC__GAMA, true);
394 dec->set_report_metadata(WUFFS_BASE__FOURCC__ICCP, true);
395 dec->set_report_metadata(WUFFS_BASE__FOURCC__KVP, true);
396 dec->set_report_metadata(WUFFS_BASE__FOURCC__SRGB, true);
397 dec->set_report_metadata(WUFFS_BASE__FOURCC__XMP, true);
398
399 while (true) {
400 auto dfc_status = dec->decode_frame_config(NULL, &src);
401 if (dfc_status.is_ok()) {
402 // No-op.
403 } else if (dfc_status.repr == wuffs_base__note__end_of_data) {
404 break;
405 } else if (dfc_status.repr == wuffs_base__note__metadata_reported) {
406 TRY(print_metadata(dec.get(), &src, f));
407 } else if (dfc_status.repr == wuffs_base__note__i_o_redirect) {
408 if (redirected) {
409 return "main: unsupported file format";
410 }
411 redirected = true;
412 TRY(handle_redirect(&fourcc, dec.get(), &src, f));
413 goto redirect;
414 } else if (dfc_status.repr == wuffs_base__suspension__short_read) {
415 TRY(read_buffer_from_file(&src, f));
416 } else {
417 return dfc_status.message();
418 }
419 }
420 } while (false);
421
422 return nullptr;
423}
424
425int //
426main(int argc, char** argv) {
427 for (int i = 1; i < argc; i++) {
428 FILE* f = fopen(argv[i], "r");
429 if (!f) {
430 printf("%s\n %s\n", argv[i], strerror(errno));
431 continue;
432 }
433 printf("%s\n", argv[i]);
434 const char* err = handle(argv[i], f);
435 if (err) {
436 printf(" %s\n", err);
437 }
438 fclose(f);
439 }
440 return 0;
441}