pnginfo: extract some ImageMagick profiles
This commit is contained in:
parent
fa15707d9b
commit
5f4090aaee
128
tools/pnginfo.c
128
tools/pnginfo.c
|
@ -20,6 +20,7 @@
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
#include <jv.h>
|
#include <jv.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -65,28 +66,120 @@ strfmt(const char *format, ...)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t *
|
||||||
|
hexbin(const char *string, size_t *len)
|
||||||
|
{
|
||||||
|
static const char *alphabet = "0123456789abcdef";
|
||||||
|
uint8_t *buf = calloc(1, strlen(string) / 2 + 1), *p = buf;
|
||||||
|
while (true) {
|
||||||
|
while (*string && strchr(" \t\n\r\v\f", *string))
|
||||||
|
string++;
|
||||||
|
if (!*string)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const char *hi, *lo;
|
||||||
|
if (!(hi = strchr(alphabet, tolower(*string++))) || !*string ||
|
||||||
|
!(lo = strchr(alphabet, tolower(*string++)))) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p++ = (hi - alphabet) << 4 | (lo - alphabet);
|
||||||
|
}
|
||||||
|
*len = p - buf;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Analysis ----------------------------------------------------------------
|
// --- Analysis ----------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static uint8_t *
|
||||||
redirect_libpng_error(png_structp pngp, const char* message)
|
extract_imagemagick_attribute(const char *string, size_t *len)
|
||||||
{
|
{
|
||||||
char **storage = png_get_error_ptr(pngp);
|
if (*string++ != '\n')
|
||||||
*storage = strfmt("%s", message);
|
return NULL;
|
||||||
|
|
||||||
|
// TODO(p): Try to verify this profile type, also present in the key,
|
||||||
|
// though beware that it may contain "generic profile" for APP1, etc.
|
||||||
|
const char *type = string;
|
||||||
|
if (!(string = strchr(type, '\n')))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// strtol() skips initial whitespace, this is mostly desired.
|
||||||
|
char *end = NULL;
|
||||||
|
long size = strtol(++string, &end, 10);
|
||||||
|
if (size < 0 || end == string || *end++ != '\n')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
uint8_t *bin = hexbin(end, len);
|
||||||
|
if (!bin || (long) *len != size) {
|
||||||
|
free(bin);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jv
|
static jv
|
||||||
retrieve_texts(png_structp pngp, png_infop infop)
|
extract_imagemagick_exif(jv o, const char *string)
|
||||||
|
{
|
||||||
|
size_t exif_len = 0;
|
||||||
|
uint8_t *exif = extract_imagemagick_attribute(string, &exif_len);
|
||||||
|
if (!exif)
|
||||||
|
return add_warning(o, "invalid ImageMagick 'exif'");
|
||||||
|
|
||||||
|
o = parse_exif(o, exif, exif_len);
|
||||||
|
free(exif);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jv
|
||||||
|
extract_imagemagick_psir(jv o, const char *string)
|
||||||
|
{
|
||||||
|
size_t psir_len = 0;
|
||||||
|
uint8_t *psir = extract_imagemagick_attribute(string, &psir_len);
|
||||||
|
if (!psir)
|
||||||
|
return add_warning(o, "invalid ImageMagick '8bim'");
|
||||||
|
|
||||||
|
o = parse_psir(o, psir, psir_len);
|
||||||
|
free(psir);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
process_text(jv *o, png_textp text)
|
||||||
|
{
|
||||||
|
// TODO(p): Refactor info.h, so that it's the value of the text chunk,
|
||||||
|
// and that warnings are added to the top-level JSON.
|
||||||
|
|
||||||
|
// These seem to originate in ImageMagick,
|
||||||
|
// but are also used by ExifTool and GIMP, among others.
|
||||||
|
// https://exiftool.org/TagNames/PNG.html
|
||||||
|
// TODO(p): "iptc": may contain 8BIM or IPTC IIM directly.
|
||||||
|
// TODO(p): "APP1": may contain Exif or XMP.
|
||||||
|
if (!strcmp(text->key, "Raw profile type exif")) {
|
||||||
|
*o = extract_imagemagick_exif(*o, text->text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!strcmp(text->key, "Raw profile type 8bim")) {
|
||||||
|
*o = extract_imagemagick_psir(*o, text->text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jv
|
||||||
|
retrieve_texts(jv o, png_structp pngp, png_infop infop)
|
||||||
{
|
{
|
||||||
int texts_len = 0;
|
int texts_len = 0;
|
||||||
png_textp texts = NULL;
|
png_textp texts = NULL;
|
||||||
png_get_text(pngp, infop, &texts, &texts_len);
|
png_get_text(pngp, infop, &texts, &texts_len);
|
||||||
|
|
||||||
jv o = jv_object();
|
jv to = jv_object();
|
||||||
for (int i = 0; i < texts_len; i++) {
|
for (int i = 0; i < texts_len; i++) {
|
||||||
png_textp text = texts + i;
|
png_textp text = texts + i;
|
||||||
o = jv_object_set(o, jv_string(text->key), jv_string(text->text));
|
to = jv_object_set(to, jv_string(text->key),
|
||||||
|
process_text(&o, text) ? jv_true() : jv_string(text->text));
|
||||||
}
|
}
|
||||||
return o;
|
return jv_object_set(o, jv_string("texts"), to);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jv
|
static jv
|
||||||
|
@ -145,13 +238,19 @@ extract_chunks(png_structp pngp, png_infop infop)
|
||||||
if (png_get_iCCP(pngp, infop, &name, NULL, &profile, &profile_len))
|
if (png_get_iCCP(pngp, infop, &name, NULL, &profile, &profile_len))
|
||||||
o = jv_object_set(o, jv_string("ICC"), jv_string(name));
|
o = jv_object_set(o, jv_string("ICC"), jv_string(name));
|
||||||
|
|
||||||
// https://ftp-osl.osuosl.org/pub/libpng/documents/pngext-1.5.0.html#C.eXIf
|
|
||||||
jv set = jv_object();
|
jv set = jv_object();
|
||||||
png_unknown_chunkp unknowns = NULL;
|
png_unknown_chunkp unknowns = NULL;
|
||||||
int unknowns_len = png_get_unknown_chunks(pngp, infop, &unknowns);
|
int unknowns_len = png_get_unknown_chunks(pngp, infop, &unknowns);
|
||||||
for (int i = 0; i < unknowns_len; i++) {
|
for (int i = 0; i < unknowns_len; i++) {
|
||||||
set = jv_object_set(set,
|
set = jv_object_set(set,
|
||||||
jv_string((const char *) unknowns[i].name), jv_true());
|
jv_string((const char *) unknowns[i].name), jv_true());
|
||||||
|
|
||||||
|
// https://ftp-osl.osuosl.org/pub/libpng/documents/pngext-1.5.0.html
|
||||||
|
//
|
||||||
|
// Some software also supports the adjacent zXIf proposal,
|
||||||
|
// which ended up being rejected. Such files are rare, and best ignored.
|
||||||
|
// http://www.simplesystems.org/png-group/proposals/zXIf/history
|
||||||
|
// /png-proposed-zXIf-chunk-2017-03-05.html
|
||||||
if (!strcmp((const char *) unknowns[i].name, "eXIf"))
|
if (!strcmp((const char *) unknowns[i].name, "eXIf"))
|
||||||
o = parse_exif(o, unknowns[i].data, unknowns[i].size);
|
o = parse_exif(o, unknowns[i].data, unknowns[i].size);
|
||||||
}
|
}
|
||||||
|
@ -162,8 +261,14 @@ extract_chunks(png_structp pngp, png_infop infop)
|
||||||
o = jv_object_set(o, jv_string("chunks"), a);
|
o = jv_object_set(o, jv_string("chunks"), a);
|
||||||
jv_free(set);
|
jv_free(set);
|
||||||
|
|
||||||
o = jv_object_set(o, jv_string("texts"), retrieve_texts(pngp, infop));
|
return retrieve_texts(o, pngp, infop);
|
||||||
return o;
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
redirect_libpng_error(png_structp pngp, const char* message)
|
||||||
|
{
|
||||||
|
char **storage = png_get_error_ptr(pngp);
|
||||||
|
*storage = strfmt("%s", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jv
|
static jv
|
||||||
|
@ -179,6 +284,7 @@ do_file(const char *filename, volatile jv o)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(p): Extract libpng warnings.
|
||||||
png_structp pngp = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
png_structp pngp = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||||
(png_voidp) &err, redirect_libpng_error, NULL);
|
(png_voidp) &err, redirect_libpng_error, NULL);
|
||||||
if (!pngp) {
|
if (!pngp) {
|
||||||
|
|
Loading…
Reference in New Issue