#!/usr/bin/env python # -*- coding: utf-8 -*- # This is the main file of btn4ws. # # Copyright (c) 1999-2007 Jan Dittberner # # This file is part of btn4ws. # # btn4ws is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # btn4ws 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 # along with btn4ws; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # version: $Id$ # """ Gimp script to generate button images for websites. This script is a port of the older gimp-perl version to python. (c) 2007 Jan Dittberner """ import os, urllib, logging, sys import gimp, gimpplugin import pygtk pygtk.require('2.0') import gtk from gimpenums import * from gimpshelf import shelf pdb = gimp.pdb btn4ws_version = "0.7.0" logging.basicConfig(level=logging.DEBUG, 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 class Btn4wsDialog: """This class is the input dialog field for btn4ws""" def delete_event(self, widget, event, data = None): return False def destroy(self, widget, data = None): gtk.main_quit() def __init__(self, filename, outdir, font, strcolor, transparency, bgcolor, glow, glowcolor, usepattern, pattern, buttoncolor, roundradius, padding, glowsize, bevelwidth, nova, novasparkles, novaradius, novacolor, writexcf, makeinactive, makeactive, makepressed, makejscript): self.window = gtk.Assistant() self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) self.window.show() def main(self): gtk.main() class btn4wsplugin(gimpplugin.plugin): """This is the btn4ws gimp plugin.""" 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 = 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) def btn4ws(self, runmode, filename = None, outdir = None, font = None, strcolor = None, transparency = None, bgcolor = None, glow = False, glowcolor = None, usepattern = False, pattern = None, buttoncolor = None, roundradius = None, padding = None, glowsize = None, bevelwidth = None, nova = False, novasparkles = None, novaradius = None, novacolor = None, writexcf = False, makeinactive = True, makeactive = True, makepressed = True, makejscript = True): """ This function controls the creation of the buttons and is registered as gimp plugin. """ if runmode == RUN_INTERACTIVE: logging.debug("runmode interactive") dialog = Btn4wsDialog(filename, outdir, font, strcolor, transparency, bgcolor, glow, glowcolor, usepattern, pattern, buttoncolor, roundradius, padding, glowsize, bevelwidth, nova, novasparkles, novaradius, novacolor, writexcf, makeinactive, makeactive, makepressed, makejscript) dialog.main() elif runmode == RUN_NONINTERACTIVE: logging.debug("runmode noninteractive") elif runmode == RUN_WITH_LASTVALS: logging.debug("runmode with lastvals") if shelf.has_key("btn4ws"): initialvalues = shelf["btn4ws"] return # import used gimp pdb functions createtext = pdb['gimp_text_fontname'] selectionlayeralpha = pdb['gimp_selection_layer_alpha'] selectionfeather = pdb['gimp_selection_feather'] bucketfill = pdb['gimp_edit_bucket_fill'] selectionall = pdb['gimp_selection_all'] editclear = pdb['gimp_edit_clear'] rectselect = pdb['gimp_rect_select'] ellipseselect = pdb['gimp_ellipse_select'] selectionshrink = pdb['gimp_selection_shrink'] selectionnone = pdb['gimp_selection_none'] fill = pdb['gimp_edit_fill'] bumpmap = pdb['plug_in_bump_map'] novaplugin = pdb['plug_in_nova'] xcfsave = pdb['gimp_xcf_save'] pngsave = pdb['file_png_save'] dupimage = pdb['gimp_image_duplicate'] 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: 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() def start(self): gimp.main(self.init, self.quit, self.query, self._run) def init(self): logging.debug("init") def quit(self): logging.debug("quit") def query(self): logging.debug("query") gimp.install_procedure( "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.""", "Jan Dittberner", "Jan Dittberner ", "%s, %s" % (btn4ws_version, "$Date$"), "/Xtns/Render/Buttons for website ...", "", PLUGIN, [(PDB_INT32, "run_mode", "Run mode"), (PDB_STRING, "string_filename", "File containing the strings"), (PDB_STRING, "output_directory", "Directory for the output files"), (PDB_STRING, "font", "Font for the strings"), # "Sans 18"), (PDB_COLOR, "string_color", "Color of the strings"), #, (255, 255, 0)), (PDB_INT8, "transparent_background", "Keep the background transparent (This doesn't work in MS Internet Explorer <= 6.0)"), #, 0), (PDB_COLOR, "background_color", "Color of the background"), #, (7, 135, 255)), (PDB_INT8, "apply_glow", "Enable glow effect"), #, 1), (PDB_COLOR, "glow_color", "Color of the Glow effect"), #, (255, 180, 0)), (PDB_INT8, "use_pattern", "Use a pattern for the button"), #, 1), (PDB_STRING, "button_pattern", "Fill pattern of the button"), #, "Rain"), (PDB_COLOR, "button_color", "Button color (if you don't use a pattern)"), #, (0, 0, 255)), (PDB_INT32, "round_radius", "Radius of the round corners"), #, 20), (PDB_INT32, "padding", "Space around the text"), #, 3), (PDB_INT32, "glow_size", "Size of the Glow effect"), #, 10), (PDB_INT32, "bevel_width", "Width of the bevel"), #, 5), (PDB_INT8, "apply_nova", "Nova or not Nova?"), #, 1), (PDB_INT32, "nova_sparkles", "Sparkles of the Nova"), #, 5), (PDB_INT32, "nova_radius", "Radius of the Nova"), #, 2), (PDB_COLOR, "nova_color", "Color of the Nova effect"), #, (255, 238, 0)), (PDB_INT8, "write_xcf", "Write a GIMP xcf file"), #, 0), (PDB_INT8, "create_inactive", "Create Inactive Button"), #, 1), (PDB_INT8, "create_active", "Create Active Button"), #, 1), (PDB_INT8, "create_pressed", "Create Pressed Button"), #, 1), (PDB_INT8, "create_jscript", "Create JavaScript, HTML and CSS"), #, 1) ], []) if __name__ == '__main__': btn4wsplugin().start()