Introducing the icon cache.
authorDana Jansens <danakj@orodu.net>
Thu, 14 Feb 2008 09:47:49 +0000 (10:47 +0100)
committerMikael Magnusson <mikachu@comhem.se>
Thu, 14 Feb 2008 10:44:28 +0000 (11:44 +0100)
If an icon is the same as one in the cache, then it uses that one.
icons of different sizes (from the same client) are linked together into
one, and resizes of icons are cached and linked to all the various sizes.
so you only need one icon in memory for all your terminals now. ya!

24 files changed:
Makefile.am
openbox/client.c
openbox/client.h
openbox/client_list_combined_menu.c
openbox/client_list_menu.c
openbox/focus_cycle_popup.c
openbox/framerender.c
openbox/menu.c
openbox/menu.h
openbox/menuframe.c
openbox/openbox.c
openbox/openbox.h
openbox/popup.c
openbox/popup.h
render/image.c
render/image.h
render/imagecache.c [new file with mode: 0644]
render/imagecache.h [new file with mode: 0644]
render/render.c
render/render.h
render/theme.c
render/theme.h
tests/Makefile
tests/icons.c

index d8dba2c..dd5bb6c 100644 (file)
@@ -86,6 +86,8 @@ render_libobrender_la_SOURCES = \
        render/icon.h \
        render/image.h \
        render/image.c \
+       render/imagecache.h \
+       render/imagecache.c \
        render/instance.h \
        render/instance.c \
        render/mask.h \
index e65ed07..c7a82d1 100644 (file)
@@ -67,9 +67,10 @@ typedef struct
     gpointer data;
 } ClientCallback;
 
-GList            *client_list          = NULL;
+GList            *client_list           = NULL;
 
-static GSList *client_destroy_notifies = NULL;
+static GSList  *client_destroy_notifies = NULL;
+static RrImage *client_default_icon     = NULL;
 
 static void client_get_all(ObClient *self, gboolean real);
 static void client_get_startup_id(ObClient *self);
@@ -109,6 +110,19 @@ static void client_prompt_kill(ObClient *self);
 
 void client_startup(gboolean reconfig)
 {
+    if ((client_default_icon = RrImageCacheFind(ob_rr_icons,
+                                                ob_rr_theme->def_win_icon,
+                                                ob_rr_theme->def_win_icon_w,
+                                                ob_rr_theme->def_win_icon_h)))
+        RrImageRef(client_default_icon);
+    else {
+        client_default_icon = RrImageNew(ob_rr_icons);
+        RrImageAddPicture(client_default_icon,
+                          ob_rr_theme->def_win_icon,
+                          ob_rr_theme->def_win_icon_w,
+                          ob_rr_theme->def_win_icon_h);
+    }
+
     if (reconfig) return;
 
     client_set_list();
@@ -116,6 +130,9 @@ void client_startup(gboolean reconfig)
 
 void client_shutdown(gboolean reconfig)
 {
+    RrImageUnref(client_default_icon);
+    client_default_icon = NULL;
+
     if (reconfig) return;
 }
 
@@ -587,7 +604,6 @@ void client_unmanage_all(void)
 
 void client_unmanage(ObClient *self)
 {
-    guint j;
     GSList *it;
     gulong ignore_start;
 
@@ -719,11 +735,8 @@ void client_unmanage(ObClient *self)
     ob_debug("Unmanaged window 0x%lx", self->window);
 
     /* free all data allocated in the client struct */
+    RrImageUnref(self->icon_set);
     g_slist_free(self->transients);
-    for (j = 0; j < self->nicons; ++j)
-        g_free(self->icons[j].data);
-    if (self->nicons > 0)
-        g_free(self->icons);
     g_free(self->startup_id);
     g_free(self->wm_command);
     g_free(self->title);
@@ -2009,143 +2022,134 @@ void client_update_strut(ObClient *self)
     }
 }
 
-/* Avoid storing icons above this size if possible */
-#define AVOID_ABOVE 64
-
 void client_update_icons(ObClient *self)
 {
     guint num;
     guint32 *data;
     guint w, h, i, j;
     guint num_seen;  /* number of icons present */
-    guint num_small_seen;  /* number of icons small enough present */
-    guint smallest, smallest_area;
+    RrImage *img;
+
+    img = NULL;
 
-    for (i = 0; i < self->nicons; ++i)
-        g_free(self->icons[i].data);
-    if (self->nicons > 0)
-        g_free(self->icons);
-    self->nicons = 0;
+    /* grab the server, because we might be setting the window's icon and
+       we don't want them to set it in between and we overwrite their own
+       icon */
+    grab_server(TRUE);
 
     if (OBT_PROP_GETA32(self->window, NET_WM_ICON, CARDINAL, &data, &num)) {
         /* figure out how many valid icons are in here */
         i = 0;
-        num_seen = num_small_seen = 0;
-        smallest = smallest_area = 0;
-        if (num > 2)
-            while (i < num) {
+        num_seen = 0;
+        while (i + 2 < num) { /* +2 is to make sure there is a w and h */
+            w = data[i++];
+            h = data[i++];
+            /* watch for the data being too small for the specified size,
+               or for zero sized icons. */
+            if (i + w*h > num || w == 0 || h == 0) break;
+
+            /* convert it to the right bit order for ObRender */
+            for (j = 0; j < w*h; ++j)
+                data[i+j] =
+                    (((data[i+j] >> 24) & 0xff) << RrDefaultAlphaOffset) +
+                    (((data[i+j] >> 16) & 0xff) << RrDefaultRedOffset)   +
+                    (((data[i+j] >>  8) & 0xff) << RrDefaultGreenOffset) +
+                    (((data[i+j] >>  0) & 0xff) << RrDefaultBlueOffset);
+
+            /* is it in the cache? */
+            img = RrImageCacheFind(ob_rr_icons, &data[i], w, h);
+            if (img) RrImageRef(img); /* own it */
+
+            i += w*h;
+            ++num_seen;
+
+            /* don't bother looping anymore if we already found it in the cache
+               since we'll just use that! */
+            if (img) break;
+        }
+
+        /* if it's not in the cache yet, then add it to the cache now.
+           we have already converted it to the correct bit order above */
+        if (!img && num_seen > 0) {
+            img = RrImageNew(ob_rr_icons);
+            i = 0;
+            for (j = 0; j < num_seen; ++j) {
                 w = data[i++];
                 h = data[i++];
-                i += w * h;
-                /* watch for it being too small for the specified size, or for
-                   zero sized icons. */
-                if (i > num || w == 0 || h == 0) break;
-
-                if (!smallest_area || w*h < smallest_area) {
-                    smallest = num_seen;
-                    smallest_area = w*h;
-                }
-                ++num_seen;
-                if (w <= AVOID_ABOVE && h <= AVOID_ABOVE)
-                    ++num_small_seen;
-            }
-        if (num_small_seen > 0)
-            self->nicons = num_small_seen;
-        else if (num_seen)
-            self->nicons = 1;
-
-        self->icons = g_new(ObClientIcon, self->nicons);
-
-        /* store the icons */
-        i = 0;
-        for (j = 0; j < self->nicons;) {
-            guint x, y, t;
-
-            w = self->icons[j].width = data[i++];
-            h = self->icons[j].height = data[i++];
-
-            /* if there are some icons smaller than the threshold, we're
-               skipping all the ones above */
-            if (num_small_seen > 0) {
-                if (w > AVOID_ABOVE || h > AVOID_ABOVE) {
-                    i += w*h;
-                    continue;
-                }
-            }
-            /* if there were no icons smaller than the threshold, then we are
-               only taking the smallest available one we saw */
-            else if (j != smallest) {
+                RrImageAddPicture(img, &data[i], w, h);
                 i += w*h;
-                continue;
             }
-
-            self->icons[j].data = g_new(RrPixel32, w * h);
-            for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
-                if (x >= w) {
-                    x = 0;
-                    ++y;
-                }
-                self->icons[j].data[t] =
-                    (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
-                    (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
-                    (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
-                    (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
-            }
-            g_assert(i <= num);
-
-            ++j;
         }
 
         g_free(data);
-    } else {
+    }
+
+    /* if we didn't find an image from the NET_WM_ICON stuff, then try the
+       legacy X hints */
+    if (!img) {
         XWMHints *hints;
 
         if ((hints = XGetWMHints(obt_display, self->window))) {
             if (hints->flags & IconPixmapHint) {
-                self->nicons = 1;
-                self->icons = g_new(ObClientIcon, self->nicons);
+                gboolean xicon;
                 obt_display_ignore_errors(TRUE);
-                if (!RrPixmapToRGBA(ob_rr_inst,
-                                    hints->icon_pixmap,
-                                    (hints->flags & IconMaskHint ?
-                                     hints->icon_mask : None),
-                                    &self->icons[0].width,
-                                    &self->icons[0].height,
-                                    &self->icons[0].data))
-                {
-                    g_free(self->icons);
-                    self->nicons = 0;
-                }
+                xicon = RrPixmapToRGBA(ob_rr_inst,
+                                       hints->icon_pixmap,
+                                       (hints->flags & IconMaskHint ?
+                                        hints->icon_mask : None),
+                                       (gint*)&w, (gint*)&h, &data);
                 obt_display_ignore_errors(FALSE);
+
+
+                if (xicon) {
+                    if (w > 0 && h > 0) {
+                        /* is this icon in the cache yet? */
+                        img = RrImageCacheFind(ob_rr_icons, data, w, h);
+                        if (img) RrImageRef(img); /* own it */
+
+                        /* if not, then add it */
+                        if (!img) {
+                            img = RrImageNew(ob_rr_icons);
+                            RrImageAddPicture(img, data, w, h);
+                        }
+                    }
+
+                    g_free(data);
+                }
             }
             XFree(hints);
         }
     }
 
-    /* set the default icon onto the window
-       in theory, this could be a race, but if a window doesn't set an icon
-       or removes it entirely, it's not very likely it is going to set one
-       right away afterwards
+    /* set the client's icons to be whatever we found */
+    RrImageUnref(self->icon_set);
+    self->icon_set = img;
 
-       if it has parents, then one of them will have an icon already
+    /* if the client has no icon at all, then we set a default icon onto it.
+       but, if it has parents, then one of them will have an icon already
     */
-    if (self->nicons == 0 && !self->parents) {
+    if (!self->icon_set && !self->parents) {
         RrPixel32 *icon = ob_rr_theme->def_win_icon;
-        gulong *data;
-
-        data = g_new(gulong, 48*48+2);
-        data[0] = data[1] =  48;
-        for (i = 0; i < 48*48; ++i)
-            data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
+        gulong *ldata; /* use a long here to satisfy OBT_PROP_SETA32 */
+
+        w = ob_rr_theme->def_win_icon_w;
+        h = ob_rr_theme->def_win_icon_h;
+        ldata = g_new(gulong, w*h+2);
+        ldata[0] = w;
+        ldata[1] = h;
+        for (i = 0; i < w*h; ++i)
+            ldata[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
                 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
                 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
                 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
-        OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, data, 48*48+2);
-        g_free(data);
+        OBT_PROP_SETA32(self->window, NET_WM_ICON, CARDINAL, ldata, w*h+2);
+        g_free(ldata);
     } else if (self->frame)
         /* don't draw the icon empty if we're just setting one now anyways,
            we'll get the property change any second */
         frame_adjust_icon(self->frame);
+
+    grab_server(FALSE);
 }
 
 void client_update_icon_geometry(ObClient *self)
@@ -3840,53 +3844,21 @@ gboolean client_focused(ObClient *self)
     return self == focus_client;
 }
 
-static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
-{
-    guint i;
-    gulong min_diff, min_i;
 
-    if (!self->nicons) {
-        ObClientIcon *parent = NULL;
-        GSList *it;
 
-        for (it = self->parents; it; it = g_slist_next(it)) {
-            ObClient *c = it->data;
-            if ((parent = client_icon_recursive(c, w, h)))
-                break;
-        }
-
-        return parent;
-    }
-
-    /* some kind of crappy approximation to find the icon closest in size to
-       what we requested, but icons are generally all the same ratio as
-       eachother so it's good enough. */
-
-    min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
-    min_i = 0;
-
-    for (i = 1; i < self->nicons; ++i) {
-        gulong diff;
-
-        diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
-        if (diff < min_diff) {
-            min_diff = diff;
-            min_i = i;
-        }
-    }
-    return &self->icons[min_i];
-}
-
-const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
+RrImage* client_icon(ObClient *self)
 {
-    ObClientIcon *ret;
-    static ObClientIcon deficon;
+    RrImage *ret = NULL;
 
-    if (!(ret = client_icon_recursive(self, w, h))) {
-        deficon.width = deficon.height = 48;
-        deficon.data = ob_rr_theme->def_win_icon;
-        ret = &deficon;
+    if (self->icon_set)
+        ret = self->icon_set;
+    else if (self->parents) {
+        GSList *it;
+        for (it = self->parents; it && !ret; it = g_slist_next(it))
+            ret = client_icon(it->data);
     }
+    if (!ret)
+        ret = client_default_icon;
     return ret;
 }
 
index bb63137..c24fcb2 100644 (file)
@@ -40,15 +40,6 @@ struct _ObSessionState;
 struct _ObPrompt;
 
 typedef struct _ObClient      ObClient;
-typedef struct _ObClientIcon  ObClientIcon;
-
-/*! Holds an icon in ARGB format */
-struct _ObClientIcon
-{
-    gint width;
-    gint height;
-    RrPixel32 *data;
-};
 
 /*! Possible window types */
 typedef enum
@@ -307,10 +298,8 @@ struct _ObClient
     */
     guint functions;
 
-    /*! Icons for the client as specified on the client window */
-    ObClientIcon *icons;
-    /*! The number of icons in icons */
-    guint nicons;
+    /* The window's icon, in a variety of shapes and sizes */
+    RrImage *icon_set;
 
     /*! Where the window should iconify to/from */
     Rect icon_geometry;
@@ -642,7 +631,10 @@ void client_setup_decor_and_functions(ObClient *self, gboolean reconfig);
 /*! Sets the window's type and transient flag */
 void client_get_type_and_transientness(ObClient *self);
 
-const ObClientIcon *client_icon(ObClient *self, gint w, gint h);
+/*! Returns a client's icon set, or its parents (recursively) if it doesn't
+  have one
+*/
+RrImage* client_icon(ObClient *self);
 
 /*! Return TRUE if the client is transient for some other window. Return
   FALSE if it's not transient or there is no window for it to be
index 76a819f..a04d07d 100644 (file)
@@ -56,8 +56,6 @@ static gboolean self_update(ObMenuFrame *frame, gpointer data)
             if (client_normal(c) && (!c->skip_taskbar || c->iconic) &&
                 (c->desktop == desktop || c->desktop == DESKTOP_ALL))
             {
-                const ObClientIcon *icon;
-
                 empty = FALSE;
 
                 if (c->iconic) {
@@ -69,11 +67,9 @@ static gboolean self_update(ObMenuFrame *frame, gpointer data)
                     e = menu_add_normal(menu, desktop, c->title, NULL, FALSE);
                 }
 
-                if (config_menu_client_list_icons
-                        && (icon = client_icon(c, 32, 32))) {
-                    e->data.normal.icon_width = icon->width;
-                    e->data.normal.icon_height = icon->height;
-                    e->data.normal.icon_data = icon->data;
+                if (config_menu_client_list_icons) {
+                    e->data.normal.icon = client_icon(c);
+                    RrImageRef(e->data.normal.icon);
                     e->data.normal.icon_alpha =
                         c->iconic ? OB_ICONIC_ALPHA : 0xff;
                 }
index e6521a0..4ec6e78 100644 (file)
@@ -58,7 +58,6 @@ static gboolean desk_menu_update(ObMenuFrame *frame, gpointer data)
             (c->desktop == d->desktop || c->desktop == DESKTOP_ALL))
         {
             ObMenuEntry *e;
-            const ObClientIcon *icon;
 
             empty = FALSE;
 
@@ -71,11 +70,9 @@ static gboolean desk_menu_update(ObMenuFrame *frame, gpointer data)
                 e = menu_add_normal(menu, d->desktop, c->title, NULL, FALSE);
             }
 
-            if (config_menu_client_list_icons
-                && (icon = client_icon(c, 32, 32))) {
-                e->data.normal.icon_width = icon->width;
-                e->data.normal.icon_height = icon->height;
-                e->data.normal.icon_data = icon->data;
+            if (config_menu_client_list_icons) {
+                e->data.normal.icon = client_icon(c);
+                RrImageRef(e->data.normal.icon);
                 e->data.normal.icon_alpha = c->iconic ? OB_ICONIC_ALPHA : 0xff;
             }
 
index 4d1dfaa..3feac6a 100644 (file)
@@ -54,6 +54,7 @@ typedef struct _ObFocusCyclePopupTarget ObFocusCyclePopupTarget;
 struct _ObFocusCyclePopupTarget
 {
     ObClient *client;
+    RrImage *icon;
     gchar *text;
     Window iconwin;
     /* This is used when the popup is in list mode */
@@ -139,8 +140,10 @@ void focus_cycle_popup_startup(gboolean reconfig)
        may or may not be used */
     RrAppearanceAddTextures(popup.a_icon, 2);
 
-    popup.a_icon->texture[0].type = RR_TEXTURE_RGBA;
+    RrAppearanceClearTextures(popup.a_icon);
+    popup.a_icon->texture[0].type = RR_TEXTURE_IMAGE;
 
+    RrAppearanceClearTextures(popup.a_arrow);
     popup.a_arrow->texture[0].type = RR_TEXTURE_MASK;
     popup.a_arrow->texture[0].data.mask.color =
         ob_rr_theme->osd_color;
@@ -212,9 +215,11 @@ void focus_cycle_popup_shutdown(gboolean reconfig)
     while(popup.targets) {
         ObFocusCyclePopupTarget *t = popup.targets->data;
 
+        RrImageUnref(t->icon);
         g_free(t->text);
         XDestroyWindow(obt_display, t->iconwin);
         XDestroyWindow(obt_display, t->textwin);
+        g_free(t);
 
         popup.targets = g_list_delete_link(popup.targets, popup.targets);
     }
@@ -272,6 +277,8 @@ static void popup_setup(ObFocusCyclePopup *p, gboolean create_targets,
 
                 t->client = ft;
                 t->text = text;
+                t->icon = client_icon(t->client);
+                RrImageRef(t->icon); /* own the icon so it won't go away */
                 t->iconwin = create_window(p->bg, 0, 0, NULL);
                 t->textwin = create_window(p->bg, 0, 0, NULL);
 
@@ -547,7 +554,6 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
             /* row and column start from 0 */
             const gint row = i / icons_per_row - p->scroll;
             const gint col = i % icons_per_row;
-            const ObClientIcon *icon;
             gint iconx, icony;
             gint list_mode_textx, list_mode_texty;
             RrAppearance *text;
@@ -590,16 +596,13 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c)
             }
 
             /* get the icon from the client */
-            icon = client_icon(target->client, ICON_SIZE, ICON_SIZE);
-            p->a_icon->texture[0].data.rgba.width = icon->width;
-            p->a_icon->texture[0].data.rgba.height = icon->height;
-            p->a_icon->texture[0].data.rgba.twidth = ICON_SIZE;
-            p->a_icon->texture[0].data.rgba.theight = ICON_SIZE;
-            p->a_icon->texture[0].data.rgba.tx = HILITE_OFFSET;
-            p->a_icon->texture[0].data.rgba.ty = HILITE_OFFSET;
-            p->a_icon->texture[0].data.rgba.alpha =
+            p->a_icon->texture[0].data.image.twidth = ICON_SIZE;
+            p->a_icon->texture[0].data.image.theight = ICON_SIZE;
+            p->a_icon->texture[0].data.image.tx = HILITE_OFFSET;
+            p->a_icon->texture[0].data.image.ty = HILITE_OFFSET;
+            p->a_icon->texture[0].data.image.alpha =
                 target->client->iconic ? OB_ICONIC_ALPHA : 0xff;
-            p->a_icon->texture[0].data.rgba.data = icon->data;
+            p->a_icon->texture[0].data.image.image = target->icon;
 
             /* Draw the hilite? */
             p->a_icon->texture[1].type = (target == newtarget) ?
@@ -685,6 +688,7 @@ void focus_cycle_popup_hide(void)
     while(popup.targets) {
         ObFocusCyclePopupTarget *t = popup.targets->data;
 
+        RrImageUnref(t->icon);
         g_free(t->text);
         XDestroyWindow(obt_display, t->iconwin);
         XDestroyWindow(obt_display, t->textwin);
@@ -726,8 +730,7 @@ void focus_cycle_popup_single_show(struct _ObClient *c,
     }
 
     text = popup_get_name(c);
-    icon_popup_show(single_popup, text, client_icon(c, HILITE_SIZE,
-                                                    HILITE_SIZE));
+    icon_popup_show(single_popup, text, client_icon(c));
     g_free(text);
     screen_hide_desktop_popup();
 }
index 87706b2..bf71d2c 100644 (file)
@@ -354,21 +354,21 @@ static void framerender_label(ObFrame *self, RrAppearance *a)
 
 static void framerender_icon(ObFrame *self, RrAppearance *a)
 {
-    const ObClientIcon *icon;
+    RrImage *icon;
 
     if (!self->icon_on) return;
 
-    icon = client_icon(self->client,
-                       ob_rr_theme->button_size + 2,
-                       ob_rr_theme->button_size + 2);
+    icon = client_icon(self->client);
+
     if (icon) {
-        a->texture[0].type = RR_TEXTURE_RGBA;
-        a->texture[0].data.rgba.width = icon->width;
-        a->texture[0].data.rgba.height = icon->height;
-        a->texture[0].data.rgba.alpha = 0xff;
-        a->texture[0].data.rgba.data = icon->data;
-    } else
+        RrAppearanceClearTextures(a);
+        a->texture[0].type = RR_TEXTURE_IMAGE;
+        a->texture[0].data.image.alpha = 0xff;
+        a->texture[0].data.image.image = icon;
+    } else {
+        RrAppearanceClearTextures(a);
         a->texture[0].type = RR_TEXTURE_NONE;
+    }
 
     RrPaint(a, self->icon,
             ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
index 768630b..63b74b9 100644 (file)
@@ -500,6 +500,7 @@ void menu_entry_unref(ObMenuEntry *self)
     if (self && --self->ref == 0) {
         switch (self->type) {
         case OB_MENU_ENTRY_TYPE_NORMAL:
+            RrImageUnref(self->data.normal.icon);
             g_free(self->data.normal.label);
             while (self->data.normal.actions) {
                 actions_act_unref(self->data.normal.actions->data);
index 752a677..c488f67 100644 (file)
@@ -115,11 +115,9 @@ struct _ObNormalMenuEntry {
     /* List of ObActions */
     GSList *actions;
 
-    /* Icon shit */
-    gint icon_width;
-    gint icon_height;
-    gint icon_alpha;
-    RrPixel32 *icon_data;
+    /* Icon stuff.  If you set this, make sure you RrImageRef() it too. */
+    RrImage *icon;
+    gint     icon_alpha;
 
     /* Mask icon */
     RrPixmapMask *mask;
index 598bc00..c25e485 100644 (file)
@@ -338,6 +338,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
     default:
         g_assert_not_reached();
     }
+
     RECT_SET_SIZE(self->area, self->frame->inner_w, th);
     XResizeWindow(obt_display, self->window,
                   self->area.width, self->area.height);
@@ -442,6 +443,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                               self->area.width - 2*PADDING, SEPARATOR_HEIGHT);
 
             clear = ob_rr_theme->a_clear_tex;
+            RrAppearanceClearTextures(clear);
             clear->texture[0].type = RR_TEXTURE_LINE_ART;
             clear->surface.parent = item_a;
             clear->surface.parentx = PADDING;
@@ -459,7 +461,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
     }
 
     if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
-        self->entry->data.normal.icon_data)
+        self->entry->data.normal.icon)
     {
         RrAppearance *clear;
 
@@ -471,15 +473,12 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                           - frame->item_margin.bottom);
 
         clear = ob_rr_theme->a_clear_tex;
-        clear->texture[0].type = RR_TEXTURE_RGBA;
-        clear->texture[0].data.rgba.width =
-            self->entry->data.normal.icon_width;
-        clear->texture[0].data.rgba.height =
-            self->entry->data.normal.icon_height;
-        clear->texture[0].data.rgba.alpha =
+        RrAppearanceClearTextures(clear);
+        clear->texture[0].type = RR_TEXTURE_IMAGE;
+        clear->texture[0].data.image.image =
+            self->entry->data.normal.icon;
+        clear->texture[0].data.image.alpha =
             self->entry->data.normal.icon_alpha;
-        clear->texture[0].data.rgba.data =
-            self->entry->data.normal.icon_data;
         clear->surface.parent = item_a;
         clear->surface.parentx = PADDING;
         clear->surface.parenty = frame->item_margin.top;
@@ -503,6 +502,7 @@ static void menu_entry_frame_render(ObMenuEntryFrame *self)
                           - frame->item_margin.bottom);
 
         clear = ob_rr_theme->a_clear_tex;
+        RrAppearanceClearTextures(clear);
         clear->texture[0].type = RR_TEXTURE_MASK;
         clear->texture[0].data.mask.mask =
             self->entry->data.normal.mask;
@@ -694,7 +694,7 @@ void menu_frame_render(ObMenuFrame *self)
             tw = MIN(tw, MAX_MENU_WIDTH);
             th = ob_rr_theme->menu_font_height;
 
-            if (e->entry->data.normal.icon_data ||
+            if (e->entry->data.normal.icon ||
                 e->entry->data.normal.mask)
                 has_icon = TRUE;
             break;
@@ -705,7 +705,7 @@ void menu_frame_render(ObMenuFrame *self)
             tw = MIN(tw, MAX_MENU_WIDTH);
             th = ob_rr_theme->menu_font_height;
 
-            if (e->entry->data.normal.icon_data ||
+            if (e->entry->data.normal.icon ||
                 e->entry->data.normal.mask)
                 has_icon = TRUE;
 
index a6a81ce..ec6cc44 100644 (file)
 #include <X11/Xlib.h>
 #include <X11/keysym.h>
 
-RrInstance  *ob_rr_inst;
-RrTheme     *ob_rr_theme;
-ObtMainLoop *ob_main_loop;
-gint         ob_screen;
-gboolean     ob_replace_wm = FALSE;
-gboolean     ob_sm_use = TRUE;
-gchar       *ob_sm_id = NULL;
-gchar       *ob_sm_save_file = NULL;
-gboolean     ob_sm_restore = TRUE;
-gboolean     ob_debug_xinerama = FALSE;
+RrInstance   *ob_rr_inst;
+RrImageCache *ob_rr_icons;
+RrTheme      *ob_rr_theme;
+ObtMainLoop  *ob_main_loop;
+gint          ob_screen;
+gboolean      ob_replace_wm = FALSE;
+gboolean      ob_sm_use = TRUE;
+gchar        *ob_sm_id = NULL;
+gchar        *ob_sm_save_file = NULL;
+gboolean      ob_sm_restore = TRUE;
+gboolean      ob_debug_xinerama = FALSE;
 
 static ObState   state;
 static gboolean  xsync = FALSE;
@@ -167,6 +168,7 @@ gint main(gint argc, gchar **argv)
     ob_rr_inst = RrInstanceNew(obt_display, ob_screen);
     if (ob_rr_inst == NULL)
         ob_exit_with_error(_("Failed to initialize the obrender library."));
+    ob_rr_icons = RrImageCacheNew();
 
     XSynchronize(obt_display, xsync);
 
@@ -373,6 +375,7 @@ gint main(gint argc, gchar **argv)
     XSync(obt_display, FALSE);
 
     RrThemeFree(ob_rr_theme);
+    RrImageCacheUnref(ob_rr_icons);
     RrInstanceFree(ob_rr_inst);
 
     session_shutdown(being_replaced);
index 47d9204..471aa77 100644 (file)
@@ -30,6 +30,7 @@
 #include <X11/Xlib.h>
 
 extern RrInstance *ob_rr_inst;
+extern RrImageCache *ob_rr_icons;
 extern RrTheme    *ob_rr_theme;
 
 extern ObtMainLoop *ob_main_loop;
index 02c8784..fd31846 100644 (file)
@@ -366,16 +366,17 @@ void icon_popup_free(ObIconPopup *self)
 }
 
 void icon_popup_delay_show(ObIconPopup *self, gulong usec,
-                           gchar *text, const ObClientIcon *icon)
+                           gchar *text, RrImage *icon)
 {
     if (icon) {
-        self->a_icon->texture[0].type = RR_TEXTURE_RGBA;
-        self->a_icon->texture[0].data.rgba.width = icon->width;
-        self->a_icon->texture[0].data.rgba.height = icon->height;
-        self->a_icon->texture[0].data.rgba.alpha = 0xff;
-        self->a_icon->texture[0].data.rgba.data = icon->data;
-    } else
+        RrAppearanceClearTextures(self->a_icon);
+        self->a_icon->texture[0].type = RR_TEXTURE_IMAGE;
+        self->a_icon->texture[0].data.image.alpha = 0xff;
+        self->a_icon->texture[0].data.image.image = icon;
+    } else {
+        RrAppearanceClearTextures(self->a_icon);
         self->a_icon->texture[0].type = RR_TEXTURE_NONE;
+    }
 
     popup_delay_show(self->popup, usec, text);
 }
index 0072f53..2b01ce2 100644 (file)
@@ -110,7 +110,7 @@ void icon_popup_free(ObIconPopup *self);
 
 #define icon_popup_show(s, t, i) icon_popup_delay_show((s),0,(t),(i))
 void icon_popup_delay_show(ObIconPopup *self, gulong usec,
-                           gchar *text, const struct _ObClientIcon *icon);
+                           gchar *text, RrImage *icon);
 #define icon_popup_hide(p) popup_hide((p)->popup)
 #define icon_popup_position(p, g, x, y) popup_position((p)->popup,(g),(x),(y))
 #define icon_popup_text_width(p, w) popup_text_width((p)->popup,(w))
index 2eb043a..d22ef9e 100644 (file)
@@ -20,6 +20,7 @@
 #include "geom.h"
 #include "image.h"
 #include "color.h"
+#include "imagecache.h"
 
 #include <glib.h>
 
 #define FLOOR(i)        ((i) & (~0UL << FRACTION))
 #define AVERAGE(a, b)   (((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)))
 
-static void ImageCopyResampled(RrPixel32 *dst, RrPixel32 *src,
-                               gulong dstW, gulong dstH,
-                               gulong srcW, gulong srcH)
+static void AddPicture(RrImage *self, RrImagePic ***list, gint *len,
+                       RrImagePic *pic)
 {
+    gint i;
+
+    g_assert(pic->width > 0 && pic->height > 0);
+
+    g_assert(g_hash_table_lookup(self->cache->table, pic) == NULL);
+
+    /* grow the list */
+    *list = g_renew(RrImagePic*, *list, ++*len);
+
+    /* move everything else down one */
+    for (i = *len-1; i > 0; --i)
+        (*list)[i] = (*list)[i-1];
+
+    /* set the new picture up at the front of the list */
+    (*list)[0] = pic;
+
+    /* add the picture as a key to point to this image in the cache */
+    g_hash_table_insert(self->cache->table, (*list)[0], self);
+
+#ifdef DEBUG
+    g_print("Adding %s picture to the cache: "
+            "Image 0x%x, w %d h %d Hash %u\n",
+            (*list == self->original ? "ORIGINAL" : "RESIZED"),
+            (guint)self, pic->width, pic->height, RrImagePicHash(pic));
+#endif
+}
+
+static void RemovePicture(RrImage *self, RrImagePic ***list,
+                          gint i, gint *len)
+{
+    gint j;
+
+#ifdef DEBUG
+    g_print("Removing %s picture from the cache: "
+            "Image 0x%x, w %d h %d Hash %u\n",
+            (*list == self->original ? "ORIGINAL" : "RESIZED"),
+            (guint)self, (*list)[i]->width, (*list)[i]->height,
+            RrImagePicHash((*list)[i]));
+#endif
+
+    /* remove the picture as a key in the cache */
+    g_hash_table_remove(self->cache->table, (*list)[i]);
+
+    /* free the picture (and its rgba data) */
+    g_free((*list)[i]);
+    g_free((*list)[i]->data);
+    /* shift everything down one */
+    for (j = i; j < *len-1; ++j)
+        (*list)[j] = (*list)[j+1];
+    /* shrink the list */
+    *list = g_renew(RrImagePic*, *list, --*len);
+}
+
+static RrImagePic* ResizeImage(RrPixel32 *src,
+                                       gulong srcW, gulong srcH,
+                                       gulong dstW, gulong dstH)
+{
+    RrPixel32 *dst;
+    RrImagePic *pic;
     gulong dstX, dstY, srcX, srcY;
     gulong srcX1, srcX2, srcY1, srcY2;
     gulong ratioX, ratioY;
+    gulong aspectW, aspectH;
+
+    /* keep the aspect ratio */
+    aspectW = dstW;
+    aspectH = (gint)(dstW * ((gdouble)srcH / srcW));
+    if (aspectH > dstH) {
+        aspectH = dstH;
+        aspectW = (gint)(dstH * ((gdouble)srcW / srcH));
+    }
+    dstW = aspectW;
+    dstH = aspectH;
+
+    if (srcW == dstW && srcH == dstH)
+        return NULL; /* no scaling needed ! */
+
+    pic = g_new(RrImagePic, 1);
+    dst = g_new(RrPixel32, dstW * dstH);
+    pic->width = dstW;
+    pic->height = dstH;
+    pic->data = dst;
 
     ratioX = (srcW << FRACTION) / dstW;
     ratioY = (srcH << FRACTION) / dstH;
@@ -104,56 +183,42 @@ static void ImageCopyResampled(RrPixel32 *dst, RrPixel32 *src,
                      (alpha << RrDefaultAlphaOffset);
         }
     }
+
+    return pic;
 }
 
-void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
-                 gint target_w, gint target_h,
-                 RrRect *area)
+/*! This drawns an RGBA picture into the target, within the rectangle specified
+  by the area parameter.  If the area's size differs from the source's then it
+  will be centered within the rectangle */
+void DrawRGBA(RrPixel32 *target, gint target_w, gint target_h,
+              RrPixel32 *source, gint source_w, gint source_h,
+              gint alpha, RrRect *area)
 {
     RrPixel32 *dest;
-    RrPixel32 *source;
-    gint sw, sh, dw, dh;
     gint col, num_pixels;
+    gint dw, dh;
 
-    sw = rgba->width;
-    sh = rgba->height;
+    g_assert(source_w <= area->width && source_h <= area->height);
 
-    /* keep the ratio */
+    /* keep the aspect ratio */
     dw = area->width;
-    dh = (gint)(dw * ((gdouble)sh / sw));
+    dh = (gint)(dw * ((gdouble)source_h / source_w));
     if (dh > area->height) {
         dh = area->height;
-        dw = (gint)(dh * ((gdouble)sw / sh));
-    }
-
-    if (!(dw && dh))
-        return; /* XXX sanity check */
-
-    if (sw != dw || sh != dh) {
-        /*if (!(rgba->cache && dw == rgba->cwidth && dh == rgba->cheight))*/ {
-            g_free(rgba->cache);
-            rgba->cache = g_new(RrPixel32, dw * dh);
-            ImageCopyResampled(rgba->cache, rgba->data, dw, dh, sw, sh);
-            rgba->cwidth = dw;
-            rgba->cheight = dh;
-        }
-        source = rgba->cache;
-    } else {
-        source = rgba->data;
+        dw = (gint)(dh * ((gdouble)source_w / source_h));
     }
 
     /* copy source -> dest, and apply the alpha channel.
-
        center the image if it is smaller than the area */
     col = 0;
     num_pixels = dw * dh;
     dest = target + area->x + (area->width - dw) / 2 +
         (target_w * (area->y + (area->height - dh) / 2));
     while (num_pixels-- > 0) {
-        guchar alpha, r, g, b, bgr, bgg, bgb;
+        guchar a, r, g, b, bgr, bgg, bgb;
 
         /* apply the rgba's opacity as well */
-        alpha = ((*source >> RrDefaultAlphaOffset) * rgba->alpha) >> 8;
+        a = ((*source >> RrDefaultAlphaOffset) * alpha) >> 8;
         r = *source >> RrDefaultRedOffset;
         g = *source >> RrDefaultGreenOffset;
         b = *source >> RrDefaultBlueOffset;
@@ -163,9 +228,9 @@ void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
         bgg = *dest >> RrDefaultGreenOffset;
         bgb = *dest >> RrDefaultBlueOffset;
 
-        r = bgr + (((r - bgr) * alpha) >> 8);
-        g = bgg + (((g - bgg) * alpha) >> 8);
-        b = bgb + (((b - bgb) * alpha) >> 8);
+        r = bgr + (((r - bgr) * a) >> 8);
+        g = bgg + (((g - bgg) * a) >> 8);
+        b = bgb + (((b - bgb) * a) >> 8);
 
         *dest = ((r << RrDefaultRedOffset) |
                  (g << RrDefaultGreenOffset) |
@@ -180,3 +245,185 @@ void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
         }
     }
 }
+
+void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
+                     gint target_w, gint target_h,
+                     RrRect *area)
+{
+    RrImagePic *scaled;
+
+    scaled = ResizeImage(rgba->data, rgba->width, rgba->height,
+                         area->width, area->height);
+
+    if (scaled) {
+#ifdef DEBUG
+            g_warning("Scaling an RGBA! You should avoid this and just make "
+                      "it the right size yourself!");
+#endif
+            DrawRGBA(target, target_w, target_h,
+                     scaled->data, scaled->width, scaled->height,
+                     rgba->alpha, area);
+    }
+    else
+        DrawRGBA(target, target_w, target_h,
+                 rgba->data, rgba->width, rgba->height,
+                 rgba->alpha, area);
+}
+
+RrImage* RrImageNew(RrImageCache *cache)
+{
+    RrImage *self;
+
+    self = g_new0(RrImage, 1);
+    self->ref = 1;
+    self->cache = cache;
+    return self;
+}
+
+void RrImageRef(RrImage *self)
+{
+    ++self->ref;
+}
+
+void RrImageUnref(RrImage *self)
+{
+    if (self && --self->ref == 0) {
+#ifdef DEBUG
+        g_print("Refcount to 0, removing ALL pictures from the cache: "
+                  "Image 0x%x\n", (guint)self);
+#endif
+        while (self->n_original > 0)
+            RemovePicture(self, &self->original, 0, &self->n_original);
+        while (self->n_resized > 0)
+            RemovePicture(self, &self->resized, 0, &self->n_resized);
+        g_free(self);
+    }
+}
+
+void RrImageAddPicture(RrImage *self, RrPixel32 *data, gint w, gint h)
+{
+    gint i;
+    RrImagePic *pic;
+
+    /* make sure we don't already have this size.. */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == w && self->original[i]->height == h) {
+#ifdef DEBUG
+            g_print("Found duplicate ORIGINAL image: "
+                    "Image 0x%x, w %d h %d\n", (guint)self, w, h);
+#endif
+            return;
+        }
+
+    /* remove any resized pictures of this same size */
+    for (i = 0; i < self->n_resized; ++i)
+        if (self->resized[i]->width == w || self->resized[i]->height == h) {
+            RemovePicture(self, &self->resized, i, &self->n_resized);
+            break;
+        }
+
+    /* add the new picture */
+    pic = g_new(RrImagePic, 1);
+    pic->width = w;
+    pic->height = h;
+    pic->data = g_memdup(data, w*h*sizeof(RrPixel32));
+    AddPicture(self, &self->original, &self->n_original, pic);
+}
+
+void RrImageRemovePicture(RrImage *self, gint w, gint h)
+{
+    gint i;
+
+    /* remove any resized pictures of this same size */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == w && self->original[i]->height == h) {
+            RemovePicture(self, &self->original, i, &self->n_original);
+            break;
+        }
+}
+
+void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
+                      gint target_w, gint target_h,
+                      RrRect *area)
+{
+    gint i, min_diff, min_i;
+    RrImage *self;
+    RrImagePic *pic;
+
+    self = img->image;
+    pic = NULL;
+
+    /* is there an original of this size? (only w or h has to be right cuz
+       we maintain aspect ratios) */
+    for (i = 0; i < self->n_original; ++i)
+        if (self->original[i]->width == area->width ||
+            self->original[i]->height == area->height)
+        {
+            pic = self->original[i];
+            break;
+        }
+
+    /* is there a resize of this size? */
+    for (i = 0; i < self->n_resized; ++i)
+        if (self->resized[i]->width == area->width ||
+            self->resized[i]->height == area->height)
+        {
+            gint j;
+            RrImagePic *saved;
+
+            /* save the selected one */
+            saved = self->resized[i];
+
+            /* shift all the others down */
+            for (j = i; j > 0; --j)
+                self->resized[j] = self->resized[j-1];
+
+            /* and move the selected one to the top of the list */
+            self->resized[0] = saved;
+
+            pic = self->resized[0];
+            break;
+        }
+
+    if (!pic) {
+        /* find an original with a close size */
+        min_diff = -1;
+        min_i = 0;
+        for (i = 0; i < self->n_original; ++i) {
+            gint diff;
+            gint wdiff, hdiff;
+
+            /* our size difference metric.. */
+            wdiff = self->original[i]->width - area->width;
+            hdiff = self->original[i]->height - area->height;
+            diff = (wdiff * wdiff) + (hdiff * hdiff);
+
+            if (min_diff < 0 || diff < min_diff) {
+                min_diff = diff;
+                min_i = i;
+            }
+        }
+
+        /* resize the original to the given area */
+        pic = ResizeImage(self->original[min_i]->data,
+                          self->original[min_i]->width,
+                          self->original[min_i]->height,
+                          area->width, area->height);
+
+        /* add the resized image to the image, as the first in the resized
+           list */
+        if (self->n_resized >= MAX_CACHE_RESIZED) {
+            /* remove the last one (last used one) */
+            RemovePicture(self, &self->resized, self->n_resized - 1,
+                          &self->n_resized);
+        }
+        /* add it to the top of the resized list */
+        AddPicture(self, &self->resized, &self->n_resized, pic);
+    }
+
+    g_assert(pic != NULL);
+
+    DrawRGBA(target, target_w, target_h,
+             pic->data, pic->width, pic->height,
+             img->alpha, area);
+}
index 1c53596..28f29c2 100644 (file)
 #include "render.h"
 #include "geom.h"
 
-void RrImageDraw(RrPixel32 *target, RrTextureRGBA *rgba,
-                 gint target_w, gint target_h,
-                 RrRect *area);
+void RrImageDrawImage(RrPixel32 *target, RrTextureImage *img,
+                      gint target_w, gint target_h,
+                      RrRect *area);
+void RrImageDrawRGBA(RrPixel32 *target, RrTextureRGBA *rgba,
+                     gint target_w, gint target_h,
+                     RrRect *area);
 
 #endif
diff --git a/render/imagecache.c b/render/imagecache.c
new file mode 100644 (file)
index 0000000..ec2ff4d
--- /dev/null
@@ -0,0 +1,149 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   imagecache.c for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "render.h"
+#include "imagecache.h"
+
+static gboolean RrImagePicEqual(const RrImagePic *p1,
+                                const RrImagePic *p2);
+
+RrImageCache* RrImageCacheNew()
+{
+    RrImageCache *self;
+
+    self = g_new(RrImageCache, 1);
+    self->ref = 1;
+    self->table = g_hash_table_new((GHashFunc)RrImagePicHash,
+                                   (GEqualFunc)RrImagePicEqual);
+    return self;
+}
+
+void RrImageCacheRef(RrImageCache *self)
+{
+    ++self->ref;
+}
+
+void RrImageCacheUnref(RrImageCache *self)
+{
+    if (self && --self->ref == 0) {
+        g_assert(g_hash_table_size(self->table) == 0);
+        g_hash_table_unref(self->table);
+
+        g_free(self);
+    }
+}
+
+/*! Finds an image in the cache, if it is already in there */
+RrImage* RrImageCacheFind(RrImageCache *self,
+                          RrPixel32 *data, gint w, gint h)
+{
+    RrImagePic pic;
+    pic.width = w;
+    pic.height = h;
+    pic.data = data;
+    return g_hash_table_lookup(self->table, &pic);
+}
+
+/* This is a fast, reversable hash function called "lookup3", found here:
+   http://burtleburtle.net/bob/c/lookup3.c
+*/
+#define hashsize(n) ((RrPixel32)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+/* mix -- mix 3 32-bit values reversibly. */
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+/* final -- final mixing of 3 32-bit values (a,b,c) into c */
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+guint32 hashword(const guint32 *key, gint length, guint32 initval)
+{
+    guint32 a,b,c;
+
+    /* Set up the internal state */
+    a = b = c = 0xdeadbeef + (((guint32)length)<<2) + initval;
+
+    /* handle most of the key */
+    while (length > 3)
+    {
+        a += key[0];
+        b += key[1];
+        c += key[2];
+        mix(a,b,c);
+        length -= 3;
+        key += 3;
+    }
+
+    /* handle the last 3 guint32's */
+    switch(length)      /* all the case statements fall through */
+    { 
+    case 3: c+=key[2];
+    case 2: b+=key[1];
+    case 1: a+=key[0];
+        final(a,b,c);
+    case 0:             /* case 0: nothing left to add */
+        break;
+    }
+    /* report the result */
+    return c;
+}
+
+#define HASH_INITVAL 0xf00d
+
+guint RrImagePicHash(const RrImagePic *p)
+{
+    return hashword(p->data, p->width * p->height, HASH_INITVAL);
+}
+
+static gboolean RrImagePicEqual(const RrImagePic *p1,
+                                const RrImagePic *p2)
+{
+    guint s1, s2;
+    RrPixel32 *data1, *data2;
+    gint i;
+
+    if (p1->width != p2->width || p1->height != p2->height) return FALSE;
+
+    /* strcmp() would probably suck on 4k of data.. sum all their values and
+       see if they get the same thing.  they already matched on their hashes
+       at this point. */
+    s1 = s2 = 0;
+    data1 = p1->data;
+    data2 = p2->data;
+    for (i = 0; i < p1->width * p1->height; ++i, ++data1)
+        s1 += *data1;
+    for (i = 0; i < p2->width * p2->height; ++i, ++data2)
+        s2 += *data2;
+    return s1 == s2;
+}
diff --git a/render/imagecache.h b/render/imagecache.h
new file mode 100644 (file)
index 0000000..8c96caf
--- /dev/null
@@ -0,0 +1,37 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   imagecache.h for the Openbox window manager
+   Copyright (c) 2008        Dana Jansens
+
+   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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#ifndef __imagecache_h
+#define __imagecache_h
+
+#include <glib.h>
+
+/* the number of resized pictures to cache for an image */
+#define MAX_CACHE_RESIZED 3
+
+struct _RrImagePic;
+
+guint RrImagePicHash(const struct _RrImagePic *p);
+
+struct _RrImageCache {
+    gint ref;
+
+    GHashTable *table;
+};
+
+#endif
index 49485c8..304148d 100644 (file)
@@ -130,6 +130,24 @@ Pixmap RrPaintPixmap(RrAppearance *a, gint w, gint h)
             }
             RrPixmapMaskDraw(a->pixmap, &a->texture[i].data.mask, &tarea);
             break;
+        case RR_TEXTURE_IMAGE:
+            g_assert(!transferred);
+            {
+                RrRect narea = tarea;
+                RrTextureImage *img = &a->texture[i].data.image;
+                if (img->twidth)
+                    narea.width = MIN(tarea.width, img->twidth);
+                if (img->theight)
+                    narea.height = MIN(tarea.height, img->theight);
+                narea.x += img->tx;
+                narea.y += img->ty;
+                RrImageDrawImage(a->surface.pixel_data,
+                                 &a->texture[i].data.image,
+                                 a->w, a->h,
+                                 &narea);
+            }
+            force_transfer = 1;
+            break;
         case RR_TEXTURE_RGBA:
             g_assert(!transferred);
             {
@@ -141,10 +159,10 @@ Pixmap RrPaintPixmap(RrAppearance *a, gint w, gint h)
                     narea.height = MIN(tarea.height, rgb->theight);
                 narea.x += rgb->tx;
                 narea.y += rgb->ty;
-                RrImageDraw(a->surface.pixel_data,
-                            &a->texture[i].data.rgba,
-                            a->w, a->h,
-                            &narea);
+                RrImageDrawRGBA(a->surface.pixel_data,
+                                &a->texture[i].data.rgba,
+                                a->w, a->h,
+                                &narea);
             }
             force_transfer = 1;
         break;
@@ -202,11 +220,15 @@ void RrAppearanceAddTextures(RrAppearance *a, gint numtex)
     if (numtex) a->texture = g_new0(RrTexture, numtex);
 }
 
+void RrAppearanceClearTextures(RrAppearance *a)
+{
+    memset(a->texture, 0, a->textures * sizeof(RrTexture));
+}
+
 RrAppearance *RrAppearanceCopy(RrAppearance *orig)
 {
     RrSurface *spo, *spc;
     RrAppearance *copy = g_new(RrAppearance, 1);
-    gint i;
 
     copy->inst = orig->inst;
 
@@ -282,10 +304,6 @@ RrAppearance *RrAppearanceCopy(RrAppearance *orig)
     copy->textures = orig->textures;
     copy->texture = g_memdup(orig->texture,
                              orig->textures * sizeof(RrTexture));
-    for (i = 0; i < copy->textures; ++i)
-        if (copy->texture[i].type == RR_TEXTURE_RGBA) {
-            copy->texture[i].data.rgba.cache = NULL;
-        }
     copy->pixmap = None;
     copy->xftdraw = NULL;
     copy->w = copy->h = 0;
@@ -294,17 +312,10 @@ RrAppearance *RrAppearanceCopy(RrAppearance *orig)
 
 void RrAppearanceFree(RrAppearance *a)
 {
-    gint i;
-
     if (a) {
         RrSurface *p;
         if (a->pixmap != None) XFreePixmap(RrDisplay(a->inst), a->pixmap);
         if (a->xftdraw != NULL) XftDrawDestroy(a->xftdraw);
-        for (i = 0; i < a->textures; ++i)
-            if (a->texture[i].type == RR_TEXTURE_RGBA) {
-                g_free(a->texture[i].data.rgba.cache);
-                a->texture[i].data.rgba.cache = NULL;
-            }
         if (a->textures)
             g_free(a->texture);
         p = &a->surface;
@@ -403,6 +414,9 @@ gint RrMinWidth(RrAppearance *a)
         case RR_TEXTURE_RGBA:
             w += MAX(w, a->texture[i].data.rgba.width);
             break;
+        case RR_TEXTURE_IMAGE:
+            /* images resize so they don't contribute anything to the min */
+            break;
         case RR_TEXTURE_LINE_ART:
             w += MAX(w, MAX(a->texture[i].data.lineart.x1,
                             a->texture[i].data.lineart.x2));
@@ -455,6 +469,9 @@ gint RrMinHeight(RrAppearance *a)
         case RR_TEXTURE_RGBA:
             h += MAX(h, a->texture[i].data.rgba.height);
             break;
+        case RR_TEXTURE_IMAGE:
+            /* images resize so they don't contribute anything to the min */
+            break;
         case RR_TEXTURE_LINE_ART:
             h += MAX(h, MAX(a->texture[i].data.lineart.y1,
                             a->texture[i].data.lineart.y2));
index 260ebeb..5d1d155 100644 (file)
@@ -37,11 +37,15 @@ typedef struct _RrFont             RrFont;
 typedef struct _RrTexture          RrTexture;
 typedef struct _RrTextureMask      RrTextureMask;
 typedef struct _RrTextureRGBA      RrTextureRGBA;
+typedef struct _RrTextureImage     RrTextureImage;
 typedef struct _RrTextureText      RrTextureText;
 typedef struct _RrTextureLineArt   RrTextureLineArt;
 typedef struct _RrPixmapMask       RrPixmapMask;
 typedef struct _RrInstance         RrInstance;
 typedef struct _RrColor            RrColor;
+typedef struct _RrImage            RrImage;
+typedef struct _RrImagePic         RrImagePic;
+typedef struct _RrImageCache       RrImageCache;
 
 typedef guint32 RrPixel32;
 typedef guint16 RrPixel16;
@@ -76,7 +80,8 @@ typedef enum {
     RR_TEXTURE_MASK,
     RR_TEXTURE_TEXT,
     RR_TEXTURE_LINE_ART,
-    RR_TEXTURE_RGBA
+    RR_TEXTURE_RGBA,
+    RR_TEXTURE_IMAGE
 } RrTextureType;
 
 typedef enum {
@@ -163,11 +168,19 @@ struct _RrTextureRGBA {
     gint height;
     gint alpha;
     RrPixel32 *data;
-/* cached scaled so we don't have to scale often */
-    gint cwidth;
-    gint cheight;
-    RrPixel32 *cache;
-/* size and position to draw at */
+    /* size and position to draw at (if these are zero, then it will be
+       drawn to fill the entire texture */
+    gint tx;
+    gint ty;
+    gint twidth;
+    gint theight;
+};
+
+struct _RrTextureImage {
+    RrImage *image;
+    gint alpha;
+    /* size and position to draw at (if these are zero, then it will be
+       drawn to fill the entire texture */
     gint tx;
     gint ty;
     gint twidth;
@@ -184,12 +197,15 @@ struct _RrTextureLineArt {
 
 union _RrTextureData {
     RrTextureRGBA rgba;
+    RrTextureImage image;
     RrTextureText text;
     RrTextureMask mask;
     RrTextureLineArt lineart;
 };
 
 struct _RrTexture {
+    /* If changing the type of a texture, you should DEFINITELY call
+       RrAppearanceClearTextures() first! */
     RrTextureType type;
     RrTextureData data;
 };
@@ -207,6 +223,25 @@ struct _RrAppearance {
     gint w, h;
 };
 
+/*! Holds a RGBA image picture */
+struct _RrImagePic {
+    gint width, height;
+    RrPixel32 *data;
+};
+
+/*! An RrImage is a sort of meta-image.  It can contain multiple versions of
+  an image at different sizes, which may or may not be completely different
+  pictures */
+struct _RrImage {
+    gint ref;
+    struct _RrImageCache *cache;
+
+    struct _RrImagePic **original;
+    gint n_original;
+    struct _RrImagePic **resized;
+    gint n_resized;
+};
+
 /* these are the same on all endian machines because it seems to be dependant
    on the endianness of the gfx card, not the cpu. */
 #define RrDefaultAlphaOffset 24
@@ -253,6 +288,8 @@ RrAppearance *RrAppearanceCopy (RrAppearance *a);
 void          RrAppearanceFree (RrAppearance *a);
 void          RrAppearanceRemoveTextures(RrAppearance *a);
 void          RrAppearanceAddTextures(RrAppearance *a, gint numtex);
+/*! Always call this when changing the type of a texture in an appearance */
+void          RrAppearanceClearTextures(RrAppearance *a);
 
 RrFont *RrFontOpen          (const RrInstance *inst, const gchar *name,
                              gint size, RrFontWeight weight, RrFontSlant slant);
@@ -280,6 +317,21 @@ gboolean RrPixmapToRGBA(const RrInstance *inst,
                         Pixmap pmap, Pixmap mask,
                         gint *w, gint *h, RrPixel32 **data);
 
+RrImageCache* RrImageCacheNew();
+void          RrImageCacheRef(RrImageCache *self);
+void          RrImageCacheUnref(RrImageCache *self);
+
+/*! Finds an image in the cache, if it is already in there */
+RrImage*      RrImageCacheFind(RrImageCache *self,
+                               RrPixel32 *data, gint w, gint h);
+
+RrImage* RrImageNew(RrImageCache *cache);
+void     RrImageRef(RrImage *im);
+void     RrImageUnref(RrImage *im);
+
+void     RrImageAddPicture(RrImage *im, RrPixel32 *data, gint w, gint h);
+void     RrImageRemovePicture(RrImage *im, gint w, gint h);
+
 G_END_DECLS
 
 #endif /*__render_h*/
index a6931be..e1cff0c 100644 (file)
@@ -552,6 +552,9 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name,
     theme->def_win_icon = read_c_image(OB_DEFAULT_ICON_WIDTH,
                                        OB_DEFAULT_ICON_HEIGHT,
                                        OB_DEFAULT_ICON_pixel_data);
+    theme->def_win_icon_w = OB_DEFAULT_ICON_WIDTH;
+    theme->def_win_icon_h = OB_DEFAULT_ICON_HEIGHT;
+
 
     /* read the decoration textures */
     if (!read_appearance(db, inst,
index 9f51fab..5b4e785 100644 (file)
@@ -106,7 +106,9 @@ struct _RrTheme {
     gchar    menu_text_disabled_selected_shadow_alpha;
 
     /* style settings - pics */
-    RrPixel32 *def_win_icon; /* 48x48 RGBA */
+    RrPixel32 *def_win_icon; /* RGBA */
+    gint       def_win_icon_w;
+    gint       def_win_icon_h;
 
     /* style settings - masks */
     RrPixmapMask *max_mask;
index 6374b39..9fc5fe8 100644 (file)
@@ -3,4 +3,4 @@ files=$(wildcard *.c)
 all: $(files:.c=)
 
 %: %.c
-       $(CC) $(CFLAGS) -o $@ $^ -lX11 -lXext -L/usr/X11R6/lib -I/usr/X11R6/include
+       $(CC) `pkg-config --cflags --libs glib-2.0` $(CFLAGS) -o $@ $^ -lX11 -lXext -L/usr/X11R6/lib -I/usr/X11R6/include
index 315a10f..af13e0b 100644 (file)
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <assert.h>
+#include <glib.h>
 
 Window findClient(Display *d, Window win)
 {
@@ -156,6 +157,70 @@ int main(int argc, char **argv)
         ++image;
     } while (ret_bytesleft > 0 && image < MAX_IMAGES);
 
+#define hashsize(n) ((guint32)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+    /* hash the images */
+    for (j = 0; j < image; ++j) {
+        unsigned int w, h, length;
+        guint32 a,b,c;
+        guint32 initval = 0xf00d;
+        const guint32 *k = (guint32*)i[j]->data;
+
+        w = i[j]->width;
+        h = i[j]->height;
+        length = w * h;
+
+        /* Set up the internal state */
+        a = b = c = 0xdeadbeef + (((guint32)length)<<2) + initval;
+
+        /*---------------------------------------- handle most of the key */
+        while (length > 3)
+        {
+            a += k[0];
+            b += k[1];
+            c += k[2];
+            mix(a,b,c);
+            length -= 3;
+            k += 3;
+        }
+
+        /*--------------------------------- handle the last 3 uint32_t's */
+        switch(length)           /* all the case statements fall through */
+        {
+        case 3 : c+=k[2];
+        case 2 : b+=k[1];
+        case 1 : a+=k[0];
+            final(a,b,c);
+        case 0:     /* case 0: nothing left to add */
+            break;
+        }
+        /*------------------------------------ report the result */
+        printf("image[%d] %ux%u %lu\n", j, w, h, c);
+    }
+
     win = XCreateSimpleWindow(d, RootWindow(d, s), 0, 0, winw, winh,
                               0, 0, 0);
     assert(win);