| // Copyright 2022 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| // |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| // ---------------- |
| |
| /* |
| print-image-metadata prints images' metadata. |
| */ |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| |
| // Wuffs ships as a "single file C library" or "header file library" as per |
| // https://github.com/nothings/stb/blob/master/docs/stb_howto.txt |
| // |
| // To use that single file as a "foo.c"-like implementation, instead of a |
| // "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or |
| // compiling it. |
| #define WUFFS_IMPLEMENTATION |
| |
| // Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when |
| // combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs' |
| // functions have static storage. |
| // |
| // This can help the compiler ignore or discard unused code, which can produce |
| // faster compiles and smaller binaries. Other motivations are discussed in the |
| // "ALLOW STATIC IMPLEMENTATION" section of |
| // https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt |
| #define WUFFS_CONFIG__STATIC_FUNCTIONS |
| |
| // Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of |
| // release/c/etc.c choose which parts of Wuffs to build. That file contains the |
| // entire Wuffs standard library, implementing a variety of codecs and file |
| // formats. Without this macro definition, an optimizing compiler or linker may |
| // very well discard Wuffs code for unused codecs, but listing the Wuffs |
| // modules we use makes that process explicit. Preprocessing means that such |
| // code simply isn't compiled. |
| #define WUFFS_CONFIG__MODULES |
| #define WUFFS_CONFIG__MODULE__ADLER32 |
| #define WUFFS_CONFIG__MODULE__BASE |
| #define WUFFS_CONFIG__MODULE__BMP |
| #define WUFFS_CONFIG__MODULE__CRC32 |
| #define WUFFS_CONFIG__MODULE__DEFLATE |
| #define WUFFS_CONFIG__MODULE__GIF |
| #define WUFFS_CONFIG__MODULE__JPEG |
| #define WUFFS_CONFIG__MODULE__NETPBM |
| #define WUFFS_CONFIG__MODULE__NIE |
| #define WUFFS_CONFIG__MODULE__PNG |
| #define WUFFS_CONFIG__MODULE__TGA |
| #define WUFFS_CONFIG__MODULE__VP8 |
| #define WUFFS_CONFIG__MODULE__WBMP |
| #define WUFFS_CONFIG__MODULE__WEBP |
| #define WUFFS_CONFIG__MODULE__ZLIB |
| |
| // If building this program in an environment that doesn't easily accommodate |
| // relative includes, you can use the script/inline-c-relative-includes.go |
| // program to generate a stand-alone C file. |
| #include "../release/c/wuffs-unsupported-snapshot.c" |
| |
| // ---- |
| |
| #ifndef SRC_BUFFER_ARRAY_SIZE |
| #define SRC_BUFFER_ARRAY_SIZE (64 * 1024) |
| #endif |
| |
| #ifndef META_BUFFER_ARRAY_SIZE |
| #define META_BUFFER_ARRAY_SIZE (64 * 1024) |
| #endif |
| |
| #ifndef WORKBUF_ARRAY_SIZE |
| #define WORKBUF_ARRAY_SIZE (256 * 1024 * 1024) |
| #endif |
| |
| #define PRINTBUF_ARRAY_SIZE 80 |
| |
| uint8_t g_src_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0}; |
| uint8_t g_meta_buffer_array[META_BUFFER_ARRAY_SIZE] = {0}; |
| uint8_t g_workbuf_array[WORKBUF_ARRAY_SIZE] = {0}; |
| |
| uint8_t g_printbuf_array[PRINTBUF_ARRAY_SIZE] = {0}; |
| uint32_t g_printbuf_index = 0; |
| |
| // ---- |
| |
| #define TRY(error_msg) \ |
| do { \ |
| const char* z = error_msg; \ |
| if (z) { \ |
| return z; \ |
| } \ |
| } while (false) |
| |
| const uint8_t // |
| hexify[16] = { |
| '0', '1', '2', '3', '4', '5', '6', '7', // |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', // |
| }; |
| |
| const uint8_t // |
| printable_ascii[256] = { |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // |
| 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // |
| 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // |
| 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // |
| |
| 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // |
| 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // |
| 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // |
| 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, // |
| 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // |
| 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // |
| 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // |
| 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, // |
| |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, // |
| }; |
| |
| // ---- |
| |
| const char* // |
| read_buffer_from_file(wuffs_base__io_buffer* buf, FILE* f) { |
| if (buf->meta.closed) { |
| return "main: unexpected end of file"; |
| } |
| buf->compact(); |
| size_t n = fread(buf->writer_pointer(), 1, buf->writer_length(), f); |
| buf->meta.wi += n; |
| buf->meta.closed = feof(f); |
| return ferror(f) ? "main: error reading file" : nullptr; |
| } |
| |
| void // |
| print_fourcc(uint32_t fourcc) { |
| printf(" %c%c%c%c\n", // |
| (0xFF & (fourcc >> 24)), // |
| (0xFF & (fourcc >> 16)), // |
| (0xFF & (fourcc >> 8)), // |
| (0xFF & (fourcc >> 0))); |
| } |
| |
| void // |
| flush_hex_dump() { |
| if (g_printbuf_index == 0) { |
| return; |
| } |
| puts(static_cast<const char*>(static_cast<const void*>(g_printbuf_array))); |
| g_printbuf_index = 0; |
| } |
| |
| void // |
| print_hex_dump(const uint8_t* ptr, size_t len) { |
| while (len--) { |
| if (g_printbuf_index == 0) { |
| const char* s = |
| " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- " |
| "----------------"; |
| size_t n = strlen(s); |
| if ((n + 1) > PRINTBUF_ARRAY_SIZE) { |
| exit(1); |
| } |
| memcpy(&g_printbuf_array[0], s, n + 1); |
| } |
| const uint8_t c = *ptr++; |
| g_printbuf_array[(3 * g_printbuf_index) + 4] = hexify[c >> 4]; |
| g_printbuf_array[(3 * g_printbuf_index) + 5] = hexify[c & 15]; |
| g_printbuf_array[g_printbuf_index + 55] = printable_ascii[c]; |
| g_printbuf_index++; |
| if (g_printbuf_index == 16) { |
| puts( |
| static_cast<const char*>(static_cast<const void*>(g_printbuf_array))); |
| g_printbuf_index = 0; |
| } |
| } |
| } |
| |
| const char* // |
| print_raw_passthrough(wuffs_base__io_buffer* src, |
| FILE* f, |
| wuffs_base__range_ie_u64 r) { |
| if (r.is_empty()) { |
| return nullptr; |
| } |
| |
| // Advance src so that its reader_position is r.min_incl. |
| if (src->reader_position() > r.min_incl) { |
| return "main: unsupported metadata range"; |
| } |
| while (src->reader_position() < r.min_incl) { |
| if (src->writer_position() >= r.min_incl) { |
| src->meta.ri = r.min_incl - src->meta.pos; |
| break; |
| } |
| src->meta.ri = src->meta.wi; |
| TRY(read_buffer_from_file(src, f)); |
| } |
| |
| // Print the passthrough bytes until src's reader_position is r.max_excl. |
| while (true) { |
| uint64_t n0 = r.max_excl - src->reader_position(); |
| if (n0 == 0) { |
| break; |
| } |
| uint64_t n1 = src->reader_length(); |
| while (n1 == 0) { |
| TRY(read_buffer_from_file(src, f)); |
| n1 = src->reader_length(); |
| } |
| uint64_t n = wuffs_base__u64__min(n0, n1); |
| print_hex_dump(src->reader_pointer(), n); |
| src->meta.ri += n; |
| } |
| |
| return nullptr; |
| } |
| |
| const char* // |
| print_metadata(wuffs_base__image_decoder* dec, |
| wuffs_base__io_buffer* src, |
| FILE* f) { |
| bool printed_fourcc = false; |
| while (true) { |
| auto meta = wuffs_base__ptr_u8__writer(&g_meta_buffer_array[0], |
| META_BUFFER_ARRAY_SIZE); |
| auto minfo = wuffs_base__empty_more_information(); |
| auto tmm_status = dec->tell_me_more(&meta, &minfo, src); |
| |
| if (minfo.flavor) { |
| if (!printed_fourcc) { |
| printed_fourcc = true; |
| print_fourcc(minfo.metadata__fourcc()); |
| } |
| |
| switch (minfo.flavor) { |
| case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH: |
| TRY(print_raw_passthrough(src, f, |
| minfo.metadata_raw_passthrough__range())); |
| break; |
| |
| case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM: |
| print_hex_dump(meta.reader_pointer(), meta.reader_length()); |
| meta.meta.ri = meta.meta.wi; |
| break; |
| |
| case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED: |
| switch (minfo.metadata__fourcc()) { |
| case WUFFS_BASE__FOURCC__CHRM: |
| for (uint32_t i = 0; i < 8; i++) { |
| printf(" %" PRId32 "\n", minfo.metadata_parsed__chrm(i)); |
| } |
| break; |
| case WUFFS_BASE__FOURCC__GAMA: |
| printf(" %" PRIu32 "\n", minfo.metadata_parsed__gama()); |
| break; |
| case WUFFS_BASE__FOURCC__SRGB: |
| printf(" %" PRIu32 "\n", minfo.metadata_parsed__srgb()); |
| break; |
| default: |
| return "main: unsupported metadata FourCC"; |
| } |
| break; |
| |
| default: |
| return "main: unsupported metadata flavor"; |
| } |
| } |
| |
| if (tmm_status.is_ok()) { |
| break; |
| } else if (tmm_status.repr == wuffs_base__suspension__short_read) { |
| TRY(read_buffer_from_file(src, f)); |
| continue; |
| } else if (tmm_status.repr == wuffs_base__suspension__short_write) { |
| continue; |
| } else if (tmm_status.repr != |
| wuffs_base__suspension__even_more_information) { |
| return tmm_status.message(); |
| } |
| } |
| flush_hex_dump(); |
| |
| return nullptr; |
| } |
| |
| const char* // |
| handle_redirect(int32_t* out_fourcc, |
| wuffs_base__image_decoder* dec, |
| wuffs_base__io_buffer* src, |
| FILE* f) { |
| auto empty = wuffs_base__empty_io_buffer(); |
| auto minfo = wuffs_base__empty_more_information(); |
| auto tmm_status = dec->tell_me_more(&empty, &minfo, src); |
| if (tmm_status.repr != NULL) { |
| return tmm_status.message(); |
| } else if (minfo.flavor != |
| WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT) { |
| return "main: unsupported file format"; |
| } |
| *out_fourcc = (int32_t)(minfo.io_redirect__fourcc()); |
| if (*out_fourcc <= 0) { |
| return "main: unsupported file format"; |
| } |
| |
| // Advance src so that its reader_position is r.min_incl. |
| auto r = minfo.io_redirect__range(); |
| if (src->reader_position() > r.min_incl) { |
| return "main: unsupported I/O redirect range"; |
| } |
| while (src->reader_position() < r.min_incl) { |
| if (src->writer_position() >= r.min_incl) { |
| src->meta.ri = r.min_incl - src->meta.pos; |
| break; |
| } |
| src->meta.ri = src->meta.wi; |
| TRY(read_buffer_from_file(src, f)); |
| } |
| return nullptr; |
| } |
| |
| const char* // |
| handle(const char* filename, FILE* f) { |
| auto src = |
| wuffs_base__ptr_u8__writer(&g_src_buffer_array[0], SRC_BUFFER_ARRAY_SIZE); |
| auto work = |
| wuffs_base__ptr_u8__writer(&g_workbuf_array[0], WORKBUF_ARRAY_SIZE); |
| |
| int32_t fourcc = 0; |
| while (true) { |
| fourcc = wuffs_base__magic_number_guess_fourcc(src.reader_slice(), |
| src.meta.closed); |
| if (fourcc > 0) { |
| break; |
| } else if (fourcc == 0) { |
| return "main: unrecognized file format"; |
| } else { |
| TRY(read_buffer_from_file(&src, f)); |
| } |
| } |
| |
| bool redirected = false; |
| redirect: |
| do { |
| print_fourcc(fourcc); |
| wuffs_base__image_decoder::unique_ptr dec; |
| switch (fourcc) { |
| case WUFFS_BASE__FOURCC__BMP: |
| dec = wuffs_bmp__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__GIF: |
| dec = wuffs_gif__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__JPEG: |
| dec = wuffs_jpeg__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__NIE: |
| dec = wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__NPBM: |
| dec = wuffs_netpbm__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__PNG: |
| dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__TGA: |
| dec = wuffs_tga__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__WBMP: |
| dec = wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| case WUFFS_BASE__FOURCC__WEBP: |
| dec = wuffs_webp__decoder::alloc_as__wuffs_base__image_decoder(); |
| break; |
| default: |
| return "main: unsupported file format"; |
| } |
| |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__CHRM, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__EXIF, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__GAMA, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__ICCP, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__KVP, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__SRGB, true); |
| dec->set_report_metadata(WUFFS_BASE__FOURCC__XMP, true); |
| |
| while (true) { |
| auto dfc_status = dec->decode_frame_config(NULL, &src); |
| if (dfc_status.is_ok()) { |
| // No-op. |
| } else if (dfc_status.repr == wuffs_base__note__end_of_data) { |
| break; |
| } else if (dfc_status.repr == wuffs_base__note__metadata_reported) { |
| TRY(print_metadata(dec.get(), &src, f)); |
| } else if (dfc_status.repr == wuffs_base__note__i_o_redirect) { |
| if (redirected) { |
| return "main: unsupported file format"; |
| } |
| redirected = true; |
| TRY(handle_redirect(&fourcc, dec.get(), &src, f)); |
| goto redirect; |
| } else if (dfc_status.repr == wuffs_base__suspension__short_read) { |
| TRY(read_buffer_from_file(&src, f)); |
| } else { |
| return dfc_status.message(); |
| } |
| } |
| } while (false); |
| |
| return nullptr; |
| } |
| |
| int // |
| main(int argc, char** argv) { |
| for (int i = 1; i < argc; i++) { |
| FILE* f = fopen(argv[i], "r"); |
| if (!f) { |
| printf("%s\n %s\n", argv[i], strerror(errno)); |
| continue; |
| } |
| printf("%s\n", argv[i]); |
| const char* err = handle(argv[i], f); |
| if (err) { |
| printf(" %s\n", err); |
| } |
| fclose(f); |
| } |
| return 0; |
| } |