From d87ff5223ac748c035bf09266577455c719a2cb3 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Tue, 4 Dec 2007 13:58:29 +0000 Subject: [PATCH] fixes #8 addresses #7 * all functionality that is available in btn4ws.pl 0.6 is available now * Makefile uses a variable for gimptool now * compatibility with GIMP 2.4 --- ChangeLog | 7 + Makefile | 8 +- btn4ws.py | 432 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 438 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index db89897..ffb8c84 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2007-12-04 Jan Dittberner + + * btn4ws.py: add the functionality available in btn4ws.pl 0.6 + + * Makefile (GIMPTOOL, clean, dist): use setup.py to build + distribution, use find for clean, create a variable for gimptool + 2007-12-03 Jan Dittberner * MANIFEST.in: add AUTHORS, ChangeLog and COPYING to distributed diff --git a/Makefile b/Makefile index 3cc8b66..d8e433e 100644 --- a/Makefile +++ b/Makefile @@ -21,12 +21,14 @@ # version: $Id$ # +GIMPTOOL = gimptool-2.0 + install: - install -d $(PREFIX)/$$(gimptool --gimpplugindir)/plug-ins - install btn4ws.py $(PREFIX)/$$(gimptool --gimpplugindir)/plug-ins + install -d $(PREFIX)/$$($(GIMPTOOL) --gimpplugindir)/plug-ins + install btn4ws.py $(PREFIX)/$$($(GIMPTOOL) --gimpplugindir)/plug-ins install-user: - gimptool --install-bin btn4ws.py + $(GIMPTOOL) --install-bin btn4ws.py clean: find . -type f -name '*~' -exec rm {} \; diff --git a/btn4ws.py b/btn4ws.py index 5f515cc..777da30 100644 --- a/btn4ws.py +++ b/btn4ws.py @@ -29,7 +29,7 @@ port of the older gimp-perl version to python. (c) 2007 Jan Dittberner """ -import logging, sys +import os, urllib, logging, sys from gimpfu import * btn4ws_version = "0.7.0" @@ -38,20 +38,439 @@ logging.basicConfig(level=logging.INFO, format='$(asctime)s %(levelname)s %(message)s', stream=sys.stderr) +class text_to_name_mapper: + """ + Text string to name mapper class. This class provides mappings for several target + environments. + """ + def __init__(self, strings): + self.mapping = {} + for string in strings: self.mapping[string] = {} + self.idnum = 1 + logging.debug("self.mapping=" + str(self.mapping)) + + def asitemid(self, text): + """ + Get a img itemid for the given text. + """ + if 'itemid' not in self.mapping[text]: + self.mapping[text]['itemid'] = "id%03d" % (self.idnum) + self.idnum += 1 + logging.debug("self.mapping=" + str(self.mapping)) + return self.mapping[text]['itemid'] + + def asjavascriptid(self, text): + """ + Get a javascript itemid for the given text. + """ + if 'jsid' not in self.mapping[text]: + self.mapping[text]['jsid'] = "img%03d" % (self.idnum) + self.idnum += 1 + logging.debug("self.mapping=" + str(self.mapping)) + return self.mapping[text]['jsid'] + + def aslinktarget(self, text): + """ + Get a link target for the given text. + """ + if 'link' not in self.mapping[text]: + self.mapping[text]['link'] = urllib.quote(text) + logging.debug("self.mapping=" + str(self.mapping)) + return "%s.html" % (self.mapping[text]['link']) + + def asfilename(self, text, extension = 'png', prefix= '', dirname = None): + """ + Get a filename for the given text with optional extension, prefix and dirname. + """ + if 'file' not in self.mapping[text]: + self.mapping[text]['file'] = text.encode('ascii', 'ignore') + fname = "%s%s.%s" % (prefix, self.mapping[text]['file'], extension) + logging.debug("self.mapping=" + str(self.mapping)) + if dirname: + return os.path.join(dirname, fname) + return fname + +class text_to_name_mapper: + """ + Text string to name mapper class. This class provides mappings for + several target environments. + """ + def __init__(self, strings): + self.mapping = {} + for string in strings: self.mapping[string] = {} + self.idnum = 1 + logging.debug("self.mapping=" + str(self.mapping)) + + def asitemid(self, text): + """ + Get a img itemid for the given text. + """ + if 'itemid' not in self.mapping[text]: + self.mapping[text]['itemid'] = "id%03d" % (self.idnum) + self.idnum += 1 + logging.debug("self.mapping=" + str(self.mapping)) + return self.mapping[text]['itemid'] + + def asjavascriptid(self, text): + """ + Get a javascript itemid for the given text. + """ + if 'jsid' not in self.mapping[text]: + self.mapping[text]['jsid'] = "img%03d" % (self.idnum) + self.idnum += 1 + logging.debug("self.mapping=" + str(self.mapping)) + return self.mapping[text]['jsid'] + + def aslinktarget(self, text): + """ + Get a link target for the given text. + """ + if 'link' not in self.mapping[text]: + self.mapping[text]['link'] = urllib.quote(text) + logging.debug("self.mapping=" + str(self.mapping)) + return "%s.html" % (self.mapping[text]['link']) + + def asfilename(self, text, extension = 'png', prefix= '', dirname = None): + """ + Get a filename for the given text with optional extension, + prefix and dirname. + """ + if 'file' not in self.mapping[text]: + self.mapping[text]['file'] = text.encode('ascii', 'ignore') + fname = "%s%s.%s" % (prefix, self.mapping[text]['file'], extension) + logging.debug("self.mapping=" + str(self.mapping)) + if dirname: + return os.path.join(dirname, fname) + return fname + def python_btn4ws(filename, outdir, font, strcolor, transparency, bgcolor, glow, glowcolor, usepattern, pattern, buttoncolor, roundradius, padding, glowsize, bevelwidth, nova, novasparkles, novaradius, novacolor, writexcf, makeinactive, makeactive, makepressed, makejscript): """ - This function controls the creation of the buttons and is registered as - gimp plugin. + This function controls the creation of the buttons and is + registered as gimp plugin. """ - pass + # import used gimp pdb functions + createtext = gimp.pdb['gimp_text_fontname'] + selectionlayeralpha = gimp.pdb['gimp_selection_layer_alpha'] + selectionfeather = gimp.pdb['gimp_selection_feather'] + bucketfill = gimp.pdb['gimp_edit_bucket_fill'] + selectionall = gimp.pdb['gimp_selection_all'] + editclear = gimp.pdb['gimp_edit_clear'] + rectselect = gimp.pdb['gimp_rect_select'] + ellipseselect = gimp.pdb['gimp_ellipse_select'] + selectionshrink = gimp.pdb['gimp_selection_shrink'] + selectionnone = gimp.pdb['gimp_selection_none'] + fill = gimp.pdb['gimp_edit_fill'] + bumpmap = gimp.pdb['plug_in_bump_map'] + novaplugin = gimp.pdb['plug_in_nova'] + xcfsave = gimp.pdb['gimp_xcf_save'] + pngsave = gimp.pdb['file_png_save'] + dupimage = gimp.pdb['gimp_image_duplicate'] + + def parsefont(font): + """ + Parses a font into its fontname and size parts. + """ + parts = font.split(" ") + return (" ".join(parts[:-1]), parts[-1]) + + def gimp2html_color(color): + """ + Converts a color tuple to a hex encoded color for CSS. + """ + return "#%02x%02x%02x" % (color[0], color[1], color[2]) + + def toprocess(item): + """ + Decides whether the plugin is able to process the item or not. + """ + item = item.strip() + return len(item) > 0 and not item.startswith('#') + + def getmaxextents(strings, fontsize, fontname): + """ + Gets the maximum width and height of texts in strings array + with the given font. + """ + getextents = gimp.pdb['gimp_text_get_extents_fontname'] + maxx = 0 + maxy = 0 + for extents in [getextents(string, fontsize, 1, fontname) + for string in strings]: + maxx = max(maxx, extents[0]) + maxy = max(maxy, extents[1]) + return (maxx, maxy) + + def writejs(dirname, strings): + buf = [ + "//", + "// JavaScript generated by btn4ws version %s" % (btn4ws_version), + "//", + "", + "// function to show image for given image_object", + "function hilite(ObjID, imgObjName) {", + " ObjID.src = eval(imgObjName + '.src');", + " return true;", + "}", + "" + ] + for item in strings: + for prefix in ('a_', 'i_', 'p_'): + buf.append( + "%(prefix)s%(jsid)s = new Image(%(width)d, %(height)d); " + "%(prefix)s%(jsid)s.src = '%(fname)s';" + % { + 'prefix' : prefix, + 'jsid' : t2nm.asjavascriptid(item), + 'width' : width, + 'height' : height, + 'fname' : urllib.quote(t2nm.asfilename(item, 'png', + prefix))}) + jsfile = open(os.path.join(dirname, 'imgobjs.js'), 'w') + jsfile.write("\n".join(buf)) + jsfile.close() + + def writecss(dirname): + buf = [ + "html, body { background-color:%s; }" % (gimp2html_color(bgcolor)), + "a img { border-width: 0; }" + ] + cssfile = open(os.path.join(dirname, 'format.css'), 'w') + cssfile.write("\n".join(buf)) + cssfile.close() + + def writehtml(dirname, strings): + buf = [ + '', + '', + '', + '', + ' A JavaScript MouseOver Example', + ' ', + ' ', + '', + '', + '
' + ] + for item in strings: + buf.append( + '' + '%(text)s
' % { + 'target' : t2nm.aslinktarget(item), + 'imgid' : t2nm.asitemid(item), + 'jsid' : t2nm.asjavascriptid(item), + 'fname' : urllib.quote(t2nm.asfilename(item, 'png', 'i_')), + 'width' : width, + 'height' : height, + 'text' : item}) + buf.extend([ + '
', + '

' + 'Valid XHTML 1.1!

', + '', + '' + ]) + htmlfile = open(os.path.join(outdir, 'example.html'), 'w') + htmlfile.write("\n".join(buf)) + htmlfile.close() + + def saveaspng(fname, image): + imgcopy = dupimage(image) + if transparency: + imgcopy.merge_visible_layers(CLIP_TO_BOTTOM_LAYER) + else: + imgcopy.flatten() + pngsave(imgcopy, imgcopy.active_layer, fname, fname, False, 9, + False, False, False, False, True) + gimp.delete(imgcopy) + + try: + if not os.path.isfile(filename): + logging.error("%s is not a file.", filename) + return + if not outdir: + outdir = os.path.dirname(filename) + if not os.path.isdir(outdir): + logging.error("%s is not a directory.", outdir) + return + except OSError, e: + logging.error(e) + return + + gimp.progress_init() + stringfile = open(filename) + strings = [line.strip() + for line in stringfile.readlines() + if toprocess(line)] + stringfile.close() + t2nm = text_to_name_mapper(strings) + (fontname, fontsize) = parsefont(font) + (maxx, maxy) = getmaxextents(strings, fontsize, fontname) + logging.debug("fontname: %s, fontsize: %d, maxx: %d, maxy: %d", + fontname, int(fontsize), maxx, maxy) + width = maxx + (padding*4) + height = maxy + (padding*4) + logging.debug("width: %d, height: %d", width, height) + + if roundradius > height/2: + roundradius = height/2 - 1 + if roundradius > width/2: + roundradius = width/2 - 1 + logging.debug("roundradius: %d", roundradius) + + for text in strings: + image = gimp.Image(width, height, RGB) + image.disable_undo() + gimp.set_foreground(strcolor) + textlayer = createtext(image, None, padding*2, padding*2, text, 0, 1, + fontsize, 1, fontname) + # center the text + textlayer.set_offsets((image.width - textlayer.width)/2 - 1, + (image.height - textlayer.height)/2 - 1) + textlayer.lock_alpha = True + textlayer.name = text + if glow: + texteffect = textlayer.copy(True) + image.add_layer(texteffect, len(image.layers)) + offs = texteffect.offsets + texteffect.resize(image.width, image.height, offs[0], offs[1]) + texteffect.lock_alpha = False + image.active_layer = texteffect + selectionlayeralpha(texteffect) + selectionfeather(image, glowsize) + gimp.set_foreground(glowcolor) + bucketfill(texteffect, FG_BUCKET_FILL, NORMAL_MODE, 100, 0, True, + 0, 0) + btnlayer0 = gimp.Layer(image, "Background", width, height, RGBA_IMAGE, + 100, NORMAL_MODE) + image.add_layer(btnlayer0, len(image.layers)) + selectionall(image) + editclear(btnlayer0) + offs = btnlayer0.offsets + rectselect(image, offs[0] + roundradius, offs[1], + btnlayer0.width - roundradius*2, btnlayer0.height, + CHANNEL_OP_REPLACE, 0, 0) + rectselect(image, offs[0], offs[1] + roundradius, + btnlayer0.width, btnlayer0.height - roundradius*2, + CHANNEL_OP_ADD, 0, 0) + ellipseselect(image, offs[0], offs[1], + roundradius*2, roundradius*2, + CHANNEL_OP_ADD, False, 0, 0) + ellipseselect(image, offs[0] + btnlayer0.width - roundradius*2, + offs[1], + roundradius*2, roundradius*2, + CHANNEL_OP_ADD, False, 0, 0) + ellipseselect(image, offs[0], + offs[1] + btnlayer0.height - roundradius*2, + roundradius*2, roundradius*2, + CHANNEL_OP_ADD, False, 0, 0) + ellipseselect(image, offs[0] + btnlayer0.width - roundradius*2, + offs[1] + btnlayer0.height - roundradius*2, + roundradius*2, roundradius*2, + CHANNEL_OP_ADD, False, 0, 0) + selectionshrink(image, 1) + selectionfeather(image, 2) + if usepattern: + gimp.pdb['gimp_context_set_pattern'](pattern) + bucketfill(btnlayer0, PATTERN_BUCKET_FILL, NORMAL_MODE, 100, 0, + True, 0, 0) + else: + gimp.set_background(buttoncolor) + bucketfill(btnlayer0, BG_BUCKET_FILL, NORMAL_MODE, 100, 0, True, + 0, 0) + selectionnone(image) + selectionlayeralpha(btnlayer0) + selectionfeather(image, 2) + bumplayer = gimp.Layer(image, "Bumpmap", width, height, RGBA_IMAGE, + 100, NORMAL_MODE) + gimp.set_background(0, 0, 0) + image.add_layer(bumplayer, 0) + fill(bumplayer, BACKGROUND_FILL) + for index in range(1, bevelwidth -1): + greyness = index*255/bevelwidth; + gimp.set_background(greyness, greyness, greyness) + bucketfill(bumplayer, BG_BUCKET_FILL, NORMAL_MODE, 100, 0, False, + 0, 0) + selectionshrink(image, 1) + gimp.set_background(255, 255, 255) + bucketfill(bumplayer, BG_BUCKET_FILL, NORMAL_MODE, 100, 0, False, 0, 0) + selectionnone(image) + btnlayer1 = btnlayer0.copy(True) + btnlayer2 = btnlayer0.copy(True) + image.add_layer(btnlayer1, len(image.layers)) + image.add_layer(btnlayer2, len(image.layers)) + bumpmap(image, btnlayer1, bumplayer, 125, 45, 3, 0, 0, 0, 0, 0, 0, 1) + bumpmap(image, btnlayer2, bumplayer, 125, 45, 3, 0, 0, 0, 0, 0, 1, 1) + image.remove_layer(bumplayer) + #gimp.delete(bumplayer) + if nova: + novalayer = gimp.Layer(image, "Nova", width, height, RGBA_IMAGE, + 75, NORMAL_MODE) + image.add_layer(novalayer, 0) + selectionall(image) + image.active_layer = novalayer + editclear(novalayer) + selectionnone(image) + novaplugin(image, novalayer, width/4, height/4, + novacolor, novaradius, novasparkles, 0) + blackboard = gimp.Layer(image, "Blackboard", width, height, RGBA_IMAGE, + 100, NORMAL_MODE) + image.add_layer(blackboard, len(image.layers)) + selectionall(image) + if transparency: + blackboard.preserve_trans = True + editclear(blackboard) + else: + gimp.set_background(bgcolor) + bucketfill(blackboard, BG_BUCKET_FILL, NORMAL_MODE, 100, 0, False, + 0, 0) + selectionnone(image) + if writexcf: + fname = t2nm.asfilename(text, 'xcf', dirname = outdir) + xcfsave(0, image, textlayer, fname, fname) + if makepressed: + btnlayer0.visible = False + btnlayer1.visible = False + btnlayer2.visible = True + if nova: novalayer.visible = True + saveaspng(t2nm.asfilename(text, 'png', 'p_', outdir), image) + if makeactive: + btnlayer0.visible = False + btnlayer1.visible = True + btnlayer2.visible = False + if nova: novalayer.visible = True + saveaspng(t2nm.asfilename(text, 'png', 'a_', outdir), image) + if makeinactive: + btnlayer0.visible = True + btnlayer1.visible = False + btnlayer2.visible = False + if nova: novalayer.visible = False + saveaspng(t2nm.asfilename(text, 'png', 'i_', outdir), image) + image.enable_undo() + #gimp.Display(image) + gimp.progress_update((strings.index(text)+1)/len(strings)) + gimp.delete(image) + if makejscript: + writejs(outdir, strings) + writecss(outdir) + writehtml(outdir, strings) + #gimp.displays_flush() register( "python_fu_btn4ws", "Buttons for website", - "Creates buttons for a website. Which have the same size, layout, effects on it. It's possible to create JavaScript code, CSS and XHTML examples for MouseOver effects also.", + """Creates buttons for a website. Which have the same size, + layout, effects on it. It's possible to create JavaScript code, + CSS and XHTML examples for MouseOver effects also.""", "Jan Dittberner", "Jan Dittberner ", "%s, %s" % (btn4ws_version, @@ -63,7 +482,8 @@ register( (PF_FONT, "font", "Font for the strings", "Sans 18"), (PF_COLOR, "string_color", "Color of the strings", (255, 255, 0)), (PF_TOGGLE, "transparent_background", - "Keep the background transparent (This doesn't work in MS Internet Explorer <= 6.0)", 0), + """Keep the background transparent (This doesn't work in MS + Internet Explorer <= 6.0)""", 0), (PF_COLOR, "background_color", "Color of the background", (7, 135, 255)), (PF_TOGGLE, "apply_glow", "Enable glow effect", 1), (PF_COLOR, "glow_color", "Color of the Glow effect", (255, 180, 0)),