furware/TextureCreator/lib/TexturePainter.py
2013-08-25 23:06:03 +02:00

326 lines
12 KiB
Python

'''
This script is part of the FURWARE texture creator, a texture
generation script for the FURWARE text text-on-prim script
for Second Life(R).
Please see the included README and LICENSE files in the root
directory for more information.
'''
'''
This script is the heart of the texture creator. It creates a
texture, executes script files to perform actions on it and
writes the result out to an image file.
'''
import cairo
import itertools
import math
import os
from . import FontFaceCreator
from . import TGAWriter
from ScriptReader import ScriptReader
OUTPUT_DIR = "output"
SCRIPTS_DIR = "scripts"
class GridConfig:
def __init__(self, args):
self.imageSize = (args[0], args[1])
self.cellSize = (32, 64)
self.begin = (32, 44)
self.spacing = (40, 72)
self.cellCount = (25, 14)
class CharConfig:
def __init__(self, args):
self.offsetX = args[0]
self.offsetY = args[1]
self.scaleX = args[2]
self.scaleY = args[3]
class FontConfig:
def __init__(self, fontConfPath):
self.offsetX = 0
self.offsetY = 0
self.offsetXUnit = ""
self.offsetYUnit = ""
self.perCharConfigs = {}
self.guessingChar = "X"
self.scale = 0.75
self.scaleUnit = "cellHeight"
self.autoShrink = False
if not os.path.exists(fontConfPath):
return
fontConf = ScriptReader(fontConfPath)
while fontConf.more():
if fontConf.getCmd() == "offset":
[self.offsetX, self.offsetY, self.offsetXUnit, self.offsetYUnit] = fontConf.getArgs([int, int, str, str])
elif fontConf.getCmd() == "scale":
[self.scale, self.scaleUnit] = fontConf.getArgs([float, str])
elif fontConf.getCmd() == "guessingChar":
self.guessingChar = fontConf.getArgs([unicode])[0]
elif fontConf.getCmd() == "enableAutoShrink":
self.autoShrink = True
elif fontConf.getCmd() == "charOffset":
args = fontConf.getArgs([unicode, int, int, float, float])
self.perCharConfigs[args[0]] = CharConfig(args[1:])
def getCharConfig(self, char):
try:
return self.perCharConfigs[char]
except KeyError:
return CharConfig([0, 0, 1.0, 1.0])
class FontExtents:
def __init__(self, cairoContext):
self.cairoContext = cairoContext
def update(self, text):
self.xBearing, self.yBearing, self.xSize, self.ySize, self.xAdvance, self.yAdvance \
= self.cairoContext.text_extents(text)
class TexturePainter:
def __init__(self, outputName):
self.debugGrid = False
self.outputName = outputName
self.defaultOutputName = outputName
def runScript(self, scriptName, defaultFont):
scriptPath = os.path.join(os.path.dirname(defaultFont), scriptName)
if not os.path.exists(scriptPath):
# Then try the default scripts directory.
scriptPath = os.path.join(SCRIPTS_DIR, scriptName)
if not os.path.exists(scriptPath):
print(" WARNING: Script \"" + scriptName + "\" not found.")
return False
script = ScriptReader(scriptPath)
while script.more():
if script.getCmd() == "runScript":
if not self.runScript(script.getRawArg(), defaultFont):
return False
elif script.getCmd() == "noBuild":
return False
elif script.getCmd() == "setOutputName":
self.outputName = script.getRawArg()
elif script.getCmd() == "setOutputNameSuffix":
self.outputName = self.defaultOutputName + script.getRawArg()
elif script.getCmd() == "init":
self.init(script.getArgs([int, int, float, float]))
elif script.getCmd() == "setCellSize":
self.gridConfig.cellSize = script.getArgs([int, int])
elif script.getCmd() == "setCellOffset":
self.gridConfig.begin = script.getArgs([int, int])
elif script.getCmd() == "setCellSpacing":
self.gridConfig.spacing = script.getArgs([int, int])
elif script.getCmd() == "setCellCount":
self.gridConfig.cellCount = script.getArgs([int, int])
elif script.getCmd() == "drawLineBetweenCells":
self.lineBetweenCells(*[int(arg) for arg in script.getArgs([int, int, int, int, int, int, int, int, int])])
elif script.getCmd() == "drawRectBetweenCells":
self.rectangleBetweenCells(*[int(arg) for arg in script.getArgs([int, int, int, int, int, int, int, int, int])])
elif script.getCmd() == "drawFilledRectBetweenCells":
self.rectangleBetweenCells(*[int(arg) for arg in script.getArgs([int, int, int, int, int, int, int, int, int])], filled = True)
elif script.getCmd() == "drawChars":
for char in script.getRawArg():
self.drawChar(char)
elif script.getCmd() == "loadFont":
fontPath = script.getRawArg()
if fontPath == "":
fontPath = defaultFont
self.loadFont(fontPath)
elif script.getCmd() == "jumpToCell":
self.jumpToCell(*script.getArgs([int, int]))
elif script.getCmd() == "drawDebugGrid":
self.debugGrid = True
self.cairoContext.save()
self.cairoContext.set_source_rgb(0,0,0)
self.cairoContext.set_matrix(cairo.Matrix(
xx = self.gridConfig.imageSize[0],
yy = self.gridConfig.imageSize[1]
))
self.cairoContext.rectangle(0,0,1,1)
self.cairoContext.fill()
self.cairoContext.restore()
self.cairoContext.save()
self.cairoContext.set_source_rgb(0.5,0.5,0.5)
thickness = 2
for y in range(0, self.gridConfig.cellCount[1]):
for x in range(0, self.gridConfig.cellCount[0]):
dx = self.gridConfig.cellSize[0]/2 + thickness/2
dy = self.gridConfig.cellSize[1]/2 + thickness/2
self.rectangleBetweenCells(thickness, x, y, x, y, -dx, -dy, dx, dy)
self.cairoContext.restore()
else:
print(" ERROR: Unknown script command \"" + script.getCmd() + "\".")
return False
return True
def writeTexture(self):
self.cairoSurface.flush()
buf = self.cairoSurface.get_data()
arr = bytearray(buf)
del arr[0::4]
del arr[0::3]
if not self.debugGrid:
arr[0::2] = itertools.repeat(255, self.gridConfig.imageSize[0] * self.gridConfig.imageSize[1])
targetPath = os.path.join(OUTPUT_DIR, self.outputName + ".tga")
TGAWriter.writeTGA(self.gridConfig.imageSize[0], self.gridConfig.imageSize[1], 8, 16, arr, targetPath)
def init(self, args):
imageSizeX = int(math.floor(args[2] * args[0]))
imageSizeY = int(math.floor(args[3] * args[1]))
self.gridConfig = GridConfig([imageSizeX, imageSizeY])
self.cairoSurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, imageSizeX, imageSizeY)
self.cairoContext = cairo.Context(self.cairoSurface)
self.cairoContext.set_matrix(cairo.Matrix(
xx = args[2], yy = args[3]
))
self.cairoContext.set_source_rgb(1,1,1)
fontOptions = cairo.FontOptions()
fontOptions.set_antialias(cairo.ANTIALIAS_GRAY)
fontOptions.set_hint_style(cairo.HINT_STYLE_NONE)
self.cairoContext.set_font_options(fontOptions)
self.currentRow = 0
self.currentCol = 0
def loadFont(self, fontPath):
self.fontConfig = FontConfig(os.path.join(os.path.dirname(fontPath), "font.conf"))
fontFace = FontFaceCreator.fontFaceFromFile(fontPath)
self.cairoContext.set_font_face(fontFace)
self.fontSize = self.fontConfig.scale * self.convertScaleUnit(self.fontConfig.scaleUnit)
self.cairoContext.set_font_size(self.fontSize)
self.fontExtents = FontExtents(self.cairoContext)
self.fontExtents.update(self.fontConfig.guessingChar)
self.guessedYOffset = self.fontExtents.ySize / 2
def jumpToCell(self, x, y):
self.currentCol = int(x)
self.currentRow = int(y)
def drawChar(self, char):
fontSizeX = self.fontSize
fontSizeY = self.fontSize
if self.fontConfig.autoShrink:
self.cairoContext.set_font_size(1)
self.fontExtents.update(char)
if self.fontExtents.xSize > 0:
widthGuess = self.gridConfig.cellSize[0] / self.fontExtents.xSize
if widthGuess < self.fontSize:
fontSizeX = widthGuess
charConfig = self.fontConfig.getCharConfig(char)
fontSizeX *= charConfig.scaleX
fontSizeY *= charConfig.scaleY
self.cairoContext.set_font_matrix(cairo.Matrix(xx = fontSizeX, yy = fontSizeY))
self.fontExtents.update(char)
xOffset = -self.fontExtents.xAdvance / 2
yOffset = self.guessedYOffset
xOffset += self.fontConfig.offsetX * self.convertScaleUnit(self.fontConfig.offsetXUnit)
yOffset += self.fontConfig.offsetY * self.convertScaleUnit(self.fontConfig.offsetYUnit)
xOffset += charConfig.offsetX
yOffset += charConfig.offsetY
x = self.gridConfig.begin[0] + self.currentCol * self.gridConfig.spacing[0] + xOffset
y = self.gridConfig.begin[1] + self.currentRow * self.gridConfig.spacing[1] + yOffset
self.cairoContext.move_to(x, y)
self.cairoContext.show_text(char)
self.currentCol += 1
if self.currentCol >= self.gridConfig.cellCount[0]:
self.currentRow += 1
self.currentCol = 0
def cellPos(self, x, y):
return (
self.gridConfig.begin[0] + x * self.gridConfig.spacing[0],
self.gridConfig.begin[1] + y * self.gridConfig.spacing[1]
)
def lineBetweenCells(self, lineWidth, x0, y0, x1, y1, dx0 = 0, dy0 = 0, dx1 = 0, dy1 = 0):
self.cairoContext.set_line_width(lineWidth)
beg = self.cellPos(x0, y0)
end = self.cellPos(x1, y1)
begD = (dx0, dy0)
endD = (dx1, dy1)
self.cairoContext.move_to(beg[0]+begD[0], beg[1]+begD[1])
self.cairoContext.line_to(end[0]+endD[0], end[1]+endD[1])
self.cairoContext.stroke()
def rectangleBetweenCells(self, lineWidth, begX, begY, endX, endY, \
begDX = 0, begDY = 0, endDX = 0, endDY = 0, filled = False):
self.cairoContext.set_line_width(lineWidth)
beg = self.cellPos(begX, begY)
end = self.cellPos(endX, endY)
begD = (begDX, begDY)
endD = (endDX, endDY)
self.cairoContext.rectangle(
beg[0]+begD[0],
beg[1]+begD[1],
end[0]-beg[0]-begD[0]+endD[0],
end[1]-beg[1]-begD[1]+endD[1]
)
if filled:
self.cairoContext.fill()
else:
self.cairoContext.stroke()
def convertScaleUnit(self, unitString):
if unitString == "cellWidth":
return self.gridConfig.cellSize[0]
elif unitString == "cellHeight":
return self.gridConfig.cellSize[1]
return 1