aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-11-23 21:48:14 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-11-24 20:08:15 +0100
commit2ea21787247e8fa145309dd15b77310915d8c3f6 (patch)
tree3e153a5b41d34bac73ff3997837475bd435f19ac
parentc597e7bc2c073cb40e8cccc9e9bc13048ab712d0 (diff)
downloadfiv-2ea21787247e8fa145309dd15b77310915d8c3f6.tar.gz
fiv-2ea21787247e8fa145309dd15b77310915d8c3f6.tar.xz
fiv-2ea21787247e8fa145309dd15b77310915d8c3f6.zip
Read Exif and ICC profile metadata from JPEGs
-rw-r--r--fastiv-io.c91
-rw-r--r--fastiv-io.h3
2 files changed, 94 insertions, 0 deletions
diff --git a/fastiv-io.c b/fastiv-io.c
index fd956ef..95c32fc 100644
--- a/fastiv-io.c
+++ b/fastiv-io.c
@@ -361,6 +361,93 @@ trivial_cmyk_to_bgra(unsigned char *p, int len)
}
}
+static void
+parse_jpeg_metadata(cairo_surface_t *surface, const gchar *data, gsize len)
+{
+ // Because the JPEG file format is simple, just do it manually.
+ // See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ enum {
+ APP0 = 0xE0,
+ APP1,
+ APP2,
+ RST0 = 0xD0,
+ RST1,
+ RST2,
+ RST3,
+ RST4,
+ RST5,
+ RST6,
+ RST7,
+ SOI = 0xD8,
+ EOI = 0xD9,
+ SOS = 0xDA,
+ TEM = 0x01,
+ };
+
+ GByteArray *exif = g_byte_array_new(), *icc = g_byte_array_new();
+ int icc_sequence = 0, icc_done = FALSE;
+
+ const guint8 *p = (const guint8 *) data, *end = p + len;
+ while (p + 3 < end && *p++ == 0xFF && *p != SOS && *p != EOI) {
+ // The previous byte is a fill byte, restart.
+ if (*p == 0xFF)
+ continue;
+
+ // These markers stand alone, not starting a marker segment.
+ guint8 marker = *p++;
+ switch (marker) {
+ case RST0:
+ case RST1:
+ case RST2:
+ case RST3:
+ case RST4:
+ case RST5:
+ case RST6:
+ case RST7:
+ case SOI:
+ case TEM:
+ continue;
+ }
+
+ // Do not bother validating the structure.
+ guint16 length = p[0] << 8 | p[1];
+ const guint8 *payload = p + 2;
+ if ((p += length) > end)
+ break;
+
+ // https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf 4.7.2
+ // Do not check the padding byte.
+ if (marker == APP1 && p - payload >= 6 &&
+ !memcmp(payload, "Exif\0", 5) && !exif->len) {
+ payload += 6;
+ g_byte_array_append(exif, payload, p - payload);
+ }
+
+ // https://www.color.org/specification/ICC1v43_2010-12.pdf B.4
+ if (marker == APP2 && p - payload >= 14 &&
+ !memcmp(payload, "ICC_PROFILE\0", 12) && !icc_done &&
+ payload[12] == ++icc_sequence && payload[13] >= payload[12]) {
+ payload += 14;
+ g_byte_array_append(icc, payload, p - payload);
+ icc_done = payload[-1] == icc_sequence;
+ }
+ }
+
+ if (exif->len)
+ cairo_surface_set_user_data(surface, &fastiv_io_key_exif,
+ g_byte_array_free_to_bytes(exif),
+ (cairo_destroy_func_t) g_byte_array_unref);
+ else
+ g_byte_array_free(exif, TRUE);
+
+ if (icc_done)
+ cairo_surface_set_user_data(surface, &fastiv_io_key_icc,
+ g_byte_array_free_to_bytes(icc),
+ (cairo_destroy_func_t) g_byte_array_unref);
+ else
+ g_byte_array_free(icc, TRUE);
+}
+
static cairo_surface_t *
open_libjpeg_turbo(const gchar *data, gsize len, GError **error)
{
@@ -417,6 +504,7 @@ open_libjpeg_turbo(const gchar *data, gsize len, GError **error)
cairo_surface_mark_dirty(surface);
tjDestroy(dec);
+ parse_jpeg_metadata(surface, data, len);
return surface;
}
@@ -764,6 +852,9 @@ open_gdkpixbuf(const gchar *data, gsize len, GError **error)
#endif // HAVE_GDKPIXBUF ------------------------------------------------------
+cairo_user_data_key_t fastiv_io_key_exif;
+cairo_user_data_key_t fastiv_io_key_icc;
+
cairo_surface_t *
fastiv_io_open(const gchar *path, GError **error)
{
diff --git a/fastiv-io.h b/fastiv-io.h
index d4fe58e..aa21a33 100644
--- a/fastiv-io.h
+++ b/fastiv-io.h
@@ -52,6 +52,9 @@ typedef struct _FastivIoThumbnailSizeInfo {
extern FastivIoThumbnailSizeInfo
fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT];
+extern cairo_user_data_key_t fastiv_io_key_exif;
+extern cairo_user_data_key_t fastiv_io_key_icc;
+
cairo_surface_t *fastiv_io_open(const gchar *path, GError **error);
cairo_surface_t *fastiv_io_open_from_data(
const char *data, size_t len, const gchar *path, GError **error);