1#!/usr/bin/env python 2# -*- mode: python; coding: utf-8; -*- 3# ---------------------------------------------------------------------------## 4# 5# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer 6# Copyright (C) 2003 Mt. Hood Playing Card Co. 7# Copyright (C) 2005-2009 Skomoroh 8# 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22# ---------------------------------------------------------------------------## 23 24 25import os 26 27from pysollib.mfxutil import Image, ImageTk, USE_PIL, print_err 28from pysollib.pysoltk import copyImage, createBottom, createImage, loadImage 29from pysollib.pysoltk import shadowImage 30from pysollib.resource import CSI 31from pysollib.settings import TOOLKIT 32 33# ************************************************************************ 34# * Images 35# ************************************************************************ 36 37 38class ImagesCardback: 39 def __init__(self, index, name, image, menu_image=None): 40 if menu_image is None: 41 menu_image = image 42 self.index = index 43 self.name = name 44 self.image = image 45 self.menu_image = menu_image 46 47 48class Images: 49 def __init__(self, dataloader, cs, r=1): 50 self.d = dataloader 51 self.cs = cs 52 self.reduced = r 53 self._xfactor = 1.0 54 self._yfactor = 1.0 55 if cs is None: 56 return 57 self._setSize() 58 self._card = [] 59 self._back = [] 60 # bottom of stack (link to _bottom_negative/_bottom_positive) 61 self._bottom = [] 62 self._bottom_negative = [] # negative bottom of stack (white) 63 self._bottom_positive = [] # positive bottom of stack (black) 64 self._blank_bottom = None # blank (transparent) bottom of stack 65 self._letter = [] # images of letter 66 self._letter_negative = [] 67 self._letter_positive = [] 68 # vertical shadow of card (used when we drag a card) 69 self._shadow = [] 70 self._xshadow = [] # horizontal shadow of card 71 self._pil_shadow = {} # key: (width, height) 72 self._highlight = [] # highlight of card (tip) 73 self._highlight_index = 0 # 74 self._highlighted_images = {} # key: (suit, rank) 75 76 def destruct(self): 77 pass 78 79 def __loadCard(self, filename, check_w=1, check_h=1): 80 # print '__loadCard:', filename 81 f = os.path.join(self.cs.dir, filename) 82 if not os.path.exists(f): 83 print_err('card image path %s does not exist' % f) 84 return None 85 try: 86 img = loadImage(file=f) 87 except Exception: 88 return None 89 90 if TOOLKIT == 'kivy': 91 w = img.texture.size[0] 92 h = img.texture.size[1] 93 else: 94 w, h = img.width(), img.height() 95 96 if self.CARDW < 0: 97 self.CARDW, self.CARDH = w, h 98 else: 99 if ((check_w and w != self.CARDW) or 100 (check_h and h != self.CARDH)): 101 raise ValueError("Invalid size %dx%d of image %s" % (w, h, f)) 102 return img 103 104 def __loadBottom(self, filename, check_w=1, check_h=1, color='white'): 105 cs_type = CSI.TYPE_ID[self.cs.type] 106 imagedir = None 107 d = os.path.join('images', 'cards', 'bottoms') 108 try: 109 imagedir = self.d.findDir(cs_type, d) 110 except Exception: 111 pass 112 if (not USE_PIL and TOOLKIT != 'kivy') or imagedir is None: 113 # load image 114 img = self.__loadCard(filename+self.cs.ext, check_w, check_h) 115 if USE_PIL and img is not None: 116 # we have no bottom images 117 # (data/images/cards/bottoms/<cs_type>) 118 img = img.resize(self._xfactor, self._yfactor) 119 return img 120 # create image 121 d = os.path.join('images', 'cards', 'bottoms', cs_type) 122 try: 123 fn = self.d.findImage(filename, d) 124 except Exception: 125 fn = None 126 img = createBottom(self._card[0], color, fn) 127 return img 128 129 def __addBack(self, im1, name): 130 r = max(self.CARDW / 40.0, self.CARDH / 60.0) 131 r = max(2, int(round(r))) 132 im2 = im1.subsample(r) 133 self._back.append(ImagesCardback(len(self._back), name, im1, im2)) 134 135 def _createMissingImages(self): 136 cw, ch = self.getSize() 137 # back 138 if not self._back: 139 im = createImage(cw, ch, fill="#a0a0a0", outline="#000000") 140 name = "" 141 self.__addBack(im, name) 142 self.cs.backnames = tuple(self.cs.backnames) + (name,) 143 # bottoms / letters 144 bottom = None 145 neg_bottom = None 146 while len(self._bottom_positive) < max(7, self.cs.nbottoms): 147 if bottom is None: 148 bottom = createImage(cw, ch, fill=None, outline="#000000") 149 self._bottom_positive.append(bottom) 150 while len(self._bottom_negative) < max(7, self.cs.nbottoms): 151 if neg_bottom is None: 152 neg_bottom = createImage(cw, ch, fill=None, outline="#ffffff") 153 self._bottom_negative.append(neg_bottom) 154 while len(self._letter_positive) < 4: 155 if bottom is None: 156 bottom = createImage(cw, ch, fill=None, outline="#000000") 157 self._letter_positive.append(bottom) 158 while len(self._letter_negative) < 4: 159 if neg_bottom is None: 160 neg_bottom = createImage(cw, ch, fill=None, outline="#ffffff") 161 self._letter_negative.append(neg_bottom) 162 self._blank_bottom = createImage(cw, ch, fill=None, outline=None) 163 164 def load(self, app, progress=None): 165 ext = self.cs.ext[1:] 166 pstep = 0 167 if progress: 168 pstep = self.cs.ncards + len(self.cs.backnames) + \ 169 self.cs.nbottoms + self.cs.nletters 170 pstep += self.cs.nshadows + 1 # shadows & shade 171 pstep = max(0, (80.0 - progress.percent) / pstep) 172 # load face cards 173 for n in self.cs.getFaceCardNames(): 174 self._card.append(self.__loadCard(n + self.cs.ext)) 175 self._card[-1].filename = n 176 if progress: 177 progress.update(step=pstep) 178 assert len(self._card) == self.cs.ncards 179 # load backgrounds 180 for name in self.cs.backnames: 181 if name: 182 im = self.__loadCard(name) 183 if im: 184 self.__addBack(im, name) 185 else: 186 print_err('in {cs_dir}/config.txt: card back "{fname}" ' 187 'does not exist'.format( 188 cs_dir=self.cs.dir, fname=name)) 189 if progress: 190 progress.update(step=1) 191 # load bottoms 192 for i in range(self.cs.nbottoms): 193 name = "bottom%02d" % (i + 1) 194 bottom = self.__loadBottom(name, color='black') 195 if bottom is not None: 196 self._bottom_positive.append(bottom) 197 if progress: 198 progress.update(step=pstep) 199 # load negative bottoms 200 name = "bottom%02d-n" % (i + 1) 201 bottom = self.__loadBottom(name, color='white') 202 if bottom is not None: 203 self._bottom_negative.append(bottom) 204 if progress: 205 progress.update(step=pstep) 206 # load letters 207 for rank in range(self.cs.nletters): 208 name = "l%02d" % (rank + 1) 209 self._letter_positive.append( 210 self.__loadBottom(name, color='black')) 211 if progress: 212 progress.update(step=pstep) 213 # load negative letters 214 name = "l%02d-n" % (rank + 1) 215 self._letter_negative.append( 216 self.__loadBottom(name, color='white')) 217 if progress: 218 progress.update(step=pstep) 219 # shadow 220 if not USE_PIL: 221 for i in range(self.cs.nshadows): 222 name = "shadow%02d.%s" % (i, ext) 223 im = self.__loadCard(name, check_w=0, check_h=0) 224 self._shadow.append(im) 225 if i > 0: # skip 0 226 name = "xshadow%02d.%s" % (i, ext) 227 im = self.__loadCard(name, check_w=0, check_h=0) 228 self._xshadow.append(im) 229 if progress: 230 progress.update(step=pstep) 231 # shade 232 if USE_PIL: 233 self._highlight.append( 234 self._getHighlight(self._card[0], None, '#3896f8')) 235 else: 236 self._highlight.append(self.__loadCard("shade." + ext)) 237 if progress: 238 progress.update(step=pstep) 239 # create missing 240 self._createMissingImages() 241 # 242 self._bottom = self._bottom_positive 243 self._letter = self._letter_positive 244 # 245 return 1 246 247 def getFace(self, deck, suit, rank): 248 index = suit * len(self.cs.ranks) + rank 249 # print "getFace:", suit, rank, index 250 return self._card[index % self.cs.ncards] 251 252 def getBack(self, update=False): 253 if update: 254 self._shadow_back = None 255 index = self.cs.backindex % len(self._back) 256 return self._back[index].image 257 258 def getTalonBottom(self): 259 return self._bottom[0] 260 261 def getReserveBottom(self): 262 return self._bottom[0] 263 264 def getBlankBottom(self): 265 if TOOLKIT == 'kivy': 266 return self._bottom[0] 267 return self._blank_bottom 268 269 def getSuitBottom(self, suit=-1): 270 assert isinstance(suit, int) 271 if suit == -1: 272 return self._bottom[1] # any suit 273 i = 3 + suit 274 if i >= len(self._bottom): 275 # Trump (for Tarock type games) 276 return self._bottom[1] 277 return self._bottom[i] 278 279 def getBraidBottom(self): 280 return self._bottom[2] 281 282 def getLetter(self, rank): 283 assert 0 <= rank <= 3 284 if rank >= len(self._letter): 285 return self._bottom[0] 286 return self._letter[rank] 287 288 def getShadow(self, ncards): 289 if ncards >= 0: 290 if ncards >= len(self._shadow): 291 # ncards = len(self._shadow) - 1 292 return None 293 return self._shadow[ncards] 294 else: 295 ncards = abs(ncards)-2 296 if ncards >= len(self._xshadow): 297 return None 298 return self._xshadow[ncards] 299 300 def getShadowPIL(self, stack, cards): 301 x0, y0 = stack.getPositionFor(cards[0]) 302 x1, y1 = stack.getPositionFor(cards[-1]) 303 x0, x1 = min(x1, x0), max(x1, x0) 304 y0, y1 = min(y1, y0), max(y1, y0) 305 cw, ch = self.getSize() 306 x1 += cw 307 y1 += ch 308 w, h = x1-x0, y1-y0 309 if (w, h) in self._pil_shadow: 310 return self._pil_shadow[(w, h)] 311 # create mask 312 mask = Image.new('RGBA', (w, h)) 313 for c in cards: 314 x, y = stack.getPositionFor(c) 315 x, y = x-x0, y-y0 316 im = c._active_image._pil_image 317 mask.paste(im, (x, y), im) 318 # create shadow 319 sh_color = (0x00, 0x00, 0x00, 0x50) 320 shadow = Image.new('RGBA', (w, h)) 321 shadow.paste(sh_color, (0, 0, w, h), mask) 322 sx, sy = self.SHADOW_XOFFSET, self.SHADOW_YOFFSET 323 mask = mask.crop((sx, sy, w, h)) 324 tmp = Image.new('RGBA', (w-sx, h-sy)) 325 shadow.paste(tmp, (0, 0), mask) 326 shadow = ImageTk.PhotoImage(shadow) 327 self._pil_shadow[(w, h)] = shadow 328 return shadow 329 330 def getShade(self): 331 # highlight 332 return self._highlight[self._highlight_index] 333 334 def _getHighlight(self, image, card, color='#3896f8', factor=0.3): 335 if USE_PIL: 336 # use semitransparent image; one for each color (PIL >= 1.1.7) 337 if color in self._highlighted_images: 338 shade = self._highlighted_images[color] 339 else: 340 shade = shadowImage(image, color, factor) 341 self._highlighted_images[color] = shade 342 else: 343 # use alpha blending (PIL <= 1.1.6) 344 if card in self._highlighted_images: 345 shade = self._highlighted_images[card] 346 else: 347 shade = shadowImage(image, color, factor) 348 self._highlighted_images[card] = shade 349 if not shade: 350 # we have not PIL 351 return self.getShade() 352 return shade 353 354 def getHighlightedCard(self, deck, suit, rank, color=None): 355 image = self.getFace(deck, suit, rank) 356 if color: 357 return self._getHighlight(image, (suit, rank, color), color) 358 return self._getHighlight(image, (suit, rank)) 359 360 def getHighlightedBack(self): 361 image = self.getBack() 362 return self._getHighlight(image, 'back') 363 364 def getCardbacks(self): 365 return self._back 366 367 def setNegative(self, flag=0): 368 if flag: 369 self._bottom = self._bottom_negative 370 self._letter = self._letter_negative 371 else: 372 self._bottom = self._bottom_positive 373 self._letter = self._letter_positive 374 375 def setOffsets(self): 376 cs = self.cs 377 if cs is None: 378 return 379 r = self.reduced 380 if r > 1: 381 self.CARD_XOFFSET = max(10, cs.CARD_XOFFSET // r) 382 self.CARD_YOFFSET = max(10, cs.CARD_YOFFSET // r) 383 else: 384 self.CARD_XOFFSET = cs.CARD_XOFFSET 385 self.CARD_YOFFSET = cs.CARD_YOFFSET 386 self.SHADOW_XOFFSET = cs.SHADOW_XOFFSET 387 self.SHADOW_YOFFSET = cs.SHADOW_YOFFSET 388 self.CARD_DX, self.CARD_DY = cs.CARD_DX, cs.CARD_DY 389 390 def _setSize(self, xf=1, yf=1): 391 # print 'image._setSize', xf, yf 392 self._xfactor = xf 393 self._yfactor = yf 394 cs = self.cs 395 if cs is None: 396 return 397 r = self.reduced 398 xf = float(xf)/r 399 yf = float(yf)/r 400 # from cardset 401 self.CARDW, self.CARDH = int(cs.CARDW*xf), int(cs.CARDH*yf) 402 self.setOffsets() 403 404 def getSize(self): 405 return (int(self.CARDW * self._xfactor), 406 int(self.CARDH * self._yfactor)) 407 408 def getOffsets(self): 409 return (int(self.CARD_XOFFSET * self._xfactor), 410 int(self.CARD_YOFFSET * self._yfactor)) 411 412 def getDelta(self): 413 return (int(self.CARD_DX * self._xfactor), 414 int(self.CARD_DY * self._yfactor)) 415 416 def resize(self, xf, yf): 417 # print 'Images.resize:', xf, yf, self._card[0].width(), self.CARDW 418 if self._xfactor == xf and self._yfactor == yf: 419 # print 'no resize' 420 return 421 self._xfactor = xf 422 self._yfactor = yf 423 # ???self._setSize(xf, yf) 424 self.setOffsets() 425 # cards 426 cards = [] 427 for c in self._card: 428 c = c.resize(xf, yf) 429 cards.append(c) 430 self._card = cards 431 # back 432 for b in self._back: 433 b.image = b.image.resize(xf, yf) 434 # stack bottom image 435 neg = self._bottom is self._bottom_negative 436 self._bottom_negative = [] 437 self._bottom_positive = [] 438 for i in range(self.cs.nbottoms): 439 name = "bottom%02d" % (i + 1) 440 bottom = self.__loadBottom(name, color='black') 441 if bottom is not None: 442 self._bottom_positive.append(bottom) 443 name = "bottom%02d-n" % (i + 1) 444 bottom = self.__loadBottom(name, color='white') 445 if bottom is not None: 446 self._bottom_negative.append(bottom) 447 # letters 448 self._letter_positive = [] 449 self._letter_negative = [] 450 for rank in range(self.cs.nletters): 451 name = "l%02d" % (rank + 1) 452 self._letter_positive.append( 453 self.__loadBottom(name, color='black')) 454 name = "l%02d-n" % (rank + 1) 455 self._letter_negative.append( 456 self.__loadBottom(name, color='white')) 457 self._createMissingImages() 458 self.setNegative(neg) 459 # 460 self._highlighted_images = {} 461 self._highlight = [] 462 self._highlight.append( 463 self._getHighlight(self._card[0], None, '#3896f8')) 464 self._pil_shadow = {} 465 466 def reset(self): 467 print('Image.reset') 468 self.resize(1, 1) 469 470 471# ************************************************************************ 472# * 473# ************************************************************************ 474 475class SubsampledImages(Images): 476 def __init__(self, images, r=2): 477 size_cap = 100 478 if images.CARDW // r > size_cap or images.CARDH // r > size_cap: 479 r = max(images.CARDW, images.CARDH) // size_cap 480 481 Images.__init__(self, None, images.cs, r=r) 482 self._card = self._subsample(images._card, r) 483 self._bottom_positive = self._subsample(images._bottom_positive, r) 484 self._letter_positive = self._subsample(images._letter_positive, r) 485 self._bottom_negative = self._subsample(images._bottom_negative, r) 486 self._letter_negative = self._subsample(images._letter_negative, r) 487 self._bottom = self._bottom_positive 488 self._letter = self._letter_positive 489 490 # 491 for _back in images._back: 492 if _back is None: 493 self._back.append(None) 494 else: 495 im = _back.image.subsample(r) 496 self._back.append( 497 ImagesCardback(len(self._back), _back.name, im, im)) 498 # 499 CW, CH = self.CARDW, self.CARDH 500 for im in images._highlight: 501 # self._highlight.append(None) 502 self._highlight.append(copyImage(im, 0, 0, CW, CH)) 503 504 def getShadow(self, ncards): 505 return None 506 507 def _subsample(self, images_list, r): 508 s = [] 509 for im in images_list: 510 if im is None or r == 1: 511 s.append(im) 512 else: 513 s.append(im.subsample(r)) 514 return s 515