1#!/usr/local/bin/python
2# -*- coding: utf-8 -*-
3
4#Copyright (c) Manish Singh
5#javascript animation support by Joao S. O. Bueno Calligaris (2004)
6
7#   Gimp-Python - allows the writing of Gimp plugins in Python.
8#   Copyright (C) 2003, 2005  Manish Singh <yosh@gimp.org>
9#
10#   This program is free software: you can redistribute it and/or modify
11#   it under the terms of the GNU General Public License as published by
12#   the Free Software Foundation; either version 3 of the License, or
13#   (at your option) any later version.
14#
15#   This program is distributed in the hope that it will be useful,
16#   but WITHOUT ANY WARRANTY; without even the implied warranty of
17#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18#   GNU General Public License for more details.
19#
20#   You should have received a copy of the GNU General Public License
21#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
22
23# (c) 2003 Manish Singh.
24#"Guillotine implemented ala python, with html output
25# (based on perlotine by Seth Burgess)",
26# Modified by João S. O. Bueno Calligaris to allow  dhtml animations (2005)
27
28import os
29
30from gimpfu import *
31import os.path
32
33gettext.install("gimp20-python", gimp.locale_directory, unicode=True)
34
35def pyslice(image, drawable, save_path, html_filename,
36            image_basename, image_extension, separate,
37            image_path, cellspacing, animate, skip_caps):
38
39    cellspacing = int (cellspacing)
40
41    if animate:
42        count = 0
43        drw = []
44        #image.layers is a reversed list of the layers on the image
45        #so, count indexes from number of layers to 0.
46        for i in xrange (len (image.layers) -1, -1, -1):
47            if image.layers[i].visible:
48                drw.append(image.layers[i])
49                count += 1
50                if count == 3:
51                    break
52
53
54    vert, horz = get_guides(image)
55
56    if len(vert) == 0 and len(horz) == 0:
57        return
58
59    gimp.progress_init(_("Slice"))
60    progress_increment = 1 / ((len(horz) + 1) * (len(vert) + 1))
61    progress = 0.0
62
63    def check_path(path):
64        path = os.path.abspath(path)
65
66        if not os.path.exists(path):
67            os.mkdir(path)
68
69        return path
70
71    save_path = check_path(save_path)
72
73    if not os.path.isdir(save_path):
74        save_path = os.path.dirname(save_path)
75
76    if separate:
77        image_relative_path = image_path
78        if not image_relative_path.endswith("/"):
79            image_relative_path += "/"
80        image_path = check_path(os.path.join(save_path, image_path))
81    else:
82        image_relative_path = ''
83        image_path = save_path
84
85    tw = TableWriter(os.path.join(save_path, html_filename),
86                     cellspacing=cellspacing, animate=animate)
87
88    top = 0
89
90    for i in range(0, len(horz) + 1):
91        if i == len(horz):
92            bottom = image.height
93        else:
94            bottom = image.get_guide_position(horz[i])
95
96        tw.row_start()
97
98        left = 0
99
100        for j in range(0, len(vert) + 1):
101            if j == len(vert):
102                right = image.width
103            else:
104                right = image.get_guide_position(vert[j])
105            if (skip_caps   and
106                 (
107                   (len(horz) >= 2 and (i == 0 or i == len(horz) )) or
108                   (len(vert) >= 2 and (j == 0 or j == len(vert) ))
109                 )
110               ):
111                skip_stub = True
112            else:
113                skip_stub = False
114
115            if (not animate or skip_stub):
116                src = (image_relative_path +
117                       slice (image, None, image_path,
118                              image_basename, image_extension,
119                              left, right, top, bottom, i, j, ""))
120            else:
121                src = []
122                for layer, postfix in zip (drw, ("", "hover", "clicked")):
123                    src.append (image_relative_path +
124                                slice(image, layer, image_path,
125                                      image_basename, image_extension,
126                                      left, right, top, bottom, i, j, postfix))
127
128            tw.cell(src, right - left, bottom - top, i, j, skip_stub)
129
130            left = right + cellspacing
131
132            progress += progress_increment
133            gimp.progress_update(progress)
134
135        tw.row_end()
136
137        top = bottom + cellspacing
138
139    tw.close()
140
141def slice(image, drawable, image_path, image_basename, image_extension,
142          left, right, top, bottom, i, j, postfix):
143    if postfix:
144        postfix = "_" + postfix
145    src = "%s_%d_%d%s.%s" % (image_basename, i, j, postfix, image_extension)
146    filename = os.path.join(image_path, src)
147
148    if not drawable:
149        temp_image = image.duplicate()
150        temp_drawable = temp_image.active_layer
151    else:
152        if image.base_type == INDEXED:
153            #gimp_layer_new_from_drawable doesn't work for indexed images.
154            #(no colormap on new images)
155            original_active = image.active_layer
156            image.active_layer = drawable
157            temp_image = image.duplicate()
158            temp_drawable = temp_image.active_layer
159            image.active_layer = original_active
160            temp_image.disable_undo()
161            #remove all layers but the intended one
162            while len (temp_image.layers) > 1:
163                if temp_image.layers[0] != temp_drawable:
164                    pdb.gimp_image_remove_layer (temp_image, temp_image.layers[0])
165                else:
166                    pdb.gimp_image_remove_layer (temp_image, temp_image.layers[1])
167        else:
168            temp_image = pdb.gimp_image_new (drawable.width, drawable.height,
169                                         image.base_type)
170            temp_drawable = pdb.gimp_layer_new_from_drawable (drawable, temp_image)
171            temp_image.insert_layer (temp_drawable)
172
173    temp_image.disable_undo()
174    temp_image.crop(right - left, bottom - top, left, top)
175    if image_extension == "gif" and image.base_type == RGB:
176        pdb.gimp_image_convert_indexed (temp_image, CONVERT_DITHER_NONE,
177                                        CONVERT_PALETTE_GENERATE, 255,
178                                        True, False, False)
179    if image_extension == "jpg" and image.base_type == INDEXED:
180        pdb.gimp_image_convert_rgb (temp_image)
181
182    pdb.gimp_file_save(temp_image, temp_drawable, filename, filename)
183
184    gimp.delete(temp_image)
185    return src
186
187class GuideIter:
188    def __init__(self, image):
189        self.image = image
190        self.guide = 0
191
192    def __iter__(self):
193        return iter(self.next_guide, 0)
194
195    def next_guide(self):
196        self.guide = self.image.find_next_guide(self.guide)
197        return self.guide
198
199def get_guides(image):
200    vguides = []
201    hguides = []
202
203    for guide in GuideIter(image):
204        orientation = image.get_guide_orientation(guide)
205
206        guide_position = image.get_guide_position(guide)
207
208        if guide_position > 0:
209            if orientation == ORIENTATION_VERTICAL:
210                if guide_position < image.width:
211                    vguides.append((guide_position, guide))
212            elif orientation == ORIENTATION_HORIZONTAL:
213                if guide_position < image.height:
214                    hguides.append((guide_position, guide))
215
216    def position_sort(x, y):
217        return cmp(x[0], y[0])
218
219    vguides.sort(position_sort)
220    hguides.sort(position_sort)
221
222    vguides = [g[1] for g in vguides]
223    hguides = [g[1] for g in hguides]
224
225    return vguides, hguides
226
227class TableWriter:
228    def __init__(self, filename, cellpadding=0, cellspacing=0, border=0,
229                 animate=False):
230
231        self.filename = filename
232        self.table_attrs = {}
233
234        #Hellraisen IE 6 doesn't support CSS for table control.
235        self.table_attrs['cellpadding'] = cellpadding
236        self.table_attrs['cellspacing'] = cellspacing
237        self.table_attrs['border'] = border
238
239        self.image_prefix = os.path.basename (filename)
240        self.image_prefix = self.image_prefix.split(".")[0]
241        self.image_prefix = self.image_prefix.replace ("-", "_")
242        self.image_prefix = self.image_prefix.replace (" ", "_")
243
244
245        if animate:
246            self.animate = True
247            self.images = []
248        else:
249            self.animate = False
250
251        if os.path.exists (filename):
252            #The plug-in is running to overwrite a previous
253            #version of the file. This will parse the href targets already
254            #in the file to preserve them.
255            self.urls = self.parse_urls ()
256        else:
257            self.urls = []
258
259        self.url_index = 0
260
261        self.html = open(filename, 'wt')
262        self.open()
263
264    def next_url (self):
265        if self.url_index < len (self.urls):
266            self.url_index += 1
267            return self.urls [self.url_index - 1]
268        else:
269            #Default url to use in the anchor tags:
270            return ("#")
271
272    def write(self, s, vals=None):
273        if vals:
274            s = s % vals
275
276        self.html.write(s + '\n')
277
278    def open(self):
279        out = '''<!--HTML SNIPPET GENERATED BY GIMP
280
281WARNING!! This is NOT a fully valid HTML document, it is rather a piece of
282HTML generated by GIMP's py-slice plugin that should be embedded in an HTML
283or XHTML document to be valid.
284
285Replace the href targets in the anchor (<a >) for your URLS to have it working
286as a menu.
287 -->\n'''
288        out += '<table'
289
290        for attr, value in self.table_attrs.iteritems():
291            out += ' %s="%s"' % (attr, value)
292
293        out += '>'
294
295        self.write(out)
296
297    def close(self):
298        self.write('</table>\n')
299        prefix = self.image_prefix
300        if self.animate:
301            out = """
302<script language="javascript" type="text/javascript">
303/* Made with GIMP */
304
305/* Preload images: */
306    images_%s = new Array();
307                   \n"""    % prefix
308            for image in self.images:
309                for type_ in ("plain", "hover", "clicked"):
310                    if image.has_key(type_):
311                        image_index = ("%d_%d_%s" %
312                                       (image["index"][0],
313                                        image["index"][1], type_))
314                        out += ("    images_%s[\"%s\"] = new  Image();\n" %
315                                (prefix, image_index))
316                        out += ("    images_%s[\"%s\"].src = \"%s\";\n" %
317                            (prefix, image_index, image[type_]))
318
319            out+= """
320function exchange (image, images_array_name, event)
321  {
322    name = image.name;
323    images = eval (images_array_name);
324
325    switch (event)
326      {
327        case 0:
328          image.src = images[name + "_plain"].src;
329          break;
330        case 1:
331          image.src = images[name + "_hover"].src;
332          break;
333        case 2:
334          image.src = images[name + "_clicked"].src;
335          break;
336        case 3:
337          image.src = images[name + "_hover"].src;
338          break;
339      }
340
341  }
342</script>
343<!--
344End of the part generated by GIMP
345-->
346"""
347            self.write (out)
348
349
350    def row_start(self):
351        self.write('  <tr>')
352
353    def row_end(self):
354        self.write('</tr>\n')
355
356    def cell(self, src, width, height, row=0, col=0, skip_stub = False):
357        if isinstance (src, list):
358            prefix = "images_%s" % self.image_prefix
359            self.images.append ({"index" : (row, col), "plain" : src[0]})
360
361            out = ('    <td><a href="%s"><img alt="" src="%s" ' +
362                  'style="width: %dpx; height: %dpx; border-width: 0px" \n') %\
363                  (self.next_url(), src[0], width, height)
364            out += 'name="%d_%d" \n' % (row, col)
365            if len(src) >= 2:
366                self.images[-1]["hover"] = src [1]
367                out += """      onmouseout="exchange(this, '%s', 0);"\n""" % \
368                       prefix
369                out += """      onmouseover="exchange(this, '%s', 1);"\n""" % \
370                       prefix
371            if len(src) >= 3:
372                self.images[-1]["clicked"] = src [2]
373                out += """      onmousedown="exchange(this, '%s', 2);"\n""" % \
374                       prefix
375                out += """      onmouseup="exchange(this, '%s', 3);"\n""" % \
376                       prefix
377
378
379
380            out += "/></a></td>\n"
381
382        else:
383            if skip_stub:
384                out =  ('    <td><img alt=" " src="%s" style="width: %dpx; ' +
385                        ' height: %dpx; border-width: 0px;"></td>') % \
386                        (src, width, height)
387            else:
388                out = ('    <td><a href="#"><img alt=" " src="%s" ' +
389                      ' style="width: %dpx; height: %dpx; border-width: 0px;">' +
390                      '</a></td>') %  (src, width, height)
391        self.write(out)
392    def parse_urls (self):
393        """
394           This will parse any url targets in the href="XX" fields
395           of the given file and return then as a list
396        """
397        import re
398        url_list = []
399        try:
400            html_file = open (self.filename)
401
402            # Regular expression to pick everything up to the next
403            # doublequote character after finding the sequence 'href="'.
404            # The found sequences will be returned as a list by the
405            # "findall" method.
406            expr = re.compile (r"""href\=\"([^\"]*?)\"""")
407            url_list = expr.findall (html_file.read (2 ** 18))
408            html_file.close()
409
410        except:
411            # silently ignore any errors parsing this. The file being
412            # overwritten may not be a file created by py-slice.
413            pass
414
415        return url_list
416
417
418register(
419    "python-fu-slice",
420    # table snippet means a small piece of HTML code here
421    N_("Cuts an image along its guides, creates images and a HTML table snippet"),
422    """Add guides to an image. Then run this. It will cut along the guides,
423    and give you the html to reassemble the resulting images. If you
424    choose to generate javascript for onmouseover and clicked events, it
425    will use the lower three visible layers on the image for normal,
426    onmouseover and clicked states, in that order. If skip caps is
427    enabled, table cells on the edge of the table won't become animated,
428    and its images will be taken from the active layer.""",
429    "Manish Singh",
430    "Manish Singh",
431    "2003",
432    _("_Slice..."),
433    "*",
434    [
435        (PF_IMAGE, "image", "Input image", None),
436        (PF_DRAWABLE, "drawable", "Input drawable", None),
437        (PF_DIRNAME, "save-path",     _("Path for HTML export"), os.getcwd()),
438        (PF_STRING, "html-filename",  _("Filename for export"),  "slice.html"),
439        (PF_STRING, "image-basename", _("Image name prefix"),    "slice"),
440        (PF_RADIO, "image-extension", _("Image format"),         "gif", (("gif", "gif"), ("jpg", "jpg"), ("png", "png"))),
441        (PF_TOGGLE, "separate-image-dir",  _("Separate image folder"),
442         False),
443        (PF_STRING, "relative-image-path", _("Folder for image export"), "images"),
444        (PF_SPINNER, "cellspacing", _("Space between table elements"), 0,
445        (0,15,1)),
446        (PF_TOGGLE, "animate",      _("Javascript for onmouseover and clicked"),
447         False),
448        # table caps are table cells on the edge of the table
449        (PF_TOGGLE, "skip-caps",    _("Skip animation for table caps"), True)
450    ],
451    [],
452    pyslice,
453    menu="<Image>/Filters/Web",
454    domain=("gimp20-python", gimp.locale_directory)
455    )
456
457main()
458