Support opaque 16-bit images as RGB30 with Wuffs

Do not check whether the window's visual can make use of them,
since they're arguably rare enough.

With transparent images, we're limited by Cairo's formats.
This commit is contained in:
Přemysl Eric Janouch 2021-11-15 09:28:16 +01:00
parent e835c889a1
commit 11b7969459
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 117 additions and 40 deletions

View File

@ -50,6 +50,10 @@
#include "xdg.h" #include "xdg.h"
#include "fastiv-io.h" #include "fastiv-io.h"
#if CAIRO_VERSION >= 11702 && X11_ACTUALLY_SUPPORTS_RGBA128F_OR_WE_USE_OPENGL
#define FASTIV_CAIRO_RGBA128F
#endif
// A subset of shared-mime-info that produces an appropriate list of // A subset of shared-mime-info that produces an appropriate list of
// file extensions. Chiefly motivated by the suckiness of RAW images: // file extensions. Chiefly motivated by the suckiness of RAW images:
// someone else will maintain the list of file extensions for us. // someone else will maintain the list of file extensions for us.
@ -112,27 +116,52 @@ open_wuffs(
return NULL; return NULL;
} }
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
// wuffs_base__pixel_format__transparency() doesn't reflect the image file.
bool opaque = wuffs_base__image_config__first_frame_is_opaque(&cfg);
// Wuffs' API is kind of awful--we want to catch deep RGB and deep grey.
wuffs_base__pixel_format srcfmt =
wuffs_base__pixel_config__pixel_format(&cfg.pixcfg);
uint32_t bpp = wuffs_base__pixel_format__bits_per_pixel(&srcfmt);
// Cairo doesn't support transparency with RGB30, so no premultiplication.
bool pack_16_10 = opaque && (bpp > 24 || (bpp < 24 && bpp > 8));
#ifdef FASTIV_CAIRO_RGBA128F
bool expand_16_float = !opaque && (bpp > 24 || (bpp < 24 && bpp > 8));
#endif // FASTIV_CAIRO_RGBA128F
// In Wuffs, /doc/note/pixel-formats.md declares "memory order", which,
// for our purposes, means big endian, and BGRA results in 32-bit ARGB
// on most machines.
//
// 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;
// CAIRO_FORMAT_ARGB32: "The 32-bit quantities are stored native-endian. // CAIRO_FORMAT_ARGB32: "The 32-bit quantities are stored native-endian.
// Pre-multiplied alpha is used." // Pre-multiplied alpha is used." CAIRO_FORMAT_RGB{24,30} are analogous.
// CAIRO_FORMAT_RGB{24,30}: analogous, not going to use these so far. cairo_format_t cairo_format = CAIRO_FORMAT_ARGB32;
//
// Wuffs: /doc/note/pixel-formats.md specifies it as "memory order", which, #ifdef FASTIV_CAIRO_RGBA128F
// for our purposes, means big endian. Currently useful formats, as per if (expand_16_float) {
// support within wuffs_base__pixel_swizzler__prepare__*(): wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE;
// - WUFFS_BASE__PIXEL_FORMAT__ARGB_PREMUL: big-endian cairo_format = CAIRO_FORMAT_RGBA128F;
// - WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: little-endian } else
// - WUFFS_BASE__PIXEL_FORMAT__XRGB: big-endian #endif // FASTIV_CAIRO_RGBA128F
// - WUFFS_BASE__PIXEL_FORMAT__BGRX: little-endian if (pack_16_10) {
// // TODO(p): Make Wuffs support RGB30 as a destination format;
// TODO(p): Make Wuffs support RGB30 as a destination format, // in general, 16-bit depth swizzlers are stubbed.
// so far we only have WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE // See also wuffs_base__pixel_swizzler__prepare__*().
// and in general, 16-bit depth swizzlers are stubbed. wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE;
wuffs_base__pixel_config__set(&cfg.pixcfg, cairo_format = CAIRO_FORMAT_RGB30;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN } else if (opaque) {
WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL, wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRX;
#else cairo_format = CAIRO_FORMAT_RGB24;
WUFFS_BASE__PIXEL_FORMAT__ARGB_PREMUL, }
#endif
wuffs_base__pixel_config__set(&cfg.pixcfg, wuffs_format,
WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height); WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height);
wuffs_base__slice_u8 workbuf = {0}; wuffs_base__slice_u8 workbuf = {0};
@ -146,29 +175,42 @@ open_wuffs(
} }
} }
cairo_surface_t *surface = unsigned char *targetbuf = NULL;
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_surface_t *result = NULL, *surface =
cairo_image_surface_create(cairo_format, width, height);
cairo_status_t surface_status = cairo_surface_status(surface); cairo_status_t surface_status = cairo_surface_status(surface);
if (surface_status != CAIRO_STATUS_SUCCESS) { if (surface_status != CAIRO_STATUS_SUCCESS) {
set_error(error, cairo_status_to_string(surface_status)); set_error(error, cairo_status_to_string(surface_status));
cairo_surface_destroy(surface); goto fail;
free(workbuf.ptr);
return NULL;
} }
// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with // CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with
// ARGB/BGR/XRGB/BGRX. This function does not support a stride different // ARGB/BGR/XRGB/BGRX. This function does not support a stride different
// from the width, maybe Wuffs internals do not either. // from the width, maybe Wuffs internals do not either.
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}; wuffs_base__pixel_buffer pb = {0};
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, #ifdef FASTIV_CAIRO_RGBA128F
wuffs_base__make_slice_u8(cairo_image_surface_get_data(surface), if (expand_16_float) {
cairo_image_surface_get_stride(surface) * uint32_t targetbuf_size = height * width * 64;
cairo_image_surface_get_height(surface))); targetbuf = g_malloc(targetbuf_size);
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg,
wuffs_base__make_slice_u8(targetbuf, targetbuf_size));
} else
#endif // FASTIV_CAIRO_RGBA128F
if (pack_16_10) {
uint32_t targetbuf_size = height * width * 16;
targetbuf = g_malloc(targetbuf_size);
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg,
wuffs_base__make_slice_u8(targetbuf, targetbuf_size));
} else {
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg,
wuffs_base__make_slice_u8(surface_data,
surface_stride * cairo_image_surface_get_height(surface)));
}
if (!wuffs_base__status__is_ok(&status)) { if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status)); set_error(error, wuffs_base__status__message(&status));
cairo_surface_destroy(surface); goto fail;
free(workbuf.ptr);
return NULL;
} }
#if 0 // We're not using this right now. #if 0 // We're not using this right now.
@ -176,9 +218,7 @@ open_wuffs(
status = wuffs_png__decoder__decode_frame_config(&dec, &fc, &src); status = wuffs_png__decoder__decode_frame_config(&dec, &fc, &src);
if (!wuffs_base__status__is_ok(&status)) { if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status)); set_error(error, wuffs_base__status__message(&status));
cairo_surface_destroy(surface); goto fail;
free(workbuf.ptr);
return NULL;
} }
#endif #endif
@ -189,16 +229,49 @@ open_wuffs(
dec, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC, workbuf, NULL); dec, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC, workbuf, NULL);
if (!wuffs_base__status__is_ok(&status)) { if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status)); set_error(error, wuffs_base__status__message(&status));
cairo_surface_destroy(surface); goto fail;
free(workbuf.ptr); }
return NULL;
#ifdef FASTIV_CAIRO_RGBA128F
if (expand_16_float) {
g_debug("Wuffs to Cairo RGBA128F");
uint16_t *in = (uint16_t *) targetbuf;
float *out = (float *) surface_data;
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
float b = *in++ / 65535., g = *in++ / 65535.,
r = *in++ / 65535., a = *in++ / 65535.;
*out++ = r * a;
*out++ = g * a;
*out++ = b * a;
*out++ = a;
}
}
} else
#endif // FASTIV_CAIRO_RGBA128F
if (pack_16_10) {
g_debug("Wuffs to Cairo RGB30");
uint16_t *in = (uint16_t *) targetbuf;
uint32_t *out = (uint32_t *) surface_data;
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
uint16_t b = *in++, g = *in++, r = *in++, x = *in++;
*out++ = (x >> 14) << 30 |
(r >> 6) << 20 | (g >> 6) << 10 | (b >> 6);
}
}
} }
// Pixel data has been written, need to let Cairo know. // Pixel data has been written, need to let Cairo know.
cairo_surface_mark_dirty(surface); cairo_surface_mark_dirty((result = surface));
fail:
if (!result)
cairo_surface_destroy(surface);
g_free(targetbuf);
free(workbuf.ptr); free(workbuf.ptr);
return surface; return result;
} }
static cairo_surface_t * static cairo_surface_t *

View File

@ -181,6 +181,10 @@ fastiv_view_realize(GtkWidget *widget)
gtk_widget_register_window(widget, window); gtk_widget_register_window(widget, window);
gtk_widget_set_window(widget, window); gtk_widget_set_window(widget, window);
gtk_widget_set_realized(widget, TRUE); gtk_widget_set_realized(widget, TRUE);
// Without the following call, or the rendering mode set to "recording",
// RGB30 degrades to RGB24.
gdk_window_ensure_native(window);
} }
static gboolean static gboolean