py-slice.py 18.6 KB
Newer Older
Jehan's avatar
Jehan committed
1
#!/usr/bin/env python3
2 3 4 5
# -*- coding: utf-8 -*-

#Copyright (c) Manish Singh
#javascript animation support by Joao S. O. Bueno Calligaris (2004)
6

7 8 9
#   Gimp-Python - allows the writing of Gimp plugins in Python.
#   Copyright (C) 2003, 2005  Manish Singh <yosh@gimp.org>
#
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 14 15 16 17 18 19 20
#   (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
21
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
22

23 24 25 26 27
# (c) 2003 Manish Singh.
#"Guillotine implemented ala python, with html output
# (based on perlotine by Seth Burgess)",
# Modified by João S. O. Bueno Calligaris to allow  dhtml animations (2005)

Jehan's avatar
Jehan committed
28 29 30 31 32 33
import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gio
34

Jehan's avatar
Jehan committed
35 36 37
import gettext
_ = gettext.gettext
def N_(message): return message
38

Jehan's avatar
Jehan committed
39 40 41 42 43 44 45 46 47 48 49 50 51 52
import os
import os.path
import sys

def pyslice(procedure, run_mode, image, drawable, args, data):
    save_path       = args.index(0)
    html_filename   = args.index(1)
    image_basename  = args.index(2)
    image_extension = args.index(3)
    separate        = args.index(4)
    image_path      = args.index(5)
    cellspacing     = args.index(6)
    animate         = args.index(7)
    skip_caps       = args.index(8)
53 54 55

    cellspacing = int (cellspacing)

56 57 58 59 60 61 62 63 64 65 66 67
    if animate:
        count = 0
        drw = []
        #image.layers is a reversed list of the layers on the image
        #so, count indexes from number of layers to 0.
        for i in xrange (len (image.layers) -1, -1, -1):
            if image.layers[i].visible:
                drw.append(image.layers[i])
                count += 1
                if count == 3:
                    break

68 69 70

    vert, horz = get_guides(image)

71
    if len(vert) == 0 and len(horz) == 0:
72 73
        return

Jehan's avatar
Jehan committed
74
    Gimp.progress_init(_("Slice"))
75 76 77 78 79 80
    progress_increment = 1 / ((len(horz) + 1) * (len(vert) + 1))
    progress = 0.0

    def check_path(path):
        path = os.path.abspath(path)

81
        if not os.path.exists(path):
82 83 84
            os.mkdir(path)

        return path
85

86 87
    save_path = check_path(save_path)

88 89 90
    if not os.path.isdir(save_path):
        save_path = os.path.dirname(save_path)

91
    if separate:
92
        image_relative_path = image_path
93 94
        if not image_relative_path.endswith("/"):
            image_relative_path += "/"
95 96
        image_path = check_path(os.path.join(save_path, image_path))
    else:
97
        image_relative_path = ''
98 99 100
        image_path = save_path

    tw = TableWriter(os.path.join(save_path, html_filename),
101
                     cellspacing=cellspacing, animate=animate)
102 103 104 105 106

    top = 0

    for i in range(0, len(horz) + 1):
        if i == len(horz):
Jehan's avatar
Jehan committed
107
            bottom = image.height()
108 109 110 111 112 113 114 115 116
        else:
            bottom = image.get_guide_position(horz[i])

        tw.row_start()

        left = 0

        for j in range(0, len(vert) + 1):
            if j == len(vert):
Jehan's avatar
Jehan committed
117
                right = image.width()
118 119
            else:
                right = image.get_guide_position(vert[j])
120 121 122 123 124 125 126 127 128
            if (skip_caps   and
                 (
                   (len(horz) >= 2 and (i == 0 or i == len(horz) )) or
                   (len(vert) >= 2 and (j == 0 or j == len(vert) ))
                 )
               ):
                skip_stub = True
            else:
                skip_stub = False
129

130 131 132 133 134
            if (not animate or skip_stub):
                src = (image_relative_path +
                       slice (image, None, image_path,
                              image_basename, image_extension,
                              left, right, top, bottom, i, j, ""))
135
            else:
136 137 138 139 140 141
                src = []
                for layer, postfix in zip (drw, ("", "hover", "clicked")):
                    src.append (image_relative_path +
                                slice(image, layer, image_path,
                                      image_basename, image_extension,
                                      left, right, top, bottom, i, j, postfix))
142

143
            tw.cell(src, right - left, bottom - top, i, j, skip_stub)
144 145 146 147

            left = right + cellspacing

            progress += progress_increment
Jehan's avatar
Jehan committed
148
            Gimp.progress_update(progress)
149 150 151 152 153 154

        tw.row_end()

        top = bottom + cellspacing

    tw.close()
Jehan's avatar
Jehan committed
155
    return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
156

157 158 159 160 161
def slice(image, drawable, image_path, image_basename, image_extension,
          left, right, top, bottom, i, j, postfix):
    if postfix:
        postfix = "_" + postfix
    src = "%s_%d_%d%s.%s" % (image_basename, i, j, postfix, image_extension)
162 163
    filename = os.path.join(image_path, src)

164 165
    if not drawable:
        temp_image = image.duplicate()
Jehan's avatar
Jehan committed
166
        temp_drawable = temp_image.get_active_layer()
167
    else:
Jehan's avatar
Jehan committed
168
        if image.base_type() == Gimp.ImageBaseType.INDEXED:
169 170
            #gimp_layer_new_from_drawable doesn't work for indexed images.
            #(no colormap on new images)
Jehan's avatar
Jehan committed
171
            original_active = image.get_active_layer()
172 173
            image.active_layer = drawable
            temp_image = image.duplicate()
Jehan's avatar
Jehan committed
174
            temp_drawable = temp_image.get_active_layer()
175
            image.active_layer = original_active
Jehan's avatar
Jehan committed
176
            temp_image.undo_disable()
177 178 179
            #remove all layers but the intended one
            while len (temp_image.layers) > 1:
                if temp_image.layers[0] != temp_drawable:
Jehan's avatar
Jehan committed
180
                    temp_image.remove_layer (temp_image.layers[0])
181
                else:
Jehan's avatar
Jehan committed
182
                    temp_image.remove_layer (temp_image.layers[1])
183
        else:
Jehan's avatar
Jehan committed
184 185 186 187
            temp_image = Gimp.image_new (drawable.width(),
                                         drawable.height(),
                                         image.base_type())
            temp_drawable = Gimp.layer_new_from_drawable (drawable, temp_image)
188
            temp_image.insert_layer (temp_drawable)
189

Jehan's avatar
Jehan committed
190
    temp_image.undo_disable()
191
    temp_image.crop(right - left, bottom - top, left, top)
Jehan's avatar
Jehan committed
192 193 194 195 196 197
    if image_extension == "gif" and image.base_type() == Gimp.ImageBaseType.RGB:
        temp_image.convert_indexed (Gimp.ConvertDitherType.NONE,
                                    Gimp.ConvertPaletteType.GENERATE, 255,
                                    True, False, "")
    if image_extension == "jpg" and image.base_type() == Gimp.ImageBaseType.INDEXED:
        temp_image.convert_rgb ()
Sven Neumann's avatar
Sven Neumann committed
198

Jehan's avatar
Jehan committed
199
    Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, temp_image, temp_drawable, filename, filename)
Sven Neumann's avatar
Sven Neumann committed
200

Jehan's avatar
Jehan committed
201
    temp_image.delete()
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    return src

class GuideIter:
    def __init__(self, image):
        self.image = image
        self.guide = 0

    def __iter__(self):
        return iter(self.next_guide, 0)

    def next_guide(self):
        self.guide = self.image.find_next_guide(self.guide)
        return self.guide

def get_guides(image):
    vguides = []
    hguides = []

    for guide in GuideIter(image):
        orientation = image.get_guide_orientation(guide)

223 224 225
        guide_position = image.get_guide_position(guide)

        if guide_position > 0:
Jehan's avatar
Jehan committed
226 227
            if orientation == Gimp.OrientationType.VERTICAL:
                if guide_position < image.width():
228
                    vguides.append((guide_position, guide))
Jehan's avatar
Jehan committed
229 230
            elif orientation == Gimp.OrientationType.HORIZONTAL:
                if guide_position < image.height():
231
                    hguides.append((guide_position, guide))
232

Jehan's avatar
Jehan committed
233 234
    def position_sort_key(x):
        return x[0]
235

Jehan's avatar
Jehan committed
236 237
    vguides.sort(key = position_sort_key)
    hguides.sort(key = position_sort_key)
238 239 240 241 242 243 244 245

    vguides = [g[1] for g in vguides]
    hguides = [g[1] for g in hguides]

    return vguides, hguides

class TableWriter:
    def __init__(self, filename, cellpadding=0, cellspacing=0, border=0,
246 247 248
                 animate=False):

        self.filename = filename
249 250
        self.table_attrs = {}

251
        #Hellraisen IE 6 doesn't support CSS for table control.
252 253 254 255
        self.table_attrs['cellpadding'] = cellpadding
        self.table_attrs['cellspacing'] = cellspacing
        self.table_attrs['border'] = border

256 257 258 259
        self.image_prefix = os.path.basename (filename)
        self.image_prefix = self.image_prefix.split(".")[0]
        self.image_prefix = self.image_prefix.replace ("-", "_")
        self.image_prefix = self.image_prefix.replace (" ", "_")
260

Sven Neumann's avatar
Sven Neumann committed
261

262 263 264 265 266
        if animate:
            self.animate = True
            self.images = []
        else:
            self.animate = False
267

268 269 270 271 272 273 274
        if os.path.exists (filename):
            #The plug-in is running to overwrite a previous
            #version of the file. This will parse the href targets already
            #in the file to preserve them.
            self.urls = self.parse_urls ()
        else:
            self.urls = []
Sven Neumann's avatar
Sven Neumann committed
275

276
        self.url_index = 0
Sven Neumann's avatar
Sven Neumann committed
277 278

        self.html = open(filename, 'wt')
279
        self.open()
Sven Neumann's avatar
Sven Neumann committed
280

281 282 283 284
    def next_url (self):
        if self.url_index < len (self.urls):
            self.url_index += 1
            return self.urls [self.url_index - 1]
285
        else:
286 287
            #Default url to use in the anchor tags:
            return ("#")
288

289
    def write(self, s, vals=None):
290 291 292 293 294 295
        if vals:
            s = s % vals

        self.html.write(s + '\n')

    def open(self):
296
        out = '''<!--HTML SNIPPET GENERATED BY GIMP
297 298

WARNING!! This is NOT a fully valid HTML document, it is rather a piece of
299
HTML generated by GIMP's py-slice plugin that should be embedded in an HTML
300 301 302 303 304 305
or XHTML document to be valid.

Replace the href targets in the anchor (<a >) for your URLS to have it working
as a menu.
 -->\n'''
        out += '<table'
306

Jehan's avatar
Jehan committed
307
        for attr, value in self.table_attrs.items():
308
            out += ' %s="%s"' % (attr, value)
309 310 311 312 313 314

        out += '>'

        self.write(out)

    def close(self):
315 316 317 318 319
        self.write('</table>\n')
        prefix = self.image_prefix
        if self.animate:
            out = """
<script language="javascript" type="text/javascript">
320
/* Made with GIMP */
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360

/* Preload images: */
    images_%s = new Array();
                   \n"""    % prefix
            for image in self.images:
                for type_ in ("plain", "hover", "clicked"):
                    if image.has_key(type_):
                        image_index = ("%d_%d_%s" %
                                       (image["index"][0],
                                        image["index"][1], type_))
                        out += ("    images_%s[\"%s\"] = new  Image();\n" %
                                (prefix, image_index))
                        out += ("    images_%s[\"%s\"].src = \"%s\";\n" %
                            (prefix, image_index, image[type_]))

            out+= """
function exchange (image, images_array_name, event)
  {
    name = image.name;
    images = eval (images_array_name);

    switch (event)
      {
        case 0:
          image.src = images[name + "_plain"].src;
          break;
        case 1:
          image.src = images[name + "_hover"].src;
          break;
        case 2:
          image.src = images[name + "_clicked"].src;
          break;
        case 3:
          image.src = images[name + "_hover"].src;
          break;
      }

  }
</script>
<!--
361
End of the part generated by GIMP
362 363 364 365
-->
"""
            self.write (out)

366 367

    def row_start(self):
368
        self.write('  <tr>')
369 370

    def row_end(self):
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
        self.write('</tr>\n')

    def cell(self, src, width, height, row=0, col=0, skip_stub = False):
        if isinstance (src, list):
            prefix = "images_%s" % self.image_prefix
            self.images.append ({"index" : (row, col), "plain" : src[0]})

            out = ('    <td><a href="%s"><img alt="" src="%s" ' +
                  'style="width: %dpx; height: %dpx; border-width: 0px" \n') %\
                  (self.next_url(), src[0], width, height)
            out += 'name="%d_%d" \n' % (row, col)
            if len(src) >= 2:
                self.images[-1]["hover"] = src [1]
                out += """      onmouseout="exchange(this, '%s', 0);"\n""" % \
                       prefix
                out += """      onmouseover="exchange(this, '%s', 1);"\n""" % \
                       prefix
            if len(src) >= 3:
                self.images[-1]["clicked"] = src [2]
                out += """      onmousedown="exchange(this, '%s', 2);"\n""" % \
                       prefix
                out += """      onmouseup="exchange(this, '%s', 3);"\n""" % \
                       prefix



            out += "/></a></td>\n"
398

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
        else:
            if skip_stub:
                out =  ('    <td><img alt=" " src="%s" style="width: %dpx; ' +
                        ' height: %dpx; border-width: 0px;"></td>') % \
                        (src, width, height)
            else:
                out = ('    <td><a href="#"><img alt=" " src="%s" ' +
                      ' style="width: %dpx; height: %dpx; border-width: 0px;">' +
                      '</a></td>') %  (src, width, height)
        self.write(out)
    def parse_urls (self):
        """
           This will parse any url targets in the href="XX" fields
           of the given file and return then as a list
        """
        import re
Sven Neumann's avatar
Sven Neumann committed
415
        url_list = []
416 417
        try:
            html_file = open (self.filename)
Sven Neumann's avatar
Sven Neumann committed
418

419 420 421 422
            # Regular expression to pick everything up to the next
            # doublequote character after finding the sequence 'href="'.
            # The found sequences will be returned as a list by the
            # "findall" method.
423 424 425
            expr = re.compile (r"""href\=\"([^\"]*?)\"""")
            url_list = expr.findall (html_file.read (2 ** 18))
            html_file.close()
Sven Neumann's avatar
Sven Neumann committed
426

427
        except:
428 429
            # silently ignore any errors parsing this. The file being
            # overwritten may not be a file created by py-slice.
430
            pass
Sven Neumann's avatar
Sven Neumann committed
431

432
        return url_list
Sven Neumann's avatar
Sven Neumann committed
433

Jehan's avatar
Jehan committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
class PySlice (Gimp.PlugIn):
    ## Parameters ##
    __gproperties__ = {
        "save-path": (str,
                      _("Path for HTML export"),
                      _("Path for HTML export"),
                      os.getcwd(),
                      GObject.ParamFlags.READWRITE),
        "html-filename": (str,
                          _("Filename for export"),
                          _("Filename for export"),
                          "slice.html",
                          GObject.ParamFlags.READWRITE),
        "image-basename": (str,
                           _("Image name prefix"),
                           _("Image name prefix"),
                           "slice",
                           GObject.ParamFlags.READWRITE),
        "image-extension": (str,
                             _("Image format (gif, jpg, png)"),
                             _("Image format (gif, jpg, png)"),
                             "gif",
                           GObject.ParamFlags.READWRITE),
        "separate-image-dir": (bool,
                               _("Separate image folder"),
                               _("Separate image folder"),
                               False,
                               GObject.ParamFlags.READWRITE),
        "relative-image-path": (str,
                                _("Folder for image export"),
                                _("Folder for image export"),
                                "images",
                                GObject.ParamFlags.READWRITE),
        "cellspacing": (int,
                        _("Space between table elements"),
                        _("Space between table elements"),
                        0, 15, 0,
                        GObject.ParamFlags.READWRITE),
        "animate": (bool,
                    _("Javascript for onmouseover and clicked"),
                    _("Javascript for onmouseover and clicked"),
                    False,
                    GObject.ParamFlags.READWRITE),
477
        # table caps are table cells on the edge of the table
Jehan's avatar
Jehan committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
        "skip-caps": (bool,
                      _("Skip animation for table caps"),
                      _("Skip animation for table caps"),
                      True,
                    GObject.ParamFlags.READWRITE),
    }

    ## GimpPlugIn virtual methods ##
    def do_query_procedures(self):
        self.set_translation_domain("gimp30-python",
                                    Gio.file_new_for_path(Gimp.locale_directory()))

        return [ 'python-fu-slice' ]

    def do_create_procedure(self, name):
        procedure = None
        if name == 'python-fu-slice':
            procedure = Gimp.ImageProcedure.new(self, name,
                                                Gimp.PDBProcType.PLUGIN,
                                                pyslice, None)
            procedure.set_image_types("*");
            # table snippet means a small piece of HTML code here
            procedure.set_documentation (N_("Cuts an image along its guides, creates images and a HTML table snippet"),
                                         """Add guides to an image. Then run this. It will cut along the guides,
                                         and give you the html to reassemble the resulting images. If you
                                         choose to generate javascript for onmouseover and clicked events, it
                                         will use the lower three visible layers on the image for normal,
                                         onmouseover and clicked states, in that order. If skip caps is
                                         enabled, table cells on the edge of the table won't become animated,
                                         and its images will be taken from the active layer.""",
                                         name)
            procedure.set_menu_label(_("_Slice..."))
            procedure.set_attribution("Manish Singh",
                                      "Manish Singh",
                                      "2003")
            procedure.add_menu_path ("<Image>/Filters/Web")

            procedure.add_argument_from_property(self, "save-path")
            procedure.add_argument_from_property(self, "html-filename")
            procedure.add_argument_from_property(self, "image-basename")
            procedure.add_argument_from_property(self, "image-extension")
            procedure.add_argument_from_property(self, "separate-image-dir")
            procedure.add_argument_from_property(self, "relative-image-path")
            procedure.add_argument_from_property(self, "cellspacing")
            procedure.add_argument_from_property(self, "animate")
            procedure.add_argument_from_property(self, "skip-caps")
        return procedure

Gimp.main(PySlice.__gtype__, sys.argv)