[RofiIconFetcher] Switch to gdk-pixbuf.

This commit is contained in:
Dave Davenport 2020-10-20 20:38:13 +02:00
parent 4fd7128d4c
commit deb1e6a208
7 changed files with 169 additions and 348 deletions

View File

@ -31,17 +31,15 @@ You can also use [Meson](https://mesonbuild.com/) as an alternative.
* libglib2.0 >= 2.40
* gmodule-2.0
* gio-unix-2.0
* librsvg2.0
* libgdk-pixbuf-2.0
* libstartup-notification-1.0
* libxkbcommon >= 0.4.1
* libxkbcommon-x11
* libjpeg
* libxcb (sometimes split, you need libxcb, libxcb-xkb and libxcb-randr libxcb-xinerama)
* xcb-util
* xcb-util-wm (sometimes split as libxcb-ewmh and libxcb-icccm)
* xcb-util-xrm [new module might not be available in your distribution. The source can be found
here](https://github.com/Airblader/xcb-util-xrm/)
* libgif (optional)
On debian based systems, the developer packages are in the form of: `<package>-dev` on rpm based
`<package>-devel`.

View File

@ -145,7 +145,7 @@ rofi_CFLAGS=\
$(pango_CFLAGS)\
$(libsn_CFLAGS)\
$(cairo_CFLAGS)\
$(librsvg_CFLAGS)\
$(gdkpixbuf_CFLAGS)\
-DMANPAGE_PATH="\"$(mandir)/\""\
-I$(top_srcdir)/include/\
-I$(top_builddir)/lexer/\
@ -166,9 +166,7 @@ rofi_LDADD=\
$(libsn_LIBS)\
$(pango_LIBS)\
$(cairo_LIBS)\
$(librsvg_LIBS)\
$(libjpeg_LIBS)\
$(libgif_LIBS)\
$(gdkpixbuf_LIBS)\
$(LIBS)
##
@ -326,8 +324,7 @@ textbox_test_CFLAGS=\
$(GW_XCB_CFLAGS)\
$(cairo_CFLAGS)\
$(libsn_CFLAGS)\
$(librsvg_CFLAGS)\
$(libjpeg_CFLAGS)\
$(gdkpixbuf_CFLAGS)\
-DPLUGIN_PATH=\"${libdir}/rofi\"\
-DTHEME_DIR=\"$(themedir)\"\
-I$(top_srcdir)/include/\
@ -342,7 +339,6 @@ textbox_test_LDADD=\
$(NKUTILS_LIBS)\
$(GW_XCB_LIBS)\
$(cairo_LIBS)\
$(librsvg_LIBS)\
$(libsn_LIBS)
helper_pidfile_CFLAGS=$(textbox_test_CFLAGS)
@ -479,7 +475,7 @@ helper_test_CFLAGS=\
$(NKUTILS_CFLAGS)\
$(GW_XCB_CFLAGS)\
$(cairo_CFLAGS)\
$(librsvg_CFLAGS)\
$(gdkpixbuf_CFLAGS)\
$(libsn_CFLAGS)\
-DPLUGIN_PATH=\"${libdir}/rofi\"\
-DTHEME_DIR=\"$(themedir)\"\
@ -495,7 +491,6 @@ helper_test_LDADD=\
$(NKUTILS_LIBS)\
$(GW_XCB_LIBS)\
$(libsn_LIBS)\
$(librsvg_LIBS)\
$(cairo_LIBS)

View File

@ -148,8 +148,7 @@ GW_CHECK_XCB([xcb-aux xcb-xkb xkbcommon xkbcommon-x11 xcb-ewmh xcb-icccm xcb-xrm
PKG_CHECK_MODULES([pango], [pango pangocairo])
PKG_CHECK_MODULES([cairo], [cairo cairo-xcb])
PKG_CHECK_MODULES([libsn], [libstartup-notification-1.0 ])
PKG_CHECK_MODULES([librsvg], [librsvg-2.0 ])
PKG_CHECK_MODULES([libjpeg], [libjpeg])
PKG_CHECK_MODULES([gdkpixbuf], [gdk-pixbuf-2.0])
dnl ---------------------------------------------------------------------
dnl check - Unit testing.
@ -165,38 +164,6 @@ dnl Gets the resource compile tool path.
dnl ---------------------------------------------------------------------
AM_PATH_GLIB_2_0
dnl Test for libgif or libungif
AC_ARG_WITH([libgif],
AS_HELP_STRING([--with-libgif=PREFIX],
[Prefix where libgif is installed, or 'no' to disable]),
[libgif_prefix="$withval"], [libgif_prefix="${prefix}"])
if test x$with_libgif != xno && test -z "$libgif_LIBS"; then
GIF_CFLAGS="-I${libgif_prefix}/include"
GIF_LIBS="-L${libgif_prefix}/lib"
save_cflags=$CFLAGS; CFLAGS=$GIF_CFLAGS
save_libs=$LIBS; LIBS=$GIF_LIBS
AC_CHECK_LIB(gif, DGifOpenFileName,
[AC_CHECK_HEADER(gif_lib.h,
GIF='gif'; libgif_LIBS='-lgif'; gif_ok=yes,
AC_MSG_WARN(*** GIF loader will not be built (giflibrary not found) ***))],
AC_MSG_WARN(*** GIF loader will not be built (giflibrary not found) ***))
AC_CHECK_LIB(ungif, DGifOpenFileName,
[AC_CHECK_HEADER(gif_lib.h,
GIF='ungif'; libgif_LIBS='-lungif'; gif_ok=yes,
AC_MSG_WARN(*** GIF loader will not be built (ungiflibrary not found) ***))],
AC_MSG_WARN(*** GIF loader will not be built (ungiflibrary not found) ***))
CFLAGS+=$save_cflags
LIBS+=$save_libs
fi
if test x$GIF != x; then
AC_SUBST(libgif_LIBS)
AC_DEFINE(HAVE_LIBGIF, 1, Define if gif support is available)
else
gif_ok="no (See http://sourceforge.net/projects/libgif)"
fi
dnl ---------------------------------------------------------------------
dnl Add extra compiler flags
dnl ---------------------------------------------------------------------

View File

@ -47,9 +47,7 @@ deps = [
dependency('pango'),
dependency('pangocairo'),
dependency('xkbcommon'),
dependency('librsvg-2.0'),
dependency('libjpeg'),
c_compiler.find_library('m', required: false),
dependency('gdk-pixbuf-2.0'),
]

View File

@ -47,7 +47,6 @@
#include <pango/pango.h>
#include <pango/pango-fontmap.h>
#include <pango/pangocairo.h>
#include <librsvg/rsvg.h>
#include "display.h"
#include "xcb.h"
#include "helper.h"
@ -1137,50 +1136,6 @@ char *helper_get_theme_path ( const char *file )
return filename;
}
cairo_surface_t* cairo_image_surface_create_from_svg ( const gchar* file, int height )
{
GError *error = NULL;
cairo_surface_t *surface = NULL;
RsvgHandle * handle;
handle = rsvg_handle_new_from_file ( file, &error );
if ( G_LIKELY ( handle != NULL ) ) {
RsvgDimensionData dimensions;
// Update DPI.
rsvg_handle_set_dpi ( handle, config.dpi );
// Get size.
rsvg_handle_get_dimensions ( handle, &dimensions );
// Create cairo surface in the right size.
double scale = (double) height / dimensions.height;
surface = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32,
(double) dimensions.width * scale,
(double) dimensions.height * scale );
gboolean failed = cairo_surface_status ( surface ) != CAIRO_STATUS_SUCCESS;
if ( G_LIKELY ( failed == FALSE ) ) {
cairo_t *cr = cairo_create ( surface );
cairo_scale ( cr, scale, scale );
failed = rsvg_handle_render_cairo ( handle, cr ) == FALSE;
cairo_destroy ( cr );
}
rsvg_handle_close ( handle, &error );
g_object_unref ( handle );
/** Rendering fails */
if ( G_UNLIKELY ( failed ) ) {
g_warning ( "Failed to render file: '%s'", file );
cairo_surface_destroy ( surface );
surface = NULL;
}
}
if ( G_UNLIKELY ( error != NULL ) ) {
g_warning ( "Failed to render SVG file: '%s': %s", file, error->message );
g_error_free ( error );
}
return surface;
}
static void parse_pair ( char *input, rofi_range_pair *item )
{
// Skip leading blanks.

View File

@ -44,9 +44,8 @@
#include "nkutils-enum.h"
#include <stdint.h>
#include <jpeglib.h>
#include <setjmp.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
typedef struct
{
@ -58,6 +57,8 @@ typedef struct
// On uid.
GHashTable *icon_cache_uid;
// list extensions
GList *supported_extensions;
uint32_t last_uid;
} IconFetcher;
@ -87,6 +88,7 @@ typedef struct
*/
IconFetcher *rofi_icon_fetcher_data = NULL;
static void rofi_icon_fetch_entry_free ( gpointer data )
{
IconFetcherNameEntry *entry = (IconFetcherNameEntry *) data;
@ -123,6 +125,22 @@ void rofi_icon_fetcher_init ( void )
rofi_icon_fetcher_data->icon_cache_uid = g_hash_table_new ( g_direct_hash, g_direct_equal );
rofi_icon_fetcher_data->icon_cache = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, rofi_icon_fetch_entry_free );
GSList *l = gdk_pixbuf_get_formats();
for ( GSList *li = l; li != NULL; li = g_slist_next(li))
{
gchar **exts = gdk_pixbuf_format_get_extensions ( (GdkPixbufFormat *)li->data );
for ( unsigned int i = 0; exts && exts[i]; i ++ ) {
rofi_icon_fetcher_data->supported_extensions = g_list_append ( rofi_icon_fetcher_data->supported_extensions, exts[i]);
exts[i] = NULL;
}
g_free ( exts );
}
}
void rofi_icon_fetcher_destroy ( void )
@ -136,180 +154,106 @@ void rofi_icon_fetcher_destroy ( void )
g_hash_table_unref ( rofi_icon_fetcher_data->icon_cache_uid );
g_hash_table_unref ( rofi_icon_fetcher_data->icon_cache );
g_list_foreach ( rofi_icon_fetcher_data->supported_extensions, (GFunc)g_free, NULL );
g_list_free ( rofi_icon_fetcher_data->supported_extensions );
g_free ( rofi_icon_fetcher_data );
}
static cairo_surface_t* cairo_image_surface_create_from_jpeg_private ( struct jpeg_decompress_struct* cinfo )
{
cairo_surface_t* surface = 0;
unsigned char * data = 0;
unsigned char * rgb = 0;
jpeg_read_header ( cinfo, TRUE );
jpeg_start_decompress ( cinfo );
surface = cairo_image_surface_create ( CAIRO_FORMAT_RGB24, cinfo->image_width, cinfo->image_height );
data = cairo_image_surface_get_data ( surface );
rgb = (unsigned char *) ( malloc ( cinfo->output_width * cinfo->output_components ) );
while ( cinfo->output_scanline < cinfo->output_height ) {
unsigned int i;
int scanline = cinfo->output_scanline * cairo_image_surface_get_stride ( surface );
jpeg_read_scanlines ( cinfo, &rgb, 1 );
for ( i = 0; i < cinfo->output_width; i++ ) {
int offset = scanline + ( i * 4 );
data[offset + 3] = 255;
data[offset + 2] = rgb[( i * 3 )];
data[offset + 1] = rgb[( i * 3 ) + 1];
data[offset ] = rgb[( i * 3 ) + 2];
}
}
free ( rgb );
jpeg_finish_decompress ( cinfo );
jpeg_destroy_decompress ( cinfo );
cairo_surface_mark_dirty ( surface );
return surface;
}
struct jpegErrorManager {
/* "public" fields */
struct jpeg_error_mgr pub;
/* for return to caller */
jmp_buf setjmp_buffer;
};
char jpegLastErrorMsg[JMSG_LENGTH_MAX];
static void jpegErrorExit (j_common_ptr cinfo)
{
/* cinfo->err actually points to a jpegErrorManager struct */
struct jpegErrorManager* myerr = (struct jpegErrorManager*) cinfo->err;
/* Create the message */
( *(cinfo->err->format_message) ) (cinfo, jpegLastErrorMsg);
g_warning ( jpegLastErrorMsg, NULL );
/* Jump to the setjmp point */
longjmp(myerr->setjmp_buffer, 1);
}
static cairo_surface_t* cairo_image_surface_create_from_jpeg ( const char* file )
{
struct jpeg_decompress_struct cinfo;
cairo_surface_t * surface;
FILE * infile;
if ( ( infile = fopen ( file, "rb" ) ) == NULL ) {
return NULL;
}
struct jpegErrorManager jerr;
cinfo.err = jpeg_std_error( &jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(jerr.setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error. */
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return NULL;
}
jpeg_create_decompress ( &cinfo );
jpeg_stdio_src ( &cinfo, infile );
surface = cairo_image_surface_create_from_jpeg_private ( &cinfo );
fclose ( infile );
return surface;
}
#ifdef HAVE_LIBGIF
#include <gif_lib.h>
static cairo_surface_t* cairo_image_surface_create_from_gif(const char* file )
{
cairo_surface_t* img = NULL;
int err;
GifFileType* gif = DGifOpenFileName(file, &err);
if (!gif) {
g_warning( "[%i] %s", err, GifErrorString(err));
return NULL;
}
// decode with high-level API
if (DGifSlurp(gif) != GIF_OK) {
g_warning("Decoder error: %s", GifErrorString(gif->Error));
goto done;
}
if (!gif->SavedImages) {
g_warning("No saved images");
goto done;
}
// create canvas
img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, gif->SWidth, gif->SHeight);
if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
g_warning("Unable to create surface: %s",
cairo_status_to_string(cairo_surface_status(img)));
cairo_surface_destroy(img);
img = NULL;
goto done;
}
// we don't support animation, show the first frame only
const GifImageDesc* frame = &gif->SavedImages->ImageDesc;
const GifColorType* colors = gif->SColorMap ? gif->SColorMap->Colors :
frame->ColorMap->Colors;
uint32_t* base = (uint32_t*)(cairo_image_surface_get_data(img) +
frame->Top * cairo_image_surface_get_stride(img));
for (int y = 0; y < frame->Height; ++y) {
uint32_t* pixel = base + y * gif->SWidth + frame->Left;
const uint8_t* raster = &gif->SavedImages->RasterBits[y * gif->SWidth];
for (int x = 0; x < frame->Width; ++x) {
const uint8_t color = raster[x];
if (color != gif->SBackGroundColor) {
const GifColorType* rgb = &colors[color];
*pixel = 0xff000000 |
rgb->Red << 16 | rgb->Green << 8 | rgb->Blue;
}
++pixel;
}
}
cairo_surface_mark_dirty(img);
done:
DGifCloseFile(gif, NULL);
return img;
}
/*
* _rofi_icon_fetcher_get_icon_surface and alpha_mult
* are inspired by gdk_cairo_set_source_pixbuf
* GDK is:
* Copyright (C) 2011-2018 Red Hat, Inc.
*/
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define RED_BYTE 2
#define GREEN_BYTE 1
#define BLUE_BYTE 0
#define ALPHA_BYTE 3
#else
#define RED_BYTE 1
#define GREEN_BYTE 2
#define BLUE_BYTE 3
#define ALPHA_BYTE 0
#endif
enum {
IMAGE_PNG,
IMAGE_JPG,
IMAGE_JPEG,
IMAGE_SVG,
#ifdef HAVE_LIBGIF
IMAGE_GIF,
#endif
IMAGE_MAX_VALUES
} RofiIconFetchDecoder;
static inline guchar alpha_mult(guchar c, guchar a)
{
guint16 t;
switch ( a )
{
case 0xff:
return c;
case 0x00:
return 0x00;
default:
t = c * a + 0x7f;
return ((t >> 8) + t) >> 8;
}
}
static const gchar * const _image_exts[IMAGE_MAX_VALUES] = {
[IMAGE_PNG] = ".png",
[IMAGE_JPG] = ".jpg",
[IMAGE_JPEG] = ".jpeg",
[IMAGE_SVG] = ".svg",
#ifdef HAVE_LIBGIF
[IMAGE_GIF] = ".gif",
#endif
};
static cairo_surface_t * rofi_icon_fetcher_get_surface_from_pixbuf(GdkPixbuf
*pixbuf)
{
gint width, height;
const guchar *pixels;
gint stride;
gboolean alpha;
if ( pixbuf == NULL )
return NULL;
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
pixels = gdk_pixbuf_read_pixels(pixbuf);
stride = gdk_pixbuf_get_rowstride(pixbuf);
alpha = gdk_pixbuf_get_has_alpha(pixbuf);
cairo_surface_t *surface = NULL;
gint cstride;
guint lo, o;
guchar a = 0xff;
const guchar *pixels_end, *line, *line_end;
guchar *cpixels, *cline;
pixels_end = pixels + height * stride;
o = alpha ? 4 : 3;
lo = o * width;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cpixels = cairo_image_surface_get_data(surface);
cstride = cairo_image_surface_get_stride(surface);
cairo_surface_flush(surface);
while ( pixels < pixels_end )
{
line = pixels;
line_end = line + lo;
cline = cpixels;
while ( line < line_end )
{
if ( alpha )
a = line[3];
cline[RED_BYTE] = alpha_mult(line[0], a);
cline[GREEN_BYTE] = alpha_mult(line[1], a);
cline[BLUE_BYTE] = alpha_mult(line[2], a);
cline[ALPHA_BYTE] = a;
line += o;
cline += 4;
}
pixels += stride;
cpixels += cstride;
}
cairo_surface_mark_dirty(surface);
cairo_surface_flush(surface);
return surface;
}
gboolean rofi_icon_fetcher_file_is_image ( const char * const path )
{
@ -320,101 +264,66 @@ gboolean rofi_icon_fetcher_file_is_image ( const char * const path )
if ( suf == NULL ) {
return FALSE;
}
suf++;
guint64 type;
gboolean r = nk_enum_parse ( suf, _image_exts, G_N_ELEMENTS(_image_exts), TRUE, FALSE, &type );
return r;
for ( GList *iter = rofi_icon_fetcher_data->supported_extensions; iter != NULL ; iter = g_list_next ( iter ) ) {
if ( g_ascii_strcasecmp(iter->data, suf ) == 0 ) {
return TRUE;
}
}
return FALSE;
}
static void rofi_icon_fetcher_worker ( thread_state *sdata, G_GNUC_UNUSED gpointer user_data )
{
g_debug ( "starting up icon fetching thread." );
// as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
// this should be fine running in another thread.
IconFetcherEntry *sentry = (IconFetcherEntry *) sdata;
const gchar *themes[] = {
config.icon_theme,
NULL
};
g_debug ( "starting up icon fetching thread." );
// as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
// this should be fine running in another thread.
IconFetcherEntry *sentry = (IconFetcherEntry *) sdata;
const gchar *themes[] = {
config.icon_theme,
NULL
};
const gchar *icon_path;
gchar *icon_path_ = NULL;
const gchar *icon_path;
gchar *icon_path_ = NULL;
if ( g_path_is_absolute ( sentry->entry->name ) ) {
icon_path = sentry->entry->name;
if ( g_path_is_absolute ( sentry->entry->name ) ) {
icon_path = sentry->entry->name;
}
else {
icon_path = icon_path_ = nk_xdg_theme_get_icon ( rofi_icon_fetcher_data->xdg_context, themes, NULL, sentry->entry->name, sentry->size, 1, TRUE );
if ( icon_path_ == NULL ) {
g_debug ( "failed to get icon %s(%d): n/a", sentry->entry->name, sentry->size );
return;
}
else {
icon_path = icon_path_ = nk_xdg_theme_get_icon ( rofi_icon_fetcher_data->xdg_context, themes, NULL, sentry->entry->name, sentry->size, 1, TRUE );
if ( icon_path_ == NULL ) {
g_debug ( "failed to get icon %s(%d): n/a", sentry->entry->name, sentry->size );
return;
}
else{
g_debug ( "found icon %s(%d): %s", sentry->entry->name, sentry->size, icon_path );
}
else{
g_debug ( "found icon %s(%d): %s", sentry->entry->name, sentry->size, icon_path );
}
cairo_surface_t *icon_surf = NULL;
}
cairo_surface_t *icon_surf = NULL;
const char *suf = strrchr(icon_path, '.');
if ( suf == NULL ) {
return ;
const char *suf = strrchr(icon_path, '.');
if ( suf == NULL ) {
return ;
}
GError *error = NULL;
GdkPixbuf *pb = gdk_pixbuf_new_from_file_at_scale ( icon_path, sentry->size, sentry->size, TRUE, &error );
if ( error != NULL ) {
g_warning ( "Failed to load image: %s", error->message);
g_error_free( error );
if ( pb ) {
g_object_unref ( pb );
}
} else {
icon_surf = rofi_icon_fetcher_get_surface_from_pixbuf(pb );
g_object_unref ( pb );
}
guint64 type;
gboolean is_image = nk_enum_parse ( suf, _image_exts, G_N_ELEMENTS(_image_exts), TRUE, FALSE, &type );
if ( is_image )
{
if ( type == IMAGE_PNG ) {
icon_surf = cairo_image_surface_create_from_png ( icon_path );
}
#ifdef HAVE_LIBGIF
else if ( type == IMAGE_GIF ) {
icon_surf = cairo_image_surface_create_from_gif ( icon_path );
}
#endif
else if ( type == IMAGE_JPG || type == IMAGE_JPEG ) {
icon_surf = cairo_image_surface_create_from_jpeg ( icon_path );
}
else if ( type == IMAGE_SVG ) {
icon_surf = cairo_image_surface_create_from_svg ( icon_path, sentry->size );
}
else {
g_debug ( "icon type not yet supported: %s", icon_path );
}
}
if ( icon_surf ) {
if ( cairo_surface_status ( icon_surf ) == CAIRO_STATUS_SUCCESS ) {
float sw = sentry->size / (float) cairo_image_surface_get_width ( icon_surf );
float sh = sentry->size / (float) cairo_image_surface_get_height ( icon_surf );
float scale = ( sw > sh ) ? sh : sw;
if ( scale < 0.5 ) {
cairo_surface_t * surface = cairo_image_surface_create (
cairo_image_surface_get_format ( icon_surf ),
cairo_image_surface_get_width ( icon_surf ) * scale,
cairo_image_surface_get_height ( icon_surf ) * scale );
cairo_t *d = cairo_create ( surface );
cairo_scale ( d, scale, scale );
cairo_set_source_surface ( d, icon_surf, 0.0, 0.0 );
cairo_pattern_set_filter ( cairo_get_source ( d ), CAIRO_FILTER_FAST );
cairo_paint ( d );
cairo_destroy ( d );
cairo_surface_destroy ( icon_surf );
icon_surf = surface;
}
}
// check if surface is valid.
if ( cairo_surface_status ( icon_surf ) != CAIRO_STATUS_SUCCESS ) {
g_debug ( "icon failed to open: %s(%d): %s", sentry->entry->name, sentry->size, icon_path );
cairo_surface_destroy ( icon_surf );
icon_surf = NULL;
}
sentry->surface = icon_surf;
}
g_free ( icon_path_ );
rofi_view_reload ();
sentry->surface = icon_surf;
g_free ( icon_path_ );
rofi_view_reload ();
}
uint32_t rofi_icon_fetcher_query ( const char *name, const int size )

View File

@ -38,7 +38,6 @@
#include <glib.h>
#include <cairo.h>
#include <cairo-xcb.h>
#include <librsvg/rsvg.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>