mirror of
https://github.com/google/wuffs.git
synced 2026-01-18 17:11:32 +01:00
470 lines
15 KiB
C++
470 lines
15 KiB
C++
// 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__ETC2
|
|
#define WUFFS_CONFIG__MODULE__GIF
|
|
#define WUFFS_CONFIG__MODULE__HANDSUM
|
|
#define WUFFS_CONFIG__MODULE__JPEG
|
|
#define WUFFS_CONFIG__MODULE__NETPBM
|
|
#define WUFFS_CONFIG__MODULE__NIE
|
|
#define WUFFS_CONFIG__MODULE__PNG
|
|
#define WUFFS_CONFIG__MODULE__QOI
|
|
#define WUFFS_CONFIG__MODULE__TARGA
|
|
#define WUFFS_CONFIG__MODULE__THUMBHASH
|
|
#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__ETC2:
|
|
dec = wuffs_etc2__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__HNSM:
|
|
dec = wuffs_handsum__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__QOI:
|
|
dec = wuffs_qoi__decoder::alloc_as__wuffs_base__image_decoder();
|
|
break;
|
|
case WUFFS_BASE__FOURCC__TGA:
|
|
dec = wuffs_targa__decoder::alloc_as__wuffs_base__image_decoder();
|
|
break;
|
|
case WUFFS_BASE__FOURCC__TH:
|
|
dec = wuffs_thumbhash__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;
|
|
}
|