Get rid of our spng dependency
Thumbnails can be properly loaded using Wuffs now.
This commit is contained in:
parent
ac6b606ccc
commit
024b5117b4
@ -38,7 +38,7 @@ Building and Running
|
||||
--------------------
|
||||
Build dependencies: Meson, pkg-config +
|
||||
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
||||
libturbojpeg, libwebp, spng>=0.7.0 +
|
||||
libturbojpeg, libwebp +
|
||||
Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff,
|
||||
ExifTool, resvg (unstable API, needs to be requested explicitly)
|
||||
|
||||
|
166
fiv-io.c
166
fiv-io.c
@ -582,6 +582,9 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(p): Maybe pre-clear with
|
||||
// wuffs_base__frame_config__background_color(&fc).
|
||||
|
||||
// Wuffs' test/data/animated-red-blue.gif, e.g., needs this handling.
|
||||
cairo_format_t decode_format = ctx->cairo_format;
|
||||
if (wuffs_base__frame_config__index(&fc) > 0 &&
|
||||
@ -785,7 +788,7 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
|
||||
struct load_wuffs_frame_context ctx = {
|
||||
.dec = dec, .src = &src, .target = ioctx->screen_profile};
|
||||
|
||||
// TODO(p): PNG text chunks (Wuffs #58).
|
||||
// TODO(p): PNG text chunks, like we do with PNG thumbnails.
|
||||
// TODO(p): See if something could and should be done about
|
||||
// https://www.w3.org/TR/png-hdr-pq/
|
||||
wuffs_base__image_decoder__set_report_metadata(
|
||||
@ -969,6 +972,166 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
|
||||
return surface;
|
||||
}
|
||||
|
||||
// --- Wuffs for PNG thumbnails ------------------------------------------------
|
||||
|
||||
static bool
|
||||
pull_metadata_kvp(wuffs_png__decoder *dec, wuffs_base__io_buffer *src,
|
||||
GHashTable *texts, gchar **key, GError **error)
|
||||
{
|
||||
wuffs_base__more_information minfo = {};
|
||||
GBytes *bytes = NULL;
|
||||
if (!(bytes = pull_metadata(
|
||||
wuffs_png__decoder__upcast_as__wuffs_base__image_decoder(dec),
|
||||
src, &minfo, error)))
|
||||
return false;
|
||||
|
||||
switch (wuffs_base__more_information__metadata__fourcc(&minfo)) {
|
||||
case WUFFS_BASE__FOURCC__KVPK:
|
||||
g_assert(*key == NULL);
|
||||
*key = g_strndup(
|
||||
g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes));
|
||||
break;
|
||||
case WUFFS_BASE__FOURCC__KVPV:
|
||||
g_assert(*key != NULL);
|
||||
g_hash_table_insert(texts, *key, g_strndup(
|
||||
g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)));
|
||||
*key = NULL;
|
||||
}
|
||||
|
||||
g_bytes_unref(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
// An uncomplicated variant of fiv_io_open(), might be up for refactoring.
|
||||
cairo_surface_t *
|
||||
fiv_io_open_png_thumbnail(const char *path, GError **error)
|
||||
{
|
||||
wuffs_png__decoder dec = {};
|
||||
wuffs_base__status status = wuffs_png__decoder__initialize(
|
||||
&dec, sizeof dec, WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED);
|
||||
if (!wuffs_base__status__is_ok(&status)) {
|
||||
set_error(error, wuffs_base__status__message(&status));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *data = NULL;
|
||||
gsize len = 0;
|
||||
if (!g_file_get_contents(path, &data, &len, error))
|
||||
return NULL;
|
||||
|
||||
wuffs_base__io_buffer src =
|
||||
wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE);
|
||||
wuffs_png__decoder__set_report_metadata(
|
||||
&dec, WUFFS_BASE__FOURCC__KVP, true);
|
||||
|
||||
wuffs_base__image_config cfg = {};
|
||||
wuffs_base__slice_u8 workbuf = {};
|
||||
cairo_surface_t *surface = NULL;
|
||||
bool success = false;
|
||||
|
||||
GHashTable *texts =
|
||||
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
gchar *key = NULL;
|
||||
while (true) {
|
||||
status = wuffs_png__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;
|
||||
}
|
||||
if (!pull_metadata_kvp(&dec, &src, texts, &key, error))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_assert(key == NULL);
|
||||
|
||||
uint32_t width = wuffs_base__pixel_config__width(&cfg.pixcfg);
|
||||
uint32_t height = wuffs_base__pixel_config__height(&cfg.pixcfg);
|
||||
if (width > INT16_MAX || height > INT16_MAX) {
|
||||
set_error(error, "image dimensions overflow");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
wuffs_base__pixel_config__set(&cfg.pixcfg,
|
||||
WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL,
|
||||
WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height);
|
||||
|
||||
uint64_t workbuf_len_max_incl =
|
||||
wuffs_png__decoder__workbuf_len(&dec).max_incl;
|
||||
if (workbuf_len_max_incl) {
|
||||
workbuf = wuffs_base__malloc_slice_u8(malloc, workbuf_len_max_incl);
|
||||
if (!workbuf.ptr) {
|
||||
set_error(error, "failed to allocate a work buffer");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
surface = cairo_image_surface_create(
|
||||
wuffs_base__image_config__first_frame_is_opaque(&cfg)
|
||||
? CAIRO_FORMAT_RGB24
|
||||
: CAIRO_FORMAT_ARGB32,
|
||||
width, height);
|
||||
|
||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||
set_error(error, cairo_status_to_string(surface_status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
wuffs_base__pixel_buffer pb = {};
|
||||
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg,
|
||||
wuffs_base__make_slice_u8(cairo_image_surface_get_data(surface),
|
||||
cairo_image_surface_get_stride(surface) *
|
||||
cairo_image_surface_get_height(surface)));
|
||||
if (!wuffs_base__status__is_ok(&status)) {
|
||||
set_error(error, wuffs_base__status__message(&status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = wuffs_png__decoder__decode_frame(&dec, &pb, &src,
|
||||
WUFFS_BASE__PIXEL_BLEND__SRC, workbuf, NULL);
|
||||
if (!wuffs_base__status__is_ok(&status)) {
|
||||
set_error(error, wuffs_base__status__message(&status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// The specification does not say where the required metadata should be,
|
||||
// it could very well be broken up into two parts.
|
||||
wuffs_base__frame_config fc = {};
|
||||
while (true) {
|
||||
// Not interested in APNG, might even throw an error in that case.
|
||||
status = wuffs_png__decoder__decode_frame_config(&dec, &fc, &src);
|
||||
if (status.repr == wuffs_base__note__end_of_data ||
|
||||
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;
|
||||
}
|
||||
if (!pull_metadata_kvp(&dec, &src, texts, &key, error))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_assert(key == NULL);
|
||||
|
||||
cairo_surface_mark_dirty(surface);
|
||||
cairo_surface_set_user_data(surface, &fiv_io_key_text,
|
||||
g_hash_table_ref(texts), (cairo_destroy_func_t) g_hash_table_unref);
|
||||
success = true;
|
||||
|
||||
fail:
|
||||
if (!success)
|
||||
g_clear_pointer(&surface, cairo_surface_destroy);
|
||||
|
||||
free(workbuf.ptr);
|
||||
g_free(data);
|
||||
g_hash_table_unref(texts);
|
||||
return surface;
|
||||
}
|
||||
|
||||
// --- JPEG --------------------------------------------------------------------
|
||||
|
||||
static GBytes *
|
||||
@ -2555,6 +2718,7 @@ cairo_user_data_key_t fiv_io_key_orientation;
|
||||
cairo_user_data_key_t fiv_io_key_icc;
|
||||
cairo_user_data_key_t fiv_io_key_xmp;
|
||||
cairo_user_data_key_t fiv_io_key_thum;
|
||||
cairo_user_data_key_t fiv_io_key_text;
|
||||
|
||||
cairo_user_data_key_t fiv_io_key_frame_next;
|
||||
cairo_user_data_key_t fiv_io_key_frame_previous;
|
||||
|
4
fiv-io.h
4
fiv-io.h
@ -53,6 +53,9 @@ extern cairo_user_data_key_t fiv_io_key_icc;
|
||||
extern cairo_user_data_key_t fiv_io_key_xmp;
|
||||
/// GBytes with a WebP's THUM chunk, used for our thumbnails.
|
||||
extern cairo_user_data_key_t fiv_io_key_thum;
|
||||
/// GHashTable with key-value pairs from PNG's tEXt, zTXt, iTXt chunks.
|
||||
/// Currently only read by fiv_io_open_png_thumbnail().
|
||||
extern cairo_user_data_key_t fiv_io_key_text;
|
||||
|
||||
/// The next frame in a sequence, as a surface, in a chain, pre-composited.
|
||||
/// There is no wrap-around.
|
||||
@ -95,6 +98,7 @@ typedef struct {
|
||||
cairo_surface_t *fiv_io_open(const FivIoOpenContext *ctx, GError **error);
|
||||
cairo_surface_t *fiv_io_open_from_data(
|
||||
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error);
|
||||
cairo_surface_t *fiv_io_open_png_thumbnail(const char *path, GError **error);
|
||||
|
||||
// --- Thumbnail passing utilities ---------------------------------------------
|
||||
|
||||
|
148
fiv-thumbnail.c
148
fiv-thumbnail.c
@ -18,7 +18,6 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <spng.h>
|
||||
#include <webp/demux.h>
|
||||
#include <webp/encode.h>
|
||||
#include <webp/mux.h>
|
||||
@ -446,138 +445,33 @@ read_wide_thumbnail(
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static int // tri-state
|
||||
check_spng_thumbnail_texts(struct spng_text *texts, uint32_t texts_len,
|
||||
const gchar *target, time_t mtime)
|
||||
{
|
||||
// May contain Thumb::Image::Width Thumb::Image::Height,
|
||||
// but those aren't interesting currently (would be for fast previews).
|
||||
bool need_uri = true, need_mtime = true;
|
||||
for (uint32_t i = 0; i < texts_len; i++) {
|
||||
struct spng_text *text = texts + i;
|
||||
if (!strcmp(text->keyword, THUMB_URI)) {
|
||||
need_uri = false;
|
||||
if (strcmp(target, text->text))
|
||||
return false;
|
||||
}
|
||||
if (!strcmp(text->keyword, THUMB_MTIME)) {
|
||||
need_mtime = false;
|
||||
if (atol(text->text) != mtime)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return need_uri || need_mtime ? -1 : true;
|
||||
}
|
||||
|
||||
static int // tri-state
|
||||
check_spng_thumbnail(spng_ctx *ctx, const gchar *target, time_t mtime, int *err)
|
||||
{
|
||||
uint32_t texts_len = 0;
|
||||
if ((*err = spng_get_text(ctx, NULL, &texts_len)))
|
||||
return false;
|
||||
|
||||
int result = false;
|
||||
struct spng_text *texts = g_malloc0_n(texts_len, sizeof *texts);
|
||||
if (!(*err = spng_get_text(ctx, texts, &texts_len)))
|
||||
result = check_spng_thumbnail_texts(texts, texts_len, target, mtime);
|
||||
g_free(texts);
|
||||
return result;
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
read_spng_thumbnail(
|
||||
read_png_thumbnail(
|
||||
const gchar *path, const gchar *uri, time_t mtime, GError **error)
|
||||
{
|
||||
FILE *fp;
|
||||
cairo_surface_t *result = NULL;
|
||||
if (!(fp = fopen(path, "rb"))) {
|
||||
set_error(error, g_strerror(errno));
|
||||
cairo_surface_t *surface = fiv_io_open_png_thumbnail(path, error);
|
||||
if (!surface)
|
||||
return NULL;
|
||||
|
||||
GHashTable *texts = cairo_surface_get_user_data(surface, &fiv_io_key_text);
|
||||
if (!texts) {
|
||||
set_error(error, "not a thumbnail");
|
||||
cairo_surface_destroy(surface);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
spng_ctx *ctx = spng_ctx_new(0);
|
||||
if (!ctx) {
|
||||
set_error(error, g_strerror(errno));
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
int err;
|
||||
size_t size = 0;
|
||||
if ((err = spng_set_png_file(ctx, fp)) ||
|
||||
(err = spng_set_image_limits(ctx, INT16_MAX, INT16_MAX)) ||
|
||||
(err = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &size))) {
|
||||
set_error(error, spng_strerror(err));
|
||||
goto fail;
|
||||
}
|
||||
if (check_spng_thumbnail(ctx, uri, mtime, &err) == false) {
|
||||
set_error(error, err ? spng_strerror(err) : "mismatch");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct spng_ihdr ihdr = {};
|
||||
struct spng_trns trns = {};
|
||||
spng_get_ihdr(ctx, &ihdr);
|
||||
bool may_be_translucent = !spng_get_trns(ctx, &trns) ||
|
||||
ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA ||
|
||||
ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
|
||||
|
||||
cairo_surface_t *surface = cairo_image_surface_create(
|
||||
may_be_translucent ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
|
||||
ihdr.width, ihdr.height);
|
||||
|
||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||
set_error(error, cairo_status_to_string(surface_status));
|
||||
goto fail_data;
|
||||
}
|
||||
|
||||
uint32_t *data = (uint32_t *) cairo_image_surface_get_data(surface);
|
||||
g_assert((size_t) cairo_image_surface_get_stride(surface) *
|
||||
cairo_image_surface_get_height(surface) == size);
|
||||
|
||||
cairo_surface_flush(surface);
|
||||
if ((err = spng_decode_image(ctx, data, size, SPNG_FMT_RGBA8,
|
||||
SPNG_DECODE_TRNS | SPNG_DECODE_GAMMA))) {
|
||||
set_error(error, spng_strerror(err));
|
||||
goto fail_data;
|
||||
}
|
||||
|
||||
// The specification does not say where the required metadata should be,
|
||||
// it could very well be broken up into two parts.
|
||||
if (check_spng_thumbnail(ctx, uri, mtime, &err) != true) {
|
||||
set_error(
|
||||
error, err ? spng_strerror(err) : "mismatch or not a thumbnail");
|
||||
goto fail_data;
|
||||
}
|
||||
|
||||
// pixman can be mildly abused to do this operation, but it won't be faster.
|
||||
if (may_be_translucent) {
|
||||
for (size_t i = size / sizeof *data; i--; ) {
|
||||
const uint8_t *unit = (const uint8_t *) &data[i];
|
||||
uint32_t a = unit[3],
|
||||
b = PREMULTIPLY8(a, unit[2]),
|
||||
g = PREMULTIPLY8(a, unit[1]),
|
||||
r = PREMULTIPLY8(a, unit[0]);
|
||||
data[i] = a << 24 | r << 16 | g << 8 | b;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = size / sizeof *data; i--; ) {
|
||||
uint32_t rgba = g_ntohl(data[i]);
|
||||
data[i] = rgba << 24 | rgba >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty((result = surface));
|
||||
|
||||
fail_data:
|
||||
if (!result)
|
||||
// May contain Thumb::Image::Width Thumb::Image::Height,
|
||||
// but those aren't interesting currently (would be for fast previews).
|
||||
const char *text_uri = g_hash_table_lookup(texts, THUMB_URI);
|
||||
const char *text_mtime = g_hash_table_lookup(texts, THUMB_MTIME);
|
||||
if (!text_uri || strcmp(text_uri, uri) ||
|
||||
!text_mtime || atol(text_mtime) != mtime) {
|
||||
set_error(error, "mismatch or not a thumbnail");
|
||||
cairo_surface_destroy(surface);
|
||||
fail:
|
||||
spng_ctx_free(ctx);
|
||||
fail_init:
|
||||
fclose(fp);
|
||||
return result;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
cairo_surface_t *
|
||||
@ -616,7 +510,7 @@ fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)
|
||||
|
||||
gchar *path =
|
||||
g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum);
|
||||
result = read_spng_thumbnail(path, uri, mtime_msec / 1000, &error);
|
||||
result = read_png_thumbnail(path, uri, mtime_msec / 1000, &error);
|
||||
if (error) {
|
||||
g_debug("%s: %s", path, error->message);
|
||||
g_clear_error(&error);
|
||||
|
@ -39,11 +39,6 @@ dependencies = [
|
||||
dependency('libwebpdemux'),
|
||||
dependency('libwebpdecoder', required : false),
|
||||
dependency('libwebpmux'),
|
||||
# https://github.com/google/wuffs/issues/58
|
||||
dependency('spng', version : '>=0.7.0',
|
||||
default_options: 'default_library=static',
|
||||
# fallback : ['spng', 'spng_dep'],
|
||||
),
|
||||
|
||||
lcms2,
|
||||
libjpegqs,
|
||||
|
@ -1,8 +0,0 @@
|
||||
[wrap-file]
|
||||
directory = libspng-0.7.2
|
||||
source_url = https://github.com/randy408/libspng/archive/refs/tags/v0.7.2.tar.gz
|
||||
source_filename = libspng-0.7.2.tar.gz
|
||||
source_hash = 4acf25571d31f540d0b7ee004f5461d68158e0a13182505376805da99f4ccc4e
|
||||
|
||||
[provide]
|
||||
spng = spng_dep
|
Loading…
Reference in New Issue
Block a user