Fix variant-dependent pieces
[xboard.git] / draw.c
1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #include "config.h"
53
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60
61 #if STDC_HEADERS
62 # include <stdlib.h>
63 # include <string.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
66 # if HAVE_STRING_H
67 #  include <string.h>
68 # else /* not HAVE_STRING_H */
69 #  include <strings.h>
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
72
73 #if ENABLE_NLS
74 #include <locale.h>
75 #endif
76
77
78 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
79 #include "common.h"
80
81 #include "frontend.h"
82 #include "backend.h"
83 #include "xevalgraph.h"
84 #include "board.h"
85 #include "menus.h"
86 #include "dialogs.h"
87 #include "gettext.h"
88 #include "draw.h"
89
90
91 #ifdef __EMX__
92 #ifndef HAVE_USLEEP
93 #define HAVE_USLEEP
94 #endif
95 #define usleep(t)   _sleep2(((t)+500)/1000)
96 #endif
97
98 #ifdef ENABLE_NLS
99 # define  _(s) gettext (s)
100 # define N_(s) gettext_noop (s)
101 #else
102 # define  _(s) (s)
103 # define N_(s)  s
104 #endif
105
106 #define SOLID 0
107 #define OUTLINE 1
108 Boolean cairoAnimate;
109 Option *currBoard;
110 cairo_surface_t *csBoardWindow;
111 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
112 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
113 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
114 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
115 static cairo_surface_t *pngBoardBitmap[2];
116 int useTexture, textureW[2], textureH[2];
117
118 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
119 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
120
121 #define White(piece) ((int)(piece) < (int)BlackPawn)
122
123 struct {
124   int x1, x2, y1, y2;
125 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
126
127 static int dual = 0;
128
129 void
130 SwitchWindow ()
131 {
132     dual = !dual;
133     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
134     csBoardWindow = DRAWABLE(currBoard);
135 }
136
137 void
138 SelectPieces(VariantClass v)
139 {
140     int i;
141     for(i=0; i<2; i++) {
142         int p;
143         for(p=0; p<=(int)WhiteKing; p++)
144            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
145         if(v == VariantShogi) {
146            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
147            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
148            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
149            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
150            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
151         }
152 #ifdef GOTHIC
153         if(v == VariantGothic) {
154            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
155         }
156 #endif
157         if(v == VariantSChess) {
158            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
159            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
160         }
161     }
162 }
163
164 #define BoardSize int
165 void
166 InitDrawingSizes (BoardSize boardSize, int flags)
167 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
168     int boardWidth, boardHeight;
169     int i;
170     static int oldWidth, oldHeight;
171     static VariantClass oldVariant;
172     static int oldMono = -1, oldTwoBoards = 0;
173     extern Widget formWidget;
174
175     if(!formWidget) return;
176
177     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
178     oldTwoBoards = twoBoards;
179
180     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
181     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
182     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
183
184   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
185
186     oldWidth = boardWidth; oldHeight = boardHeight;
187     CreateGrid();
188
189     /*
190      * Inhibit shell resizing.
191      */
192     ResizeBoardWindow(boardWidth, boardHeight, 0);
193
194     DelayedDrag();
195   }
196
197     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
198     // (only for xpm)
199
200   if(gameInfo.variant != oldVariant) { // and only if variant changed
201
202     SelectPieces(gameInfo.variant);
203
204     oldMono = -10; // kludge to force recreation of animation masks
205     oldVariant = gameInfo.variant;
206   }
207   CreateAnimVars();
208   oldMono = appData.monoMode;
209 }
210
211 void
212 ExposeRedraw (Option *graph, int x, int y, int w, int h)
213 {   // copy a selected part of the buffer bitmap to the display
214     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
215     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
216     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
217     cairo_rectangle(cr, x, y, w, h);
218     cairo_fill(cr);
219     cairo_destroy(cr);
220 }
221
222 static void
223 CreatePNGBoard (char *s, int kind)
224 {
225     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
226     if(strstr(s, ".png")) {
227         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
228         if(img) {
229             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
230             textureW[kind] = cairo_image_surface_get_width (img);
231             textureH[kind] = cairo_image_surface_get_height (img);
232         }
233     }
234 }
235
236 char *pngPieceNames[] = // must be in same order as internal piece encoding
237 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
238   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
239   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
240 };
241
242 RsvgHandle *
243 LoadSVG (char *dir, int color, int piece)
244 {
245     char buf[MSG_SIZ];
246   RsvgHandle *svg=svgPieces[color][piece];
247   RsvgDimensionData svg_dimensions;
248   GError **svgerror=NULL;
249   cairo_surface_t *img;
250   cairo_t *cr;
251
252     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
253
254     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, svgerror))) {
255
256       rsvg_handle_get_dimensions(svg, &svg_dimensions);
257       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
258
259       cr = cairo_create(img);
260       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
261       rsvg_handle_render_cairo(svg, cr);
262       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
263         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
264         pngPieceImages[color][piece] = img;
265       }
266       cairo_destroy(cr);
267
268       return svg;
269     }
270     return NULL;
271 }
272
273 static void
274 ScaleOnePiece (int color, int piece)
275 {
276   float w, h;
277   char buf[MSG_SIZ];
278   cairo_surface_t *img, *cs;
279   cairo_t *cr;
280
281   g_type_init ();
282
283   if(!svgPieces[color][piece]) { // try to freshly render cached svg pieces first, to supply the source bitmap
284     svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
285   }
286
287   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
288     if(*appData.pieceDirectory) { // user specified piece directory
289       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
290       pngPieceImages[color][piece] = img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
291       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
292         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
293       }
294     }
295   }
296
297   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
298     static int warned = 0;
299     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
300       char *msg = _("No default pieces installed\nSelect your own -pieceImageDirectory");
301       printf("%s\n", msg); // give up
302       DisplayError(msg, 0);
303       warned = 1; // prevent error message being repeated for each piece type
304     }
305   }
306
307   img = pngPieceImages[color][piece];
308
309   // create new bitmap to hold scaled piece image (and remove any old)
310   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
311   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
312
313   if(!img) return;
314
315   // scaled copying of the raw png image
316   cr = cairo_create(cs);
317   w = cairo_image_surface_get_width (img);
318   h = cairo_image_surface_get_height (img);
319   cairo_scale(cr, squareSize/w, squareSize/h);
320   cairo_set_source_surface (cr, img, 0, 0);
321   cairo_paint (cr);
322   cairo_destroy (cr);
323
324   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
325     int stride = cairo_image_surface_get_stride(cs)/4;
326     int *buf = (int *) cairo_image_surface_get_data(cs);
327     int i, j, p;
328     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
329     cairo_surface_flush(cs);
330     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
331         int r, a;
332         float f;
333         unsigned int c = buf[i*stride + j];
334         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
335         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
336         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
337         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
338         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
339         if(appData.monoMode) {
340             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
341             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
342             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
343         }
344     }
345     cairo_surface_mark_dirty(cs);
346   }
347 }
348
349 void
350 CreatePNGPieces ()
351 {
352   int p;
353
354   for(p=0; pngPieceNames[p]; p++) {
355     ScaleOnePiece(0, p);
356     ScaleOnePiece(1, p);
357   }
358   SelectPieces(gameInfo.variant);
359 }
360
361 void
362 CreateAnyPieces ()
363 {   // [HGM] taken out of main
364     CreatePNGPieces();
365     CreatePNGBoard(appData.liteBackTextureFile, 1);
366     CreatePNGBoard(appData.darkBackTextureFile, 0);
367 }
368
369 void
370 InitDrawingParams (int reloadPieces)
371 {
372     int i, p;
373     if(reloadPieces)
374     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
375         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
376         pngPieceImages[i][p] = NULL;
377         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
378         svgPieces[i][p] = NULL;
379     }
380     CreateAnyPieces();
381 }
382
383 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
384
385 float
386 Color (char *col, int n)
387 {
388   int c;
389   sscanf(col, "#%x", &c);
390   c = c >> 4*n & 255;
391   return c/255.;
392 }
393
394 void
395 SetPen (cairo_t *cr, float w, char *col, int dash)
396 {
397   static const double dotted[] = {4.0, 4.0};
398   static int len  = sizeof(dotted) / sizeof(dotted[0]);
399   cairo_set_line_width (cr, w);
400   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
401   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
402 }
403
404 void DrawSeekAxis( int x, int y, int xTo, int yTo )
405 {
406     cairo_t *cr;
407
408     /* get a cairo_t */
409     cr = cairo_create (csBoardWindow);
410
411     cairo_move_to (cr, x, y);
412     cairo_line_to(cr, xTo, yTo );
413
414     SetPen(cr, 2, "#000000", 0);
415     cairo_stroke(cr);
416
417     /* free memory */
418     cairo_destroy (cr);
419     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
420 }
421
422 void DrawSeekBackground( int left, int top, int right, int bottom )
423 {
424     cairo_t *cr = cairo_create (csBoardWindow);
425
426     cairo_rectangle (cr, left, top, right-left, bottom-top);
427
428     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
429     cairo_fill(cr);
430
431     /* free memory */
432     cairo_destroy (cr);
433     GraphExpose(currBoard, left, top, right-left, bottom-top);
434 }
435
436 void DrawSeekText(char *buf, int x, int y)
437 {
438     cairo_t *cr = cairo_create (csBoardWindow);
439
440     cairo_select_font_face (cr, "Sans",
441                             CAIRO_FONT_SLANT_NORMAL,
442                             CAIRO_FONT_WEIGHT_NORMAL);
443
444     cairo_set_font_size (cr, 12.0);
445
446     cairo_move_to (cr, x, y+4);
447     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
448     cairo_show_text( cr, buf);
449
450     /* free memory */
451     cairo_destroy (cr);
452     GraphExpose(currBoard, x-5, y-10, 60, 15);
453 }
454
455 void DrawSeekDot(int x, int y, int colorNr)
456 {
457     cairo_t *cr = cairo_create (csBoardWindow);
458     int square = colorNr & 0x80;
459     colorNr &= 0x7F;
460
461     if(square)
462         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
463     else
464         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
465
466     SetPen(cr, 2, "#000000", 0);
467     cairo_stroke_preserve(cr);
468     switch (colorNr) {
469       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
470       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
471       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
472     }
473     cairo_fill(cr);
474
475     /* free memory */
476     cairo_destroy (cr);
477     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
478 }
479
480 void
481 InitDrawingHandle (Option *opt)
482 {
483     csBoardWindow = DRAWABLE(opt);
484 }
485
486 void
487 CreateGrid ()
488 {
489     int i, j;
490
491     if (lineGap == 0) return;
492
493     /* [HR] Split this into 2 loops for non-square boards. */
494
495     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
496         gridSegments[i].x1 = 0;
497         gridSegments[i].x2 =
498           lineGap + BOARD_WIDTH * (squareSize + lineGap);
499         gridSegments[i].y1 = gridSegments[i].y2
500           = lineGap / 2 + (i * (squareSize + lineGap));
501     }
502
503     for (j = 0; j < BOARD_WIDTH + 1; j++) {
504         gridSegments[j + i].y1 = 0;
505         gridSegments[j + i].y2 =
506           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
507         gridSegments[j + i].x1 = gridSegments[j + i].x2
508           = lineGap / 2 + (j * (squareSize + lineGap));
509     }
510 }
511
512 void
513 DrawGrid()
514 {
515   /* draws a grid starting around Nx, Ny squares starting at x,y */
516   int i;
517   float odd = (lineGap & 1)/2.;
518   cairo_t *cr;
519
520   /* get a cairo_t */
521   cr = cairo_create (csBoardWindow);
522
523   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
524   SetPen(cr, lineGap, "#000000", 0);
525
526   /* lines in X */
527   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
528     {
529       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
530       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
531       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
532       cairo_stroke (cr);
533     }
534
535   /* free memory */
536   cairo_destroy (cr);
537
538   return;
539 }
540
541 void
542 DrawBorder (int x, int y, int type, int odd)
543 {
544     cairo_t *cr;
545     char *col;
546
547     switch(type) {
548         case 0: col = "#000000"; break;
549         case 1: col = appData.highlightSquareColor; break;
550         case 2: col = appData.premoveHighlightColor; break;
551     }
552     cr = cairo_create(csBoardWindow);
553     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
554     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
555     SetPen(cr, lineGap, col, 0);
556     cairo_stroke(cr);
557     cairo_destroy(cr);
558     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
559 }
560
561 static int
562 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
563 {
564     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
565     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
566     *x0 = 0; *y0 = 0;
567     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
568     if(textureW[kind] < W*squareSize)
569         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
570     else
571         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
572     if(textureH[kind] < H*squareSize)
573         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
574     else
575         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
576     return 1;
577 }
578
579 void
580 DrawLogo (Option *opt, void *logo)
581 {
582     cairo_surface_t *img;
583     cairo_t *cr;
584     int w, h;
585
586     if(!logo || !opt) return;
587     img = cairo_image_surface_create_from_png (logo);
588     w = cairo_image_surface_get_width (img);
589     h = cairo_image_surface_get_height (img);
590     cr = cairo_create(DRAWABLE(opt));
591     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
592     cairo_set_source_surface (cr, img, 0, 0);
593     cairo_paint (cr);
594     cairo_destroy (cr);
595     cairo_surface_destroy (img);
596     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
597 }
598
599 static void
600 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
601 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
602     int x0, y0;
603     cairo_t *cr;
604
605     cr = cairo_create (dest);
606
607     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
608             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
609             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
610             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
611             cairo_fill (cr);
612             cairo_destroy (cr);
613     } else { // evenly colored squares
614         char *col;
615         switch (color) {
616           case 0: col = appData.darkSquareColor; break;
617           case 1: col = appData.lightSquareColor; break;
618           case 2: col = "#000000"; break;
619         }
620         SetPen(cr, 2.0, col, 0);
621         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
622         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
623         cairo_fill (cr);
624         cairo_destroy (cr);
625     }
626 }
627
628 static void
629 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
630 {
631     int kind, p = piece;
632     cairo_t *cr;
633
634     if ((int)piece < (int) BlackPawn) {
635         kind = 0;
636     } else {
637         kind = 1;
638         piece -= BlackPawn;
639     }
640     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
641     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
642     cr = cairo_create (dest);
643     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
644     cairo_paint(cr);
645     cairo_destroy (cr);
646 }
647
648 void
649 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
650 {
651         cairo_t *cr;
652
653         cr = cairo_create(cs);
654         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
655         if(appData.monoMode) {
656             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
657             cairo_stroke_preserve(cr);
658             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
659         } else {
660             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
661         }
662         cairo_fill(cr);
663
664         cairo_destroy(cr);
665 }
666
667 void
668 DrawDot (int marker, int x, int y, int r)
669 { // used for atomic captures; no need to draw on backup
670   DoDrawDot(csBoardWindow, marker, x, y, r);
671   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
672 }
673
674 void
675 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
676 {   // basic front-end board-draw function: takes care of everything that can be in square:
677     // piece, background, coordinate/count, marker dot
678     cairo_t *cr;
679
680     if (piece == EmptySquare) {
681         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
682     } else {
683         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
684     }
685
686     if(align) { // square carries inscription (coord or piece count)
687         int xx = x, yy = y;
688         cairo_text_extents_t te;
689
690         cr = cairo_create (csBoardWindow);
691         cairo_select_font_face (cr, "Sans",
692                     CAIRO_FONT_SLANT_NORMAL,
693                     CAIRO_FONT_WEIGHT_BOLD);
694
695         cairo_set_font_size (cr, squareSize/4);
696         // calculate where it goes
697         cairo_text_extents (cr, string, &te);
698
699         if (align == 1) {
700             xx += squareSize - te.width - te.x_bearing - 1;
701             yy += squareSize - te.height - te.y_bearing - 1;
702         } else if (align == 2) {
703             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
704         } else if (align == 3) {
705             xx += squareSize - te.width -te.x_bearing - 1;
706             yy += -te.y_bearing + 3;
707         } else if (align == 4) {
708             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
709         }
710
711         cairo_move_to (cr, xx-1, yy);
712         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
713         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
714         cairo_show_text (cr, string);
715         cairo_destroy (cr);
716     }
717
718     if(marker) { // print fat marker dot, if requested
719         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
720     }
721 }
722
723 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
724
725 /*      Masks for XPM pieces. Black and white pieces can have
726         different shapes, but in the interest of retaining my
727         sanity pieces must have the same outline on both light
728         and dark squares, and all pieces must use the same
729         background square colors/images.                */
730
731 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
732
733 static void
734 InitAnimState (AnimNr anr)
735 {
736     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
737     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
738     c_animBufs[anr+4] = csBoardWindow;
739     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
740     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
741 }
742
743 void
744 CreateAnimVars ()
745 {
746   InitAnimState(Game);
747   InitAnimState(Player);
748 }
749
750 static void
751 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
752 {
753   static cairo_t *pieceSource;
754   pieceSource = cairo_create (dest);
755   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
756   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
757   else cairo_paint(pieceSource);
758   cairo_destroy (pieceSource);
759 }
760
761 void
762 InsertPiece (AnimNr anr, ChessSquare piece)
763 {
764     CairoOverlayPiece(piece, c_animBufs[anr]);
765 }
766
767 void
768 DrawBlank (AnimNr anr, int x, int y, int startColor)
769 {
770     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
771 }
772
773 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
774                  int srcX, int srcY, int width, int height, int destX, int destY)
775 {
776         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
777         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
778         cairo_rectangle (cr, destX, destY, width, height);
779         cairo_fill (cr);
780         cairo_destroy (cr);
781         if(c_animBufs[anr+destBuf] == csBoardWindow)
782             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
783 }
784
785 void
786 SetDragPiece (AnimNr anr, ChessSquare piece)
787 {
788 }
789
790 /* [AS] Arrow highlighting support */
791
792 void
793 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
794 {
795     cairo_t *cr;
796     int i;
797     cr = cairo_create (cs);
798     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
799     for (i=0;i<nr;i++) {
800         cairo_line_to(cr, arrow[i].x, arrow[i].y);
801     }
802     if(appData.monoMode) { // should we always outline arrow?
803         cairo_line_to(cr, arrow[0].x, arrow[0].y);
804         SetPen(cr, 2, "#000000", 0);
805         cairo_stroke_preserve(cr);
806     }
807     SetPen(cr, 2, appData.highlightSquareColor, 0);
808     cairo_fill(cr);
809
810     /* free memory */
811     cairo_destroy (cr);
812 }
813
814 void
815 DrawPolygon (Pnt arrow[], int nr)
816 {
817     DoDrawPolygon(csBoardWindow, arrow, nr);
818 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
819 }
820
821