1# ##### BEGIN GPL LICENSE BLOCK #####
2#
3#  This program is free software; you can redistribute it and/or
4#  modify it under the terms of the GNU General Public License
5#  as published by the Free Software Foundation; either version 2
6#  of the License, or (at your option) any later version.
7#
8#  This program is distributed in the hope that it will be useful,
9#  but WITHOUT ANY WARRANTY; without even the implied warranty of
10#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#  GNU General Public License for more details.
12#
13#  You should have received a copy of the GNU General Public License
14#  along with this program; if not, write to the Free Software Foundation,
15#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16#
17# ##### END GPL LICENSE BLOCK #####
18
19# <pep8-80 compliant>
20
21__all__ = (
22    "load_image",
23)
24
25
26# limited replacement for BPyImage.comprehensiveImageLoad
27def load_image(
28        imagepath,
29        dirname="",
30        place_holder=False,
31        recursive=False,
32        ncase_cmp=True,
33        convert_callback=None,
34        verbose=False,
35        relpath=None,
36        check_existing=False,
37        force_reload=False,
38):
39    """
40    Return an image from the file path with options to search multiple paths
41    and return a placeholder if its not found.
42
43    :arg filepath: The image filename
44       If a path precedes it, this will be searched as well.
45    :type filepath: string
46    :arg dirname: is the directory where the image may be located - any file at
47       the end will be ignored.
48    :type dirname: string
49    :arg place_holder: if True a new place holder image will be created.
50       this is useful so later you can relink the image to its original data.
51    :type place_holder: bool
52    :arg recursive: If True, directories will be recursively searched.
53       Be careful with this if you have files in your root directory because
54       it may take a long time.
55    :type recursive: bool
56    :arg ncase_cmp: on non windows systems, find the correct case for the file.
57    :type ncase_cmp: bool
58    :arg convert_callback: a function that takes an existing path and returns
59       a new one. Use this when loading image formats blender may not support,
60       the CONVERT_CALLBACK can take the path for a GIF (for example),
61       convert it to a PNG and return the PNG's path.
62       For formats blender can read, simply return the path that is given.
63    :type convert_callback: function
64    :arg relpath: If not None, make the file relative to this path.
65    :type relpath: None or string
66    :arg check_existing: If true,
67       returns already loaded image datablock if possible
68       (based on file path).
69    :type check_existing: bool
70    :arg force_reload: If true,
71       force reloading of image (only useful when `check_existing`
72       is also enabled).
73    :type force_reload: bool
74    :return: an image or None
75    :rtype: :class:`bpy.types.Image`
76    """
77    import os
78    import bpy
79
80    # -------------------------------------------------------------------------
81    # Utility Functions
82
83    def _image_load_placeholder(path):
84        name = path
85        if type(path) is str:
86            name = name.encode("utf-8", "replace")
87        name = name.decode("utf-8", "replace")
88        name = os.path.basename(name)
89
90        image = bpy.data.images.new(name, 128, 128)
91        # allow the path to be resolved later
92        image.filepath = path
93        image.source = 'FILE'
94        return image
95
96    def _image_load(path):
97        import bpy
98
99        if convert_callback:
100            path = convert_callback(path)
101
102        # Ensure we're not relying on the 'CWD' to resolve the path.
103        if not os.path.isabs(path):
104            path = os.path.abspath(path)
105
106        try:
107            image = bpy.data.images.load(path, check_existing=check_existing)
108        except RuntimeError:
109            image = None
110
111        if verbose:
112            if image:
113                print("    image loaded '%s'" % path)
114            else:
115                print("    image load failed '%s'" % path)
116
117        # image path has been checked so the path could not be read for some
118        # reason, so be sure to return a placeholder
119        if place_holder and image is None:
120            image = _image_load_placeholder(path)
121
122        if image:
123            if force_reload:
124                image.reload()
125            if relpath is not None:
126                # make relative
127                from bpy.path import relpath as relpath_fn
128                # can't always find the relative path
129                # (between drive letters on windows)
130                try:
131                    filepath_rel = relpath_fn(path, start=relpath)
132                except ValueError:
133                    filepath_rel = None
134
135                if filepath_rel is not None:
136                    image.filepath_raw = filepath_rel
137
138        return image
139
140    def _recursive_search(paths, filename_check):
141        for path in paths:
142            for dirpath, _dirnames, filenames in os.walk(path):
143
144                # skip '.svn'
145                if dirpath[0] in {".", b'.'}:
146                    continue
147
148                for filename in filenames:
149                    if filename_check(filename):
150                        yield os.path.join(dirpath, filename)
151
152    # -------------------------------------------------------------------------
153
154    imagepath = bpy.path.native_pathsep(imagepath)
155
156    if verbose:
157        print("load_image('%s', '%s', ...)" % (imagepath, dirname))
158
159    if os.path.exists(imagepath):
160        return _image_load(imagepath)
161
162    variants = [imagepath]
163
164    if dirname:
165        variants += [
166            os.path.join(dirname, imagepath),
167            os.path.join(dirname, bpy.path.basename(imagepath)),
168        ]
169
170    for filepath_test in variants:
171        if ncase_cmp:
172            ncase_variants = (
173                filepath_test,
174                bpy.path.resolve_ncase(filepath_test),
175            )
176        else:
177            ncase_variants = (filepath_test, )
178
179        for nfilepath in ncase_variants:
180            if os.path.exists(nfilepath):
181                return _image_load(nfilepath)
182
183    if recursive:
184        search_paths = []
185
186        for dirpath_test in (os.path.dirname(imagepath), dirname):
187            if os.path.exists(dirpath_test):
188                search_paths.append(dirpath_test)
189        search_paths[:] = bpy.path.reduce_dirs(search_paths)
190
191        imagepath_base = bpy.path.basename(imagepath)
192        if ncase_cmp:
193            imagepath_base = imagepath_base.lower()
194
195            def image_filter(fn):
196                return (imagepath_base == fn.lower())
197        else:
198            def image_filter(fn):
199                return (imagepath_base == fn)
200
201        nfilepath = next(_recursive_search(search_paths, image_filter), None)
202        if nfilepath is not None:
203            return _image_load(nfilepath)
204
205    # None of the paths exist so return placeholder
206    if place_holder:
207        return _image_load_placeholder(imagepath)
208
209    # TODO comprehensiveImageLoad also searched in bpy.config.textureDir
210    return None
211