]>
Commit | Line | Data |
---|---|---|
d14a1e28 RD |
1 | #---------------------------------------------------------------------- |
2 | # Name: wxPython.tools.img2py | |
3 | # Purpose: Convert an image to Python code. | |
4 | # | |
5 | # Author: Robin Dunn | |
6 | # | |
7 | # RCS-ID: $Id$ | |
8 | # Copyright: (c) 2002 by Total Control Software | |
9 | # Licence: wxWindows license | |
10 | #---------------------------------------------------------------------- | |
d4b73b1b RD |
11 | # 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
12 | # | |
d4b73b1b | 13 | # |
cbfc9df6 RD |
14 | # 2/25/2007 - Gianluca Costa (archimede86@katamail.com) |
15 | # | |
16 | # | |
17 | # o V2.5 compatibility update | |
18 | # | |
1fded56b | 19 | |
d14a1e28 RD |
20 | """ |
21 | img2py.py -- Convert an image to PNG format and embed it in a Python | |
22 | module with appropriate code so it can be loaded into | |
23 | a program at runtime. The benefit is that since it is | |
24 | Python source code it can be delivered as a .pyc or | |
25 | 'compiled' into the program using freeze, py2exe, etc. | |
26 | ||
27 | Usage: | |
28 | ||
29 | img2py.py [options] image_file python_file | |
30 | ||
31 | Options: | |
32 | ||
33 | -m <#rrggbb> If the original image has a mask or transparency defined | |
34 | it will be used by default. You can use this option to | |
35 | override the default or provide a new mask by specifying | |
36 | a colour in the image to mark as transparent. | |
37 | ||
38 | -n <name> Normally generic names (getBitmap, etc.) are used for the | |
39 | image access functions. If you use this option you can | |
40 | specify a name that should be used to customize the access | |
41 | fucntions, (getNameBitmap, etc.) | |
42 | ||
43 | -c Maintain a catalog of names that can be used to reference | |
cbfc9df6 RD |
44 | images. Catalog can be accessed via catalog and |
45 | index attributes of the module. | |
46 | If the -n <name> option is specified then <name> | |
d14a1e28 | 47 | is used for the catalog key and index value, otherwise |
cbfc9df6 RD |
48 | the filename without any path or extension is used |
49 | as the key. | |
d14a1e28 RD |
50 | |
51 | -a This flag specifies that the python_file should be appended | |
52 | to instead of overwritten. This in combination with -n will | |
53 | allow you to put multiple images in one Python source file. | |
54 | ||
55 | -u Don't use compression. Leaves the data uncompressed. | |
56 | ||
57 | -i Also output a function to return the image as a wxIcon. | |
58 | ||
cbfc9df6 RD |
59 | |
60 | You can also import this module from your Python scripts, and use its img2py() | |
61 | function. See its docstring for more info. | |
d14a1e28 RD |
62 | """ |
63 | ||
64 | # | |
65 | # Changes: | |
66 | # - Cliff Wells <LogiplexSoftware@earthlink.net> | |
67 | # 20021206: Added catalog (-c) option. | |
68 | # | |
d4b73b1b RD |
69 | # 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
70 | # | |
d4b73b1b | 71 | # |
cbfc9df6 RD |
72 | # 2/25/2007 - Gianluca Costa (archimede86@katamail.com) |
73 | # -Refactorization of the script-creation code in a specific "img2py()" function | |
74 | # -Added regex parsing instead of module importing | |
75 | # -Added some "try/finally" statements | |
76 | # -Added default values as named constants | |
77 | # -Made some parts of code a bit easier to read | |
78 | # -Updated the module docstring | |
79 | # -Corrected a bug with EmptyIcon | |
80 | # | |
81 | # o V2.5 compatibility update | |
82 | # | |
d4b73b1b RD |
83 | import cPickle |
84 | import cStringIO | |
85 | import getopt | |
86 | import glob | |
87 | import os | |
cbfc9df6 | 88 | import os.path |
d4b73b1b RD |
89 | import sys |
90 | import tempfile | |
91 | import zlib | |
cbfc9df6 | 92 | import re |
d14a1e28 | 93 | |
d4b73b1b | 94 | import wx |
d4b73b1b | 95 | import img2img |
d14a1e28 RD |
96 | |
97 | ||
98 | def crunch_data(data, compressed): | |
99 | # compress it? | |
100 | if compressed: | |
101 | data = zlib.compress(data, 9) | |
102 | ||
103 | # convert to a printable format, so it can be in a Python source file | |
104 | data = repr(data) | |
105 | ||
106 | # This next bit is borrowed from PIL. It is used to wrap the text intelligently. | |
107 | fp = cStringIO.StringIO() | |
cbfc9df6 | 108 | data += " " # buffer for the +1 test |
d14a1e28 RD |
109 | c = i = 0 |
110 | word = "" | |
111 | octdigits = "01234567" | |
112 | hexdigits = "0123456789abcdef" | |
113 | while i < len(data): | |
114 | if data[i] != "\\": | |
115 | word = data[i] | |
cbfc9df6 | 116 | i += 1 |
d14a1e28 RD |
117 | else: |
118 | if data[i+1] in octdigits: | |
cbfc9df6 | 119 | for n in xrange(2, 5): |
d14a1e28 RD |
120 | if data[i+n] not in octdigits: |
121 | break | |
122 | word = data[i:i+n] | |
cbfc9df6 | 123 | i += n |
d14a1e28 | 124 | elif data[i+1] == 'x': |
cbfc9df6 | 125 | for n in xrange(2, 5): |
d14a1e28 RD |
126 | if data[i+n] not in hexdigits: |
127 | break | |
128 | word = data[i:i+n] | |
cbfc9df6 | 129 | i += n |
d14a1e28 RD |
130 | else: |
131 | word = data[i:i+2] | |
cbfc9df6 | 132 | i += 2 |
d14a1e28 RD |
133 | |
134 | l = len(word) | |
135 | if c + l >= 78-1: | |
136 | fp.write("\\\n") | |
137 | c = 0 | |
138 | fp.write(word) | |
cbfc9df6 | 139 | c += l |
d14a1e28 RD |
140 | |
141 | # return the formatted compressed data | |
142 | return fp.getvalue() | |
143 | ||
144 | ||
2240b1aa | 145 | app = None |
cbfc9df6 RD |
146 | DEFAULT_APPEND = False |
147 | DEFAULT_COMPRESSED = True | |
148 | DEFAULT_MASKCLR = None | |
149 | DEFAULT_IMGNAME = "" | |
150 | DEFAULT_ICON = False | |
151 | DEFAULT_CATALOG = False | |
152 | ||
153 | #THIS IS USED TO IDENTIFY, IN THE GENERATED SCRIPT, LINES IN THE FORM "index.append('Image name')" | |
154 | indexPattern = re.compile(r"\s*index.append\('(.+)'\)\s*") | |
155 | ||
156 | def img2py(image_file, python_file, append=DEFAULT_APPEND, compressed=DEFAULT_COMPRESSED, maskClr=DEFAULT_MASKCLR, imgName=DEFAULT_IMGNAME, icon=DEFAULT_ICON, catalog=DEFAULT_CATALOG): | |
157 | """ | |
158 | Converts an image file to a data structure written in a Python file | |
159 | --image_file: string; the path of the source image file | |
160 | --python_file: string; the path of the destination python file | |
161 | --other arguments: they are equivalent to the command-line arguments | |
162 | """ | |
163 | global app | |
164 | if not wx.GetApp(): | |
165 | app = wx.PySimpleApp() | |
166 | ||
167 | # convert the image file to a temporary file | |
168 | tfname = tempfile.mktemp() | |
169 | try: | |
170 | ok, msg = img2img.convert(image_file, maskClr, None, tfname, wx.BITMAP_TYPE_PNG, ".png") | |
171 | if not ok: | |
172 | print msg | |
173 | return | |
174 | ||
175 | data = open(tfname, "rb").read() | |
176 | data = crunch_data(data, compressed) | |
177 | finally: | |
178 | if os.path.exists(tfname): | |
179 | os.remove(tfname) | |
180 | ||
181 | ||
182 | old_index = [] | |
183 | if catalog and append: | |
184 | # check to see if catalog exists already (file may have been created | |
185 | # with an earlier version of img2py or without -c option) | |
186 | pyPath, pyFile = os.path.split(python_file) | |
187 | ||
188 | append_catalog = True | |
189 | ||
190 | sourcePy = open(python_file, "r") | |
191 | try: | |
192 | for line in sourcePy: | |
193 | ||
194 | if line == "catalog = {}\n": | |
195 | append_catalog = False | |
196 | else: | |
197 | lineMatcher = indexPattern.match(line) | |
198 | if lineMatcher: | |
199 | old_index.append(lineMatcher.groups()[0]) | |
200 | finally: | |
201 | sourcePy.close() | |
202 | ||
203 | ||
204 | if append_catalog: | |
205 | out = open(python_file, "a") | |
206 | try: | |
207 | out.write("\n# ***************** Catalog starts here *******************") | |
208 | out.write("\n\ncatalog = {}\n") | |
209 | out.write("index = []\n\n") | |
210 | out.write("class ImageClass: pass\n\n") | |
211 | finally: | |
212 | out.close() | |
213 | ||
214 | ||
215 | ||
216 | if append: | |
217 | out = open(python_file, "a") | |
218 | else: | |
219 | out = open(python_file, "w") | |
220 | ||
221 | try: | |
222 | if catalog: | |
223 | imgPath, imgFile = os.path.split(image_file) | |
224 | ||
225 | if not imgName: | |
226 | imgName = os.path.splitext(imgFile)[0] | |
227 | print "\nWarning: -n not specified. Using filename (%s) for catalog entry." % imgName | |
228 | ||
229 | out.write("#" + "-" * 70 + "\n") | |
230 | if not append: | |
231 | out.write("# This file was generated by %s\n#\n" % sys.argv[0]) | |
232 | out.write("from wx import ImageFromStream, BitmapFromImage, EmptyIcon\n") | |
233 | if compressed: | |
234 | out.write("import cStringIO, zlib\n\n\n") | |
235 | else: | |
236 | out.write("import cStringIO\n\n\n") | |
237 | ||
238 | if catalog: | |
239 | out.write("catalog = {}\n") | |
240 | out.write("index = []\n\n") | |
241 | out.write("class ImageClass: pass\n\n") | |
242 | ||
243 | if compressed: | |
244 | out.write("def get%sData():\n" | |
245 | " return zlib.decompress(\n%s)\n\n" | |
246 | % (imgName, data)) | |
247 | else: | |
248 | out.write("def get%sData():\n" | |
249 | " return \\\n%s\n\n" | |
250 | % (imgName, data)) | |
251 | ||
252 | ||
253 | out.write("def get%sBitmap():\n" | |
254 | " return BitmapFromImage(get%sImage())\n\n" | |
255 | "def get%sImage():\n" | |
256 | " stream = cStringIO.StringIO(get%sData())\n" | |
257 | " return ImageFromStream(stream)\n\n" | |
258 | % tuple([imgName] * 4)) | |
259 | if icon: | |
260 | out.write("def get%sIcon():\n" | |
261 | " icon = EmptyIcon()\n" | |
262 | " icon.CopyFromBitmap(get%sBitmap())\n" | |
263 | " return icon\n\n" | |
264 | % tuple([imgName] * 2)) | |
265 | ||
266 | if catalog: | |
267 | if imgName in old_index: | |
268 | print "Warning: %s already in catalog." % imgName | |
269 | print " Only the last entry will be accessible.\n" | |
270 | old_index.append(imgName) | |
271 | out.write("index.append('%s')\n" % imgName) | |
272 | out.write("catalog['%s'] = ImageClass()\n" % imgName) | |
273 | out.write("catalog['%s'].getData = get%sData\n" % tuple([imgName] * 2)) | |
274 | out.write("catalog['%s'].getImage = get%sImage\n" % tuple([imgName] * 2)) | |
275 | out.write("catalog['%s'].getBitmap = get%sBitmap\n" % tuple([imgName] * 2)) | |
276 | if icon: | |
277 | out.write("catalog['%s'].getIcon = get%sIcon\n" % tuple([imgName] * 2)) | |
278 | out.write("\n\n") | |
279 | ||
280 | ||
281 | if imgName: | |
282 | n_msg = ' using "%s"' % imgName | |
283 | else: | |
284 | n_msg = "" | |
285 | ||
286 | if maskClr: | |
287 | m_msg = " with mask %s" % maskClr | |
288 | else: | |
289 | m_msg = "" | |
290 | ||
291 | print "Embedded %s%s into %s%s" % (image_file, n_msg, python_file, m_msg) | |
292 | finally: | |
293 | out.close() | |
294 | ||
295 | ||
296 | ||
d14a1e28 RD |
297 | def main(args): |
298 | if not args or ("-h" in args): | |
299 | print __doc__ | |
300 | return | |
cbfc9df6 RD |
301 | |
302 | append = DEFAULT_APPEND | |
303 | compressed = DEFAULT_COMPRESSED | |
304 | maskClr = DEFAULT_MASKCLR | |
305 | imgName = DEFAULT_IMGNAME | |
306 | icon = DEFAULT_ICON | |
307 | catalog = DEFAULT_CATALOG | |
d14a1e28 RD |
308 | |
309 | try: | |
310 | opts, fileArgs = getopt.getopt(args, "auicn:m:") | |
311 | except getopt.GetoptError: | |
312 | print __doc__ | |
313 | return | |
314 | ||
315 | for opt, val in opts: | |
316 | if opt == "-a": | |
cbfc9df6 | 317 | append = True |
d14a1e28 | 318 | elif opt == "-u": |
cbfc9df6 | 319 | compressed = False |
d14a1e28 RD |
320 | elif opt == "-n": |
321 | imgName = val | |
322 | elif opt == "-m": | |
323 | maskClr = val | |
324 | elif opt == "-i": | |
cbfc9df6 | 325 | icon = True |
d14a1e28 | 326 | elif opt == "-c": |
cbfc9df6 | 327 | catalog = True |
d14a1e28 RD |
328 | |
329 | if len(fileArgs) != 2: | |
330 | print __doc__ | |
331 | return | |
332 | ||
333 | image_file, python_file = fileArgs | |
cbfc9df6 RD |
334 | img2py(image_file, python_file, append, compressed, maskClr, imgName, icon, catalog) |
335 | ||
d14a1e28 | 336 | |
cbfc9df6 | 337 | |
d14a1e28 RD |
338 | if __name__ == "__main__": |
339 | main(sys.argv[1:]) |