diff options
32 files changed, 1424 insertions, 239 deletions
diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index 5f235ae3958..fa97714eee6 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -38,6 +38,7 @@ __all__ = ( "unregister_manual_map", "make_rna_paths", "manual_map", + "previews", "resource_path", "script_path_user", "script_path_pref", diff --git a/release/scripts/modules/bpy/utils/previews.py b/release/scripts/modules/bpy/utils/previews.py new file mode 100644 index 00000000000..4e8adf8e209 --- /dev/null +++ b/release/scripts/modules/bpy/utils/previews.py @@ -0,0 +1,137 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +""" +This module contains utility functions to handle custom previews. + +It behaves as a high-level 'cached' previews manager. + +This allows addons to generate their own previews, and use them as icons in UI widgets +('icon_value' of UILayout functions). +""" + +__all__ = ( + "new", + "remove", + ) + +import _bpy +_utils_previews = _bpy._utils_previews +del _bpy + + +_uuid_open = set() + + +# High-level previews manager. +# not accessed directly +class _BPyImagePreviewCollection(dict): + """ + Dict-like class of previews. + """ + + # Internal notes: + # - keys in the dict are stored by name + # - values are instances of bpy.types.ImagePreview + # - Blender's internal 'PreviewImage' struct uses 'self._uuid' prefix. + + def __init__(self): + super().__init__() + self._uuid = hex(id(self)) + _uuid_open.add(self._uuid) + + def __del__(self): + if self._uuid not in _uuid_open: + return + + raise ResourceWarning( + "<%s id=%s[%d]>: left open, remove with " + "'bpy.utils.previews.remove()'" % + (self.__class__.__name__, self._uuid, len(self))) + self.close() + + def _gen_key(self, name): + return ":".join((self._uuid, name)) + + def new(self, name): + if name in self: + raise KeyException("key %r already exists") + p = self[name] = _utils_previews.new( + self._gen_key(name)) + return p + new.__doc__ = _utils_previews.new.__doc__ + + def load(self, name, path, path_type, force_reload=False): + if name in self: + raise KeyException("key %r already exists") + p = self[name] = _utils_previews.load( + self._gen_key(name), path, path_type, force_reload) + return p + load.__doc__ = _utils_previews.load.__doc__ + + def release(self, name): + p = self.pop(name, None) + if p is not None: + _utils_previews.release(self._gen_key(name)) + release.__doc__ = _utils_previews.release.__doc__ + + def clear(self): + for name in self.keys(): + _utils_previews.release(self._gen_key(name)) + super().clear() + + def close(self): + self.clear() + _uuid_open.remove(self._uuid) + + def __delitem__(self, key): + return self.release(key) + + def __repr__(self): + return "<%s id=%s[%d], %s>" % ( + self.__class__.__name__, + self._uuid, + len(self), + super().__repr__()) + + +def new(): + """ + Return a new preview collection. + """ + + return _BPyImagePreviewCollection() + + +def remove(p): + """ + Remove the specified previews collection. + """ + p.close() + + +# don't complain about resources on exit (only unregister) +import atexit + +def exit_clear_warning(): + del _BPyImagePreviewCollection.__del__ + +atexit.register(exit_clear_warning) +del atexit, exit_clear_warning diff --git a/release/scripts/templates_py/ui_previews_custom_icon.py b/release/scripts/templates_py/ui_previews_custom_icon.py new file mode 100644 index 00000000000..defa2d266e6 --- /dev/null +++ b/release/scripts/templates_py/ui_previews_custom_icon.py @@ -0,0 +1,82 @@ +# This sample script demonstrates how to place a custom icon on a button or +# menu entry. +# +# IMPORTANT NOTE: if you run this sample, there will be no icon in the button +# You need to replace the image path with a real existing one. +# For distributable addons, it is recommended to place the icons inside the +# addon folder and access it relative to the py script file for portability +# +# +# Other use cases for UI-previews: +# - provide a fixed list of previews to select from +# - provide a dynamic list of preview (eg. calculated from reading a directory) +# +# For the above use cases, see the template 'ui_previews_dynamic_enum.py" + + +import os +import bpy + + +class PreviewsExamplePanel(bpy.types.Panel): + """Creates a Panel in the Object properties window""" + bl_label = "Previews Example Panel" + bl_idname = "OBJECT_PT_previews" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + + def draw(self, context): + layout = self.layout + pcoll = preview_collections["main"] + + row = layout.row() + my_icon = pcoll.get("my_icon") + row.operator("render.render", icon_value=my_icon.icon_id) + + # my_icon.icon_id can be used in any UI function that accepts + # icon_value # try also setting text="" + # to get an icon only operator button + + +# We can store multiple preview collections here, +# however in this example we only store "main" +preview_collections = {} + + +def register(): + + # Note that preview collections returned by bpy.utils.previews + # are regular py objects - you can use them to store custom data. + import bpy.utils.previews + pcoll = bpy.utils.previews.new() + + # path to the folder where the icon is + # the path is calculated relative to this py file inside the addon folder + my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") + + # load a preview thumbnail of a file and store in the previews collection + pcoll.load( + # identifier + "my_icon", + # path to image + os.path.join(my_icons_dir, "icon-image.png"), + # file type to generate preview from. others are: MOVIE, FONT, BLEND + 'IMAGE') + + preview_collections["main"] = pcoll + + bpy.utils.register_class(PreviewsExamplePanel) + + +def unregister(): + + for pcoll in preview_collections.values(): + bpy.utils.previews.remove(pcoll) + preview_collections.clear() + + bpy.utils.unregister_class(PreviewsExamplePanel) + + +if __name__ == "__main__": + register() diff --git a/release/scripts/templates_py/ui_previews_dynamic_enum.py b/release/scripts/templates_py/ui_previews_dynamic_enum.py new file mode 100644 index 00000000000..1603df0335f --- /dev/null +++ b/release/scripts/templates_py/ui_previews_dynamic_enum.py @@ -0,0 +1,136 @@ +# This sample script demonstrates a dynamic EnumProperty with custom icons. +# The EnumProperty is populated dynamically with thumbnails of the contents of +# a chosen directory in 'enum_previews_from_directory_items'. +# Then, the same enum is displayed with different interfaces. Note that the +# generated icon previews do not have Blender IDs, which means that they can +# not be used with UILayout templates that require IDs, +# such as template_list and template_ID_preview. +# +# Other use cases: +# - make a fixed list of enum_items instead of calculating them in a function +# - generate isolated thumbnails to use as custom icons in buttons +# and menu items +# +# For custom icons, see the template "ui_previews_custom_icon.py". +# +# For distributable addons, it is recommended to place the icons inside the +# addon directory and access it relative to the py script file for portability: +# +# os.path.join(os.path.dirname(__file__), "images") + + +import os +import bpy + + +def enum_previews_from_directory_items(self, context): + """EnumProperty callback""" + wm = context.window_manager + + enum_items = [] + directory = wm.my_previews_dir + + # Get the preview collection (defined in register func). + pcoll = preview_collections["main"] + + if directory == pcoll.my_previews_dir: + return pcoll.my_previews + + print("Scanning directory: %s" % directory) + + if directory and os.path.exists(directory): + # Scan the directory for png files + image_paths = [] + for fn in os.listdir(directory): + if fn.lower().endswith(".png"): + image_paths.append(fn) + + for i, name in enumerate(image_paths): + # generates a thumbnail preview for a file. + # Also works with previews for 'MOVIE', 'BLEND' and 'FONT' + filepath = os.path.join(directory, name) + thumb = pcoll.load(filepath, filepath, 'IMAGE') + # enum item: (identifier, name, description, icon, number) + enum_items.append((name, name, name, thumb.icon_id, i)) + + pcoll.my_previews = enum_items + pcoll.my_previews_dir = directory + return pcoll.my_previews + + +class PreviewsExamplePanel(bpy.types.Panel): + """Creates a Panel in the Object properties window""" + bl_label = "Previews Example Panel" + bl_idname = "OBJECT_PT_previews" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + + def draw(self, context): + layout = self.layout + wm = context.window_manager + + row = layout.row() + row.prop(wm, "my_previews_dir") + + row = layout.row() + row.template_icon_view(wm, "my_previews") + + row = layout.row() + row.prop(wm, "my_previews") + + +# We can store multiple preview collections here, +# however in this example we only store "main" +preview_collections = {} + + +def register(): + from bpy.types import WindowManager + from bpy.props import ( + StringProperty, + EnumProperty, + ) + + WindowManager.my_previews_dir = StringProperty( + name="Folder Path", + subtype='DIR_PATH', + default="" + ) + + WindowManager.my_previews = EnumProperty( + items=enum_previews_from_directory_items, + ) + + # Note that preview collections returned by bpy.utils.previews + # are regular Python objects - you can use them to store custom data. + # + # This is especially useful here, since: + # - It avoids us regenerating the whole enum over and over. + # - It can store enum_items' strings + # (remember you have to keep those strings somewhere in py, + # else they get freed and Blender references invalid memory!). + import bpy.utils.previews + pcoll = bpy.utils.previews.new() + pcoll.my_previews_dir = "" + pcoll.my_previews = () + + preview_collections["main"] = pcoll + + bpy.utils.register_class(PreviewsExamplePanel) + + +def unregister(): + from bpy.types import WindowManager + + del WindowManager.my_previews + + for pcoll in preview_collections.values(): + bpy.utils.previews.remove(pcoll) + preview_collections.clear() + + bpy.utils.unregister_class(PreviewsExamplePanel) + + +if __name__ == "__main__": + register() diff --git a/source/blender/blenkernel/BKE_icons.h b/source/blender/blenkernel/BKE_icons.h index 9af0d96884a..763a3874d4e 100644 --- a/source/blender/blenkernel/BKE_icons.h +++ b/source/blender/blenkernel/BKE_icons.h @@ -48,10 +48,14 @@ typedef struct Icon Icon; struct PreviewImage; struct ID; +enum eIconSizes; + void BKE_icons_init(int first_dyn_id); /* return icon id for library object or create new icon if not found */ -int BKE_icon_getid(struct ID *id); +int BKE_icon_id_ensure(struct ID *id); + +int BKE_icon_preview_ensure(struct PreviewImage *preview); /* retrieve icon for id */ struct Icon *BKE_icon_get(int icon_id); @@ -60,8 +64,10 @@ struct Icon *BKE_icon_get(int icon_id); /* used for inserting the internal icons */ void BKE_icon_set(int icon_id, struct Icon *icon); -/* remove icon and free date if library object becomes invalid */ -void BKE_icon_delete(struct ID *id); +/* remove icon and free data if library object becomes invalid */ +void BKE_icon_id_delete(struct ID *id); + +void BKE_icon_delete(int icon_id); /* report changes - icon needs to be recalculated */ void BKE_icon_changed(int icon_id); @@ -75,8 +81,17 @@ void BKE_previewimg_freefunc(void *link); /* free the preview image */ void BKE_previewimg_free(struct PreviewImage **prv); +/* clear the preview image or icon, but does not free it */ +void BKE_previewimg_clear(struct PreviewImage *prv); + +/* clear the preview image or icon at a specific size */ +void BKE_previewimg_clear_single(struct PreviewImage *prv, enum eIconSizes size); + +/* get the preview from any pointer */ +struct PreviewImage **BKE_previewimg_id_get_p(struct ID *id); + /* free the preview image belonging to the id */ -void BKE_previewimg_free_id(struct ID *id); +void BKE_previewimg_id_free(struct ID *id); /* create a new preview image */ struct PreviewImage *BKE_previewimg_create(void); @@ -85,6 +100,19 @@ struct PreviewImage *BKE_previewimg_create(void); struct PreviewImage *BKE_previewimg_copy(struct PreviewImage *prv); /* retrieve existing or create new preview image */ -struct PreviewImage *BKE_previewimg_get(struct ID *id); +struct PreviewImage *BKE_previewimg_id_ensure(struct ID *id); + +void BKE_previewimg_ensure(struct PreviewImage *prv, const int size); + +struct PreviewImage *BKE_previewimg_cached_get(const char *name); + +struct PreviewImage *BKE_previewimg_cached_ensure(const char *name); + +struct PreviewImage *BKE_previewimg_cached_thumbnail_read( + const char *name, const char *path, const int source, bool force_update); + +void BKE_previewimg_cached_release(const char *name); + +#define ICON_RENDER_DEFAULT_HEIGHT 32 #endif /* __BKE_ICONS_H__ */ diff --git a/source/blender/blenkernel/intern/icons.c b/source/blender/blenkernel/intern/icons.c index d3225f3fa35..450c1388f37 100644 --- a/source/blender/blenkernel/intern/icons.c +++ b/source/blender/blenkernel/intern/icons.c @@ -45,6 +45,7 @@ #include "BLI_utildefines.h" #include "BLI_ghash.h" +#include "BLI_string.h" #include "BKE_icons.h" #include "BKE_global.h" /* only for G.background test */ @@ -53,6 +54,10 @@ #include "GPU_extensions.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" + /* GLOBALS */ static GHash *gIcons = NULL; @@ -61,6 +66,7 @@ static int gNextIconId = 1; static int gFirstIconId = 1; +static GHash *gCachedPreviews = NULL; static void icon_free(void *val) { @@ -105,30 +111,50 @@ void BKE_icons_init(int first_dyn_id) gFirstIconId = first_dyn_id; if (!gIcons) - gIcons = BLI_ghash_int_new("icons_init gh"); + gIcons = BLI_ghash_int_new(__func__); + + if (!gCachedPreviews) { + gCachedPreviews = BLI_ghash_str_new(__func__); + } } void BKE_icons_free(void) { - if (gIcons) + if (gIcons) { BLI_ghash_free(gIcons, NULL, icon_free); - gIcons = NULL; + gIcons = NULL; + } + + if (gCachedPreviews) { + BLI_ghash_free(gCachedPreviews, MEM_freeN, BKE_previewimg_freefunc); + gCachedPreviews = NULL; + } } -PreviewImage *BKE_previewimg_create(void) +static PreviewImage *previewimg_create_ex(size_t deferred_data_size) { PreviewImage *prv_img = NULL; int i; - prv_img = MEM_callocN(sizeof(PreviewImage), "img_prv"); + prv_img = MEM_mallocN(sizeof(PreviewImage) + deferred_data_size, "img_prv"); + memset(prv_img, 0, sizeof(*prv_img)); /* leave deferred data dirty */ + + if (deferred_data_size) { + prv_img->use_deferred = true; + } for (i = 0; i < NUM_ICON_SIZES; ++i) { - prv_img->changed[i] = 1; + prv_img->flag[i] |= PRV_CHANGED; prv_img->changed_timestamp[i] = 0; } return prv_img; } +PreviewImage *BKE_previewimg_create(void) +{ + return previewimg_create_ex(0); +} + void BKE_previewimg_freefunc(void *link) { PreviewImage *prv = (PreviewImage *)link; @@ -138,7 +164,6 @@ void BKE_previewimg_freefunc(void *link) for (i = 0; i < NUM_ICON_SIZES; ++i) { if (prv->rect[i]) { MEM_freeN(prv->rect[i]); - prv->rect[i] = NULL; } if (prv->gputexture[i]) GPU_texture_free(prv->gputexture[i]); @@ -156,6 +181,25 @@ void BKE_previewimg_free(PreviewImage **prv) } } +void BKE_previewimg_clear_single(struct PreviewImage *prv, enum eIconSizes size) +{ + MEM_SAFE_FREE(prv->rect[size]); + if (prv->gputexture[size]) { + GPU_texture_free(prv->gputexture[size]); + } + prv->h[size] = prv->w[size] = 0; + prv->flag[size] |= PRV_CHANGED; + prv->changed_timestamp[size] = 0; +} + +void BKE_previewimg_clear(struct PreviewImage *prv) +{ + int i; + for (i = 0; i < NUM_ICON_SIZES; ++i) { + BKE_previewimg_clear_single(prv, i); + } +} + PreviewImage *BKE_previewimg_copy(PreviewImage *prv) { PreviewImage *prv_img = NULL; @@ -167,79 +211,186 @@ PreviewImage *BKE_previewimg_copy(PreviewImage *prv) if (prv->rect[i]) { prv_img->rect[i] = MEM_dupallocN(prv->rect[i]); } - else { - prv_img->rect[i] = NULL; - } prv_img->gputexture[i] = NULL; } } return prv_img; } -void BKE_previewimg_free_id(ID *id) +PreviewImage **BKE_previewimg_id_get_p(ID *id) { - if (GS(id->name) == ID_MA) { - Material *mat = (Material *)id; - BKE_previewimg_free(&mat->preview); - } - else if (GS(id->name) == ID_TE) { - Tex *tex = (Tex *)id; - BKE_previewimg_free(&tex->preview); - } - else if (GS(id->name) == ID_WO) { - World *wo = (World *)id; - BKE_previewimg_free(&wo->preview); + switch (GS(id->name)) { +#define ID_PRV_CASE(id_code, id_struct) case id_code: { return &((id_struct *)id)->preview; } + ID_PRV_CASE(ID_MA, Material); + ID_PRV_CASE(ID_TE, Tex); + ID_PRV_CASE(ID_WO, World); + ID_PRV_CASE(ID_LA, Lamp); + ID_PRV_CASE(ID_IM, Image); + ID_PRV_CASE(ID_BR, Brush); +#undef ID_PRV_CASE } - else if (GS(id->name) == ID_LA) { - Lamp *la = (Lamp *)id; - BKE_previewimg_free(&la->preview); - } - else if (GS(id->name) == ID_IM) { - Image *img = (Image *)id; - BKE_previewimg_free(&img->preview); + + return NULL; +} + +void BKE_previewimg_id_free(ID *id) +{ + PreviewImage **prv_p = BKE_previewimg_id_get_p(id); + if (prv_p) { + BKE_previewimg_free(prv_p); } - else if (GS(id->name) == ID_BR) { - Brush *br = (Brush *)id; - BKE_previewimg_free(&br->preview); +} + +PreviewImage *BKE_previewimg_id_ensure(ID *id) +{ + PreviewImage **prv_p = BKE_previewimg_id_get_p(id); + + if (prv_p) { + if (*prv_p == NULL) { + *prv_p = BKE_previewimg_create(); + } + return *prv_p; } + + return NULL; } -PreviewImage *BKE_previewimg_get(ID *id) +PreviewImage *BKE_previewimg_cached_get(const char *name) { - PreviewImage *prv_img = NULL; + return BLI_ghash_lookup(gCachedPreviews, name); +} - if (GS(id->name) == ID_MA) { - Material *mat = (Material *)id; - if (!mat->preview) mat->preview = BKE_previewimg_create(); - prv_img = mat->preview; +/** + * Generate an empty PreviewImage, if not yet existing. + */ +PreviewImage *BKE_previewimg_cached_ensure(const char *name) +{ + PreviewImage *prv = NULL; + void **prv_p; + + if (!BLI_ghash_ensure_p_ex(gCachedPreviews, name, &prv_p, (GHashKeyCopyFP)BLI_strdup)) { + *prv_p = BKE_previewimg_create(); } - else if (GS(id->name) == ID_TE) { - Tex *tex = (Tex *)id; - if (!tex->preview) tex->preview = BKE_previewimg_create(); - prv_img = tex->preview; + prv = *prv_p; + BLI_assert(prv); + + return prv; +} + +/** + * Generate a PreviewImage from given file path, using thumbnails management, if not yet existing. + */ +PreviewImage *BKE_previewimg_cached_thumbnail_read( + const char *name, const char *path, const int source, bool force_update) +{ + PreviewImage *prv = NULL; + void **prv_p; + + prv_p = BLI_ghash_lookup_p(gCachedPreviews, name); + + if (prv_p) { + prv = *prv_p; + BLI_assert(prv); } - else if (GS(id->name) == ID_WO) { - World *wo = (World *)id; - if (!wo->preview) wo->preview = BKE_previewimg_create(); - prv_img = wo->preview; + + if (prv && force_update) { + const char *prv_deferred_data = PRV_DEFERRED_DATA(prv); + if (((int)prv_deferred_data[0] == source) && STREQ(&prv_deferred_data[1], path)) { + /* If same path, no need to re-allocate preview, just clear it up. */ + BKE_previewimg_clear(prv); + } + else { + BKE_previewimg_free(&prv); + } } - else if (GS(id->name) == ID_LA) { - Lamp *la = (Lamp *)id; - if (!la->preview) la->preview = BKE_previewimg_create(); - prv_img = la->preview; + + if (!prv) { + /* We pack needed data for lazy loading (source type, in a single char, and path). */ + const size_t deferred_data_size = strlen(path) + 2; + char *deferred_data; + + prv = previewimg_create_ex(deferred_data_size); + deferred_data = PRV_DEFERRED_DATA(prv); + deferred_data[0] = source; + memcpy(&deferred_data[1], path, deferred_data_size - 1); + + force_update = true; } - else if (GS(id->name) == ID_IM) { - Image *img = (Image *)id; - if (!img->preview) img->preview = BKE_previewimg_create(); - prv_img = img->preview; + + if (force_update) { + if (prv_p) { + *prv_p = prv; + } + else { + BLI_ghash_insert(gCachedPreviews, BLI_strdup(name), prv); + } } - else if (GS(id->name) == ID_BR) { - Brush *br = (Brush *)id; - if (!br->preview) br->preview = BKE_previewimg_create(); - prv_img = br->preview; + + return prv; +} + +void BKE_previewimg_cached_release(const char *name) +{ + PreviewImage *prv = BLI_ghash_popkey(gCachedPreviews, name, MEM_freeN); + + if (prv) { + if (prv->icon_id) { + BKE_icon_delete(prv->icon_id); + } + BKE_previewimg_freefunc(prv); } +} - return prv_img; +/** Handle deferred (lazy) loading/generation of preview image, if needed. + * For now, only used with file thumbnails. */ +void BKE_previewimg_ensure(PreviewImage *prv, const int size) +{ + if (prv->use_deferred) { + const bool do_icon = ((size == ICON_SIZE_ICON) && !prv->rect[ICON_SIZE_ICON]); + const bool do_preview = ((size == ICON_SIZE_PREVIEW) && !prv->rect[ICON_SIZE_PREVIEW]); + + if (do_icon || do_preview) { + ImBuf *thumb; + char *prv_deferred_data = PRV_DEFERRED_DATA(prv); + int source = prv_deferred_data[0]; + char *path = &prv_deferred_data[1]; + int icon_w, icon_h; + + thumb = IMB_thumb_manage(path, THB_LARGE, source); + + if (thumb) { + /* PreviewImage assumes premultiplied alhpa... */ + IMB_premultiply_alpha(thumb); + + if (do_preview) { + prv->w[ICON_SIZE_PREVIEW] = thumb->x; + prv->h[ICON_SIZE_PREVIEW] = thumb->y; + prv->rect[ICON_SIZE_PREVIEW] = MEM_dupallocN(thumb->rect); + prv->flag[ICON_SIZE_PREVIEW] &= ~(PRV_CHANGED | PRV_USER_EDITED); + } + if (do_icon) { + if (thumb->x > thumb->y) { + icon_w = ICON_RENDER_DEFAULT_HEIGHT; + icon_h = (thumb->y * icon_w) / thumb->x + 1; + } + else if (thumb->x < thumb->y) { + icon_h = ICON_RENDER_DEFAULT_HEIGHT; + icon_w = (thumb->x * icon_h) / thumb->y + 1; + } + else { + icon_w = icon_h = ICON_RENDER_DEFAULT_HEIGHT; + } + + IMB_scaleImBuf(thumb, icon_w, icon_h); + prv->w[ICON_SIZE_ICON] = icon_w; + prv->h[ICON_SIZE_ICON] = icon_h; + prv->rect[ICON_SIZE_ICON] = MEM_dupallocN(thumb->rect); + prv->flag[ICON_SIZE_ICON] &= ~(PRV_CHANGED | PRV_USER_EDITED); + } + IMB_freeImBuf(thumb); + } + } + } } void BKE_icon_changed(int id) @@ -251,20 +402,20 @@ void BKE_icon_changed(int id) icon = BLI_ghash_lookup(gIcons, SET_INT_IN_POINTER(id)); if (icon) { - PreviewImage *prv = BKE_previewimg_get((ID *)icon->obj); + PreviewImage *prv = BKE_previewimg_id_ensure((ID *)icon->obj); /* all previews changed */ if (prv) { int i; for (i = 0; i < NUM_ICON_SIZES; ++i) { - prv->changed[i] = 1; + prv->flag[i] |= PRV_CHANGED; prv->changed_timestamp[i]++; } } } } -int BKE_icon_getid(struct ID *id) +int BKE_icon_id_ensure(struct ID *id) { Icon *new_icon = NULL; @@ -277,11 +428,11 @@ int BKE_icon_getid(struct ID *id) id->icon_id = get_next_free_id(); if (!id->icon_id) { - printf("BKE_icon_getid: Internal error - not enough IDs\n"); + printf("%s: Internal error - not enough IDs\n", __func__); return 0; } - new_icon = MEM_callocN(sizeof(Icon), "texicon"); + new_icon = MEM_mallocN(sizeof(Icon), __func__); new_icon->obj = id; new_icon->type = GS(id->name); @@ -295,6 +446,40 @@ int BKE_icon_getid(struct ID *id) return id->icon_id; } +/** + * Return icon id of given preview, or create new icon if not found. + */ +int BKE_icon_preview_ensure(PreviewImage *preview) +{ + Icon *new_icon = NULL; + + if (!preview || G.background) + return 0; + + if (preview->icon_id) + return preview->icon_id; + + preview->icon_id = get_next_free_id(); + + if (!preview->icon_id) { + printf("%s: Internal error - not enough IDs\n", __func__); + return 0; + } + + new_icon = MEM_mallocN(sizeof(Icon), __func__); + + new_icon->obj = preview; + new_icon->type = 0; /* Special, tags as non-ID icon/preview. */ + + /* next two lines make sure image gets created */ + new_icon->drawinfo = NULL; + new_icon->drawinfo_free = NULL; + + BLI_ghash_insert(gIcons, SET_INT_IN_POINTER(preview->icon_id), new_icon); + + return preview->icon_id; +} + Icon *BKE_icon_get(int icon_id) { Icon *icon = NULL; @@ -302,7 +487,7 @@ Icon *BKE_icon_get(int icon_id) icon = BLI_ghash_lookup(gIcons, SET_INT_IN_POINTER(icon_id)); if (!icon) { - printf("BKE_icon_get: Internal error, no icon for icon ID: %d\n", icon_id); + printf("%s: Internal error, no icon for icon ID: %d\n", __func__, icon_id); return NULL; } @@ -314,18 +499,39 @@ void BKE_icon_set(int icon_id, struct Icon *icon) void **val_p; if (BLI_ghash_ensure_p(gIcons, SET_INT_IN_POINTER(icon_id), &val_p)) { - printf("BKE_icon_set: Internal error, icon already set: %d\n", icon_id); + printf("%s: Internal error, icon already set: %d\n", __func__, icon_id); return; } *val_p = icon; } -void BKE_icon_delete(struct ID *id) +void BKE_icon_id_delete(struct ID *id) { - if (!id->icon_id) return; /* no icon defined for library object */ BLI_ghash_remove(gIcons, SET_INT_IN_POINTER(id->icon_id), NULL, icon_free); id->icon_id = 0; } + +/** + * Remove icon and free data. + */ +void BKE_icon_delete(int icon_id) +{ + Icon *icon; + + if (!icon_id) return; /* no icon defined for library object */ + + icon = BLI_ghash_popkey(gIcons, SET_INT_IN_POINTER(icon_id), NULL); + + if (icon) { + if (icon->type) { + ((ID *)(icon->obj))->icon_id = 0; + } + else { + ((PreviewImage *)(icon->obj))->icon_id = 0; + } + icon_free(icon); + } +} diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index f3f4775dbf7..29576e4f1f6 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -338,7 +338,7 @@ void BKE_image_free(Image *ima) image_free_packedfiles(ima); - BKE_icon_delete(&ima->id); + BKE_icon_id_delete(&ima->id); ima->id.icon_id = 0; BKE_previewimg_free(&ima->preview); @@ -3001,7 +3001,7 @@ static void image_initialize_after_load(Image *ima, ImBuf *ibuf) { /* preview is NULL when it has never been used as an icon before */ if (G.background == 0 && ima->preview == NULL) - BKE_icon_changed(BKE_icon_getid(&ima->id)); + BKE_icon_changed(BKE_icon_id_ensure(&ima->id)); /* fields */ if (ima->flag & IMA_FIELDS) { diff --git a/source/blender/blenkernel/intern/lamp.c b/source/blender/blenkernel/intern/lamp.c index 96b0b95adf3..44e35c645de 100644 --- a/source/blender/blenkernel/intern/lamp.c +++ b/source/blender/blenkernel/intern/lamp.c @@ -232,7 +232,7 @@ void BKE_lamp_free(Lamp *la) } BKE_previewimg_free(&la->preview); - BKE_icon_delete(&la->id); + BKE_icon_id_delete(&la->id); la->id.icon_id = 0; } diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index eaaf1319167..283c7a6fc88 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -106,7 +106,7 @@ void BKE_material_free_ex(Material *ma, bool do_id_user) if (ma->preview) BKE_previewimg_free(&ma->preview); - BKE_icon_delete((struct ID *)ma); + BKE_icon_id_delete((struct ID *)ma); ma->id.icon_id = 0; /* is no lib link block, but material extension */ diff --git a/source/blender/blenkernel/intern/texture.c b/source/blender/blenkernel/intern/texture.c index 8e4fcc5d1cc..246e3f2a316 100644 --- a/source/blender/blenkernel/intern/texture.c +++ b/source/blender/blenkernel/intern/texture.c @@ -567,7 +567,7 @@ void BKE_texture_free(Tex *tex) BKE_animdata_free((struct ID *)tex); BKE_previewimg_free(&tex->preview); - BKE_icon_delete((struct ID *)tex); + BKE_icon_id_delete((struct ID *)tex); tex->id.icon_id = 0; if (tex->nodetree) { diff --git a/source/blender/blenkernel/intern/world.c b/source/blender/blenkernel/intern/world.c index f26cbf7880a..e4736b1f54c 100644 --- a/source/blender/blenkernel/intern/world.c +++ b/source/blender/blenkernel/intern/world.c @@ -74,7 +74,7 @@ void BKE_world_free_ex(World *wrld, bool do_id_user) if (wrld->gpumaterial.first) GPU_material_free(&wrld->gpumaterial); - BKE_icon_delete((struct ID *)wrld); + BKE_icon_id_delete((struct ID *)wrld); wrld->id.icon_id = 0; } diff --git a/source/blender/editors/include/ED_render.h b/source/blender/editors/include/ED_render.h index 2bc0566f4a6..414126cac13 100644 --- a/source/blender/editors/include/ED_render.h +++ b/source/blender/editors/include/ED_render.h @@ -60,11 +60,15 @@ struct Scene *ED_render_job_get_current_scene(const struct bContext *C); * - PR_BUTS_RENDER: preview is rendered for buttons window * - PR_ICON_RENDER: preview is rendered for icons. hopefully fast enough for at least 32x32 * - PR_NODE_RENDER: preview is rendered for node editor + * - PR_ICON_DEFERRED: No render, we just ensure deferred icon data gets generated. */ -#define PR_BUTS_RENDER 0 -#define PR_ICON_RENDER 1 -#define PR_NODE_RENDER 2 +enum { + PR_BUTS_RENDER = 0, + PR_ICON_RENDER = 1, + PR_NODE_RENDER = 2, + PR_ICON_DEFERRED = 3, +}; void ED_preview_init_dbase(void); void ED_preview_free_dbase(void); diff --git a/source/blender/editors/include/UI_interface_icons.h b/source/blender/editors/include/UI_interface_icons.h index 9d190fa81c7..92f3b0180c6 100644 --- a/source/blender/editors/include/UI_interface_icons.h +++ b/source/blender/editors/include/UI_interface_icons.h @@ -49,7 +49,7 @@ typedef struct IconFile { #define ICON_DEFAULT_HEIGHT_SCALE ((int)(UI_UNIT_Y * 0.8f)) #define ICON_DEFAULT_WIDTH_SCALE ((int)(UI_UNIT_X * 0.8f)) -#define PREVIEW_DEFAULT_HEIGHT 96 +#define PREVIEW_DEFAULT_HEIGHT 128 /* * Resizable Icons for Blender diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index b63871f5c82..a9cc926b275 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -3237,6 +3237,18 @@ static uiBut *ui_def_but( return but; } +void ui_def_but_icon(uiBut *but, const int icon, const int flag) { + if (icon) { + ui_icon_ensure_deferred(but->block->evil_C, icon, (flag & UI_BUT_ICON_PREVIEW) != 0); + } + but->icon = (BIFIconID)icon; + but->flag |= flag; + + if (but->str && but->str[0]) { + but->drawflag |= UI_BUT_ICON_LEFT; + } +} + static void ui_def_but_rna__disable(uiBut *but) { but->flag |= UI_BUT_DISABLED; @@ -3500,11 +3512,7 @@ static uiBut *ui_def_but_rna( but->rnaindex = 0; if (icon) { - but->icon = (BIFIconID)icon; - but->flag |= UI_HAS_ICON; - if (str[0]) { - but->drawflag |= UI_BUT_ICON_LEFT; - } + ui_def_but_icon(but, icon, UI_HAS_ICON); } if ((type == UI_BTYPE_MENU) && (but->dt == UI_EMBOSS_PULLDOWN)) { @@ -3691,8 +3699,7 @@ int UI_autocomplete_end(AutoComplete *autocpl, char *autoname) static void ui_but_update_and_icon_set(uiBut *but, int icon) { if (icon) { - but->icon = (BIFIconID) icon; - but->flag |= UI_HAS_ICON; + ui_def_but_icon(but, icon, UI_HAS_ICON); } ui_but_update(but); @@ -4066,7 +4073,7 @@ void UI_but_drag_set_value(uiBut *but) void UI_but_drag_set_image(uiBut *but, const char *path, int icon, struct ImBuf *imb, float scale) { but->dragtype = WM_DRAG_PATH; - but->icon = icon; /* no flag UI_HAS_ICON, so icon doesnt draw in button */ + ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesnt draw in button */ but->dragpoin = (void *)path; but->imb = imb; but->imb_scale = scale; @@ -4220,8 +4227,7 @@ uiBut *uiDefIconTextMenuBut(uiBlock *block, uiMenuCreateFunc func, void *arg, in { uiBut *but = ui_def_but(block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->icon = (BIFIconID) icon; - but->flag |= UI_HAS_ICON; + ui_def_but_icon(but, icon, UI_HAS_ICON); but->drawflag |= UI_BUT_ICON_LEFT; but->flag |= UI_BUT_ICON_SUBMENU; @@ -4236,8 +4242,7 @@ uiBut *uiDefIconMenuBut(uiBlock *block, uiMenuCreateFunc func, void *arg, int ic { uiBut *but = ui_def_but(block, UI_BTYPE_PULLDOWN, 0, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->icon = (BIFIconID) icon; - but->flag |= UI_HAS_ICON; + ui_def_but_icon(but, icon, UI_HAS_ICON); but->drawflag &= ~UI_BUT_ICON_LEFT; but->menu_create_func = func; @@ -4253,7 +4258,7 @@ uiBut *uiDefIconTextBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, /* XXX temp, old menu calls pass on icon arrow, which is now UI_BUT_ICON_SUBMENU flag */ if (icon != ICON_RIGHTARROW_THIN) { - but->icon = (BIFIconID) icon; + ui_def_but_icon(but, icon, 0); but->drawflag |= UI_BUT_ICON_LEFT; } but->flag |= UI_HAS_ICON; @@ -4270,9 +4275,8 @@ uiBut *uiDefIconBlockBut(uiBlock *block, uiBlockCreateFunc func, void *arg, int { uiBut *but = ui_def_but(block, UI_BTYPE_BLOCK, retval, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->icon = (BIFIconID) icon; - but->flag |= UI_HAS_ICON; - + ui_def_but_icon(but, icon, UI_HAS_ICON); + but->drawflag |= UI_BUT_ICON_LEFT; but->block_create_func = func; @@ -4305,9 +4309,8 @@ uiBut *uiDefSearchBut(uiBlock *block, void *arg, int retval, int icon, int maxle { uiBut *but = ui_def_but(block, UI_BTYPE_SEARCH_MENU, retval, "", x, y, width, height, arg, 0.0, maxlen, a1, a2, tip); - but->icon = (BIFIconID) icon; - but->flag |= UI_HAS_ICON; - + ui_def_but_icon(but, icon, UI_HAS_ICON); + but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; ui_but_update(but); diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index 6536c0a869e..7c05d5f4378 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -55,6 +55,7 @@ #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" #include "BIF_gl.h" #include "BIF_glutil.h" @@ -904,10 +905,13 @@ void UI_icons_init(int first_dyn_id) static int preview_render_size(enum eIconSizes size) { switch (size) { - case ICON_SIZE_ICON: return 32; - case ICON_SIZE_PREVIEW: return PREVIEW_DEFAULT_HEIGHT; + case ICON_SIZE_ICON: + return ICON_RENDER_DEFAULT_HEIGHT; + case ICON_SIZE_PREVIEW: + return PREVIEW_RENDER_DEFAULT_HEIGHT; + default: + return 0; } - return 0; } /* Create rect for the icon @@ -923,12 +927,49 @@ static void icon_create_rect(struct PreviewImage *prv_img, enum eIconSizes size) else if (!prv_img->rect[size]) { prv_img->w[size] = render_size; prv_img->h[size] = render_size; - prv_img->changed[size] = 1; + prv_img->flag[size] |= PRV_CHANGED; prv_img->changed_timestamp[size] = 0; prv_img->rect[size] = MEM_callocN(render_size * render_size * sizeof(unsigned int), "prv_rect"); } } +void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool big) +{ + Icon *icon = BKE_icon_get(icon_id); + + if (icon) { + DrawInfo *di = (DrawInfo *)icon->drawinfo; + + if (!di) { + di = icon_create_drawinfo(); + + icon->drawinfo = di; + icon->drawinfo_free = UI_icons_free_drawinfo; + } + + if (di) { + if (di->type == ICON_TYPE_PREVIEW) { + PreviewImage *prv = (icon->type != 0) ? BKE_previewimg_id_ensure((ID *)icon->obj) : icon->obj; + + if (prv) { + const int size = big ? ICON_SIZE_PREVIEW : ICON_SIZE_ICON; + + if (!prv->use_deferred || prv->rect[size] || (prv->flag[size] & PRV_USER_EDITED)) { + return; + } + + icon_create_rect(prv, size); + + /* Always using job (background) version. */ + ED_preview_icon_job(C, prv, NULL, prv->rect[size], prv->w[size], prv->h[size]); + + prv->flag[size] &= ~PRV_CHANGED; + } + } + } + } +} + /* only called when icon has changed */ /* only call with valid pointer from UI_icon_draw */ static void icon_set_image( @@ -940,6 +981,11 @@ static void icon_set_image( return; } + if (prv_img->flag[size] & PRV_USER_EDITED) { + /* user-edited preview, do not auto-update! */ + return; + } + icon_create_rect(prv_img, size); if (use_job) { @@ -961,22 +1007,32 @@ PreviewImage *UI_icon_to_preview(int icon_id) if (icon) { DrawInfo *di = (DrawInfo *)icon->drawinfo; - if (di && di->data.buffer.image) { - ImBuf *bbuf; - - bbuf = IMB_ibImageFromMemory(di->data.buffer.image->datatoc_rect, di->data.buffer.image->datatoc_size, IB_rect, NULL, "<matcap buffer>"); - if (bbuf) { - PreviewImage *prv = BKE_previewimg_create(); - - prv->rect[0] = bbuf->rect; + if (di) { + if (di->type == ICON_TYPE_PREVIEW) { + PreviewImage *prv = (icon->type != 0) ? BKE_previewimg_id_ensure((ID *)icon->obj) : icon->obj; - prv->w[0] = bbuf->x; - prv->h[0] = bbuf->y; - - bbuf->rect = NULL; - IMB_freeImBuf(bbuf); - - return prv; + if (prv) { + return BKE_previewimg_copy(prv); + } + } + else if (di->data.buffer.image) { + ImBuf *bbuf; + + bbuf = IMB_ibImageFromMemory(di->data.buffer.image->datatoc_rect, di->data.buffer.image->datatoc_size, + IB_rect, NULL, __func__); + if (bbuf) { + PreviewImage *prv = BKE_previewimg_create(); + + prv->rect[0] = bbuf->rect; + + prv->w[0] = bbuf->x; + prv->h[0] = bbuf->y; + + bbuf->rect = NULL; + IMB_freeImBuf(bbuf); + + return prv; + } } } } @@ -987,6 +1043,10 @@ static void icon_draw_rect(float x, float y, int w, int h, float UNUSED(aspect), unsigned int *rect, float alpha, const float rgb[3], const bool is_preview) { ImBuf *ima = NULL; + int draw_w = w; + int draw_h = h; + int draw_x = x; + int draw_y = y; /* sanity check */ if (w <= 0 || h <= 0 || w > 2000 || h > 2000) { @@ -1006,21 +1066,34 @@ static void icon_draw_rect(float x, float y, int w, int h, float UNUSED(aspect), } /* rect contains image in 'rendersize', we only scale if needed */ - if (rw != w && rh != h) { + if (rw != w || rh != h) { + /* preserve aspect ratio and center */ + if (rw > rh) { + draw_w = w; + draw_h = (int)(((float)rh / (float)rw) * (float)w); + draw_y += (h - draw_h) / 2; + } + else if (rw < rh) { + draw_w = (int)(((float)rw / (float)rh) * (float)h); + draw_h = h; + draw_x += (w - draw_w) / 2; + } + /* if the image is squared, the draw_ initialization values are good */ + /* first allocate imbuf for scaling and copy preview into it */ ima = IMB_allocImBuf(rw, rh, 32, IB_rect); memcpy(ima->rect, rect, rw * rh * sizeof(unsigned int)); - IMB_scaleImBuf(ima, w, h); /* scale it */ + IMB_scaleImBuf(ima, draw_w, draw_h); /* scale it */ rect = ima->rect; } /* draw */ if (is_preview) { - glaDrawPixelsSafe(x, y, w, h, w, GL_RGBA, GL_UNSIGNED_BYTE, rect); + glaDrawPixelsSafe(draw_x, draw_y, draw_w, draw_h, draw_w, GL_RGBA, GL_UNSIGNED_BYTE, rect); } else { - glRasterPos2f(x, y); - glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, rect); + glRasterPos2f(draw_x, draw_y); + glDrawPixels(draw_w, draw_h, GL_RGBA, GL_UNSIGNED_BYTE, rect); } if (ima) @@ -1081,10 +1154,13 @@ static void icon_draw_texture( static int get_draw_size(enum eIconSizes size) { switch (size) { - case ICON_SIZE_ICON: return ICON_DEFAULT_HEIGHT; - case ICON_SIZE_PREVIEW: return PREVIEW_DEFAULT_HEIGHT; + case ICON_SIZE_ICON: + return ICON_DEFAULT_HEIGHT; + case ICON_SIZE_PREVIEW: + return PREVIEW_DEFAULT_HEIGHT; + default: + return 0; } - return 0; } @@ -1147,9 +1223,12 @@ static void icon_draw_size( glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else if (di->type == ICON_TYPE_PREVIEW) { - PreviewImage *pi = BKE_previewimg_get((ID *)icon->obj); + PreviewImage *pi = (icon->type != 0) ? BKE_previewimg_id_ensure((ID *)icon->obj) : icon->obj; if (pi) { + /* Do deferred loading/generation if needed. */ +// BKE_previewimg_ensure(pi, size); + /* no create icon on this level in code */ if (!pi->rect[size]) return; /* something has gone wrong! */ @@ -1164,17 +1243,17 @@ static void icon_draw_size( static void ui_id_preview_image_render_size( const bContext *C, Scene *scene, ID *id, PreviewImage *pi, int size, const bool use_job) { - if ((pi->changed[size] || !pi->rect[size])) { /* changed only ever set by dynamic icons */ + if (((pi->flag[size] & PRV_CHANGED) || !pi->rect[size])) { /* changed only ever set by dynamic icons */ /* create the rect if necessary */ icon_set_image(C, scene, id, pi, size, use_job); - pi->changed[size] = 0; + pi->flag[size] &= ~PRV_CHANGED; } } void UI_id_icon_render(const bContext *C, Scene *scene, ID *id, const bool big, const bool use_job) { - PreviewImage *pi = BKE_previewimg_get(id); + PreviewImage *pi = BKE_previewimg_id_ensure(id); if (pi) { if (big) @@ -1186,7 +1265,7 @@ void UI_id_icon_render(const bContext *C, Scene *scene, ID *id, const bool big, static void ui_id_brush_render(const bContext *C, ID *id) { - PreviewImage *pi = BKE_previewimg_get(id); + PreviewImage *pi = BKE_previewimg_id_ensure(id); enum eIconSizes i; if (!pi) @@ -1195,9 +1274,9 @@ static void ui_id_brush_render(const bContext *C, ID *id) for (i = 0; i < NUM_ICON_SIZES; i++) { /* check if rect needs to be created; changed * only set by dynamic icons */ - if ((pi->changed[i] || !pi->rect[i])) { + if (((pi->flag[i] & PRV_CHANGED) || !pi->rect[i])) { icon_set_image(C, NULL, id, pi, i, true); - pi->changed[i] = 0; + pi->flag[i] &= ~PRV_CHANGED; } } } @@ -1208,7 +1287,7 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id) Brush *br = (Brush *)id; if (br->flag & BRUSH_CUSTOM_ICON) { - BKE_icon_getid(id); + BKE_icon_id_ensure(id); ui_id_brush_render(C, id); } else { @@ -1270,7 +1349,7 @@ int ui_id_icon_get(const bContext *C, ID *id, const bool big) case ID_IM: /* fall through */ case ID_WO: /* fall through */ case ID_LA: /* fall through */ - iconid = BKE_icon_getid(id); + iconid = BKE_icon_id_ensure(id); /* checks if not exists, or changed */ UI_id_icon_render(C, NULL, id, big, true); break; diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index a3192d3b8d0..9461547a164 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -465,6 +465,8 @@ extern bool ui_but_string_set(struct bContext *C, uiBut *but, const char *str) A extern bool ui_but_string_set_eval_num(struct bContext *C, uiBut *but, const char *str, double *value) ATTR_NONNULL(); extern int ui_but_string_get_max_length(uiBut *but); extern uiBut *ui_but_drag_multi_edit_get(uiBut *but); + +void ui_def_but_icon(uiBut *but, const int icon, const int flag); extern uiButExtraIconType ui_but_icon_extra_get(uiBut *but); extern void ui_but_default_set(struct bContext *C, const bool all, const bool use_afterfunc); @@ -673,6 +675,7 @@ void ui_draw_preview_item(struct uiFontStyle *fstyle, rcti *rect, const char *na void uiStyleInit(void); /* interface_icons.c */ +void ui_icon_ensure_deferred(const struct bContext *C, const int icon_id, const bool big); int ui_id_icon_get(const struct bContext *C, struct ID *id, const bool big); /* resources.c */ diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index fe1a1b9096d..9a34e4be980 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -430,8 +430,8 @@ static void template_ID( but = uiDefBlockButN(block, id_search_menu, MEM_dupallocN(template), "", 0, 0, UI_UNIT_X * 6, UI_UNIT_Y * 6, TIP_(template_id_browse_tip(type))); - but->icon = id ? ui_id_icon_get(C, id, true) : RNA_struct_ui_icon(type); - UI_but_flag_enable(but, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + ui_def_but_icon(but, id ? ui_id_icon_get(C, id, true) : RNA_struct_ui_icon(type), + UI_HAS_ICON | UI_BUT_ICON_PREVIEW); if ((idfrom && idfrom->lib) || !editable) UI_but_flag_enable(but, UI_BUT_DISABLED); @@ -441,10 +441,9 @@ static void template_ID( else if (flag & UI_ID_BROWSE) { but = uiDefBlockButN(block, id_search_menu, MEM_dupallocN(template), "", 0, 0, UI_UNIT_X * 1.6, UI_UNIT_Y, TIP_(template_id_browse_tip(type))); - but->icon = RNA_struct_ui_icon(type); + ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON); /* default dragging of icon for id browse buttons */ UI_but_drag_set_id(but, id); - UI_but_flag_enable(but, UI_HAS_ICON); UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); if ((idfrom && idfrom->lib) || !editable) @@ -1666,9 +1665,9 @@ static uiBlock *ui_icon_view_menu_cb(bContext *C, ARegion *ar, void *arg_litem) icon = item[a].icon; value = item[a].value; - but = uiDefIconButR_prop(block, UI_BTYPE_ROW, 0, icon, x, y, UI_UNIT_X * 5, UI_UNIT_Y * 5, + but = uiDefIconButR_prop(block, UI_BTYPE_ROW, 0, ICON_NONE, x, y, UI_UNIT_X * 5, UI_UNIT_Y * 5, &cb.ptr, cb.prop, -1, 0, value, -1, -1, NULL); - UI_but_flag_enable(but, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); } UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); @@ -1708,8 +1707,7 @@ void uiTemplateIconView(uiLayout *layout, PointerRNA *ptr, const char *propname) but = uiDefBlockButN(block, ui_icon_view_menu_cb, cb, "", 0, 0, UI_UNIT_X * 6, UI_UNIT_Y * 6, ""); - but->icon = icon; - UI_but_flag_enable(but, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); if (free_items) { MEM_freeN(items); diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index 65d4c2301a9..5112cd84d09 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -79,7 +79,7 @@ #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" - +#include "IMB_thumbs.h" #include "BIF_gl.h" #include "BIF_glutil.h" @@ -128,7 +128,7 @@ ImBuf *get_brush_icon(Brush *brush) } if (brush->icon_imbuf) - BKE_icon_changed(BKE_icon_getid(&brush->id)); + BKE_icon_changed(BKE_icon_id_ensure(&brush->id)); } } } @@ -935,65 +935,87 @@ static void set_alpha(char *cp, int sizex, int sizey, char alpha) static void icon_preview_startjob(void *customdata, short *stop, short *do_update) { ShaderPreview *sp = customdata; - ID *id = sp->id; - short idtype = GS(id->name); - - if (idtype == ID_IM) { - Image *ima = (Image *)id; - ImBuf *ibuf = NULL; - ImageUser iuser = {NULL}; - /* ima->ok is zero when Image cannot load */ - if (ima == NULL || ima->ok == 0) - return; + if (sp->pr_method == PR_ICON_DEFERRED) { + PreviewImage *prv = sp->owner; + ImBuf *thumb; + char *deferred_data = PRV_DEFERRED_DATA(prv); + int source = deferred_data[0]; + char *path = &deferred_data[1]; - /* setup dummy image user */ - iuser.ok = iuser.framenr = 1; - iuser.scene = sp->scene; - - /* elubie: this needs to be changed: here image is always loaded if not - * already there. Very expensive for large images. Need to find a way to - * only get existing ibuf */ - ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); - if (ibuf == NULL || ibuf->rect == NULL) { - BKE_image_release_ibuf(ima, ibuf, NULL); - return; - } - - icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); +// printf("generating deferred %d×%d preview for %s\n", sp->sizex, sp->sizey, path); + + thumb = IMB_thumb_manage(path, THB_LARGE, source); - *do_update = true; + if (thumb) { + /* PreviewImage assumes premultiplied alhpa... */ + IMB_premultiply_alpha(thumb); - BKE_image_release_ibuf(ima, ibuf, NULL); + icon_copy_rect(thumb, sp->sizex, sp->sizey, sp->pr_rect); + IMB_freeImBuf(thumb); + } } - else if (idtype == ID_BR) { - Brush *br = (Brush *)id; + else { + ID *id = sp->id; + short idtype = GS(id->name); + + if (idtype == ID_IM) { + Image *ima = (Image *)id; + ImBuf *ibuf = NULL; + ImageUser iuser = {NULL}; + + /* ima->ok is zero when Image cannot load */ + if (ima == NULL || ima->ok == 0) + return; + + /* setup dummy image user */ + iuser.ok = iuser.framenr = 1; + iuser.scene = sp->scene; + + /* elubie: this needs to be changed: here image is always loaded if not + * already there. Very expensive for large images. Need to find a way to + * only get existing ibuf */ + ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); + if (ibuf == NULL || ibuf->rect == NULL) { + BKE_image_release_ibuf(ima, ibuf, NULL); + return; + } - br->icon_imbuf = get_brush_icon(br); + icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); - memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(unsigned int)); + *do_update = true; - if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) - return; + BKE_image_release_ibuf(ima, ibuf, NULL); + } + else if (idtype == ID_BR) { + Brush *br = (Brush *)id; - icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect); + br->icon_imbuf = get_brush_icon(br); - *do_update = true; - } - else { - /* re-use shader job */ - shader_preview_startjob(customdata, stop, do_update); + memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(unsigned int)); + + if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) + return; - /* world is rendered with alpha=0, so it wasn't displayed - * this could be render option for sky to, for later */ - if (idtype == ID_WO) { - set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255); + icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect); + + *do_update = true; } - else if (idtype == ID_MA) { - Material *ma = (Material *)id; + else { + /* re-use shader job */ + shader_preview_startjob(customdata, stop, do_update); - if (ma->material_type == MA_TYPE_HALO) + /* world is rendered with alpha=0, so it wasn't displayed + * this could be render option for sky to, for later */ + if (idtype == ID_WO) { set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255); + } + else if (idtype == ID_MA) { + Material *ma = (Material *)id; + + if (ma->material_type == MA_TYPE_HALO) + set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255); + } } } } @@ -1005,7 +1027,7 @@ static void common_preview_startjob(void *customdata, short *stop, short *do_upd { ShaderPreview *sp = customdata; - if (sp->pr_method == PR_ICON_RENDER) + if (ELEM(sp->pr_method, PR_ICON_RENDER, PR_ICON_DEFERRED)) icon_preview_startjob(customdata, stop, do_update); else shader_preview_startjob(customdata, stop, do_update); @@ -1041,29 +1063,34 @@ static void icon_preview_startjob_all_sizes(void *customdata, short *stop, short const bool use_new_shading = BKE_scene_use_new_shading_nodes(ip->scene); while (cur_size) { + PreviewImage *prv = ip->owner; ShaderPreview *sp = MEM_callocN(sizeof(ShaderPreview), "Icon ShaderPreview"); + const bool is_render = !prv->use_deferred; /* construct shader preview from image size and previewcustomdata */ sp->scene = ip->scene; sp->owner = ip->owner; sp->sizex = cur_size->sizex; sp->sizey = cur_size->sizey; - sp->pr_method = PR_ICON_RENDER; + sp->pr_method = is_render ? PR_ICON_RENDER : PR_ICON_DEFERRED; sp->pr_rect = cur_size->rect; sp->id = ip->id; - if (use_new_shading) { - /* texture icon rendering is hardcoded to use BI, - * so don't even think of using cycle's bmain for - * texture icons - */ - if (GS(ip->id->name) != ID_TE) - sp->pr_main = G_pr_main_cycles; - else + if (is_render) { + BLI_assert(ip->id); + if (use_new_shading) { + /* texture icon rendering is hardcoded to use BI, + * so don't even think of using cycle's bmain for + * texture icons + */ + if (GS(ip->id->name) != ID_TE) + sp->pr_main = G_pr_main_cycles; + else + sp->pr_main = G_pr_main; + } + else { sp->pr_main = G_pr_main; - } - else { - sp->pr_main = G_pr_main; + } } common_preview_startjob(sp, stop, do_update, progress); @@ -1145,7 +1172,7 @@ void ED_preview_icon_job(const bContext *C, void *owner, ID *id, unsigned int *r /* customdata for preview thread */ ip->scene = CTX_data_scene(C); - ip->owner = id; + ip->owner = owner; ip->id = id; icon_preview_add_size(ip, rect, sizex, sizey); diff --git a/source/blender/editors/render/render_update.c b/source/blender/editors/render/render_update.c index d3d5cb12ac8..dedcbb144aa 100644 --- a/source/blender/editors/render/render_update.c +++ b/source/blender/editors/render/render_update.c @@ -270,7 +270,7 @@ static void material_changed(Main *bmain, Material *ma) int texture_draw = false; /* icons */ - BKE_icon_changed(BKE_icon_getid(&ma->id)); + BKE_icon_changed(BKE_icon_id_ensure(&ma->id)); /* glsl */ if (ma->gpumaterial.first) @@ -285,7 +285,7 @@ static void material_changed(Main *bmain, Material *ma) continue; } - BKE_icon_changed(BKE_icon_getid(&parent->id)); + BKE_icon_changed(BKE_icon_id_ensure(&parent->id)); if (parent->gpumaterial.first) GPU_material_free(&parent->gpumaterial); @@ -325,7 +325,7 @@ static void lamp_changed(Main *bmain, Lamp *la) Material *ma; /* icons */ - BKE_icon_changed(BKE_icon_getid(&la->id)); + BKE_icon_changed(BKE_icon_id_ensure(&la->id)); /* glsl */ for (ob = bmain->object.first; ob; ob = ob->id.next) @@ -361,7 +361,7 @@ static void texture_changed(Main *bmain, Tex *tex) bool texture_draw = false; /* icons */ - BKE_icon_changed(BKE_icon_getid(&tex->id)); + BKE_icon_changed(BKE_icon_id_ensure(&tex->id)); /* paint overlays */ for (scene = bmain->scene.first; scene; scene = scene->id.next) @@ -372,7 +372,7 @@ static void texture_changed(Main *bmain, Tex *tex) if (!material_uses_texture(ma, tex)) continue; - BKE_icon_changed(BKE_icon_getid(&ma->id)); + BKE_icon_changed(BKE_icon_id_ensure(&ma->id)); if (ma->gpumaterial.first) GPU_material_free(&ma->gpumaterial); @@ -403,7 +403,7 @@ static void texture_changed(Main *bmain, Tex *tex) continue; } - BKE_icon_changed(BKE_icon_getid(&wo->id)); + BKE_icon_changed(BKE_icon_id_ensure(&wo->id)); if (wo->gpumaterial.first) GPU_material_free(&wo->gpumaterial); @@ -451,7 +451,7 @@ static void world_changed(Main *bmain, World *wo) Material *ma; /* icons */ - BKE_icon_changed(BKE_icon_getid(&wo->id)); + BKE_icon_changed(BKE_icon_id_ensure(&wo->id)); /* glsl */ for (ma = bmain->mat.first; ma; ma = ma->id.next) @@ -470,7 +470,7 @@ static void image_changed(Main *bmain, Image *ima) Tex *tex; /* icons */ - BKE_icon_changed(BKE_icon_getid(&ima->id)); + BKE_icon_changed(BKE_icon_id_ensure(&ima->id)); /* textures */ for (tex = bmain->tex.first; tex; tex = tex->id.next) diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 75e6f77ba49..ddb1cec9180 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -1346,7 +1346,7 @@ static int image_replace_exec(bContext *C, wmOperator *op) /* XXX unpackImage frees image buffers */ ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - BKE_icon_changed(BKE_icon_getid(&sima->image->id)); + BKE_icon_changed(BKE_icon_id_ensure(&sima->image->id)); BKE_image_signal(sima->image, &sima->iuser, IMA_SIGNAL_RELOAD); WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, sima->image); diff --git a/source/blender/imbuf/IMB_thumbs.h b/source/blender/imbuf/IMB_thumbs.h index f977a633c57..2cb56832a00 100644 --- a/source/blender/imbuf/IMB_thumbs.h +++ b/source/blender/imbuf/IMB_thumbs.h @@ -61,6 +61,8 @@ typedef enum ThumbSource { /* don't generate thumbs for images bigger then this (100mb) */ #define THUMB_SIZE_MAX (100 * 1024 * 1024) +#define PREVIEW_RENDER_DEFAULT_HEIGHT 128 + /* create thumbnail for file and returns new imbuf for thumbnail */ ImBuf *IMB_thumb_create(const char *path, ThumbSize size, ThumbSource source, ImBuf *ibuf); diff --git a/source/blender/imbuf/intern/thumbs.c b/source/blender/imbuf/intern/thumbs.c index a608aa0db34..626f02e8ade 100644 --- a/source/blender/imbuf/intern/thumbs.c +++ b/source/blender/imbuf/intern/thumbs.c @@ -314,10 +314,10 @@ static ImBuf *thumb_create_ex( switch (size) { case THB_NORMAL: - tsize = 128; + tsize = PREVIEW_RENDER_DEFAULT_HEIGHT; break; case THB_LARGE: - tsize = 256; + tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2; break; case THB_FAIL: tsize = 1; diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index b51b53cc801..f68de03614b 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -155,20 +155,36 @@ typedef struct Library { enum eIconSizes { ICON_SIZE_ICON = 0, - ICON_SIZE_PREVIEW = 1 + ICON_SIZE_PREVIEW = 1, + + NUM_ICON_SIZES +}; + +/* for PreviewImage->flag */ +enum ePreviewImage_Flag { + PRV_CHANGED = (1 << 0), + PRV_USER_EDITED = (1 << 1), /* if user-edited, do not auto-update this anymore! */ }; -#define NUM_ICON_SIZES (ICON_SIZE_PREVIEW + 1) typedef struct PreviewImage { /* All values of 2 are really NUM_ICON_SIZES */ unsigned int w[2]; unsigned int h[2]; - short changed[2]; + short flag[2]; short changed_timestamp[2]; unsigned int *rect[2]; + + /* Runtime-only data. */ struct GPUTexture *gputexture[2]; + int icon_id; /* Used by previews outside of ID context. */ + + char pad[3]; + char use_deferred; /* for now a mere bool, if we add more deferred loading methods we can switch to bitflag. */ } PreviewImage; +#define PRV_DEFERRED_DATA(prv) \ + (CHECK_TYPE_INLINE(prv, PreviewImage *), BLI_assert((prv)->use_deferred), (void *)((prv) + 1)) + /** * Defines for working with IDs. * diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index cff9e424f31..965d1d91614 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -280,6 +280,7 @@ extern StructRNA RNA_IKParam; extern StructRNA RNA_Image; extern StructRNA RNA_ImageFormatSettings; extern StructRNA RNA_ImagePaint; +extern StructRNA RNA_ImagePreview; extern StructRNA RNA_ImageSequence; extern StructRNA RNA_ImageTexture; extern StructRNA RNA_ImageUser; diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index db25786c599..b87b455b36f 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -34,6 +34,8 @@ #include "BLI_utildefines.h" +#include "BKE_icons.h" + #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" @@ -406,6 +408,183 @@ static void rna_Library_filepath_set(PointerRNA *ptr, const char *value) BKE_library_filepath_set(lib, value); } +/* ***** ImagePreview ***** */ + +static void rna_ImagePreview_is_custom_set(PointerRNA *ptr, int value, enum eIconSizes size) +{ + ID *id = ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + if ((value && (prv_img->flag[size] & PRV_USER_EDITED)) || (!value && !(prv_img->flag[size] & PRV_USER_EDITED))) { + return; + } + + if (value) + prv_img->flag[size] |= PRV_USER_EDITED; + else + prv_img->flag[size] &= ~PRV_USER_EDITED; + + prv_img->flag[size] |= PRV_CHANGED; + + BKE_previewimg_clear_single(prv_img, size); +} + +static void rna_ImagePreview_size_get(PointerRNA *ptr, int *values, enum eIconSizes size) +{ + ID *id = (ID *)ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + BKE_previewimg_ensure(prv_img, size); + + values[0] = prv_img->w[size]; + values[1] = prv_img->h[size]; +} + +static void rna_ImagePreview_size_set(PointerRNA *ptr, const int *values, enum eIconSizes size) +{ + ID *id = (ID *)ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + BKE_previewimg_ensure(prv_img, size); + + if (values[0] && values[1]) { + prv_img->rect[size] = MEM_callocN(values[0] * values[1] * sizeof(unsigned int), "prv_rect"); + } + + prv_img->w[size] = values[0]; + prv_img->h[size] = values[1]; + + prv_img->flag[size] |= (PRV_CHANGED | PRV_USER_EDITED); +} + +static int rna_ImagePreview_pixels_get_length(PointerRNA *ptr, int length[RNA_MAX_ARRAY_DIMENSION], enum eIconSizes size) +{ + ID *id = ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + BKE_previewimg_ensure(prv_img, size); + + length[0] = prv_img->w[size] * prv_img->h[size]; + + return length[0]; +} + +static void rna_ImagePreview_pixels_get(PointerRNA *ptr, int *values, enum eIconSizes size) +{ + ID *id = ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + BKE_previewimg_ensure(prv_img, size); + + memcpy(values, prv_img->rect[size], prv_img->w[size] * prv_img->h[size] * sizeof(unsigned int)); +} + +static void rna_ImagePreview_pixels_set(PointerRNA *ptr, const int *values, enum eIconSizes size) +{ + ID *id = ptr->id.data; + PreviewImage *prv_img = (PreviewImage *)ptr->data; + + if (id != NULL) { + BLI_assert(prv_img == BKE_previewimg_id_ensure(id)); + } + + memcpy(prv_img->rect[size], values, prv_img->w[size] * prv_img->h[size] * sizeof(unsigned int)); + prv_img->flag[size] |= PRV_USER_EDITED; +} + +static void rna_ImagePreview_is_image_custom_set(PointerRNA *ptr, int value) +{ + rna_ImagePreview_is_custom_set(ptr, value, ICON_SIZE_PREVIEW); +} + +static void rna_ImagePreview_image_size_get(PointerRNA *ptr, int *values) +{ + rna_ImagePreview_size_get(ptr, values, ICON_SIZE_PREVIEW); +} + +static void rna_ImagePreview_image_size_set(PointerRNA *ptr, const int *values) +{ + rna_ImagePreview_size_set(ptr, values, ICON_SIZE_PREVIEW); +} + +static int rna_ImagePreview_image_pixels_get_length(PointerRNA *ptr, int length[RNA_MAX_ARRAY_DIMENSION]) +{ + return rna_ImagePreview_pixels_get_length(ptr, length, ICON_SIZE_PREVIEW); +} + +static void rna_ImagePreview_image_pixels_get(PointerRNA *ptr, int *values) +{ + rna_ImagePreview_pixels_get(ptr, values, ICON_SIZE_PREVIEW); +} + +static void rna_ImagePreview_image_pixels_set(PointerRNA *ptr, const int *values) +{ + rna_ImagePreview_pixels_set(ptr, values, ICON_SIZE_PREVIEW); +} + +static void rna_ImagePreview_is_icon_custom_set(PointerRNA *ptr, int value) +{ + rna_ImagePreview_is_custom_set(ptr, value, ICON_SIZE_ICON); +} + +static void rna_ImagePreview_icon_size_get(PointerRNA *ptr, int *values) +{ + rna_ImagePreview_size_get(ptr, values, ICON_SIZE_ICON); +} + +static void rna_ImagePreview_icon_size_set(PointerRNA *ptr, const int *values) +{ + rna_ImagePreview_size_set(ptr, values, ICON_SIZE_ICON); +} + +static int rna_ImagePreview_icon_pixels_get_length(PointerRNA *ptr, int length[RNA_MAX_ARRAY_DIMENSION]) +{ + return rna_ImagePreview_pixels_get_length(ptr, length, ICON_SIZE_ICON); +} + +static void rna_ImagePreview_icon_pixels_get(PointerRNA *ptr, int *values) +{ + rna_ImagePreview_pixels_get(ptr, values, ICON_SIZE_ICON); +} + +static void rna_ImagePreview_icon_pixels_set(PointerRNA *ptr, const int *values) +{ + rna_ImagePreview_pixels_set(ptr, values, ICON_SIZE_ICON); +} + +static int rna_ImagePreview_icon_id_get(PointerRNA *ptr) +{ + /* Using a callback here allows us to only generate icon matching that preview when icon_id is requested. */ + return BKE_icon_preview_ensure((PreviewImage *)(ptr->data)); +} +static void rna_ImagePreview_icon_reload(PreviewImage *prv) +{ + /* will lazy load on next use, but only in case icon is not user-modified! */ + if (!(prv->flag[ICON_SIZE_ICON] & PRV_USER_EDITED) && !(prv->flag[ICON_SIZE_PREVIEW] & PRV_USER_EDITED)) { + BKE_previewimg_clear(prv); + } +} + #else static void rna_def_ID_properties(BlenderRNA *brna) @@ -524,6 +703,62 @@ static void rna_def_ID_materials(BlenderRNA *brna) RNA_def_boolean(func, "update_data", 0, "", "Update data by re-adjusting the material slots assigned"); } +static void rna_def_image_preview(BlenderRNA *brna) +{ + StructRNA *srna; + FunctionRNA *func; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ImagePreview", NULL); + RNA_def_struct_sdna(srna, "PreviewImage"); + RNA_def_struct_ui_text(srna, "Image Preview", "Preview image and icon"); + + prop = RNA_def_property(srna, "is_image_custom", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag[ICON_SIZE_PREVIEW]", PRV_USER_EDITED); + RNA_def_property_boolean_funcs(prop, NULL, "rna_ImagePreview_is_image_custom_set"); + RNA_def_property_ui_text(prop, "Custom Image", "True if this preview image has been modified by py script," + "and is no more auto-generated by Blender"); + + prop = RNA_def_int_vector(srna, "image_size", 2, NULL, 0, 0, "Image Size", + "Width and height in pixels", 0, 0); + RNA_def_property_subtype(prop, PROP_PIXEL); + RNA_def_property_int_funcs(prop, "rna_ImagePreview_image_size_get", "rna_ImagePreview_image_size_set", NULL); + + prop = RNA_def_property(srna, "image_pixels", PROP_INT, PROP_NONE); + RNA_def_property_flag(prop, PROP_DYNAMIC); + RNA_def_property_multi_array(prop, 1, NULL); + RNA_def_property_ui_text(prop, "Image Pixels", "Image pixels, as bytes (always RGBA 32bits)"); + RNA_def_property_dynamic_array_funcs(prop, "rna_ImagePreview_image_pixels_get_length"); + RNA_def_property_int_funcs(prop, "rna_ImagePreview_image_pixels_get", "rna_ImagePreview_image_pixels_set", NULL); + + + prop = RNA_def_property(srna, "is_icon_custom", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag[ICON_SIZE_ICON]", PRV_USER_EDITED); + RNA_def_property_boolean_funcs(prop, NULL, "rna_ImagePreview_is_icon_custom_set"); + RNA_def_property_ui_text(prop, "Custom Icon", "True if this preview icon has been modified by py script," + "and is no more auto-generated by Blender"); + + prop = RNA_def_int_vector(srna, "icon_size", 2, NULL, 0, 0, "Icon Size", + "Width and height in pixels", 0, 0); + RNA_def_property_subtype(prop, PROP_PIXEL); + RNA_def_property_int_funcs(prop, "rna_ImagePreview_icon_size_get", "rna_ImagePreview_icon_size_set", NULL); + + prop = RNA_def_property(srna, "icon_pixels", PROP_INT, PROP_NONE); + RNA_def_property_flag(prop, PROP_DYNAMIC); + RNA_def_property_multi_array(prop, 1, NULL); + RNA_def_property_ui_text(prop, "Icon Pixels", "Icon pixels, as bytes (always RGBA 32bits)"); + RNA_def_property_dynamic_array_funcs(prop, "rna_ImagePreview_icon_pixels_get_length"); + RNA_def_property_int_funcs(prop, "rna_ImagePreview_icon_pixels_get", "rna_ImagePreview_icon_pixels_set", NULL); + + prop = RNA_def_int(srna, "icon_id", 0, INT_MIN, INT_MAX, "Icon ID", + "Unique integer identifying this preview as an icon (zero means invalid)", INT_MIN, INT_MAX); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_int_funcs(prop, "rna_ImagePreview_icon_id_get", NULL, NULL); + + func = RNA_def_function(srna, "reload", "rna_ImagePreview_icon_reload"); + RNA_def_function_ui_description(func, "Reload the preview from its source path"); +} + static void rna_def_ID(BlenderRNA *brna) { StructRNA *srna; @@ -651,6 +886,7 @@ void RNA_def_ID(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Any Type", "RNA type used for pointers to any possible data"); rna_def_ID(brna); + rna_def_image_preview(brna); rna_def_ID_properties(brna); rna_def_ID_materials(brna); rna_def_library(brna); diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 362cf77a332..e9e4282772d 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -342,8 +342,8 @@ static void rna_Brush_reset_icon(Brush *br, const char *UNUSED(type)) return; if (id->icon_id >= BIFICONID_LAST) { - BKE_icon_delete(id); - BKE_previewimg_free_id(id); + BKE_icon_id_delete(id); + BKE_previewimg_id_free(id); } id->icon_id = 0; @@ -415,8 +415,8 @@ static void rna_Brush_icon_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poi br->id.icon_id = 0; if (br->flag & BRUSH_CUSTOM_ICON) { - BKE_previewimg_get(&br->id); - BKE_icon_changed(BKE_icon_getid(&br->id)); + BKE_previewimg_id_ensure(&br->id); + BKE_icon_changed(BKE_icon_id_ensure(&br->id)); } WM_main_add_notifier(NC_BRUSH | NA_EDITED, br); diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 168f23ceaa1..8b5ed66e217 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -35,6 +35,7 @@ #include "DNA_ID.h" #include "DNA_modifier_types.h" +#include "DNA_space_types.h" #include "BLI_utildefines.h" #include "BLI_path_util.h" @@ -58,6 +59,7 @@ #include "BKE_library.h" #include "BKE_object.h" #include "BKE_material.h" +#include "BKE_icons.h" #include "BKE_image.h" #include "BKE_texture.h" #include "BKE_scene.h" @@ -807,11 +809,12 @@ static int rna_Main_linestyle_is_updated_get(PointerRNA *ptr) { return DAG_id_ty #else -void RNA_api_main(StructRNA *srna) +void RNA_api_main(StructRNA *UNUSED(srna)) { #if 0 FunctionRNA *func; PropertyRNA *parm; + /* maybe we want to add functions in 'bpy.data' still? * for now they are all in collections bpy.data.images.new(...) */ func = RNA_def_function(srna, "add_image", "rna_Main_add_image"); @@ -820,8 +823,6 @@ void RNA_api_main(StructRNA *srna) RNA_def_property_flag(parm, PROP_REQUIRED); parm = RNA_def_pointer(func, "image", "Image", "", "New image"); RNA_def_function_return(func, parm); -#else - (void)srna; #endif } diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 8296027f044..ed04152182e 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -31,6 +31,7 @@ set(INC ../../blenloader ../../editors/include ../../gpu + ../../imbuf ../../makesdna ../../makesrna ../../windowmanager @@ -69,6 +70,7 @@ set(SRC bpy_rna_callback.c bpy_traceback.c bpy_util.c + bpy_utils_previews.c bpy_utils_units.c stubs.c @@ -94,6 +96,7 @@ set(SRC bpy_rna_callback.h bpy_traceback.h bpy_util.h + bpy_utils_previews.h bpy_utils_units.h ../BPY_extern.h ) diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index ec3c017a7ed..9a5e488850e 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -48,6 +48,7 @@ #include "bpy_props.h" #include "bpy_library.h" #include "bpy_operator.h" +#include "bpy_utils_previews.h" #include "bpy_utils_units.h" #include "../generic/py_capi_utils.h" @@ -330,6 +331,7 @@ void BPy_init_modules(void) PyModule_AddObject(mod, "ops", BPY_operator_module()); PyModule_AddObject(mod, "app", BPY_app_struct()); PyModule_AddObject(mod, "_utils_units", BPY_utils_units()); + PyModule_AddObject(mod, "_utils_previews", BPY_utils_previews_module()); /* bpy context */ RNA_pointer_create(NULL, &RNA_Context, (void *)BPy_GetContext(), &ctx_ptr); diff --git a/source/blender/python/intern/bpy_utils_previews.c b/source/blender/python/intern/bpy_utils_previews.c new file mode 100644 index 00000000000..5509ff50f5f --- /dev/null +++ b/source/blender/python/intern/bpy_utils_previews.c @@ -0,0 +1,188 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contributor(s): Bastien Montagne + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_utils_previews.c + * \ingroup pythonintern + * + * This file defines a singleton py object accessed via 'bpy.utils.previews', + * which exposes low-level API for custom previews/icons. + * It is replaced in final API by an higher-level python wrapper, that handles previews by addon, + * and automatically release them on deletion. + */ + +#include <Python.h> +#include <structmember.h> + +#include "BLI_utildefines.h" + +#include "RNA_types.h" +#include "RNA_access.h" + +#include "BPY_extern.h" +#include "bpy_utils_previews.h" +#include "bpy_rna.h" + +#include "MEM_guardedalloc.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" + +#include "BKE_icons.h" + +#include "DNA_ID.h" + +#include "../generic/python_utildefines.h" + +#define STR_SOURCE_TYPES "'IMAGE', 'MOVIE', 'BLEND', 'FONT'" + +PyDoc_STRVAR(bpy_utils_previews_new_doc, +".. method:: new(name)\n" +"\n" +" Generate a new empty preview, or return existing one matching ``name``.\n" +"\n" +" :arg name: The name (unique id) identifying the preview.\n" +" :type name: string\n" +" :return: The Preview matching given name, or a new empty one.\n" +" :rtype: :class:`bpy.types.ImagePreview`\n" +); +static PyObject *bpy_utils_previews_new(PyObject *UNUSED(self), PyObject *args) +{ + char *name; + PreviewImage *prv; + PointerRNA ptr; + + if (!PyArg_ParseTuple(args, "s:new", &name)) { + return NULL; + } + + prv = BKE_previewimg_cached_ensure(name); + RNA_pointer_create(NULL, &RNA_ImagePreview, prv, &ptr); + + return pyrna_struct_CreatePyObject(&ptr); +} + +PyDoc_STRVAR(bpy_utils_previews_load_doc, +".. method:: load(name, path, path_type, force_reload)\n" +"\n" +" Generate a new preview from given file path, or return existing one matching ``name``.\n" +"\n" +" :arg name: The name (unique id) identifying the preview.\n" +" :type name: string\n" +" :arg path: The file path to generate the preview from.\n" +" :type path: string\n" +" :arg path_type: The type of file, needed to generate the preview in [" STR_SOURCE_TYPES "].\n" +" :type path_type: string\n" +" :arg force_reload: If True, force running thumbnail manager even if preview already exists in cache.\n" +" :type force_reload: bool\n" +" :return: The Preview matching given name, or a new empty one.\n" +" :rtype: :class:`bpy.types.ImagePreview`\n" +); +static PyObject *bpy_utils_previews_load(PyObject *UNUSED(self), PyObject *args) +{ + char *name, *path, *path_type_s; + int path_type, force_reload = false; + + PreviewImage *prv; + PointerRNA ptr; + + if (!PyArg_ParseTuple( args, "sss|p:load", &name, &path, &path_type_s, &force_reload)) { + return NULL; + } + + if (STREQ(path_type_s, "IMAGE")) { + path_type = THB_SOURCE_IMAGE; + } + else if (STREQ(path_type_s, "MOVIE")) { + path_type = THB_SOURCE_MOVIE; + } + else if (STREQ(path_type_s, "BLEND")) { + path_type = THB_SOURCE_BLEND; + } + else if (STREQ(path_type_s, "FONT")) { + path_type = THB_SOURCE_FONT; + } + else { + PyErr_Format(PyExc_ValueError, + "load: invalid '%' path type, only [" STR_SOURCE_TYPES "] " + "are supported", path_type_s); + return NULL; + } + + prv = BKE_previewimg_cached_thumbnail_read(name, path, path_type, force_reload); + RNA_pointer_create(NULL, &RNA_ImagePreview, prv, &ptr); + + return pyrna_struct_CreatePyObject(&ptr); +} + +PyDoc_STRVAR(bpy_utils_previews_release_doc, +".. method:: release(name)\n" +"\n" +" Release (free) a previously created preview.\n" +"\n" +"\n" +" :arg name: The name (unique id) identifying the preview.\n" +" :type name: string\n" +); +static PyObject *bpy_utils_previews_release(PyObject *UNUSED(self), PyObject *args) +{ + char *name; + + if (!PyArg_ParseTuple(args, "s:release", &name)) { + return NULL; + } + + BKE_previewimg_cached_release(name); + + Py_RETURN_NONE; +} + +static struct PyMethodDef bpy_utils_previews_methods[] = { + /* Can't use METH_KEYWORDS alone, see http://bugs.python.org/issue11587 */ + {"new", (PyCFunction)bpy_utils_previews_new, METH_VARARGS, bpy_utils_previews_new_doc}, + {"load", (PyCFunction)bpy_utils_previews_load, METH_VARARGS, bpy_utils_previews_load_doc}, + {"release", (PyCFunction)bpy_utils_previews_release, METH_VARARGS, bpy_utils_previews_release_doc}, + {NULL, NULL, 0, NULL} +}; + +PyDoc_STRVAR(bpy_utils_previews_doc, +"This object contains basic static methods to handle cached (non-ID) previews in Blender\n" +"(low-level API, not exposed to final users)." +); +static struct PyModuleDef bpy_utils_previews_module = { + PyModuleDef_HEAD_INIT, + "bpy._utils_previews", + bpy_utils_previews_doc, + 0, + bpy_utils_previews_methods, + NULL, NULL, NULL, NULL +}; + + +PyObject *BPY_utils_previews_module(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&bpy_utils_previews_module); + + return submodule; +} diff --git a/source/blender/python/intern/bpy_utils_previews.h b/source/blender/python/intern/bpy_utils_previews.h new file mode 100644 index 00000000000..3d7ade04b9f --- /dev/null +++ b/source/blender/python/intern/bpy_utils_previews.h @@ -0,0 +1,32 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contributor(s): Bastien Montagne + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_utils_previews.h + * \ingroup pythonintern + */ + +#ifndef __BPY_UTILS_PREVIEWS_H__ +#define __BPY_UTILS_PREVIEWS_H__ + +PyObject *BPY_utils_previews_module(void); + +#endif /* __BPY_UTILS_PREVIEWS_H__ */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index b5c267e8a39..77d3e3762a8 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -172,6 +172,17 @@ void WM_init(bContext *C, int argc, const char **argv) BLF_lang_set(NULL); + if (!G.background) { + GPU_init(); + + GPU_set_mipmap(!(U.gameflags & USER_DISABLE_MIPMAP)); + GPU_set_linear_mipmap(true); + GPU_set_anisotropic(U.anisotropic_filter); + GPU_set_gpu_mipmapping(U.use_gpu_mipmap); + + UI_init(); + } + ED_spacemacros_init(); /* note: there is a bug where python needs initializing before loading the @@ -197,17 +208,6 @@ void WM_init(bContext *C, int argc, const char **argv) wm_init_reports(C); /* reports cant be initialized before the wm */ - if (!G.background) { - GPU_init(); - - GPU_set_mipmap(!(U.gameflags & USER_DISABLE_MIPMAP)); - GPU_set_linear_mipmap(true); - GPU_set_anisotropic(U.anisotropic_filter); - GPU_set_gpu_mipmapping(U.use_gpu_mipmap); - - UI_init(); - } - clear_matcopybuf(); ED_render_clear_mtex_copybuf(); |