]> git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/batch/badges/gen_badges.py
Added support for transparent background in badges (bg=-1)
[xonotic/xonstat.git] / xonstat / batch / badges / gen_badges.py
index d930d1468928263c1477da153f8d98f78b1dfcb7..e2b75de46186d7a22a8d52185d257a3dfa2aa6ca 100644 (file)
@@ -1,17 +1,16 @@
 #-*- coding: utf-8 -*-
 
 import sys
+import re
 import cairo as C
+from datetime import datetime
 import sqlalchemy as sa
 import sqlalchemy.sql.functions as func
-from datetime import datetime
-from mako.template import Template
+from colorsys import rgb_to_hls, hls_to_rgb
 from os import system
 from pyramid.paster import bootstrap
 from xonstat.models import *
-from xonstat.views.player import player_info_data
-from xonstat.util import qfont_decode
-from colorsys import rgb_to_hls, hls_to_rgb
+from xonstat.util import qfont_decode, _all_colors
 
 
 # similar to html_colors() from util.py
@@ -59,7 +58,7 @@ PLAYTIME_WIDTH  = 218
 
 # parameters to affect the output, could be changed via URL
 params = {
-    'bg':           8,                      # 0 - black, 1 - dark_wall, ...
+    'bg':           1,                      # -1 - none, 0 - black, 1 - dark_wall, ...
     'overlay':      0,                      # 0 - none, 1 - default overlay, ...
     'font':         0,                      # 0 - xolonium, 1 - dejavu sans
 }
@@ -77,7 +76,7 @@ for arg in sys.argv[1:]:
 
 def get_data(player):
     """Return player data as dict.
-    
+
     This function is similar to the function in player.py but more optimized
     for this purpose.
     """
@@ -86,11 +85,11 @@ def get_data(player):
     # wins/losses
     # kills/deaths
     # duel/dm/tdm/ctf elo + rank
-    
+
     player_id = player.player_id
-    
+
     total_stats = {}
-    
+
     games_played = DBSession.query(
             Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\
             filter(Game.game_id == PlayerGameStat.game_id).\
@@ -98,7 +97,7 @@ def get_data(player):
             group_by(Game.game_type_cd).\
             order_by(func.count().desc()).\
             limit(3).all()  # limit to 3 gametypes!
-    
+
     total_stats['games'] = 0
     total_stats['games_breakdown'] = {}  # this is a dictionary inside a dictionary .. dictception?
     total_stats['games_alivetime'] = {}
@@ -108,21 +107,31 @@ def get_data(player):
         total_stats['gametypes'].append(game_type_cd)
         total_stats['games_breakdown'][game_type_cd] = games
         total_stats['games_alivetime'][game_type_cd] = alivetime
-    
+
     (total_stats['kills'], total_stats['deaths'], total_stats['alivetime'],) = DBSession.query(
             func.sum(PlayerGameStat.kills),
             func.sum(PlayerGameStat.deaths),
             func.sum(PlayerGameStat.alivetime)).\
             filter(PlayerGameStat.player_id == player_id).\
             one()
-    
-    (total_stats['wins'],) = DBSession.query(
-            func.count("*")).\
-            filter(Game.game_id == PlayerGameStat.game_id).\
-            filter(PlayerGameStat.player_id == player_id).\
-            filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\
-            one()
-    
+
+#    (total_stats['wins'],) = DBSession.query(
+#            func.count("*")).\
+#            filter(Game.game_id == PlayerGameStat.game_id).\
+#            filter(PlayerGameStat.player_id == player_id).\
+#            filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\
+#            one()
+
+    (total_stats['wins'],) = DBSession.\
+            query("total_wins").\
+            from_statement(
+                "select count(*) total_wins "
+                "from games g, player_game_stats pgs "
+                "where g.game_id = pgs.game_id "
+                "and player_id=:player_id "
+                "and (g.winner = pgs.team or pgs.rank = 1)"
+            ).params(player_id=player_id).one()
+
     ranks = DBSession.query("game_type_cd", "rank", "max_rank").\
             from_statement(
                 "select pr.game_type_cd, pr.rank, overall.max_rank "
@@ -134,7 +143,7 @@ def get_data(player):
                 "and player_id = :player_id "
                 "order by rank").\
             params(player_id=player_id).all()
-    
+
     ranks_dict = {}
     for gtc,rank,max_rank in ranks:
         ranks_dict[gtc] = (rank, max_rank)
@@ -143,26 +152,25 @@ def get_data(player):
             filter_by(player_id=player_id).\
             order_by(PlayerElo.elo.desc()).\
             all()
-    
+
     elos_dict = {}
     for elo in elos:
         if elo.games > 32:
             elos_dict[elo.game_type_cd] = elo.elo
-    
+
     data = {
             'player':player,
             'total_stats':total_stats,
             'ranks':ranks_dict,
             'elos':elos_dict,
         }
-        
+
     #print data
     return data
 
 
 def render_image(data):
     """Render an image from the given data fields."""
-    
     font = "Xolonium"
     if params['font'] == 1:
         font = "DejaVu Sans"
@@ -175,7 +183,7 @@ def render_image(data):
 
     ## create background
 
-    surf = C.ImageSurface(C.FORMAT_RGB24, WIDTH, HEIGHT)
+    surf = C.ImageSurface(C.FORMAT_ARGB32, WIDTH, HEIGHT)
     ctx = C.Context(surf)
     ctx.set_antialias(C.ANTIALIAS_GRAY)
 
@@ -184,7 +192,13 @@ def render_image(data):
         ctx.rectangle(0, 0, WIDTH, HEIGHT)
         ctx.set_source_rgb(0.04, 0.04, 0.04)  # bgcolor of Xonotic forum
         ctx.fill()
-    
+    elif params['bg'] < 0:
+        ctx.save()
+        ctx.set_operator(C.OPERATOR_SOURCE)
+        ctx.set_source_rgba(1, 1, 1, 0)
+        ctx.paint()
+        ctx.restore()
+
     # draw background image (try to get correct tiling, too)
     if params['bg'] > 0:
         bg = None
@@ -216,7 +230,7 @@ def render_image(data):
                     ctx.paint()
                     bg_yoff += bg_h
                 bg_xoff += bg_w
-    
+
     # draw overlay graphic
     if params['overlay'] > 0:
         overlay = None
@@ -231,7 +245,7 @@ def render_image(data):
     ## draw player's nickname with fancy colors
     
     # deocde nick, strip all weird-looking characters
-    qstr = qfont_decode(player.nick).replace('^^', '^').replace(u'\x00', ' ')
+    qstr = qfont_decode(player.nick).replace('^^', '^').replace(u'\x00', '')
     chars = []
     for c in qstr:
         if ord(c) < 128:
@@ -254,51 +268,53 @@ def render_image(data):
                 xoff, yoff, tw, th = ctx.text_extents(stripped_nick)[:4]
                 if tw > NICK_MAXWIDTH:
                     ctx.set_font_size(12)
-    
+    xoff, yoff, tw, th = ctx.text_extents("_")[:4]
+    space_w = tw
     
     # split up nick into colored segments and draw each of them
     
     # split nick into colored segments
-    parts = []
-    pos = 1
-    while True:
-        npos = qstr.find('^', pos)
-        if npos < 0:
-            parts.append(qstr[pos-1:])
-            break;
-        parts.append(qstr[pos-1:npos])
-        pos = npos+1
-    
     xoffset = 0
-    for txt in parts:
+    _all_colors = re.compile(r'(\^\d|\^x[\dA-Fa-f]{3})')
+    #print qstr, _all_colors.findall(qstr), _all_colors.split(qstr)
+    
+    parts = _all_colors.split(qstr)
+    while len(parts) > 0:
+        tag = None
+        txt = parts[0]
+        if _all_colors.match(txt):
+            tag = txt[1:]  # strip leading '^'
+            if len(parts) < 2:
+                break
+            txt = parts[1]
+            del parts[1]
+        del parts[0]
+            
+        if not txt or len(txt) == 0:
+            # only colorcode and no real text, skip this
+            continue
+            
         r,g,b = _dec_colors[7]
         try:
-            if txt.startswith('^'):
-                txt = txt[1:]
-                if txt.startswith('x'):
-                    r = int(txt[1] * 2, 16) / 255.0
-                    g = int(txt[2] * 2, 16) / 255.0
-                    b = int(txt[3] * 2, 16) / 255.0
-                    hue, light, satur = rgb_to_hls(r, g, b)
-                    if light < _contrast_threshold:
-                        light = _contrast_threshold
-                        r, g, b = hls_to_rgb(hue, light, satur)
-                    txt = txt[4:]
-                else:
-                    r,g,b = _dec_colors[int(txt[0])]
-                    txt = txt[1:]
+            if tag.startswith('x'):
+                r = int(tag[1] * 2, 16) / 255.0
+                g = int(tag[2] * 2, 16) / 255.0
+                b = int(tag[3] * 2, 16) / 255.0
+                hue, light, satur = rgb_to_hls(r, g, b)
+                if light < _contrast_threshold:
+                    light = _contrast_threshold
+                    r, g, b = hls_to_rgb(hue, light, satur)
+            else:
+                r,g,b = _dec_colors[int(tag[0])]
         except:
             r,g,b = _dec_colors[7]
         
-        if len(txt) < 1:
-            # only colorcode and no real text, skip this
-            continue
-        
         ctx.set_source_rgb(r, g, b)
         ctx.move_to(NICK_POS[0] + xoffset, NICK_POS[1])
         ctx.show_text(txt)
 
         xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
+        tw += (len(txt)-len(txt.strip())) * space_w  # account for lost whitespaces
         xoffset += tw + 2
 
 
@@ -315,6 +331,7 @@ def render_image(data):
         ctx.move_to(GAMES_POS[0]+xoffset-xoff-tw/2, GAMES_POS[1]-yoff-4)
         ctx.show_text(txt)
         
+
         old_aa = ctx.get_antialias()
         ctx.set_antialias(C.ANTIALIAS_NONE)
         ctx.set_source_rgb(0.8, 0.8, 0.8)
@@ -326,7 +343,7 @@ def render_image(data):
         ctx.line_to(GAMES_POS[0]+xoffset+GAMES_WIDTH/2-5, GAMES_POS[1]+32)
         ctx.stroke()
         ctx.set_antialias(old_aa)
-        
+
         if not elos.has_key(gt) or not ranks.has_key(gt):
             ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
             ctx.set_font_size(12)
@@ -349,7 +366,7 @@ def render_image(data):
             
             ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
             ctx.set_font_size(8)
-            ctx.set_source_rgb(0.8, 0.8, 0.8)
+            ctx.set_source_rgb(0.8, 0.8, 1.0)
             txt = "Rank %d of %d" % ranks[gt]
             xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
             ctx.move_to(GAMES_POS[0]+xoffset-xoff-tw/2, GAMES_POS[1]+25-yoff-3)
@@ -359,11 +376,12 @@ def render_image(data):
 
 
     # print win percentage
-    
-    ctx.rectangle(WINLOSS_POS[0]-WINLOSS_WIDTH/2, WINLOSS_POS[1]-7, WINLOSS_WIDTH, 15)
-    ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
-    ctx.fill();
-    
+
+    if params['overlay'] == 0:
+        ctx.rectangle(WINLOSS_POS[0]-WINLOSS_WIDTH/2, WINLOSS_POS[1]-7, WINLOSS_WIDTH, 15)
+        ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
+        ctx.fill();
+
     ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
     ctx.set_font_size(10)
     ctx.set_source_rgb(0.8, 0.8, 0.8)
@@ -414,11 +432,12 @@ def render_image(data):
 
 
     # print kill/death ratio
-    
-    ctx.rectangle(KILLDEATH_POS[0]-KILLDEATH_WIDTH/2, KILLDEATH_POS[1]-7, KILLDEATH_WIDTH, 15)
-    ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
-    ctx.fill()
-    
+
+    if params['overlay'] == 0:
+        ctx.rectangle(KILLDEATH_POS[0]-KILLDEATH_WIDTH/2, KILLDEATH_POS[1]-7, KILLDEATH_WIDTH, 15)
+        ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
+        ctx.fill()
+
     ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
     ctx.set_font_size(10)
     ctx.set_source_rgb(0.8, 0.8, 0.8)
@@ -434,6 +453,7 @@ def render_image(data):
         txt = "%.3f" % round(ratio, 3)
     except:
         ratio = 0
+
     ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
     ctx.set_font_size(12)
     if ratio >= 3:
@@ -469,11 +489,12 @@ def render_image(data):
 
 
     # print playing time
-    
-    ctx.rectangle( PLAYTIME_POS[0]-PLAYTIME_WIDTH/2, PLAYTIME_POS[1]-7, PLAYTIME_WIDTH, 14)
-    ctx.set_source_rgba(0.8, 0.8, 0.8, 0.6)
-    ctx.fill();
-    
+
+    if params['overlay'] == 0:
+        ctx.rectangle( PLAYTIME_POS[0]-PLAYTIME_WIDTH/2, PLAYTIME_POS[1]-7, PLAYTIME_WIDTH, 14)
+        ctx.set_source_rgba(0.8, 0.8, 0.8, 0.6)
+        ctx.fill();
+
     ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
     ctx.set_font_size(10)
     ctx.set_source_rgb(0.1, 0.1, 0.1)
@@ -494,38 +515,51 @@ req.matchdict = {'id':3}
 
 print "Requesting player data from db ..."
 start = datetime.now()
-players = DBSession.query(Player).\
-        filter(Player.player_id == PlayerElo.player_id).\
-        filter(Player.nick != None).\
-        filter(Player.player_id > 2).\
-        filter(Player.active_ind == True).\
-        limit(NUM_PLAYERS).all()
+players = []
+if NUM_PLAYERS:
+    players = DBSession.query(Player).\
+            filter(Player.player_id == PlayerElo.player_id).\
+            filter(Player.nick != None).\
+            filter(Player.player_id > 2).\
+            filter(Player.active_ind == True).\
+            limit(NUM_PLAYERS).all()
+else:
+    players = DBSession.query(Player).\
+            filter(Player.player_id == PlayerElo.player_id).\
+            filter(Player.nick != None).\
+            filter(Player.player_id > 2).\
+            filter(Player.active_ind == True).\
+            all()
+
 stop = datetime.now()
-print "Query took %.2f seconds" % (stop-start).total_seconds()
+td = stop-start
+total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+print "Query took %.2f seconds" % (total_seconds)
 
-print "Creating badges for %d active players ..." % len(players)
+print "Creating badges for %d players ..." % len(players)
 start = datetime.now()
 data_time, render_time = 0,0
 for player in players:
     req.matchdict['id'] = player.player_id
-    
+
     sstart = datetime.now()
-    #data = player_info_data(req)
     data = get_data(player)
     sstop = datetime.now()
-    data_time += (sstop-sstart).total_seconds()
-    
-    print "\r  #%-5d" % player.player_id,
-    sys.stdout.flush()
+    td = sstop-sstart
+    total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+    data_time += total_seconds
 
     sstart = datetime.now()
     render_image(data)
     sstop = datetime.now()
-    render_time += (sstop-sstart).total_seconds()
-print
+    td = sstop-sstart
+    total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+    render_time += total_seconds
 
 stop = datetime.now()
-print "Creating the badges took %.2f seconds (%.2f s per player)" % ((stop-start).total_seconds(), (stop-start).total_seconds()/float(len(players)))
-print " Total time for getting data: %.2f s" % data_time
-print " Total time for renering images: %.2f s" % render_time
+td = stop-start
+total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+print "Creating the badges took %.2f seconds (%.2f s per player)" % (total_seconds, total_seconds/float(len(players)))
+print "Total time for redering images: %.2f s" % render_time
+print "Total time for getting data: %.2f s" % data_time