rendering svgs vs using prerendered images from the icon cache



I got once again annoyed by the fact that we continue to reduce the
effectiveness of the gtk iconcache by increasing the amount of icons
that are only shipped as svgs; so I finally sat down to finish a
little tool to fill in missing icons.

I don't quite know where to go with this yet, so I'll just post it
here for now. Not extensively tested, but simple testcases seem to
work fine.

The way this works is by calling e.g.

iconconverter /usr/share/icons/gnome icon-list

where icon-list is a file containing lines of the form

scalable/stock/generic/stock_person.svg 64
scalable/apps/system-config-users.svg 64
scalable/emblems/emblem-package.svg 64
48x48/apps/someapp.png 24

Each line specified a source icons (relative to the icon theme
directory thats specified on the cmdline) and the size to generate.
The tool tries to be smart about finding the right location for the
new icon, adding directories to the index.theme file as necessary, and
copying .icon files along with the images.


Matthias
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2008 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Matthias Clasen
 */

#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

static gboolean verbose = FALSE;
static gint fail = 0;
static gint success = 0;

static void
usage (void)
{
	g_print ("icon-converter <themedir> <path>\n");
	exit (1);
}

static GdkPixbuf *
load_svg_at_size (const gchar *filename,
                  gint         size,
                  GError      **error)
{
	GdkPixbuf *pixbuf = NULL;
	GdkPixbufLoader *loader = NULL;
	gchar *contents = NULL;
	gsize length;

	if (!g_file_get_contents (filename,
				  &contents, &length, error))
		goto bail;

	loader = gdk_pixbuf_loader_new_with_type ("svg", error);
	if (loader == NULL)
		goto bail;
	gdk_pixbuf_loader_set_size (loader, size, size);

	if (!gdk_pixbuf_loader_write (loader, contents, length, error)) {
		gdk_pixbuf_loader_close (loader, NULL);
		goto bail;
	}

	if (!gdk_pixbuf_loader_close (loader, error))
		goto bail;

	pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));

bail:
	if (loader)
		g_object_unref (loader);
	if (contents)
		g_free (contents);

	return pixbuf;
}

static gboolean
make_relative_symlink (const gchar  *oldpath, 
                       const gchar  *newpath,
		       GError      **error)
{
	gchar *dir, *s;
	const gchar *p, *q;
	gint i;
	gboolean res = FALSE;
	GString *string;
	
	g_assert (oldpath[0] == '/');
	g_assert (newpath[0] == '/');

	dir = g_path_get_dirname (newpath);

	/* strip common prefix */
	p = oldpath; 
	q = dir;
	while (*p && *p == *q) {
		p++; q++;
	} 
	while (*p != '/') {
		p--; q--;
	}
	p++; 
	if (*q)
		q++;

	/* count the number of steps we have to go up */
	i = 0;
	if (*q) {
		while (q) {
			i++;
			q++;
			q = strchr (q, '/');
		} 
	}
	string = g_string_new ("");
	for (; i > 0; i--)
		g_string_append (string, "../");
 	g_string_append (string, p);
	s = g_string_free (string, FALSE);

	res = symlink (s, newpath) == 0;
	if (verbose && res)
		g_print ("Created symlink %s -> %s\n", newpath, s);

	g_free (s);
	g_free (dir);

	if (!res)
		g_set_error (error, 0, 0,
			     "Failed to create symlink %s -> %s",
			     newpath, s);

	return res;
}

static gboolean
create_theme_directory (const gchar  *themedir,
			const gchar  *subdir,
			gint          size,
			GError      **error)
{
	GKeyFile *theme_file;
	gchar *path;
	gchar *text;
	const gchar *sd;
	gsize len;
	gboolean res = FALSE;
	gchar **dirs;
	gint i;

	theme_file = NULL;
	path = NULL;
	text = NULL;

	if (g_file_test (subdir, G_FILE_TEST_EXISTS))
		return TRUE;

	if (g_mkdir_with_parents (subdir, 0755) < 0) {
		g_set_error (error, 0, 0, "Failed to create directory %s", subdir);
		return FALSE;
	}
	if (verbose)
		g_print ("Created directory %s\n", subdir);

	path = g_build_filename (themedir, "index.theme", NULL);
	theme_file = g_key_file_new ();
	g_key_file_set_list_separator (theme_file, ',');
	g_key_file_load_from_file (theme_file, path, 0, error);
	if (*error) 
		goto bail;

	sd = subdir + strlen (themedir) + 1;
	dirs = g_key_file_get_string_list (theme_file, 
					   "Icon Theme", 
					   "Directories", 
					   &len, NULL);
	dirs = g_realloc (dirs, (len + 2) * sizeof (gchar *));
	dirs[len] = g_strdup (sd);
	dirs[len + 1] = 0;

	g_key_file_set_string_list (theme_file, 
				    "Icon Theme",
				    "Directories",
				    (const gchar * const *)dirs, len + 1);
	
	g_key_file_set_integer (theme_file, sd, "Size", size);

	text = g_key_file_to_data (theme_file, &len, error);
	if (*error) 
		goto bail;

	g_file_set_contents (path, text, len, error);
	if (*error) 
		goto bail;

	res = TRUE;
bail:

	if (theme_file)
		g_key_file_free (theme_file);
	g_free (path);
	g_free (text);
	
	return res;
}

static gboolean
scale_icon_data (GKeyFile *keyfile,
                 gdouble   scale)
{
	gint *ivalues;
	gint length;
	gchar *str;
	gboolean res = FALSE;
	gint i;

	ivalues = g_key_file_get_integer_list (keyfile,
                                               "Icon Data", 
					       "EmbeddedTextRectangle",
                                               &length, NULL);
	if (ivalues) {
		for (i = 0; i < length; i++)
			ivalues[i] = 0.5 + ivalues[i] * scale;

		g_key_file_set_integer_list (keyfile,
					     "Icon Data", 
					     "EmbeddedTextRectangle",
					     ivalues, length);

		g_free (ivalues);

		res = TRUE;
	}

	str = g_key_file_get_string (keyfile,
				     "Icon Data", 
				     "AttachPoints", 
				     NULL);
	if (str) {
		gint *x, *y, n;
		gchar **split, *split_point;
		GString *string;
		
		split = g_strsplit (str, "|", -1);

		n = g_strv_length (split);
		x = g_new (gint, n);
		y = g_new (gint, n);

		i = 0;
		while (split[i] != NULL && i < n) {
			split_point = strchr (split[i], ',');
			if (split_point) {
				*split_point = 0;
				split_point++;
				x[i] = atoi (split[i]);
				y[i] = atoi (split_point);
			}
			i++;
		}

		n = i;

		string = g_string_new ("");

		for (i = 0; i < n; i++) {
			x[i] = 0.5 + x[i] * scale;
			y[i] = 0.5 + y[i] * scale;

			g_string_append_printf (string, "%s%d,%d", 
						i > 0 ? "|" : "",
 						x[i], y[i]);
		}

		g_strfreev (split);
		g_free (str);

		str = g_string_free (string, FALSE);
		g_key_file_set_string (keyfile,
				     "Icon Data", 
				     "AttachPoints", 
				     str); 

		res = TRUE;
	}

	return res;
}
	

static void
create_icon (const char *themedir, 
             gint        line, 
             const char *source,
	     gint        size,
	     gdouble    *scalep)
{
	gchar *sizestr;
	gchar *sourcedir;
	gchar *name;
	gchar *tmp, *sourceicon;
	gchar *target, *targetdir;
	const gchar *d;
	gchar *targeticon;
	gchar *linkname, *source2, *target2;
	gint len;
	GdkPixbuf *pixbuf; 
	GError *error = NULL;
	gdouble scale;

	sourceicon = NULL;
	target = NULL;
	target2 = NULL;
	targeticon = NULL;
	targetdir = NULL;
	sizestr = NULL;
	pixbuf = NULL;
	linkname = NULL;
	sourcedir = NULL;
	source2 = NULL;
	name = NULL;
	
	sizestr = g_strdup_printf ("%dx%d", size, size);

	d = source + strlen (themedir) + 1;
	d = strchr (d, '/');
	d++;	
	target = g_build_filename (themedir, sizestr, d, NULL);
	if (g_str_has_suffix (target, ".svg")) {
		len = strlen (target);
		target[len - 3] = 'p';
		target[len - 2] = 'n';
		target[len - 1] = 'g';
	}
	name = g_path_get_basename (target);
	
	if (g_file_test (target, G_FILE_TEST_EXISTS)) 
		goto bail;

	if (g_file_test (source, G_FILE_TEST_IS_SYMLINK)) {
		linkname = g_file_read_link (source, &error);
		if (error) 
			goto bail;

		sourcedir = g_path_get_dirname (source);
		tmp = g_build_filename (sourcedir, linkname, NULL);
		source2 = realpath (tmp, NULL);
		g_free (tmp);

		create_icon (themedir, line, source2, size, &scale);

		tmp = source2 + strlen (themedir) + 1;
		tmp = strchr (tmp, '/');
		tmp++ ;	
		target2 = g_build_filename (themedir, sizestr, tmp, NULL);
		if (g_str_has_suffix (target2, ".svg")) {
			len = strlen (target2);
			target2[len - 3] = 'p';
			target2[len - 2] = 'n';
			target2[len - 1] = 'g';
		}
				
		if (!make_relative_symlink (target2, target, &error)) 
			goto bail;

		success++;
	}
	else {
		if (g_str_has_suffix (source, ".svg")) {
			scale = size / 1000.0; /* used for info scaling below */
			pixbuf = load_svg_at_size (source, size, &error);
			if (error) 
				goto bail;
		}
		else {
			gint width, height, image_size;
			GdkPixbuf *tmp;
	
			pixbuf = gdk_pixbuf_new_from_file (source, &error);
			if (error)
				goto bail;
	
			width = gdk_pixbuf_get_width (pixbuf);
			height = gdk_pixbuf_get_height (pixbuf);
			image_size = MAX (width, height);
			scale = size / (gdouble)image_size;		

			tmp = gdk_pixbuf_scale_simple (pixbuf,
						0.5 + width * scale,
						0.5 + height * scale,
						GDK_INTERP_BILINEAR);
			g_object_unref (pixbuf);
			pixbuf = tmp;
		}

		targetdir = g_path_get_dirname (target);

		if (!create_theme_directory (themedir, targetdir, size, &error))
			goto bail;

		if (!gdk_pixbuf_save (pixbuf, target, "png", &error, NULL)) 
			goto bail;

		if (verbose)
			g_print ("Created %s\n", target);

		success++;
	}

	/* handle icon file */
	tmp = g_strdup (source);
	if (g_str_has_suffix (tmp, ".svg") || g_str_has_suffix (tmp, ".png")) 
		tmp[strlen (tmp) - 4] = 0;

	sourceicon = g_strconcat (tmp, ".icon", NULL);
	g_free (tmp);
	tmp = realpath (sourceicon, NULL);
	g_free (sourceicon);
	sourceicon = tmp;

	tmp = g_strdup (target);
	tmp[strlen (tmp) - 4] = 0;
	targeticon = g_strconcat (tmp, ".icon", NULL);
	g_free (tmp);
	
	if (g_file_test (sourceicon, G_FILE_TEST_EXISTS)) {
		GKeyFile *keyfile;

		if (g_file_test (targeticon, G_FILE_TEST_EXISTS)) 
			goto bail;

		keyfile = g_key_file_new ();
		g_key_file_set_list_separator (keyfile, ',');
		g_key_file_load_from_file (keyfile, 
                                           sourceicon, 
                                           G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, 
                                           &error);
		if (error) {
			g_key_file_free (keyfile);
			goto bail;
		}

		if (scale_icon_data (keyfile, scale)) {
			gchar *text;
			gsize len;

			text = g_key_file_to_data (keyfile, &len, &error);
			if (error) {
				g_free (text);
				goto bail;
			}

			g_file_set_contents (targeticon, text, len, &error);
			if (error) {
				g_free (text);
				goto bail;
			}

			if (verbose)
				g_print ("Created %s\n", targeticon);

			g_free (text);
		}
		else {
			make_relative_symlink (sourceicon, targeticon, &error);
			if (error)
				goto bail;
		}
	}

	if (scalep)
		*scalep = scale;

bail:
	if (error) {
		fail++;
		g_print ("%s\n", error->message);
		g_error_free (error); 
	}
	if (pixbuf)
		g_object_unref (pixbuf);
	g_free (sizestr);
	g_free (sourceicon);
	g_free (target);
	g_free (target2);
	g_free (targeticon);
	g_free (targetdir);
	g_free (sourcedir);
	g_free (linkname);
	g_free (source2);
}

static void
handle_line (const char *themedir, 
             gint        line, 
             const char *spec)
{
	gchar **parts;
	gchar *source;
	gint size;

	source = NULL;
	parts = NULL;

	parts = g_strsplit (spec, " ", 0);
	
	if (g_strv_length (parts) < 2) {
		if (verbose)
			g_print ("Ignoring line %d\n", line);
		goto bail;
	}

	source = g_build_filename (themedir, parts[0], NULL);
	size = strtol (parts[1], NULL, 10);

	create_icon (themedir, line, source, size, NULL);

bail:	
	g_strfreev (parts);
	g_free (source);
}

GOptionEntry entries[] = {
	{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL },
	NULL
};

int main (int argc, char *argv[])
{
	GOptionContext *context;
	gchar *themedir;
	gchar *themefile;
	const gchar *path;
	gchar *text;
	gsize len;
	gchar **lines;
	gint i, success, fail;
	GError *error = NULL;
	
	g_type_init ();

	context = g_option_context_new ("<themedir> <file>");
	g_option_context_add_main_entries (context, entries, NULL);
	g_option_context_parse (context, &argc, &argv, NULL);
	if (argc < 3) {
		g_print (g_option_context_get_help (context, FALSE, NULL));
		exit (1);
	}	

	themedir = g_strdup (argv[1]);
	path = argv[2];

	len = strlen (themedir); 
	if (themedir[len - 1] == '/')
		themedir[len - 1] = 0;

	themefile = g_build_filename (themedir, "index.theme", NULL);

	if (!g_file_test (themedir, G_FILE_TEST_IS_DIR) ||
	    !g_file_test (themefile, G_FILE_TEST_IS_REGULAR)) {
		g_print ("'%s' doesn't appear to be a theme directory\n",
			 themedir);
		exit (1);
	}

	if (!g_file_get_contents (path, &text, &len, &error)) {
		g_print ("%s\n", error->message);
		exit (1);
	}

	lines = g_strsplit (text, "\n", 0);

	success = fail = 0;
	for (i = 0; lines[i]; i++) {
		if (lines[i][0] == '#' || lines[i][0] == '\0')
			continue;
		handle_line (themedir, i + 1, lines[i]);
	}

	if (verbose) {
		if (fail == 0)
			g_print ("All icons created successfully.\n");
		else
			g_print ("%d icons created successfully, %d failed.\n", success, fail);
	}

	return fail == 0;
}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]