Install svg pieces in themes/default
[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 #if HAVE_LIBXPM
82 #include "pixmaps/pixmaps.h"
83 #endif
84
85 #include "frontend.h"
86 #include "backend.h"
87 #include "xevalgraph.h"
88 #include "board.h"
89 #include "menus.h"
90 #include "dialogs.h"
91 #include "gettext.h"
92 #include "draw.h"
93
94
95 #ifdef __EMX__
96 #ifndef HAVE_USLEEP
97 #define HAVE_USLEEP
98 #endif
99 #define usleep(t)   _sleep2(((t)+500)/1000)
100 #endif
101
102 #ifdef ENABLE_NLS
103 # define  _(s) gettext (s)
104 # define N_(s) gettext_noop (s)
105 #else
106 # define  _(s) (s)
107 # define N_(s)  s
108 #endif
109
110 #define SOLID 0
111 #define OUTLINE 1
112 Boolean cairoAnimate;
113 Option *currBoard;
114 cairo_surface_t *csBoardWindow;
115 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
116 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
117 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
118 static cairo_surface_t *pngBoardBitmap[2];
119 int useTexture, textureW[2], textureH[2];
120
121 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
122 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
123
124 #define White(piece) ((int)(piece) < (int)BlackPawn)
125
126 struct {
127   int x1, x2, y1, y2;
128 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
129
130 static int dual = 0;
131
132 void
133 SwitchWindow ()
134 {
135     dual = !dual;
136     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
137     csBoardWindow = DRAWABLE(currBoard);
138 }
139
140 #define BoardSize int
141 void
142 InitDrawingSizes (BoardSize boardSize, int flags)
143 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
144     int boardWidth, boardHeight;
145     int i;
146     static int oldWidth, oldHeight;
147     static VariantClass oldVariant;
148     static int oldMono = -1, oldTwoBoards = 0;
149     extern Widget formWidget;
150
151     if(!formWidget) return;
152
153     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
154     oldTwoBoards = twoBoards;
155
156     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
157     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
158     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
159
160   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
161
162     oldWidth = boardWidth; oldHeight = boardHeight;
163     CreateGrid();
164
165     /*
166      * Inhibit shell resizing.
167      */
168     ResizeBoardWindow(boardWidth, boardHeight, 0);
169
170     DelayedDrag();
171   }
172
173     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
174     // (only for xpm)
175
176   if(gameInfo.variant != oldVariant) { // and only if variant changed
177
178     for(i=0; i<2; i++) {
179         int p;
180         for(p=0; p<=(int)WhiteKing; p++)
181            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
182         if(gameInfo.variant == VariantShogi) {
183            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
184            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
185            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
186            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
187            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
188         }
189 #ifdef GOTHIC
190         if(gameInfo.variant == VariantGothic) {
191            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
192         }
193 #endif
194         if(gameInfo.variant == VariantSChess) {
195            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
196            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
197         }
198     }
199     oldMono = -10; // kludge to force recreation of animation masks
200     oldVariant = gameInfo.variant;
201   }
202   CreateAnimVars();
203   oldMono = appData.monoMode;
204 }
205
206 static void
207 CreatePNGBoard (char *s, int kind)
208 {
209     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
210     if(strstr(s, ".png")) {
211         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
212         if(img) {
213             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
214             textureW[kind] = cairo_image_surface_get_width (img);
215             textureH[kind] = cairo_image_surface_get_height (img);
216         }
217     }
218 }
219
220 char *pngPieceNames[] = // must be in same order as internal piece encoding
221 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
222   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
223   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
224 };
225
226 cairo_surface_t *
227 ConvertPixmap (int color, int piece)
228 {
229   int i, j, stride, f, colcode[10], w, b;
230   char ch[10];
231   cairo_surface_t *res;
232   XpmPieces *p = builtInXpms + 10;
233   char **pixels = p->xpm[piece % BlackPawn][2*color];
234   int *buf;
235   sscanf(pixels[0], "%*d %*d %d", &f);
236   sscanf(appData.whitePieceColor+1, "%x", &w);
237   sscanf(appData.blackPieceColor+1, "%x", &b);
238   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
239   stride = cairo_image_surface_get_stride(res);
240   buf = (int *) cairo_image_surface_get_data(res);
241   for(i=0; i<f; i++) {
242     ch[i] = pixels[i+1][0];
243     colcode[i] = 0;
244     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
245     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
246   }
247   for(i=0; i<p->size; i++) {
248     for(j=0; j<p->size; j++) {
249       char c = pixels[i+f+1][j];
250       int k;
251       for(k=0; ch[k] != c && k < f; k++);
252       buf[i*p->size + j] = colcode[k];
253     }
254   }
255   cairo_surface_mark_dirty(res);
256   return res;
257 }
258
259 int
260 LoadSVG(char *dir, int color, int piece)
261 {
262     char buf[MSG_SIZ];
263   RsvgHandle *svg=NULL;
264   RsvgDimensionData svg_dimensions;
265   GError **svgerror=NULL;
266   cairo_surface_t *img;
267   cairo_t *cr;
268
269     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
270
271     if(svg = rsvg_handle_new_from_file(buf,svgerror)) {
272
273       rsvg_handle_get_dimensions(svg, &svg_dimensions);
274       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
275
276       cr = cairo_create(img);
277       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
278       rsvg_handle_render_cairo(svg, cr);
279       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
280         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
281         pngPieceImages[color][piece] = img;
282       }
283       cairo_destroy(cr);
284       rsvg_handle_close (svg,NULL);
285
286       return 1;
287     }
288     return 0;
289 }
290
291 static void
292 ScaleOnePiece (int color, int piece)
293 {
294   float w, h;
295   char buf[MSG_SIZ];
296   cairo_surface_t *img, *cs;
297   cairo_t *cr;
298
299   g_type_init ();
300
301   if(*appData.svgDirectory) { // try to freshly render svg pieces first, always from file, to supply the source bitmap
302     LoadSVG(appData.svgDirectory, color, piece);
303   }
304
305   if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
306     if(!*appData.pieceDirectory) img = ConvertPixmap(color, piece); else {
307       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
308       img = cairo_image_surface_create_from_png (buf);
309       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) img = ConvertPixmap(color, piece);
310     }
311   }
312   pngPieceImages[color][piece] = img;
313
314
315   // create new bitmap to hold scaled piece image (and remove any old)
316   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
317   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
318   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
319
320   // scaled copying of the raw png image
321   cr = cairo_create(cs);
322   w = cairo_image_surface_get_width (img);
323   h = cairo_image_surface_get_height (img);
324   cairo_scale(cr, squareSize/w, squareSize/h);
325   cairo_set_source_surface (cr, img, 0, 0);
326   cairo_paint (cr);
327   cairo_destroy (cr);
328
329   if(!appData.trueColors || !*appData.pieceDirectory && !appData.svgDirectory) { // operate on bitmap to color it (king-size hack...)
330     int stride = cairo_image_surface_get_stride(cs)/4;
331     int *buf = (int *) cairo_image_surface_get_data(cs);
332     int i, j, p;
333     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
334     cairo_surface_flush(cs);
335     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
336         int r, a;
337         float f;
338         unsigned int c = buf[i*stride + j];
339         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
340         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
341         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
342         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
343         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
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 }
359
360 void
361 CreateAnyPieces ()
362 {   // [HGM] taken out of main
363     CreatePNGPieces();
364     CreatePNGBoard(appData.liteBackTextureFile, 1);
365     CreatePNGBoard(appData.darkBackTextureFile, 0);
366 }
367
368 void
369 InitDrawingParams (int reloadPieces)
370 {
371     int i, p;
372     MakeColors();
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     }
378     CreateAnyPieces();
379 }
380
381 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
382
383 float
384 Color (char *col, int n)
385 {
386   int c;
387   sscanf(col, "#%x", &c);
388   c = c >> 4*n & 255;
389   return c/255.;
390 }
391
392 void
393 SetPen (cairo_t *cr, float w, char *col, int dash)
394 {
395   static const double dotted[] = {4.0, 4.0};
396   static int len  = sizeof(dotted) / sizeof(dotted[0]);
397   cairo_set_line_width (cr, w);
398   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
399   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
400 }
401
402 void DrawSeekAxis( int x, int y, int xTo, int yTo )
403 {
404     cairo_t *cr;
405
406     /* get a cairo_t */
407     cr = cairo_create (csBoardWindow);
408
409     cairo_move_to (cr, x, y);
410     cairo_line_to(cr, xTo, yTo );
411
412     SetPen(cr, 2, "#000000", 0);
413     cairo_stroke(cr);
414
415     /* free memory */
416     cairo_destroy (cr);
417     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
418 }
419
420 void DrawSeekBackground( int left, int top, int right, int bottom )
421 {
422     cairo_t *cr = cairo_create (csBoardWindow);
423
424     cairo_rectangle (cr, left, top, right-left, bottom-top);
425
426     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
427     cairo_fill(cr);
428
429     /* free memory */
430     cairo_destroy (cr);
431     GraphExpose(currBoard, left, top, right-left, bottom-top);
432 }
433
434 void DrawSeekText(char *buf, int x, int y)
435 {
436     cairo_t *cr = cairo_create (csBoardWindow);
437
438     cairo_select_font_face (cr, "Sans",
439                             CAIRO_FONT_SLANT_NORMAL,
440                             CAIRO_FONT_WEIGHT_NORMAL);
441
442     cairo_set_font_size (cr, 12.0);
443
444     cairo_move_to (cr, x, y+4);
445     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
446     cairo_show_text( cr, buf);
447
448     /* free memory */
449     cairo_destroy (cr);
450     GraphExpose(currBoard, x-5, y-10, 60, 15);
451 }
452
453 void DrawSeekDot(int x, int y, int colorNr)
454 {
455     cairo_t *cr = cairo_create (csBoardWindow);
456     int square = colorNr & 0x80;
457     colorNr &= 0x7F;
458
459     if(square)
460         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
461     else
462         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
463
464     SetPen(cr, 2, "#000000", 0);
465     cairo_stroke_preserve(cr);
466     switch (colorNr) {
467       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
468       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
469       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
470     }
471     cairo_fill(cr);
472
473     /* free memory */
474     cairo_destroy (cr);
475     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
476 }
477
478 void
479 InitDrawingHandle (Option *opt)
480 {
481     csBoardWindow = DRAWABLE(opt);
482 }
483
484 void
485 CreateGrid ()
486 {
487     int i, j;
488
489     if (lineGap == 0) return;
490
491     /* [HR] Split this into 2 loops for non-square boards. */
492
493     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
494         gridSegments[i].x1 = 0;
495         gridSegments[i].x2 =
496           lineGap + BOARD_WIDTH * (squareSize + lineGap);
497         gridSegments[i].y1 = gridSegments[i].y2
498           = lineGap / 2 + (i * (squareSize + lineGap));
499     }
500
501     for (j = 0; j < BOARD_WIDTH + 1; j++) {
502         gridSegments[j + i].y1 = 0;
503         gridSegments[j + i].y2 =
504           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
505         gridSegments[j + i].x1 = gridSegments[j + i].x2
506           = lineGap / 2 + (j * (squareSize + lineGap));
507     }
508 }
509
510 void
511 DrawGrid()
512 {
513   /* draws a grid starting around Nx, Ny squares starting at x,y */
514   int i;
515   float odd = (lineGap & 1)/2.;
516   cairo_t *cr;
517
518   /* get a cairo_t */
519   cr = cairo_create (csBoardWindow);
520
521   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
522   SetPen(cr, lineGap, "#000000", 0);
523
524   /* lines in X */
525   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
526     {
527       cairo_move_to (cr, gridSegments[i].x1 + odd, gridSegments[i].y1 + odd);
528       cairo_line_to (cr, gridSegments[i].x2 + odd, gridSegments[i].y2 + odd);
529       cairo_stroke (cr);
530     }
531
532   /* free memory */
533   cairo_destroy (cr);
534
535   return;
536 }
537
538 void
539 DrawBorder (int x, int y, int type, int odd)
540 {
541     cairo_t *cr;
542     char *col;
543
544     switch(type) {
545         case 0: col = "#000000"; break;
546         case 1: col = appData.highlightSquareColor; break;
547         case 2: col = appData.premoveHighlightColor; break;
548     }
549     cr = cairo_create(csBoardWindow);
550     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
551     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
552     SetPen(cr, lineGap, col, 0);
553     cairo_stroke(cr);
554     cairo_destroy(cr);
555     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
556 }
557
558 static int
559 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
560 {
561     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
562     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
563     *x0 = 0; *y0 = 0;
564     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
565     if(textureW[kind] < W*squareSize)
566         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
567     else
568         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
569     if(textureH[kind] < H*squareSize)
570         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
571     else
572         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
573     return 1;
574 }
575
576 void
577 DrawLogo (Option *opt, void *logo)
578 {
579     cairo_surface_t *img;
580     cairo_t *cr;
581     int w, h;
582
583     if(!logo || !opt) return;
584     img = cairo_image_surface_create_from_png (logo);
585     w = cairo_image_surface_get_width (img);
586     h = cairo_image_surface_get_height (img);
587     cr = cairo_create(DRAWABLE(opt));
588     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
589     cairo_set_source_surface (cr, img, 0, 0);
590     cairo_paint (cr);
591     cairo_destroy (cr);
592     cairo_surface_destroy (img);
593     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
594 }
595
596 static void
597 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
598 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
599     int x0, y0;
600     cairo_t *cr;
601
602     cr = cairo_create (dest);
603
604     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
605             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
606             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
607             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
608             cairo_fill (cr);
609             cairo_destroy (cr);
610     } else { // evenly colored squares
611         char *col;
612         switch (color) {
613           case 0: col = appData.darkSquareColor; break;
614           case 1: col = appData.lightSquareColor; break;
615           case 2: col = "#000000"; break;
616         }
617         SetPen(cr, 2.0, col, 0);
618         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
619         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
620         cairo_fill (cr);
621         cairo_destroy (cr);
622     }
623 }
624
625 static void
626 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
627 {
628     int kind, p = piece;
629     cairo_t *cr;
630
631     if ((int)piece < (int) BlackPawn) {
632         kind = 0;
633     } else {
634         kind = 1;
635         piece -= BlackPawn;
636     }
637     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
638     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
639     cr = cairo_create (dest);
640     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
641     cairo_paint(cr);
642     cairo_destroy (cr);
643 }
644
645 void
646 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
647 {
648         cairo_t *cr;
649
650         cr = cairo_create(cs);
651         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
652         if(appData.monoMode) {
653             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
654             cairo_stroke_preserve(cr);
655             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
656         } else {
657             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
658         }
659         cairo_fill(cr);
660
661         cairo_destroy(cr);
662 }
663
664 void
665 DrawDot (int marker, int x, int y, int r)
666 { // used for atomic captures; no need to draw on backup
667   DoDrawDot(csBoardWindow, marker, x, y, r);
668   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
669 }
670
671 void
672 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
673 {   // basic front-end board-draw function: takes care of everything that can be in square:
674     // piece, background, coordinate/count, marker dot
675     cairo_t *cr;
676
677     if (piece == EmptySquare) {
678         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
679     } else {
680         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
681     }
682
683     if(align) { // square carries inscription (coord or piece count)
684         int xx = x, yy = y;
685         cairo_text_extents_t te;
686
687         cr = cairo_create (csBoardWindow);
688         cairo_select_font_face (cr, "Sans",
689                     CAIRO_FONT_SLANT_NORMAL,
690                     CAIRO_FONT_WEIGHT_BOLD);
691
692         cairo_set_font_size (cr, squareSize/4);
693         // calculate where it goes
694         cairo_text_extents (cr, string, &te);
695
696         if (align == 1) {
697             xx += squareSize - te.width - te.x_bearing - 1;
698             yy += squareSize - te.height - te.y_bearing - 1;
699         } else if (align == 2) {
700             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
701         } else if (align == 3) {
702             xx += squareSize - te.width -te.x_bearing - 1;
703             yy += -te.y_bearing + 3;
704         } else if (align == 4) {
705             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
706         }
707
708         cairo_move_to (cr, xx-1, yy);
709         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
710         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
711         cairo_show_text (cr, string);
712         cairo_destroy (cr);
713     }
714
715     if(marker) { // print fat marker dot, if requested
716         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
717     }
718 }
719
720 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
721
722 /*      Masks for XPM pieces. Black and white pieces can have
723         different shapes, but in the interest of retaining my
724         sanity pieces must have the same outline on both light
725         and dark squares, and all pieces must use the same
726         background square colors/images.                */
727
728 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
729
730 static void
731 InitAnimState (AnimNr anr)
732 {
733     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
734     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
735     c_animBufs[anr+4] = csBoardWindow;
736     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
737     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
738 }
739
740 void
741 CreateAnimVars ()
742 {
743   InitAnimState(Game);
744   InitAnimState(Player);
745 }
746
747 static void
748 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
749 {
750   static cairo_t *pieceSource;
751   pieceSource = cairo_create (dest);
752   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
753   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
754   else cairo_paint(pieceSource);
755   cairo_destroy (pieceSource);
756 }
757
758 void
759 InsertPiece (AnimNr anr, ChessSquare piece)
760 {
761     CairoOverlayPiece(piece, c_animBufs[anr]);
762 }
763
764 void
765 DrawBlank (AnimNr anr, int x, int y, int startColor)
766 {
767     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
768 }
769
770 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
771                  int srcX, int srcY, int width, int height, int destX, int destY)
772 {
773         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
774         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
775         cairo_rectangle (cr, destX, destY, width, height);
776         cairo_fill (cr);
777         cairo_destroy (cr);
778         if(c_animBufs[anr+destBuf] == csBoardWindow)
779             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
780 }
781
782 void
783 SetDragPiece (AnimNr anr, ChessSquare piece)
784 {
785 }
786
787 /* [AS] Arrow highlighting support */
788
789 void
790 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
791 {
792     cairo_t *cr;
793     int i;
794     cr = cairo_create (cs);
795     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
796     for (i=0;i<nr;i++) {
797         cairo_line_to(cr, arrow[i].x, arrow[i].y);
798     }
799     if(appData.monoMode) { // should we always outline arrow?
800         cairo_line_to(cr, arrow[0].x, arrow[0].y);
801         SetPen(cr, 2, "#000000", 0);
802         cairo_stroke_preserve(cr);
803     }
804     SetPen(cr, 2, appData.highlightSquareColor, 0);
805     cairo_fill(cr);
806
807     /* free memory */
808     cairo_destroy (cr);
809 }
810
811 void
812 DrawPolygon (Pnt arrow[], int nr)
813 {
814     DoDrawPolygon(csBoardWindow, arrow, nr);
815 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
816 }
817
818