aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-12-25 21:36:23 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-12-25 21:53:39 +0100
commitccf15bc8ae625476e025348867e1bab1f7ed9572 (patch)
tree6db98ecfc759f515a83dfe3c681d36b6a60f8586
parent5e4476ff714a3eb0cbeba42dc21acaa110efdea9 (diff)
downloadfiv-ccf15bc8ae625476e025348867e1bab1f7ed9572.tar.gz
fiv-ccf15bc8ae625476e025348867e1bab1f7ed9572.tar.xz
fiv-ccf15bc8ae625476e025348867e1bab1f7ed9572.zip
Almost fully colour-managed Wuffs (BMP, GIF, PNG)
-rw-r--r--README.adoc2
-rw-r--r--fiv-io.c123
2 files changed, 95 insertions, 30 deletions
diff --git a/README.adoc b/README.adoc
index 0749a55..b364e4f 100644
--- a/README.adoc
+++ b/README.adoc
@@ -6,7 +6,7 @@ raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf
loads.
Its development status can be summarized as '`beta`'. E.g., colour management
-is partial, and certain GIFs animate wrong.
+is a bit incomplete, and certain GIFs animate wrong.
Non-goals
---------
diff --git a/fiv-io.c b/fiv-io.c
index 3341f46..15976b6 100644
--- a/fiv-io.c
+++ b/fiv-io.c
@@ -198,6 +198,14 @@ fiv_io_profile_new_sRGB(void)
#endif
}
+static FivIoProfile
+fiv_io_profile_new_from_bytes(GBytes *bytes)
+{
+ gsize len = 0;
+ gconstpointer p = g_bytes_get_data(bytes, &len);
+ return fiv_io_profile_new(p, len);
+}
+
void
fiv_io_profile_free(FivIoProfile self)
{
@@ -211,8 +219,10 @@ fiv_io_profile_free(FivIoProfile self)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F.
-#define FIV_IO_LCMS2_ARGB \
+#define FIV_IO_LCMS2_ARGB32 \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8)
+#define FIV_IO_LCMS2_4X16LE \
+ (G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_16 : TYPE_BGRA_16_SE)
// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with
// ARGB/BGRA/XRGB/BGRX.
@@ -254,7 +264,7 @@ fiv_io_profile_cmyk(
cmsHTRANSFORM transform = NULL;
if (source && target) {
transform = cmsCreateTransform(source, TYPE_CMYK_8_REV, target,
- FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0);
+ FIV_IO_LCMS2_ARGB32, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(transform, data, data, w * h);
@@ -266,18 +276,56 @@ fiv_io_profile_cmyk(
}
static void
-fiv_io_profile_xrgb32(
- cairo_surface_t *surface, FivIoProfile source, FivIoProfile target)
+fiv_io_profile_xrgb32_direct(unsigned char *data, int w, int h,
+ FivIoProfile source, FivIoProfile target)
{
#ifndef HAVE_LCMS2
- (void) surface;
+ (void) data;
+ (void) w;
+ (void) h;
(void) source;
(void) target;
#else
+ // TODO(p): We should make this optional.
+ cmsHPROFILE src_fallback = NULL;
+ if (target && !source)
+ source = src_fallback = cmsCreate_sRGBProfile();
+
+ cmsHTRANSFORM transform = NULL;
+ if (source && target) {
+ transform = cmsCreateTransform(source, FIV_IO_LCMS2_ARGB32, target,
+ FIV_IO_LCMS2_ARGB32, INTENT_PERCEPTUAL, 0);
+ }
+ if (transform) {
+ cmsDoTransform(transform, data, data, w * h);
+ cmsDeleteTransform(transform);
+ }
+ if (src_fallback)
+ cmsCloseProfile(src_fallback);
+#endif
+}
+
+static void
+fiv_io_profile_xrgb32(
+ 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);
+ fiv_io_profile_xrgb32_direct(data, w, h, source, target);
+}
+static void
+fiv_io_profile_4x16le_direct(
+ unsigned char *data, int w, int h, FivIoProfile source, FivIoProfile target)
+{
+#ifndef HAVE_LCMS2
+ (void) data;
+ (void) w;
+ (void) h;
+ (void) source;
+ (void) target;
+#else
// TODO(p): We should make this optional.
cmsHPROFILE src_fallback = NULL;
if (target && !source)
@@ -285,8 +333,8 @@ fiv_io_profile_xrgb32(
cmsHTRANSFORM transform = NULL;
if (source && target) {
- transform = cmsCreateTransform(source, FIV_IO_LCMS2_ARGB, target,
- FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0);
+ transform = cmsCreateTransform(source, FIV_IO_LCMS2_4X16LE, target,
+ FIV_IO_LCMS2_4X16LE, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(transform, data, data, w * h);
@@ -303,14 +351,11 @@ static void
fiv_io_profile_xrgb32_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);
+ if ((bytes = cairo_surface_get_user_data(page, &fiv_io_key_icc)))
+ source = fiv_io_profile_new_from_bytes(bytes);
- // TODO(p): Animations need to be composited in a linear colour space.
+ // TODO(p): All 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_xrgb32(frame, source, target);
@@ -348,6 +393,8 @@ fiv_io_premultiply_argb32(cairo_surface_t *surface)
int h = cairo_image_surface_get_height(surface);
unsigned char *data = cairo_image_surface_get_data(surface);
int stride = cairo_image_surface_get_stride(surface);
+ if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32)
+ return;
for (int y = 0; y < h; y++) {
uint32_t *dstp = (uint32_t *) (data + stride * y);
@@ -454,6 +501,9 @@ struct load_wuffs_frame_context {
GBytes *meta_iccp; ///< Reference-counted ICC profile
GBytes *meta_xmp; ///< Reference-counted XMP
+ FivIoProfile target; ///< Target device profile, if any
+ FivIoProfile source; ///< Source colour profile, if any
+
cairo_surface_t *result; ///< The resulting surface (referenced)
cairo_surface_t *result_tail; ///< The final animation frame
};
@@ -487,13 +537,8 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
unsigned char *surface_data = cairo_image_surface_get_data(surface);
int surface_stride = cairo_image_surface_get_stride(surface);
wuffs_base__pixel_buffer pb = {0};
- if (ctx->expand_16_float) {
- uint32_t targetbuf_size = ctx->height * ctx->width * 64;
- targetbuf = g_malloc(targetbuf_size);
- status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg,
- wuffs_base__make_slice_u8(targetbuf, targetbuf_size));
- } else if (ctx->pack_16_10) {
- uint32_t targetbuf_size = ctx->height * ctx->width * 16;
+ if (ctx->expand_16_float || ctx->pack_16_10) {
+ uint32_t targetbuf_size = ctx->height * ctx->width * 8;
targetbuf = g_malloc(targetbuf_size);
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg,
wuffs_base__make_slice_u8(targetbuf, targetbuf_size));
@@ -517,6 +562,18 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
goto fail;
}
+ if (ctx->target) {
+ if (ctx->expand_16_float || ctx->pack_16_10) {
+ fiv_io_profile_4x16le_direct(
+ targetbuf, ctx->width, ctx->height, ctx->source, ctx->target);
+ // The first one premultiplies below, the second doesn't need to.
+ } else {
+ fiv_io_profile_xrgb32_direct(surface_data, ctx->width, ctx->height,
+ ctx->source, ctx->target);
+ fiv_io_premultiply_argb32(surface);
+ }
+ }
+
if (ctx->expand_16_float) {
g_debug("Wuffs to Cairo RGBA128F");
uint16_t *in = (uint16_t *) targetbuf;
@@ -561,6 +618,7 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
// Apply that frame's disposal method.
wuffs_base__rect_ie_u32 bounds =
wuffs_base__frame_config__bounds(&ctx->last_fc);
+ // TODO(p): This field needs to be colour-managed.
wuffs_base__color_u32_argb_premul bg =
wuffs_base__frame_config__background_color(&ctx->last_fc);
@@ -650,10 +708,11 @@ fail:
// is pure C, and a good reference. I can't use the auxiliary libraries,
// since they depend on C++, which is undesirable.
static cairo_surface_t *
-open_wuffs(
- wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, GError **error)
+open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
+ FivIoProfile profile, GError **error)
{
- struct load_wuffs_frame_context ctx = {.dec = dec, .src = &src};
+ struct load_wuffs_frame_context ctx = {
+ .dec = dec, .src = &src, .target = profile};
// 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.
@@ -720,13 +779,16 @@ open_wuffs(
goto fail;
}
+ if (ctx.target && ctx.meta_iccp)
+ ctx.source = fiv_io_profile_new_from_bytes(ctx.meta_iccp);
+
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
// wuffs_base__pixel_format__transparency() doesn't reflect the image file.
// TODO(p): See if wuffs_base__image_config__first_frame_is_opaque() causes
// issues with animations, and eventually ensure an alpha-capable format.
bool opaque = wuffs_base__image_config__first_frame_is_opaque(&ctx.cfg);
- // Wuffs' API is kind of awful--we want to catch deep RGB and deep grey.
+ // Wuffs' API is kind of awful--we want to catch wide RGB and wide grey.
wuffs_base__pixel_format srcfmt =
wuffs_base__pixel_config__pixel_format(&ctx.cfg.pixcfg);
uint32_t bpp = wuffs_base__pixel_format__bits_per_pixel(&srcfmt);
@@ -744,7 +806,7 @@ open_wuffs(
// XXX: WUFFS_BASE__PIXEL_FORMAT__ARGB_PREMUL is not expressible, only RGBA.
// Wuffs doesn't support big-endian architectures at all, we might want to
// fall back to spng in such cases, or do a second conversion.
- uint32_t wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL;
+ uint32_t wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;
// CAIRO_FORMAT_ARGB32: "The 32-bit quantities are stored native-endian.
// Pre-multiplied alpha is used." CAIRO_FORMAT_RGB{24,30} are analogous.
@@ -764,8 +826,9 @@ open_wuffs(
ctx.cairo_format = CAIRO_FORMAT_RGB30;
} else if (opaque) {
// BGRX doesn't have as wide swizzler support, namely in GIF.
- wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;
ctx.cairo_format = CAIRO_FORMAT_RGB24;
+ } else if (!ctx.target) {
+ wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL;
}
wuffs_base__pixel_config__set(&ctx.cfg.pixcfg, wuffs_format,
@@ -794,6 +857,7 @@ fail:
g_clear_pointer(&ctx.meta_exif, g_bytes_unref);
g_clear_pointer(&ctx.meta_iccp, g_bytes_unref);
g_clear_pointer(&ctx.meta_xmp, g_bytes_unref);
+ g_clear_pointer(&ctx.source, fiv_io_profile_free);
return ctx.result;
}
@@ -807,10 +871,11 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
return NULL;
}
- cairo_surface_t *surface = open_wuffs(
- dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE), error);
+ cairo_surface_t *surface =
+ open_wuffs(dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE),
+ profile, error);
free(dec);
- return fiv_io_profile_finalize(surface, profile);
+ return surface;
}
// --- JPEG --------------------------------------------------------------------