From 4c7cc1cfa64bf5722f059eae0528d510c2ae636f Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Thu, 14 Feb 2008 10:47:49 +0100 Subject: [PATCH] Introducing the icon cache. 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! --- Makefile.am | 2 + openbox/client.c | 266 +++++++++++------------ openbox/client.h | 20 +- openbox/client_list_combined_menu.c | 10 +- openbox/client_list_menu.c | 9 +- openbox/focus_cycle_popup.c | 19 +- openbox/framerender.c | 20 +- openbox/menu.c | 1 + openbox/menu.h | 8 +- openbox/menuframe.c | 22 +- openbox/openbox.c | 25 ++- openbox/openbox.h | 1 + openbox/popup.c | 15 +- openbox/popup.h | 2 +- render/image.c | 314 +++++++++++++++++++++++++--- render/image.h | 9 +- render/imagecache.c | 149 +++++++++++++ render/imagecache.h | 37 ++++ render/render.c | 49 +++-- render/render.h | 64 +++++- render/theme.c | 3 + render/theme.h | 4 +- tests/Makefile | 2 +- tests/icons.c | 65 ++++++ 24 files changed, 830 insertions(+), 286 deletions(-) create mode 100644 render/imagecache.c create mode 100644 render/imagecache.h diff --git a/Makefile.am b/Makefile.am index df891ac0..051be7d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,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 \ diff --git a/openbox/client.c b/openbox/client.c index 62489c1d..9fa311cf 100644 --- a/openbox/client.c +++ b/openbox/client.c @@ -68,9 +68,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); @@ -110,6 +111,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(); @@ -117,6 +131,9 @@ void client_startup(gboolean reconfig) void client_shutdown(gboolean reconfig) { + RrImageUnref(client_default_icon); + client_default_icon = NULL; + if (reconfig) return; } @@ -670,7 +687,6 @@ void client_unmanage_all(void) void client_unmanage(ObClient *self) { - guint j; GSList *it; gulong ignore_start; @@ -802,11 +818,8 @@ void client_unmanage(ObClient *self) ob_debug("Unmanaged window 0x%lx\n", 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); @@ -2090,143 +2103,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 (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(ob_display, self->window))) { if (hints->flags & IconPixmapHint) { - self->nicons = 1; - self->icons = g_new(ObClientIcon, self->nicons); + gboolean xicon; xerror_set_ignore(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); xerror_set_ignore(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); - PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2); - g_free(data); + 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) @@ -3919,53 +3923,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; } diff --git a/openbox/client.h b/openbox/client.h index c34d0ab6..c2461cb9 100644 --- a/openbox/client.h +++ b/openbox/client.h @@ -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; @@ -644,7 +633,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 diff --git a/openbox/client_list_combined_menu.c b/openbox/client_list_combined_menu.c index 76a819fc..a04d07d6 100644 --- a/openbox/client_list_combined_menu.c +++ b/openbox/client_list_combined_menu.c @@ -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; } diff --git a/openbox/client_list_menu.c b/openbox/client_list_menu.c index e6521a0a..4ec6e785 100644 --- a/openbox/client_list_menu.c +++ b/openbox/client_list_menu.c @@ -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; } diff --git a/openbox/focus_cycle_popup.c b/openbox/focus_cycle_popup.c index f0cf25e2..df3558df 100644 --- a/openbox/focus_cycle_popup.c +++ b/openbox/focus_cycle_popup.c @@ -42,6 +42,7 @@ typedef struct _ObFocusCyclePopupTarget ObFocusCyclePopupTarget; struct _ObFocusCyclePopupTarget { ObClient *client; + RrImage *icon; gchar *text; Window win; }; @@ -106,7 +107,8 @@ void focus_cycle_popup_startup(gboolean reconfig) popup.a_text->surface.parent = popup.a_bg; popup.a_icon->surface.parent = popup.a_bg; - popup.a_icon->texture[0].type = RR_TEXTURE_RGBA; + RrAppearanceClearTextures(popup.a_icon); + popup.a_icon->texture[0].type = RR_TEXTURE_IMAGE; RrAppearanceAddTextures(popup.a_bg, 1); popup.a_bg->texture[0].type = RR_TEXTURE_RGBA; @@ -141,8 +143,10 @@ void focus_cycle_popup_shutdown(gboolean reconfig) while(popup.targets) { ObFocusCyclePopupTarget *t = popup.targets->data; + RrImageUnref(t->icon); g_free(t->text); XDestroyWindow(ob_display, t->win); + g_free(t); popup.targets = g_list_delete_link(popup.targets, popup.targets); } @@ -196,6 +200,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->win = create_window(p->bg, 0, 0, NULL); XMapWindow(ob_display, t->win); @@ -399,7 +405,6 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c) /* have to redraw the targetted icon and last targetted icon, they can pick up the hilite changes in the backgroud */ if (!p->mapped || newtarget == target || p->last_target == target) { - const ObClientIcon *icon; const gint row = i / icons_per_row; /* starting from 0 */ const gint col = i % icons_per_row; /* starting from 0 */ gint innerx, innery; @@ -415,12 +420,9 @@ static void popup_render(ObFocusCyclePopup *p, const ObClient *c) innerx, innery, innerw, innerh); /* get the icon from the client */ - icon = client_icon(target->client, innerw, innerh); - 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.alpha = + 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 icon */ p->a_icon->surface.parentx = innerx; @@ -479,6 +481,7 @@ void focus_cycle_popup_hide(void) while(popup.targets) { ObFocusCyclePopupTarget *t = popup.targets->data; + RrImageUnref(t->icon); g_free(t->text); XDestroyWindow(ob_display, t->win); g_free(t); @@ -522,7 +525,7 @@ void focus_cycle_popup_single_show(struct _ObClient *c, } text = popup_get_name(c); - icon_popup_show(single_popup, text, client_icon(c, ICON_SIZE, ICON_SIZE)); + icon_popup_show(single_popup, text, client_icon(c)); g_free(text); screen_hide_desktop_popup(); } diff --git a/openbox/framerender.c b/openbox/framerender.c index e4db2a06..4720d763 100644 --- a/openbox/framerender.c +++ b/openbox/framerender.c @@ -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); diff --git a/openbox/menu.c b/openbox/menu.c index d9426e90..f2458fa8 100644 --- a/openbox/menu.c +++ b/openbox/menu.c @@ -504,6 +504,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); diff --git a/openbox/menu.h b/openbox/menu.h index 8832a124..7e11545d 100644 --- a/openbox/menu.h +++ b/openbox/menu.h @@ -116,11 +116,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; diff --git a/openbox/menuframe.c b/openbox/menuframe.c index 3bbf1be2..ce77fc0f 100644 --- a/openbox/menuframe.c +++ b/openbox/menuframe.c @@ -334,6 +334,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(ob_display, self->window, self->area.width, self->area.height); @@ -438,6 +439,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; @@ -455,7 +457,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; @@ -467,15 +469,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; @@ -499,6 +498,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; @@ -690,7 +690,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; @@ -701,7 +701,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; diff --git a/openbox/openbox.c b/openbox/openbox.c index cb14187c..f2dc7159 100644 --- a/openbox/openbox.c +++ b/openbox/openbox.c @@ -86,17 +86,18 @@ #include -RrInstance *ob_rr_inst; -RrTheme *ob_rr_theme; -ObMainLoop *ob_main_loop; -Display *ob_display; -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; +ObMainLoop *ob_main_loop; +Display *ob_display; +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; @@ -182,6 +183,7 @@ gint main(gint argc, gchar **argv) ob_rr_inst = RrInstanceNew(ob_display, ob_screen); if (ob_rr_inst == NULL) ob_exit_with_error(_("Failed to initialize the obrender library.")); + ob_rr_icons = RrImageCacheNew(); XSynchronize(ob_display, xsync); @@ -398,6 +400,7 @@ gint main(gint argc, gchar **argv) XSync(ob_display, FALSE); RrThemeFree(ob_rr_theme); + RrImageCacheUnref(ob_rr_icons); RrInstanceFree(ob_rr_inst); session_shutdown(being_replaced); diff --git a/openbox/openbox.h b/openbox/openbox.h index b768fee3..4f2310f8 100644 --- a/openbox/openbox.h +++ b/openbox/openbox.h @@ -30,6 +30,7 @@ struct _ObMainLoop; extern RrInstance *ob_rr_inst; +extern RrImageCache *ob_rr_icons; extern RrTheme *ob_rr_theme; extern struct _ObMainLoop *ob_main_loop; diff --git a/openbox/popup.c b/openbox/popup.c index 15d1bf52..770f33d0 100644 --- a/openbox/popup.c +++ b/openbox/popup.c @@ -367,16 +367,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); } diff --git a/openbox/popup.h b/openbox/popup.h index 0072f53e..2b01ce24 100644 --- a/openbox/popup.h +++ b/openbox/popup.h @@ -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)) diff --git a/render/image.c b/render/image.c index fa630b74..d22ef9e3 100644 --- a/render/image.c +++ b/render/image.c @@ -20,6 +20,7 @@ #include "geom.h" #include "image.h" #include "color.h" +#include "imagecache.h" #include @@ -27,13 +28,91 @@ #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,53 +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 (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; @@ -160,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) | @@ -177,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); +} diff --git a/render/image.h b/render/image.h index 1c535960..28f29c2c 100644 --- a/render/image.h +++ b/render/image.h @@ -22,8 +22,11 @@ #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 index 00000000..ec2ff4d0 --- /dev/null +++ b/render/imagecache.c @@ -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 index 00000000..8c96caf0 --- /dev/null +++ b/render/imagecache.h @@ -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 + +/* 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 diff --git a/render/render.c b/render/render.c index b81e80a6..fd442d26 100644 --- a/render/render.c +++ b/render/render.c @@ -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; @@ -405,6 +416,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 - l - r, a->texture[i].data.lineart.x2 - l - r)); @@ -457,6 +471,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 - t - b, a->texture[i].data.lineart.y2 - t - b)); diff --git a/render/render.h b/render/render.h index 10eafc6e..e79727df 100644 --- a/render/render.h +++ b/render/render.h @@ -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*/ diff --git a/render/theme.c b/render/theme.c index b1b15ff2..cd2352c3 100644 --- a/render/theme.c +++ b/render/theme.c @@ -547,6 +547,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, diff --git a/render/theme.h b/render/theme.h index 3f87ce93..1940970a 100644 --- a/render/theme.h +++ b/render/theme.h @@ -107,7 +107,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; diff --git a/tests/Makefile b/tests/Makefile index 6374b392..9fc5fe81 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -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 diff --git a/tests/icons.c b/tests/icons.c index 315a10fb..af13e0b5 100644 --- a/tests/icons.c +++ b/tests/icons.c @@ -23,6 +23,7 @@ #include #include #include +#include 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); -- 2.45.2