aboutsummaryrefslogtreecommitdiff
path: root/fastiv-io.c
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-11-24 20:12:02 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-11-25 01:54:40 +0100
commit1d2f6243e03694096d279a01000b119ca8fcc7f5 (patch)
tree0911bcbe0adbd2c2deb6de614e404c2fb87046d8 /fastiv-io.c
parent2ea21787247e8fa145309dd15b77310915d8c3f6 (diff)
downloadfiv-1d2f6243e03694096d279a01000b119ca8fcc7f5.tar.gz
fiv-1d2f6243e03694096d279a01000b119ca8fcc7f5.tar.xz
fiv-1d2f6243e03694096d279a01000b119ca8fcc7f5.zip
Extract Exif and ICC profiles from Wuffs
Diffstat (limited to 'fastiv-io.c')
-rw-r--r--fastiv-io.c155
1 files changed, 143 insertions, 12 deletions
diff --git a/fastiv-io.c b/fastiv-io.c
index 95c32fc..e68b39b 100644
--- a/fastiv-io.c
+++ b/fastiv-io.c
@@ -142,6 +142,83 @@ set_error(GError **error, const char *message)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static bool
+pull_passthrough(const wuffs_base__more_information *minfo,
+ wuffs_base__io_buffer *src, wuffs_base__io_buffer *dst, GError **error)
+{
+ wuffs_base__range_ie_u64 r =
+ wuffs_base__more_information__metadata_raw_passthrough__range(minfo);
+ if (wuffs_base__range_ie_u64__is_empty(&r))
+ return true;
+
+ // This should currently be zero, because we read files all at once.
+ uint64_t pos = src->meta.pos;
+ if (pos > r.min_incl ||
+ wuffs_base__u64__sat_sub(r.max_excl, pos) > src->meta.wi) {
+ set_error(error, "metadata is outside the read buffer");
+ return false;
+ }
+
+ // Mimic WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM.
+ *dst = wuffs_base__make_io_buffer(src->data,
+ wuffs_base__make_io_buffer_meta(
+ wuffs_base__u64__sat_sub(r.max_excl, pos),
+ wuffs_base__u64__sat_sub(r.min_incl, pos), pos, TRUE));
+
+ // Seeking to the end of it seems to be a requirement in decode_gif.wuffs.
+ // Just not in case the block was empty. :^)
+ src->meta.ri = dst->meta.wi;
+ return true;
+}
+
+static GByteArray *
+pull_metadata(wuffs_base__image_decoder *dec, wuffs_base__io_buffer *src,
+ wuffs_base__more_information *minfo, GError **error)
+{
+ uint8_t buf[8192] = {};
+ GByteArray *array = g_byte_array_new();
+ while (true) {
+ *minfo = wuffs_base__empty_more_information();
+ wuffs_base__io_buffer dst = wuffs_base__ptr_u8__writer(buf, sizeof buf);
+ wuffs_base__status status =
+ wuffs_base__image_decoder__tell_me_more(dec, &dst, minfo, src);
+ switch (minfo->flavor) {
+ case 0:
+ // Most likely as a result of an error, we'll handle that below.
+ case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM:
+ // Wuffs is reading it into the buffer.
+ case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED:
+ // Use Wuffs accessor functions in the caller.
+ break;
+ default:
+ set_error(error, "Wuffs metadata API incompatibility");
+ goto fail;
+
+ case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH:
+ // The insane case: error checking really should come first,
+ // and it can say "even more information". See decode_gif.wuffs.
+ if (!pull_passthrough(minfo, src, &dst, error))
+ goto fail;
+ }
+
+ g_byte_array_append(array,
+ wuffs_base__io_buffer__reader_pointer(&dst),
+ wuffs_base__io_buffer__reader_length(&dst));
+ if (wuffs_base__status__is_ok(&status))
+ return array;
+
+ if (status.repr != wuffs_base__suspension__even_more_information &&
+ status.repr != wuffs_base__suspension__short_write) {
+ set_error(error, wuffs_base__status__message(&status));
+ goto fail;
+ }
+ }
+
+fail:
+ g_byte_array_unref(array);
+ return NULL;
+}
+
// https://github.com/google/wuffs/blob/main/example/gifplayer/gifplayer.c
// is pure C, and a good reference. I can't use the auxiliary libraries,
// since they depend on C++, which is undesirable.
@@ -149,16 +226,59 @@ static cairo_surface_t *
open_wuffs(
wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, GError **error)
{
- wuffs_base__image_config cfg;
- wuffs_base__status status =
- wuffs_base__image_decoder__decode_image_config(dec, &cfg, &src);
- if (!wuffs_base__status__is_ok(&status)) {
- set_error(error, wuffs_base__status__message(&status));
- return NULL;
+ cairo_surface_t *result = NULL;
+
+ // TODO(p): PNG also has sRGB and gAMA, as well as text chunks (Wuffs #58).
+ // The former two use WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED.
+ GBytes *meta_exif = NULL;
+ wuffs_base__image_decoder__set_report_metadata(
+ dec, WUFFS_BASE__FOURCC__EXIF, true);
+
+ GBytes *meta_iccp = NULL;
+ wuffs_base__image_decoder__set_report_metadata(
+ dec, WUFFS_BASE__FOURCC__ICCP, true);
+
+ wuffs_base__image_config cfg = {};
+ wuffs_base__status status = {};
+ while (true) {
+ status = wuffs_base__image_decoder__decode_image_config(
+ dec, &cfg, &src);
+ if (wuffs_base__status__is_ok(&status))
+ break;
+
+ if (status.repr != wuffs_base__note__metadata_reported) {
+ set_error(error, wuffs_base__status__message(&status));
+ goto fail_preread;
+ }
+
+ wuffs_base__more_information minfo = {};
+ GByteArray *buffer = NULL;
+ if (!(buffer = pull_metadata(dec, &src, &minfo, error)))
+ goto fail_preread;
+
+ switch (wuffs_base__more_information__metadata__fourcc(&minfo)) {
+ case WUFFS_BASE__FOURCC__EXIF:
+ if (meta_exif) {
+ g_warning("ignoring repeated Exif");
+ break;
+ }
+ meta_exif = g_byte_array_free_to_bytes(buffer);
+ continue;
+ case WUFFS_BASE__FOURCC__ICCP:
+ if (meta_iccp) {
+ g_warning("ignoring repeated ICC profile");
+ break;
+ }
+ meta_iccp = g_byte_array_free_to_bytes(buffer);
+ continue;
+ }
+
+ g_byte_array_unref(buffer);
}
+
if (!wuffs_base__image_config__is_valid(&cfg)) {
set_error(error, "invalid Wuffs image configuration");
- return NULL;
+ goto fail_preread;
}
// We need to check because of the Cairo API.
@@ -166,7 +286,7 @@ open_wuffs(
uint32_t height = wuffs_base__pixel_config__height(&cfg.pixcfg);
if (width > INT_MAX || height > INT_MAX) {
set_error(error, "image dimensions overflow");
- return NULL;
+ goto fail_preread;
}
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
@@ -225,12 +345,12 @@ open_wuffs(
workbuf = wuffs_base__malloc_slice_u8(malloc, workbuf_len_max_incl);
if (!workbuf.ptr) {
set_error(error, "failed to allocate a work buffer");
- return NULL;
+ goto fail_preread;
}
}
unsigned char *targetbuf = NULL;
- cairo_surface_t *result = NULL, *surface =
+ cairo_surface_t *surface =
cairo_image_surface_create(cairo_format, width, height);
cairo_status_t surface_status = cairo_surface_status(surface);
if (surface_status != CAIRO_STATUS_SUCCESS) {
@@ -319,12 +439,23 @@ open_wuffs(
// Pixel data has been written, need to let Cairo know.
cairo_surface_mark_dirty((result = surface));
+ if (meta_exif)
+ cairo_surface_set_user_data(surface, &fastiv_io_key_exif,
+ g_bytes_ref(meta_exif), (cairo_destroy_func_t) g_bytes_unref);
+ if (meta_iccp)
+ cairo_surface_set_user_data(surface, &fastiv_io_key_icc,
+ g_bytes_ref(meta_iccp), (cairo_destroy_func_t) g_bytes_unref);
+
fail:
if (!result)
cairo_surface_destroy(surface);
g_free(targetbuf);
free(workbuf.ptr);
+
+fail_preread:
+ g_clear_pointer(&meta_exif, g_bytes_unref);
+ g_clear_pointer(&meta_iccp, g_bytes_unref);
return result;
}
@@ -436,14 +567,14 @@ parse_jpeg_metadata(cairo_surface_t *surface, const gchar *data, gsize len)
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);
+ (cairo_destroy_func_t) g_bytes_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);
+ (cairo_destroy_func_t) g_bytes_unref);
else
g_byte_array_free(icc, TRUE);
}