Almost fully colour-managed Wuffs (BMP, GIF, PNG)
This commit is contained in:
parent
5e4476ff71
commit
ccf15bc8ae
|
@ -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
|
||||
---------
|
||||
|
|
131
fiv-io.c
131
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,16 @@ 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
|
||||
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);
|
||||
|
||||
// TODO(p): We should make this optional.
|
||||
cmsHPROFILE src_fallback = NULL;
|
||||
if (target && !source)
|
||||
|
@ -285,8 +293,48 @@ 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_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)
|
||||
source = src_fallback = cmsCreate_sRGBProfile();
|
||||
|
||||
cmsHTRANSFORM transform = NULL;
|
||||
if (source && target) {
|
||||
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 --------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue