aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-12-24 14:24:23 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-12-25 18:28:37 +0100
commit7a4b5cd065b7f944cbed22021a0ee6ed9df007d2 (patch)
treeab8da2b9909423ff9533979cfb3a4454f2ef52aa
parent40c1f8327e1fdbd48492edd2631ec05c4ade3a8b (diff)
downloadfiv-7a4b5cd065b7f944cbed22021a0ee6ed9df007d2.tar.gz
fiv-7a4b5cd065b7f944cbed22021a0ee6ed9df007d2.tar.xz
fiv-7a4b5cd065b7f944cbed22021a0ee6ed9df007d2.zip
Colour manage opaque, up to 8-bit images
-rw-r--r--README.adoc7
-rw-r--r--fiv-io.c113
2 files changed, 84 insertions, 36 deletions
diff --git a/README.adoc b/README.adoc
index 0a80fb0..0749a55 100644
--- a/README.adoc
+++ b/README.adoc
@@ -5,7 +5,8 @@ fastiv
raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf
loads.
-Its development status can be summarized as '`beta`'.
+Its development status can be summarized as '`beta`'. E.g., colour management
+is partial, and certain GIFs animate wrong.
Non-goals
---------
@@ -25,8 +26,8 @@ Building and Running
Build dependencies: Meson, pkg-config +
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
spng>=0.7.0, libturbojpeg +
-Optional dependencies: LibRaw, librsvg-2.0, xcursor, libwebp, libheif, libtiff,
-gdk-pixbuf-2.0, ExifTool
+Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libwebp, libheif,
+libtiff, gdk-pixbuf-2.0, ExifTool
$ git clone --recursive https://git.janouch.name/p/fastiv.git
$ meson builddir
diff --git a/fiv-io.c b/fiv-io.c
index 894330b..c20d445 100644
--- a/fiv-io.c
+++ b/fiv-io.c
@@ -241,19 +241,19 @@ trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len)
static void
fiv_io_profile_cmyk(
- cairo_surface_t *surface, FivIoProfile src, FivIoProfile dst)
+ cairo_surface_t *surface, FivIoProfile source, FivIoProfile target)
{
unsigned char *data = cairo_image_surface_get_data(surface);
int w = cairo_image_surface_get_width(surface);
int h = cairo_image_surface_get_height(surface);
#ifndef HAVE_LCMS2
- (void) src;
- (void) dst;
+ (void) source;
+ (void) target;
#else
cmsHTRANSFORM transform = NULL;
- if (src && dst) {
- transform = cmsCreateTransform(src, TYPE_CMYK_8_REV, dst,
+ if (source && target) {
+ transform = cmsCreateTransform(source, TYPE_CMYK_8_REV, target,
FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0);
}
if (transform) {
@@ -267,12 +267,12 @@ fiv_io_profile_cmyk(
static void
fiv_io_profile_xrgb(
- cairo_surface_t *surface, FivIoProfile src, FivIoProfile dst)
+ cairo_surface_t *surface, FivIoProfile source, FivIoProfile target)
{
#ifndef HAVE_LCMS2
(void) surface;
- (void) src;
- (void) dst;
+ (void) source;
+ (void) target;
#else
unsigned char *data = cairo_image_surface_get_data(surface);
int w = cairo_image_surface_get_width(surface);
@@ -280,12 +280,12 @@ fiv_io_profile_xrgb(
// TODO(p): We should make this optional.
cmsHPROFILE src_fallback = NULL;
- if (dst && !src)
- src = src_fallback = cmsCreate_sRGBProfile();
+ if (target && !source)
+ source = src_fallback = cmsCreate_sRGBProfile();
cmsHTRANSFORM transform = NULL;
- if (src && dst) {
- transform = cmsCreateTransform(src, FIV_IO_LCMS2_ARGB, dst,
+ if (source && target) {
+ transform = cmsCreateTransform(source, FIV_IO_LCMS2_ARGB, target,
FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0);
}
if (transform) {
@@ -297,6 +297,47 @@ fiv_io_profile_xrgb(
#endif
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+fiv_io_profile_finalize_page(cairo_surface_t *page, FivIoProfile target)
+{
+ GBytes *bytes = NULL;
+ gsize len = 0;
+ gconstpointer p = NULL;
+ FivIoProfile source = NULL;
+ if ((bytes = cairo_surface_get_user_data(page, &fiv_io_key_icc)) &&
+ (p = g_bytes_get_data(bytes, &len)))
+ source = fiv_io_profile_new(p, len);
+
+ // TODO(p): Animations need to be composited in a linear colour space.
+ for (cairo_surface_t *frame = page; frame != NULL;
+ frame = cairo_surface_get_user_data(frame, &fiv_io_key_frame_next))
+ fiv_io_profile_xrgb(frame, source, target);
+
+ if (source)
+ fiv_io_profile_free(source);
+}
+
+// TODO(p): Offer better integration, upgrade the bit depth if appropriate.
+static cairo_surface_t *
+fiv_io_profile_finalize(cairo_surface_t *image, FivIoProfile target)
+{
+ if (!image || !target)
+ return image;
+
+ for (cairo_surface_t *page = image; page != NULL;
+ page = cairo_surface_get_user_data(page, &fiv_io_key_page_next)) {
+ // TODO(p): 1. un/premultiply ARGB, 2. do colour management
+ // early enough, so that no avoidable increase of quantization error
+ // occurs beforehands, and also for correct alpha compositing.
+ // FIXME: This assumes that if the first frame is opaque, they all are.
+ if (cairo_image_surface_get_format(page) == CAIRO_FORMAT_RGB24)
+ fiv_io_profile_finalize_page(page, target);
+ }
+ return image;
+}
+
// --- Wuffs -------------------------------------------------------------------
// From libwebp, verified to exactly match [x * a / 255].
@@ -738,7 +779,7 @@ fail:
static cairo_surface_t *
open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
- const gchar *data, gsize len, GError **error)
+ const gchar *data, gsize len, FivIoProfile profile, GError **error)
{
wuffs_base__image_decoder *dec = allocate();
if (!dec) {
@@ -749,7 +790,7 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
cairo_surface_t *surface = open_wuffs(
dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE), error);
free(dec);
- return surface;
+ return fiv_io_profile_finalize(surface, profile);
}
// --- JPEG --------------------------------------------------------------------
@@ -886,7 +927,8 @@ open_libjpeg_turbo(
return NULL;
}
- int pixel_format = (colorspace == TJCS_CMYK || colorspace == TJCS_YCCK)
+ bool use_cmyk = colorspace == TJCS_CMYK || colorspace == TJCS_YCCK;
+ int pixel_format = use_cmyk
? TJPF_CMYK
: (G_BYTE_ORDER == G_LITTLE_ENDIAN ? TJPF_BGRX : TJPF_XRGB);
@@ -917,8 +959,7 @@ open_libjpeg_turbo(
}
}
- load_jpeg_finalize(
- surface, (pixel_format == TJPF_CMYK), profile, data, len);
+ load_jpeg_finalize(surface, use_cmyk, profile, data, len);
tjDestroy(dec);
return surface;
}
@@ -961,8 +1002,10 @@ open_libjpeg_enhanced(
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, (const unsigned char *) data, len);
(void) jpeg_read_header(&cinfo, true);
- if (cinfo.jpeg_color_space == JCS_CMYK ||
- cinfo.jpeg_color_space == JCS_YCCK)
+
+ bool use_cmyk = cinfo.jpeg_color_space == JCS_CMYK ||
+ cinfo.jpeg_color_space == JCS_YCCK;
+ if (use_cmyk)
cinfo.out_color_space = JCS_CMYK;
else if (G_BYTE_ORDER == G_BIG_ENDIAN)
cinfo.out_color_space = JCS_EXT_XRGB;
@@ -1003,8 +1046,7 @@ open_libjpeg_enhanced(
surface_data, cinfo.output_width * cinfo.output_height);
(void) jpegqs_finish_decompress(&cinfo);
- load_jpeg_finalize(
- surface, (cinfo.out_color_space == JCS_CMYK), profile, data, len);
+ load_jpeg_finalize(surface, use_cmyk, profile, data, len);
jpeg_destroy_decompress(&cinfo);
return surface;
}
@@ -1353,6 +1395,8 @@ open_xcursor(const gchar *data, gsize len, GError **error)
static cairo_user_data_key_t key = {};
cairo_surface_set_user_data(
pages, &key, images, (cairo_destroy_func_t) XcursorImagesDestroy);
+
+ // Do not bother doing colour correction, there is no correct rendering.
return pages;
}
@@ -1492,7 +1536,8 @@ fail:
}
static cairo_surface_t *
-open_libwebp(const gchar *data, gsize len, const gchar *path, GError **error)
+open_libwebp(const gchar *data, gsize len, const gchar *path,
+ FivIoProfile profile, GError **error)
{
// It is wholly zero-initialized by libwebp.
WebPDecoderConfig config = {};
@@ -1556,7 +1601,7 @@ open_libwebp(const gchar *data, gsize len, const gchar *path, GError **error)
fail:
WebPFreeDecBuffer(&config.output);
- return result;
+ return fiv_io_profile_finalize(result, profile);
}
#endif // HAVE_LIBWEBP --------------------------------------------------------
@@ -1703,7 +1748,8 @@ load_libheif_aux_images(const gchar *path, struct heif_image_handle *top,
}
static cairo_surface_t *
-open_libheif(const gchar *data, gsize len, const gchar *path, GError **error)
+open_libheif(const gchar *data, gsize len, const gchar *path,
+ FivIoProfile profile, GError **error)
{
// libheif will throw C++ exceptions on allocation failures.
// The library is generally awful through and through.
@@ -1747,7 +1793,7 @@ open_libheif(const gchar *data, gsize len, const gchar *path, GError **error)
g_free(ids);
fail_read:
heif_context_free(ctx);
- return result;
+ return fiv_io_profile_finalize(result, profile);
}
#endif // HAVE_LIBHEIF --------------------------------------------------------
@@ -2003,7 +2049,8 @@ fail:
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
static cairo_surface_t *
-open_gdkpixbuf(const gchar *data, gsize len, GError **error)
+open_gdkpixbuf(
+ const gchar *data, gsize len, FivIoProfile profile, GError **error)
{
// gdk-pixbuf controls the playback itself, there is no reliable method of
// extracting individual frames (due to loops).
@@ -2036,7 +2083,7 @@ open_gdkpixbuf(const gchar *data, gsize len, GError **error)
}
g_object_unref(pixbuf);
- return surface;
+ return fiv_io_profile_finalize(surface, profile);
}
#endif // HAVE_GDKPIXBUF ------------------------------------------------------
@@ -2097,17 +2144,17 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
// which is so far unsupported here.
surface = open_wuffs_using(
wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder, data, len,
- error);
+ profile, error);
break;
case WUFFS_BASE__FOURCC__GIF:
surface = open_wuffs_using(
wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder, data, len,
- error);
+ profile, error);
break;
case WUFFS_BASE__FOURCC__PNG:
surface = open_wuffs_using(
wuffs_png__decoder__alloc_as__wuffs_base__image_decoder, data, len,
- error);
+ profile, error);
break;
case WUFFS_BASE__FOURCC__JPEG:
surface = enhance
@@ -2146,7 +2193,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
#endif // HAVE_XCURSOR --------------------------------------------------------
#ifdef HAVE_LIBWEBP //---------------------------------------------------------
// TODO(p): https://github.com/google/wuffs/commit/4c04ac1
- if ((surface = open_libwebp(data, len, path, error)))
+ if ((surface = open_libwebp(data, len, path, profile, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -2154,7 +2201,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
}
#endif // HAVE_LIBWEBP --------------------------------------------------------
#ifdef HAVE_LIBHEIF //---------------------------------------------------------
- if ((surface = open_libheif(data, len, path, error)))
+ if ((surface = open_libheif(data, len, path, profile, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -2172,7 +2219,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
#endif // HAVE_LIBTIFF --------------------------------------------------------
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
// This is only used as a last resort, the rest above is special-cased.
- if ((surface = open_gdkpixbuf(data, len, error)))
+ if ((surface = open_gdkpixbuf(data, len, profile, error)))
break;
if (error && (*error)->code != GDK_PIXBUF_ERROR_UNKNOWN_TYPE)
break;