btn4ws/btn4ws.py

528 lines
22 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This is the main file of btn4ws.
#
# Copyright (c) 1999-2007 Jan Dittberner <jan@dittberner.info>
#
# 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 <jan@dittberner.info>
"""
import os, urllib, logging, sys
import gimp, gimpplugin
from gimpenums import *
pdb = gimp.pdb
btn4ws_version = "0.7.0"
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
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 = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"',
' "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
'<head>',
' <title>A JavaScript MouseOver Example</title>',
' <script src="imgobjs.js" type="text/javascript"></script>',
' <link rel="stylesheet" type="text/css" href="format.css"/>',
'</head>',
'<body>',
' <div>'
]
for item in strings:
buf.append(
'<a href="%(target)s"'
' onmouseover="return hilite(%(imgid)s, \'a_%(jsid)s\');"'
' onmouseout="return hilite(%(imgid)s, \'i_%(jsid)s\');"'
' onmousedown="return hilite(%(imgid)s, \'p_%(jsid)s\');"'
' onmouseup="return hilite(%(imgid)s, \'a_%(jsid)s\');">'
'<img src="%(fname)s" class="nav" id="%(imgid)s" width="%(width)d" height="%(height)d"'
' alt="%(text)s" /></a><br />' % {
'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([
' </div>',
' <p><a href="http://validator.w3.org/check/referer">'
'<img src="http://www.w3.org/Icons/valid-xhtml11" alt="Valid XHTML 1.1!" height="31"'
'width="88" /></a></p>',
'</html>',
''
])
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.
"""
# 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 <jan@dittberner.info>",
"%s, %s" % (btn4ws_version,
"$Date$"),
"<Toolbox>/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()