def _draw_cell(self,x,y,window): position = XY(self.position.x+ x * self.scale, self.position.y + y * self.scale) bounds = XY(self.position.x+ (x + 1) * self.scale, self.position.y + (y+ 1) * self.scale) box = self.image_class(position, bounds) box.setFill('red' if self.board.cells[x][y].is_alive == True else 'grey') box.draw(window) self.board.cells[x][y].changed = False
def project( bbox: s2.LatLngRect, size: XY, offset: XY, latlnglines: List[List[s2.LatLng]]) -> List[List[Tuple[float, float]]]: min_x = lng2x(bbox.lng_lo().degrees) d_x = lng2x(bbox.lng_hi().degrees) - min_x while d_x >= 2: d_x -= 2 while d_x < 0: d_x += 2 min_y = lat2y(bbox.lat_lo().degrees) max_y = lat2y(bbox.lat_hi().degrees) d_y = abs(max_y - min_y) scale = size.x / d_x if size.x / size.y <= d_x / d_y else size.y / d_y offset = offset + 0.5 * (size - scale * XY(d_x, -d_y)) - scale * XY( min_x, min_y) lines = [] for latlngline in latlnglines: line = [] for latlng in latlngline: if bbox.contains(latlng): line.append((offset + scale * latlng2xy(latlng)).tuple()) else: if len(line) > 0: lines.append(line) line = [] if len(line) > 0: lines.append(line) return lines
def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY): """For each track, draw it on the poster.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") cell_size, counts = utils.compute_grid(len(self.poster.tracks), size) if cell_size is None or counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] spacing_x = (0 if count_x <= 1 else (size.x - cell_size * count_x) / (count_x - 1)) spacing_y = (0 if count_y <= 1 else (size.y - cell_size * count_y) / (count_y - 1)) offset.x += (size.x - count_x * cell_size - (count_x - 1) * spacing_x) / 2 offset.y += (size.y - count_y * cell_size - (count_y - 1) * spacing_y) / 2 for (index, tr) in enumerate(self.poster.tracks): p = XY(index % count_x, index // count_x) * XY( cell_size + spacing_x, cell_size + spacing_y) self._draw_track( dr, tr, 0.9 * XY(cell_size, cell_size), offset + 0.05 * XY(cell_size, cell_size) + p, )
def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY): """Draw the circular Poster using distances broken down by time""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") if self.poster.length_range_by_date is None: return years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 4) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in range(self.poster.years.from_year, self.poster.years.to_year + 1): self._draw_year(dr, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY): """Iterate through the Poster's years, creating a calendar for each.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 8) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in range(self.poster.years.from_year, self.poster.years.to_year + 1): self._draw(dr, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def draw(self, drawer, output): """Set the Poster's drawer and draw the tracks.""" self.tracks_drawer = drawer d = svgwrite.Drawing(output, (f"{self.width}mm", f"{self.height}mm")) d.viewbox(0, 0, self.width, self.height) d.add( d.rect((0, 0), (self.width, self.height), fill=self.colors["background"])) self.__draw_header(d) self.__draw_footer(d) self.__draw_tracks(d, XY(self.width - 20, self.height - 30 - 30), XY(10, 30)) d.save()
def test_one_area(self): m = XY(-100, -100, 100, 100) m.add_area(10, 20, -30, -40, 'test') self.assertEqual(len(m.areas), 5) self.assertEqual(m.get_area_count(), (1, 4)) self.assertEqual(m.find_area(0, 0).name, 'test') self.assertEqual(m.find_area(20, 10).name, None)
def draw(self, drawer, output): """Set the Poster's drawer and draw the tracks.""" self.tracks_drawer = drawer height = self.height width = self.width if self.drawer_type == "plain": height = height - 100 self.colors["background"] = "#1a1a1a" self.colors["track"] = "red" self.colors["special"] = "yellow" self.colors["text"] = "#e1ed5e" d = svgwrite.Drawing(output, (f"{width}mm", f"{height}mm")) d.viewbox(0, 0, self.width, height) d.add(d.rect((0, 0), (width, height), fill=self.colors["background"])) if not self.drawer_type == "plain": self.__draw_header(d) self.__draw_footer(d) self.__draw_tracks(d, XY(width - 20, height - 30 - 30), XY(10, 30)) else: self.__draw_tracks(d, XY(width - 20, height), XY(10, 0)) d.save()
def click(self,pos): if self._within_bounds(pos): self.board.toggle(XY(int((pos.x-self.position.x)//self.scale),int((pos.y-self.position.y)//self.scale)))
def _draw_year(self, dr: svgwrite.Drawing, size: XY, offset: XY, year: int): min_size = min(size.x, size.y) outer_radius = 0.5 * min_size - 6 radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius) center = offset + 0.5 * size if self._rings: self._draw_rings(dr, center, radius_range) year_style = f"dominant-baseline: central; font-size:{min_size * 4.0 / 80.0}px; font-family:Arial;" month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;" dr.add( dr.text( f"{year}", insert=center.tuple(), fill=self.poster.colors["text"], text_anchor="middle", alignment_baseline="middle", style=year_style, )) df = 360.0 / (366 if calendar.isleap(year) else 365) day = 0 date = datetime.date(year, 1, 1) while date.year == year: text_date = date.strftime("%Y-%m-%d") a1 = math.radians(day * df) a2 = math.radians((day + 1) * df) if date.day == 1: (_, last_day) = calendar.monthrange(date.year, date.month) a3 = math.radians((day + last_day - 1) * df) sin_a1, cos_a1 = math.sin(a1), math.cos(a1) sin_a3, cos_a3 = math.sin(a3), math.cos(a3) r1 = outer_radius + 1 r2 = outer_radius + 6 r3 = outer_radius + 2 dr.add( dr.line( start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(), end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(), stroke=self.poster.colors["text"], stroke_width=0.3, )) path = dr.path( d=("M", center.x + r3 * sin_a1, center.y - r3 * cos_a1), fill="none", stroke="none", ) path.push( f"a{r3},{r3} 0 0,1 {r3 * (sin_a3 - sin_a1)},{r3 * (cos_a1 - cos_a3)}" ) dr.add(path) tpath = svgwrite.text.TextPath(path, date.strftime("%B"), startOffset=(0.5 * r3 * (a3 - a1))) text = dr.text( "", fill=self.poster.colors["text"], text_anchor="middle", style=month_style, ) text.add(tpath) dr.add(text) if text_date in self.poster.tracks_by_date: self._draw_circle_segment( dr, self.poster.tracks_by_date[text_date], a1, a2, radius_range, center, ) day += 1 date += datetime.timedelta(1)
import json, sys from os import path from PIL import Image, ImageDraw from argparse import ArgumentParser, ArgumentTypeError from xy import XY page_sizes = { 'none': None, 'a0': XY(33.11, 46.81), 'a1': XY(23.39, 33.11), 'a2': XY(16.54, 23.39), 'a3': XY(11.69, 16.54), 'a4': XY(8.27, 11.69), 'letter': XY(8.5, 11), 'legal': XY(8.5, 14), 'ledger': XY(11, 17), 'tabloid': XY(17, 11), '11x17': XY(11, 17) } def setupArgParser(parser=None): if not parser: parser = ArgumentParser( description= "Split an image for multi-page printing, based on DPI specified in the image" ) parser.add_argument('source_image', help='The image to split') parser.add_argument('output_base', nargs='?',
def saveTiledImages(image, basename, pageXY_inches, margin_inches, overlap_inches, ext='.png', dpi=None, register_marks=True): if not dpi: try: dpi = image.info['dpi'][0] except: sys.exit( "savedTiledImages: No DPI specified explicitly or found in image" ) if not pageXY_inches: # just save full size image fname = basename + ext image.save(fname, dpi=(dpi, dpi)) return [fname] # early return # create the split up images margin_px = int(margin_inches * dpi) overlap_px = int(overlap_inches * dpi) tileXY_px = (pageXY_inches * dpi - XY(1, 1) * 2 * margin_px).ints() fullXY_px = XY(*image.size) # Try tiling both portrait and landscape modes tiling1 = XY(subdivide(fullXY_px.x, tileXY_px.x, overlap_px), subdivide(fullXY_px.y, tileXY_px.y, overlap_px)) pages1 = XY(*map(len, tiling1)) tiling2 = XY(subdivide(fullXY_px.x, tileXY_px.y, overlap_px), subdivide(fullXY_px.y, tileXY_px.x, overlap_px)) pages2 = XY(*map(len, tiling2)) n1 = pages1.x * pages1.y n2 = pages2.x * pages2.y if n1 < n2: print "Using standard (portrait) tiling on %d pages (%dx%d vs %dx%d)" % ( n1, pages1.x, pages1.y, pages2.x, pages2.y) tiling = tiling1 pages = pages1 else: print "Using rotated (landscape) tiling on %d pages (%dx%d vs %dx%d)" % ( n2, pages2.x, pages2.y, pages1.x, pages1.y) tiling = tiling2 pages = pages2 tileXY_px = tileXY_px.swap() outputs = [] for i, x in enumerate(map(int, tiling.x)): for j, y in enumerate(map(int, tiling.y)): tile = image.crop((x, y, min(fullXY_px.x, x + tileXY_px.x), min(fullXY_px.y, y + tileXY_px.y))) tile.load() # force a non-destructive copy # possibly draw register marks if register_marks: canvas = ImageDraw.Draw(tile) xt, yt = tile.size d = overlap_px / 10 xo, yo = overlap_px / 2, overlap_px / 2 if i > 0 or j > 0: canvas.line((xo - d, yo, 0, yo), width=1, fill='black') canvas.line((xo, yo - d, xo, 0), width=1, fill='black') xo, yo = overlap_px / 2, yt - overlap_px / 2 if i > 0 or j + 1 < pages.y: canvas.line((xo - d, yo, 0, yo), width=1, fill='black') canvas.line((xo, yo + d, xo, yt), width=1, fill='black') xo, yo = xt - overlap_px / 2, overlap_px / 2 if i + 1 < pages.x or j > 0: canvas.line((xo + d, yo, xt, yo), width=1, fill='black') canvas.line((xo, yo - d, xo, 0), width=1, fill='black') xo, yo = xt - overlap_px / 2, yt - overlap_px / 2 if i + 1 < pages.x or j + 1 < pages.y: canvas.line((xo + d, yo, xt, yo), width=1, fill='black') canvas.line((xo, yo + d, xo, yt), width=1, fill='black') fname = basename + '%02d%02d' % (i + 1, j + 1) + ext outputs.append(fname) tile.save(fname, dpi=(dpi, dpi)) return outputs
def latlng2xy(latlng: s2.LatLng) -> XY: return XY(lng2x(latlng.lng().degrees), lat2y(latlng.lat().degrees))
def __init__(self,board, position = XY(0,0), scale = 5, image_class = Rectangle): self.board = board self.image_class= image_class self.position = position self.scale = scale self.bounds = XY(position.x + scale * board.size, position.y + scale * board.size)
class Board: hexXY = XY(188,217) # size of tile images in pixels unitTL = XY(44,80) # top-left corner of unit symbol tagOffset = XY(39, -42) # offset of medal tag center from hex center badgeSize = XY(64,64) # size to scale unit badges to # background_color = (79,105,66) # olive green background_color = (255,255,255) # white border_color = (0,0,0) # black border_width = 1 # pixels marginXY = hexXY.doti((1/3., 1/2.)) # whitespace around tiles dash_color = (214,35,44) # dark red dash_length = (36,9) # black/white length in pixels dash_width = 7 # pixels formats = { 'standard' : XY(13,9), # 2570 x 1737 px @ 90DPI = 28.6" x 19.3" 'overlord' : XY(26,9), # 5014 x 1737 px @ 90DPI = 55.7" x 19.3" 'brkthru' : XY(13,17) # 2570 x 3039 px @ 90DPI = 28.6" x 33.8" } # the hexagon keys we can deal with, layered from bottom up drawing_layers = [ 'terrain', # hexagonal terrain tile 'lines', # pseudo-layer to draw dotted lines between flanks 'rect_terrain', # rectangular tile, like a bunker 'obstacle', # 3D obstacle like sandbags 'unit', # unit indicator, includes nbr_units and badge sublayers 'tags', # token like victory marker 'text' # map label ] # return a list of background terrain names based on board format/style @staticmethod def backgroundTerrain(face, format): faces = { 'country' : [['countryside']], 'winter' : [['snow']], 'beach' : [['countryside'],['beach'],['coast'],['ocean']], 'desert' : [['desert']] } names = faces[face] if face == 'beach': if format == 'brkthru': repeat = (11,3,1,2) else: repeat = (4,3,1,1) else: if format == 'brkthru': repeat = (17,) else: repeat = (9,) return sum(map(operator.mul, names, repeat),[]) # coordinates - we use a system where each row and column counts 0,1,2,... # with top right starting from 0,0 @staticmethod def coords(row, col): xy = Board.hexXY.doti((col + (row%2)/2., row * 3 / 4.)) return xy + Board.marginXY # convert from m44 format where even rows have even cols, odd rows have odd cols @staticmethod def coords2(row, col2): return Board.coords(row, (col2 - (row%2))/2) def __init__(self, m44file): scenario = json.load(open(m44file)) self.info = scenario['board'] # info is a dictionary with board details like: # u'labels': [], # u'hexagons': [{u'terrain': {u'name': u'highground'}, u'col': 0, u'row': 0}, ... # u'type': u'STANDARD', # u'face': u'WINTER'} self.game_info = scenario['game_info'] # Scenario name is in a localized block, so take first localization # (maybe better to prefer a specific language?) # scenario = { "text": { "en": { "name": "My title", ... try: self.text = scenario['text'].values()[0] except: self.text = {} if not self.text.has_key('name'): self.text['name'] = '(unnamed scenario' # get the size and background icon generator format,face = self.info['type'].lower(),self.info['face'].lower() self.cols, self.rows = Board.formats[format] self.rowStyles = Board.backgroundTerrain(face,format) def render(self, icons, skipLayers = [], hexWidth = 2.0866): # create a blank empty board image with correct size and scaling (DPI) size = Board.marginXY * 2 + \ Board.hexXY.doti( (self.cols, (self.rows*3+1)/4.) ) board = Image.new('RGB', size, Board.background_color) canvas = ImageDraw.Draw(board) # use hexWidthto choose a particular hex width in inches # actual M44 tiles are 2.0866" (53mm) across the flats dpi = int(round(Board.hexXY.x / hexWidth)) try: font = ImageFont.truetype('verdanab.ttf',32) except: logging.warn("Couldn't open VerdanaBold TTF, using (ugly) system default") font = ImageFont.load_default() # paint the board background outline = icons.getImage('outline') for row in xrange(self.rows): name = self.rowStyles[row] image = icons.getImage(name) if not image: logging.warn("No background image for %s"%name) continue for col in xrange(self.cols - (row%2)): # skip last hex on odd rows xy = Board.coords(row,col) if outline: board.paste(outline, tuple(xy), outline) board.paste(image, tuple(xy), image) # medal1 - Allies Medal # medal2 - German Medal # <note there isn't a medal3> # medal4 - Victoria Cross # medal5 - Italian Medal of Valor # medal6 - Hero of the Soviet Union Medal # medal7 - Order of the Golden Kite Medal medal_dict = { # Default axis / allies medals in side_player[1|2] value 'ALLIES' : 1, 'AXIS' : 2, # Country-specific medals, coded in country_player[1|2] value 'US' : 1, 'DE' : 2, 'GB' : 4, 'IT' : 5, 'RU' : 6, 'JP' : 7 } # paint the victory medals, with player1 at the top (flipped) # and player2 at the bottom. for p in ['1','2']: vp = self.game_info.get('victory_player' + p, 6) side = self.game_info.get('side_player' + p, '') country = self.game_info.get('country_player' + p, '') medal_num = medal_dict.get(country, None) if not medal_num: medal_num = medal_dict.get(side, None) medal_name = 'medal' + (medal_num and `medal_num`) or p medal = icons.getImage(medal_name) if not medal: logging.warn("Couldn't find victory marker image %s"%name) continue medal = medal.crop(medal.getbbox()) if p == '1': # Draw top medals upside facing board edge medal = ImageOps.flip(medal) medal = medal.resize((XY(*medal.size) * 1.5).ints(),Image.ANTIALIAS) mxy = XY(*medal.size) for col in xrange(self.cols-vp,self.cols): xy = Board.coords(0, col) - mxy.doti((1/2.,3/4.)) if p == '2': # Position bottom medals by reflection xy = - xy - mxy + board.size board.paste(medal,tuple(xy),medal) # label the scenario canvas.text(Board.marginXY.doti( (1/2., 1/3.) ), self.text['name'], fill = 'black', font=font) # warn about layers we won't deal with for hexagon in self.info['labels'] + self.info['hexagons']: if any(k not in Board.drawing_layers + ['col','row'] for k in hexagon.keys()): logging.warn('unknown key in %s'%`hexagon.keys()`) # now paint on the overlay elements for key in Board.drawing_layers: if key in skipLayers: continue # skipping this layer? if key is 'lines': # placeholder for flank lines col = 0 while col < self.cols: for inc in [4,5,4]: col += inc if col >= self.cols: break # Find starting point of dashed flank line (x,y1) = Board.coords(0,col) x -= Board.dash_width / 2 - 2 y1 += Board.hexXY.y/4 # Find ending point y2 = Board.coords(self.rows,0).y # Draw the dashed line y = y1 while y < y2: ye = min(y2, y+Board.dash_length[0]) canvas.line([(x,y),(x,ye)], fill=Board.dash_color, width=Board.dash_width) y += sum(Board.dash_length) continue # on to next layer hexagons = self.info['labels' if key is 'text' else 'hexagons'] for hexagon in hexagons: col,row = hexagon['col'],hexagon['row'] content = hexagon.get(key,None) if not content: continue # make everything a list for simplicity if type(content) is not ListType: contents = [content] else: contents = content xy = Board.coords2(row,col) if key is 'text': for (i,content) in enumerate(contents): wh = XY(*canvas.textsize(content, font=font)) pos = xy + Board.hexXY.doti( (1/2., 3/4.) ) \ - wh.doti( (1/2., 1.1*(len(contents)/2. - i)) ) canvas.text(pos, content, fill="black", font=font) continue # on to next hex # sort contents by name to get consistent order for tags contents.sort(key = lambda c: c['name']) for i,content in enumerate(contents): name = content['name'] image = icons.getImage(name, content.get('orientation',1)) if not image: logging.warn("(col=%d, row=%d): No image for %s"%( col,row,name)) continue # hack to deal with multiple medal tags in a single hex # but won't work for things like 'battle stars' (named "tag1") # which is already centered in lower right if i > 0: offset = Board.tagOffset.dot( [(1,1),(-1,-1),(-1,1),(1,-1)][i%4]) - Board.tagOffset logging.debug("%s:%d:%s at (col=%d, row=%d) - offset by %s"%( key,i,name, col, row, `offset`)) xy = xy + offset if key is not 'tags': logging.warn( "Didn't expect multiple instances of [%s] at (col=%d, row=%d)"%(key, col, row)) elif i > 3: logging.warn( "Can't deal with more than four tags at (col=%d, row=%d)"%(col,row)) board.paste(image,tuple(xy),image) if i > 0: # center of medal tag is top-right, about XY(133,66), #, i.e about +39, -42 from center of hex XY(188,217) # for subsequent ones, move them around the hex # to bottom-left, bottom-right, top-left? logging.debug('(col=%d,row=%d), name=%s, item #=%d'%( col,row,name,i)) # handle nbr_units and badge attributes within unit layer if content.has_key('badge'): # unit badges # badges are not padded to hex size, and too small # so resize and center on unit top left corner image = icons.getImage(content['badge']) image = image.resize(Board.badgeSize,Image.ANTIALIAS) if image: pos = xy + Board.unitTL - Board.badgeSize / 2 board.paste(image, tuple(pos), image) if content.has_key('nbr_units'): image = icons.getImage('nbr_units', int(content['nbr_units'])) if image: board.paste(image, tuple(xy), image) board = ImageOps.expand( board, border=Board.border_width, fill=Board.border_color) board.info['dpi'] = (dpi,dpi) return board
def _draw(self, dr: svgwrite.Drawing, size: XY, offset: XY, year: int): min_size = min(size.x, size.y) year_size = min_size * 4.0 / 80.0 year_style = f"font-size:{year_size}px; font-family:Arial;" month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;" day_style = f"dominant-baseline: central; font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;" day_length_style = f"font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;" dr.add( dr.text( f"{year}", insert=offset.tuple(), fill=self.poster.colors["text"], alignment_baseline="hanging", style=year_style, )) offset.y += year_size size.y -= year_size count_x = 31 for month in range(1, 13): date = datetime.date(year, month, 1) (_, last_day) = calendar.monthrange(year, month) count_x = max(count_x, date.weekday() + last_day) cell_size = min(size.x / count_x, size.y / 36) spacing = XY( (size.x - cell_size * count_x) / (count_x - 1), (size.y - cell_size * 3 * 12) / 11, ) # chinese weekday key number is the third. keyword_num = 0 if locale.getlocale()[0] == "zh_CN": keyword_num = 2 # first character of localized day names, starting with Monday. dow = [ locale.nl_langinfo(day)[keyword_num].upper() for day in [ locale.DAY_2, locale.DAY_3, locale.DAY_4, locale.DAY_5, locale.DAY_6, locale.DAY_7, locale.DAY_1, ] ] for month in range(1, 13): date = datetime.date(year, month, 1) y = month - 1 y_pos = offset.y + (y * 3 + 1) * cell_size + y * spacing.y dr.add( dr.text( date.strftime("%B"), insert=(offset.x, y_pos - 2), fill=self.poster.colors["text"], alignment_baseline="hanging", style=month_style, )) day_offset = date.weekday() while date.month == month: x = date.day - 1 x_pos = offset.x + (day_offset + x) * cell_size + x * spacing.x pos = (x_pos + 0.05 * cell_size, y_pos + 1.15 * cell_size) dim = (cell_size * 0.9, cell_size * 0.9) text_date = date.strftime("%Y-%m-%d") if text_date in self.poster.tracks_by_date: tracks = self.poster.tracks_by_date[text_date] length = sum([t.length for t in tracks]) has_special = len([t for t in tracks if t.special]) > 0 color = self.color(self.poster.length_range_by_date, length, has_special) dr.add(dr.rect(pos, dim, fill=color)) dr.add( dr.text( utils.format_float(self.poster.m2u(length)), insert=( pos[0] + cell_size / 2, pos[1] + cell_size + cell_size / 2, ), text_anchor="middle", style=day_length_style, fill=self.poster.colors["text"], )) else: dr.add(dr.rect(pos, dim, fill="#444444")) dr.add( dr.text( dow[date.weekday()], insert=( offset.x + (day_offset + x) * cell_size + cell_size / 2, pos[1] + cell_size / 2, ), text_anchor="middle", alignment_baseline="middle", style=day_style, )) date += datetime.timedelta(1)
def setUp(self): self.m = XY(-100, -100, 100, 100)
def render(self, icons, skipLayers = [], hexWidth = 2.0866): # create a blank empty board image with correct size and scaling (DPI) size = Board.marginXY * 2 + \ Board.hexXY.doti( (self.cols, (self.rows*3+1)/4.) ) board = Image.new('RGB', size, Board.background_color) canvas = ImageDraw.Draw(board) # use hexWidthto choose a particular hex width in inches # actual M44 tiles are 2.0866" (53mm) across the flats dpi = int(round(Board.hexXY.x / hexWidth)) try: font = ImageFont.truetype('verdanab.ttf',32) except: logging.warn("Couldn't open VerdanaBold TTF, using (ugly) system default") font = ImageFont.load_default() # paint the board background outline = icons.getImage('outline') for row in xrange(self.rows): name = self.rowStyles[row] image = icons.getImage(name) if not image: logging.warn("No background image for %s"%name) continue for col in xrange(self.cols - (row%2)): # skip last hex on odd rows xy = Board.coords(row,col) if outline: board.paste(outline, tuple(xy), outline) board.paste(image, tuple(xy), image) # medal1 - Allies Medal # medal2 - German Medal # <note there isn't a medal3> # medal4 - Victoria Cross # medal5 - Italian Medal of Valor # medal6 - Hero of the Soviet Union Medal # medal7 - Order of the Golden Kite Medal medal_dict = { # Default axis / allies medals in side_player[1|2] value 'ALLIES' : 1, 'AXIS' : 2, # Country-specific medals, coded in country_player[1|2] value 'US' : 1, 'DE' : 2, 'GB' : 4, 'IT' : 5, 'RU' : 6, 'JP' : 7 } # paint the victory medals, with player1 at the top (flipped) # and player2 at the bottom. for p in ['1','2']: vp = self.game_info.get('victory_player' + p, 6) side = self.game_info.get('side_player' + p, '') country = self.game_info.get('country_player' + p, '') medal_num = medal_dict.get(country, None) if not medal_num: medal_num = medal_dict.get(side, None) medal_name = 'medal' + (medal_num and `medal_num`) or p medal = icons.getImage(medal_name) if not medal: logging.warn("Couldn't find victory marker image %s"%name) continue medal = medal.crop(medal.getbbox()) if p == '1': # Draw top medals upside facing board edge medal = ImageOps.flip(medal) medal = medal.resize((XY(*medal.size) * 1.5).ints(),Image.ANTIALIAS) mxy = XY(*medal.size) for col in xrange(self.cols-vp,self.cols): xy = Board.coords(0, col) - mxy.doti((1/2.,3/4.)) if p == '2': # Position bottom medals by reflection xy = - xy - mxy + board.size board.paste(medal,tuple(xy),medal) # label the scenario canvas.text(Board.marginXY.doti( (1/2., 1/3.) ), self.text['name'], fill = 'black', font=font) # warn about layers we won't deal with for hexagon in self.info['labels'] + self.info['hexagons']: if any(k not in Board.drawing_layers + ['col','row'] for k in hexagon.keys()): logging.warn('unknown key in %s'%`hexagon.keys()`) # now paint on the overlay elements for key in Board.drawing_layers: if key in skipLayers: continue # skipping this layer? if key is 'lines': # placeholder for flank lines col = 0 while col < self.cols: for inc in [4,5,4]: col += inc if col >= self.cols: break # Find starting point of dashed flank line (x,y1) = Board.coords(0,col) x -= Board.dash_width / 2 - 2 y1 += Board.hexXY.y/4 # Find ending point y2 = Board.coords(self.rows,0).y # Draw the dashed line y = y1 while y < y2: ye = min(y2, y+Board.dash_length[0]) canvas.line([(x,y),(x,ye)], fill=Board.dash_color, width=Board.dash_width) y += sum(Board.dash_length) continue # on to next layer hexagons = self.info['labels' if key is 'text' else 'hexagons'] for hexagon in hexagons: col,row = hexagon['col'],hexagon['row'] content = hexagon.get(key,None) if not content: continue # make everything a list for simplicity if type(content) is not ListType: contents = [content] else: contents = content xy = Board.coords2(row,col) if key is 'text': for (i,content) in enumerate(contents): wh = XY(*canvas.textsize(content, font=font)) pos = xy + Board.hexXY.doti( (1/2., 3/4.) ) \ - wh.doti( (1/2., 1.1*(len(contents)/2. - i)) ) canvas.text(pos, content, fill="black", font=font) continue # on to next hex # sort contents by name to get consistent order for tags contents.sort(key = lambda c: c['name']) for i,content in enumerate(contents): name = content['name'] image = icons.getImage(name, content.get('orientation',1)) if not image: logging.warn("(col=%d, row=%d): No image for %s"%( col,row,name)) continue # hack to deal with multiple medal tags in a single hex # but won't work for things like 'battle stars' (named "tag1") # which is already centered in lower right if i > 0: offset = Board.tagOffset.dot( [(1,1),(-1,-1),(-1,1),(1,-1)][i%4]) - Board.tagOffset logging.debug("%s:%d:%s at (col=%d, row=%d) - offset by %s"%( key,i,name, col, row, `offset`)) xy = xy + offset if key is not 'tags': logging.warn( "Didn't expect multiple instances of [%s] at (col=%d, row=%d)"%(key, col, row)) elif i > 3: logging.warn( "Can't deal with more than four tags at (col=%d, row=%d)"%(col,row)) board.paste(image,tuple(xy),image) if i > 0: # center of medal tag is top-right, about XY(133,66), #, i.e about +39, -42 from center of hex XY(188,217) # for subsequent ones, move them around the hex # to bottom-left, bottom-right, top-left? logging.debug('(col=%d,row=%d), name=%s, item #=%d'%( col,row,name,i)) # handle nbr_units and badge attributes within unit layer if content.has_key('badge'): # unit badges # badges are not padded to hex size, and too small # so resize and center on unit top left corner image = icons.getImage(content['badge']) image = image.resize(Board.badgeSize,Image.ANTIALIAS) if image: pos = xy + Board.unitTL - Board.badgeSize / 2 board.paste(image, tuple(pos), image) if content.has_key('nbr_units'): image = icons.getImage('nbr_units', int(content['nbr_units'])) if image: board.paste(image, tuple(xy), image) board = ImageOps.expand( board, border=Board.border_width, fill=Board.border_color) board.info['dpi'] = (dpi,dpi) return board