Rewrite for SVG support
heros and environments only at the moment
This commit is contained in:
parent
810369596d
commit
78ac4d1da3
|
@ -3,3 +3,4 @@
|
|||
/__pycache__/
|
||||
/build/
|
||||
/dist/
|
||||
/out/**
|
||||
|
|
|
@ -1,95 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
#note: image templates: https://boardgamegeek.com/thread/813176/card-templates
|
||||
|
||||
#TODO: dynamic text sizes
|
||||
#TODO: remote upload?
|
||||
|
||||
import json
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import textwrap
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
from PIL import Image
|
||||
|
||||
if getattr(sys, 'frozen', False): # we are running in a bundle
|
||||
bundle_dir = sys._MEIPASS
|
||||
else: # we are running in a normal Python environment
|
||||
bundle_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
IMG_HERO_CHAR_FRONT = Image.open(bundle_dir + "/images/HeroCharFront.png")
|
||||
IMG_HERO_CHAR_BACK = Image.open(bundle_dir + "/images/HeroCharBack.png")
|
||||
IMG_HERO_DECK = Image.open(bundle_dir + "/images/HeroCard.png")
|
||||
IMG_TARGET_HP = Image.open(bundle_dir + "/images/targetHP.png")
|
||||
CARD_WIDTH = 181 * 4
|
||||
CARD_HEIGHT = 253 * 4
|
||||
|
||||
FONT_KEYWORD = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=40)
|
||||
FONT_TITLE = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=60)
|
||||
FONT_DESCRIPTION = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=30)
|
||||
FONT_TARGET_HP = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=120)
|
||||
FONT_CHAR_HP = ImageFont.truetype(bundle_dir + "/fonts/ap.ttf", size=120)
|
||||
def setText(tree, id, text):
|
||||
element = tree.find('.//*[@id="' + id + '"]')
|
||||
if element is None:
|
||||
print("id", id, "not found")
|
||||
return
|
||||
elif element.tag == "{http://www.w3.org/2000/svg}flowRoot":
|
||||
element.find("{http://www.w3.org/2000/svg}flowPara").text = text
|
||||
else:
|
||||
element[0].text = str(text)
|
||||
|
||||
def drawTextIf(draw, position, font, card, key, wrap=False):
|
||||
if key in card:
|
||||
text = str(card[key])
|
||||
def removeElement(tree, id):
|
||||
mark = tree.find('.//*[@id="' + id + '"]')
|
||||
if mark is not None:
|
||||
mark.getparent().remove(mark)
|
||||
|
||||
if wrap:
|
||||
# badly wrap text to fit on card
|
||||
text = '\n'.join(textwrap.fill(line.strip(), wrap,
|
||||
break_long_words=False,
|
||||
replace_whitespace=False)
|
||||
for line in text.splitlines())
|
||||
draw.text(position, text, font=font, fill="#000000")
|
||||
def makeSVG(base, properties):
|
||||
tree = etree.parse(base)
|
||||
for id, text in properties.items():
|
||||
if id not in ["count", "art"]:
|
||||
setText(tree, id, text)
|
||||
|
||||
def addCardToBase(baseImg, baseX, cardNum, cardImg):
|
||||
# remove HP mark if there is no hp
|
||||
if "hp" not in properties:
|
||||
removeElement(tree, "hpMark")
|
||||
|
||||
if "art" in properties:
|
||||
art = tree.find('.//*[@id="art"]')
|
||||
if art is not None:
|
||||
art.set("{http://www.w3.org/1999/xlink}href",
|
||||
"file://" + bundle_dir + "/" + properties["art"])
|
||||
return tree
|
||||
|
||||
def addCardToBase(svg, baseImg, baseX, cardNum):
|
||||
# TODO: possible to remove all this writing to temp files?
|
||||
dest = "out/fig" + str(cardNum)
|
||||
svg.write(dest + ".svg")
|
||||
subprocess.call(["inkscape", "-z", "-f", dest + ".svg", "-w",
|
||||
str(CARD_WIDTH), "-e", dest + ".png"])
|
||||
cardImg = Image.open(dest + ".png")
|
||||
baseImg.paste(cardImg, ((cardNum % baseX) * cardImg.width,
|
||||
int(cardNum / baseX) * cardImg.height))
|
||||
|
||||
def makeFace(baseImage, baseX, cardNum, base, card):
|
||||
fig = makeSVG(base, card)
|
||||
addCardToBase(fig, baseImage, baseX, cardNum)
|
||||
|
||||
def makeFaces(deckJson, outfile):
|
||||
# TODO: probably a more optimal way to fit cards
|
||||
baseX = math.ceil(math.sqrt(len(deckJson['deck']) +
|
||||
len(deckJson['character']) * 2))
|
||||
baseImage = Image.new('RGB', (IMG_HERO_DECK.width * baseX,
|
||||
IMG_HERO_DECK.height * baseX))
|
||||
baseX = math.ceil(math.sqrt(len(deckJson['deck']) + len(deckJson['character']) * 2))
|
||||
baseImage = Image.new('RGB', (CARD_WIDTH * baseX,
|
||||
CARD_HEIGHT * baseX))
|
||||
|
||||
cardType = deckJson["type"]
|
||||
|
||||
cardNum = 0
|
||||
# Make a card for each hero character card
|
||||
for card in deckJson['character']:
|
||||
cardFrontImg = IMG_HERO_CHAR_FRONT.copy()
|
||||
draw = ImageDraw.Draw(cardFrontImg)
|
||||
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
|
||||
drawTextIf(draw, (100, 900), FONT_DESCRIPTION, card, 'power', 40)
|
||||
drawTextIf(draw, (53, 260), FONT_CHAR_HP, card, 'hp')
|
||||
addCardToBase(baseImage, baseX, cardNum, cardFrontImg)
|
||||
cardNum += 1
|
||||
# Make a card for each character card
|
||||
if cardType == "hero":
|
||||
for card in deckJson['character']:
|
||||
makeFace(baseImage, baseX, cardNum,
|
||||
os.path.join("images", cardType, "charFront.svg"), card)
|
||||
cardNum += 1
|
||||
|
||||
cardBackImg = IMG_HERO_CHAR_BACK.copy()
|
||||
draw = ImageDraw.Draw(cardBackImg)
|
||||
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
|
||||
drawTextIf(draw, (80, 800), FONT_DESCRIPTION, card, 'incapacitated', 43)
|
||||
addCardToBase(baseImage, baseX, cardNum, cardBackImg)
|
||||
cardNum += 1
|
||||
makeFace(baseImage, baseX, cardNum,
|
||||
os.path.join("images", cardType, "charBack.svg"), card)
|
||||
cardNum += 1
|
||||
|
||||
# Make a card for each card in the deck
|
||||
if cardType == "villain":
|
||||
pass
|
||||
|
||||
# Make a card for each card
|
||||
for card in deckJson['deck']:
|
||||
cardImg = IMG_HERO_DECK.copy()
|
||||
|
||||
if 'hp' in card: # Draw HP marker, if it exists
|
||||
# self mask for transparency
|
||||
cardImg.paste(IMG_TARGET_HP, (510, 36), IMG_TARGET_HP)
|
||||
|
||||
draw = ImageDraw.Draw(cardImg)
|
||||
drawTextIf(draw, (52, 50), FONT_TITLE, card, 'name')
|
||||
drawTextIf(draw, (85, 610), FONT_KEYWORD, card, 'type')
|
||||
drawTextIf(draw, (52, 670), FONT_DESCRIPTION, card, 'description', 46)
|
||||
drawTextIf(draw, (120, 910), FONT_DESCRIPTION, card, 'flavor')
|
||||
drawTextIf(draw, (600, 40), FONT_TARGET_HP, card, 'hp')
|
||||
addCardToBase(baseImage, baseX, cardNum, cardImg)
|
||||
makeFace(baseImage, baseX, cardNum,
|
||||
os.path.join("images", cardType, "card.svg"), card)
|
||||
cardNum += 1
|
||||
|
||||
baseImage.save(outfile + ".png", "PNG")
|
||||
return (baseX, baseX)
|
||||
return baseX
|
||||
|
||||
def makeJson(deckJson, imgWidth, imgHeight, outfile):
|
||||
def makeJson(deckJson, imgWidth, outfile):
|
||||
with open(bundle_dir + "/templates/deck.json") as f:
|
||||
outJson = json.load(f)
|
||||
with open(bundle_dir + "/templates/card.json") as f:
|
||||
|
@ -98,7 +103,7 @@ def makeJson(deckJson, imgWidth, imgHeight, outfile):
|
|||
# number of cards in x and y direction
|
||||
outJson['ObjectStates'][0]['CustomDeck']['1'].update(
|
||||
{"NumWidth": imgWidth,
|
||||
"NumHeight": imgHeight,
|
||||
"NumHeight": imgWidth,
|
||||
#"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png",
|
||||
"FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png",
|
||||
"BackURL": "http://cloud-3.steamusercontent.com/ugc/156906385556221451/CE2C3AFE1759790CB0B532FFD636D05A99EC91F4/"})
|
||||
|
@ -111,7 +116,7 @@ def makeJson(deckJson, imgWidth, imgHeight, outfile):
|
|||
# add front card with back as second state
|
||||
cardOutBack = cardTemplate.copy()
|
||||
cardOutBack.update({"Nickname": card['name'],
|
||||
"Description": "Incapacitated",
|
||||
"Description": "Incapacitated",
|
||||
"CardID": cardNum + 1})
|
||||
cardOut = cardTemplate.copy()
|
||||
cardOut.update({"Nickname": card['name'],
|
||||
|
@ -148,15 +153,5 @@ if __name__ == '__main__':
|
|||
with open(inputJson) as f:
|
||||
deckJson = json.load(f)
|
||||
|
||||
imgWidth, imgHeight = makeFaces(deckJson, outfile)
|
||||
makeJson(deckJson, imgWidth, imgHeight, outfile)
|
||||
|
||||
|
||||
else:
|
||||
with open("hero_3.json") as f:
|
||||
deckJson = json.load(f)
|
||||
|
||||
outfile = "out"
|
||||
|
||||
imgWidth, imgHeight = makeFaces(deckJson, outfile)
|
||||
makeJson(deckJson, imgWidth, imgHeight, outfile)
|
||||
imgWidth = makeFaces(deckJson, outfile)
|
||||
makeJson(deckJson, imgWidth, outfile)
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 409 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 492 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 2.5 MiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 2.5 MiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"Name": "Card",
|
||||
"Transform": {
|
||||
"posX": 0,
|
||||
"posY": 0,
|
||||
"posZ": 0,
|
||||
"rotX": 0,
|
||||
"rotY": 0,
|
||||
"rotZ": 0,
|
||||
"scaleX": 1.0,
|
||||
"scaleY": 1.0,
|
||||
"scaleZ": 1.0
|
||||
},
|
||||
"Nickname": "",
|
||||
"Description": "",
|
||||
"ColorDiffuse": {
|
||||
"r": 0.713235259,
|
||||
"g": 0.713235259,
|
||||
"b": 0.713235259
|
||||
},
|
||||
"Locked": false,
|
||||
"Grid": true,
|
||||
"Snap": true,
|
||||
"Autoraise": true,
|
||||
"Sticky": true,
|
||||
"Tooltip": true,
|
||||
"CardID": 0,
|
||||
"SidewaysCard": false
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"SaveName": "",
|
||||
"GameMode": "",
|
||||
"Date": "",
|
||||
"Table": "",
|
||||
"Sky": "",
|
||||
"Note": "",
|
||||
"Rules": "",
|
||||
"PlayerTurn": "",
|
||||
"ObjectStates": [
|
||||
{
|
||||
"Name": "DeckCustom",
|
||||
"Transform": {
|
||||
"posX": 0,
|
||||
"posY": 0,
|
||||
"posZ": 0,
|
||||
"rotX": 0,
|
||||
"rotY": 0.0,
|
||||
"rotZ": 0.0,
|
||||
"scaleX": 1.0,
|
||||
"scaleY": 1.0,
|
||||
"scaleZ": 1.0
|
||||
},
|
||||
"Nickname": "",
|
||||
"Description": "",
|
||||
"ColorDiffuse": {
|
||||
"r": 0.713239133,
|
||||
"g": 0.713239133,
|
||||
"b": 0.713239133
|
||||
},
|
||||
"Grid": true,
|
||||
"Locked": false,
|
||||
"SidewaysCard": false,
|
||||
"DeckIDs": [],
|
||||
"CustomDeck": {
|
||||
"1": {
|
||||
"FaceURL": "SET ME",
|
||||
"BackURL": "SET ME"
|
||||
}
|
||||
},
|
||||
"ContainedObjects": []
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue