Fix bug in fallback mechanism
[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     if(svgerror)
271         g_error_free(svgerror);
272     return NULL;
273 }
274
275 static void
276 ScaleOnePiece (int color, int piece)
277 {
278   float w, h;
279   char buf[MSG_SIZ];
280   cairo_surface_t *img, *cs;
281   cairo_t *cr;
282
283   g_type_init ();
284
285   if(!svgPieces[color][piece]) { // try to freshly render cached svg pieces first, to supply the source bitmap
286     svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
287   }
288
289   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
290     if(*appData.pieceDirectory) { // user specified piece directory
291       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
292       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
293       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
294         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
295       } else pngPieceImages[color][piece] = img;
296     }
297   }
298
299   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
300     static int warned = 0;
301     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
302       char *msg = _("No default pieces installed\nSelect your own -pieceImageDirectory");
303       printf("%s\n", msg); // give up
304       DisplayError(msg, 0);
305       warned = 1; // prevent error message being repeated for each piece type
306     }
307   }
308
309   img = pngPieceImages[color][piece];
310
311   // create new bitmap to hold scaled piece image (and remove any old)
312   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
313   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
314
315   if(!img) return;
316
317   // scaled copying of the raw png image
318   cr = cairo_create(cs);
319   w = cairo_image_surface_get_width (img);
320   h = cairo_image_surface_get_height (img);
321   cairo_scale(cr, squareSize/w, squareSize/h);
322   cairo_set_source_surface (cr, img, 0, 0);
323   cairo_paint (cr);
324   cairo_destroy (cr);
325
326   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
327     int stride = cairo_image_surface_get_stride(cs)/4;
328     int *buf = (int *) cairo_image_surface_get_data(cs);
329     int i, j, p;
330     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
331     cairo_surface_flush(cs);
332     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
333         int r, a;
334         float f;
335         unsigned int c = buf[i*stride + j];
336         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
337         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
338         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
339         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
340         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
341         if(appData.monoMode) {
342             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
343             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
344             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
345         }
346     }
347     cairo_surface_mark_dirty(cs);
348   }
349 }
350
351 void
352 CreatePNGPieces ()
353 {
354   int p;
355
356   for(p=0; pngPieceNames[p]; p++) {
357     ScaleOnePiece(0, p);
358     ScaleOnePiece(1, p);
359   }
360   SelectPieces(gameInfo.variant);
361 }
362
363 void
364 CreateAnyPieces ()
365 {   // [HGM] taken out of main
366     CreatePNGPieces();
367     CreatePNGBoard(appData.liteBackTextureFile, 1);
368     CreatePNGBoard(appData.darkBackTextureFile, 0);
369 }
370
371 void
372 InitDrawingParams (int reloadPieces)
373 {
374     int i, p;
375     if(reloadPieces)
376     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
377         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
378         pngPieceImages[i][p] = NULL;
379         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
380         svgPieces[i][p] = NULL;
381     }
382     CreateAnyPieces();
383 }
384
385 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
386
387 float
388 Color (char *col, int n)
389 {
390   int c;
391   sscanf(col, "#%x", &c);
392   c = c >> 4*n & 255;
393   return c/255.;
394 }
395
396 void
397 SetPen (cairo_t *cr, float w, char *col, int dash)
398 {
399   static const double dotted[] = {4.0, 4.0};
400   static int len  = sizeof(dotted) / sizeof(dotted[0]);
401   cairo_set_line_width (cr, w);
402   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
403   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
404 }
405
406 void DrawSeekAxis( int x, int y, int xTo, int yTo )
407 {
408     cairo_t *cr;
409
410     /* get a cairo_t */
411     cr = cairo_create (csBoardWindow);
412
413     cairo_move_to (cr, x, y);
414     cairo_line_to(cr, xTo, yTo );
415
416     SetPen(cr, 2, "#000000", 0);
417     cairo_stroke(cr);
418
419     /* free memory */
420     cairo_destroy (cr);
421     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
422 }
423
424 void DrawSeekBackground( int left, int top, int right, int bottom )
425 {
426     cairo_t *cr = cairo_create (csBoardWindow);
427
428     cairo_rectangle (cr, left, top, right-left, bottom-top);
429
430     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
431     cairo_fill(cr);
432
433     /* free memory */
434     cairo_destroy (cr);
435     GraphExpose(currBoard, left, top, right-left, bottom-top);
436 }
437
438 void DrawSeekText(char *buf, int x, int y)
439 {
440     cairo_t *cr = cairo_create (csBoardWindow);
441
442     cairo_select_font_face (cr, "Sans",
443                             CAIRO_FONT_SLANT_NORMAL,
444                             CAIRO_FONT_WEIGHT_NORMAL);
445
446     cairo_set_font_size (cr, 12.0);
447
448     cairo_move_to (cr, x, y+4);
449     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
450     cairo_show_text( cr, buf);
451
452     /* free memory */
453     cairo_destroy (cr);
454     GraphExpose(currBoard, x-5, y-10, 60, 15);
455 }
456
457 void DrawSeekDot(int x, int y, int colorNr)
458 {
459     cairo_t *cr = cairo_create (csBoardWindow);
460     int square = colorNr & 0x80;
461     colorNr &= 0x7F;
462
463     if(square)
464         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
465     else
466         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
467
468     SetPen(cr, 2, "#000000", 0);
469     cairo_stroke_preserve(cr);
470     switch (colorNr) {
471       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
472       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
473       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
474     }
475     cairo_fill(cr);
476
477     /* free memory */
478     cairo_destroy (cr);
479     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
480 }
481
482 void
483 InitDrawingHandle (Option *opt)
484 {
485     csBoardWindow = DRAWABLE(opt);
486 }
487
488 void
489 CreateGrid ()
490 {
491     int i, j;
492
493     if (lineGap == 0) return;
494
495     /* [HR] Split this into 2 loops for non-square boards. */
496
497     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
498         gridSegments[i].x1 = 0;
499         gridSegments[i].x2 =
500           lineGap + BOARD_WIDTH * (squareSize + lineGap);
501         gridSegments[i].y1 = gridSegments[i].y2
502           = lineGap / 2 + (i * (squareSize + lineGap));
503     }
504
505     for (j = 0; j < BOARD_WIDTH + 1; j++) {
506         gridSegments[j + i].y1 = 0;
507         gridSegments[j + i].y2 =
508           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
509         gridSegments[j + i].x1 = gridSegments[j + i].x2
510           = lineGap / 2 + (j * (squareSize + lineGap));
511     }
512 }
513
514 void
515 DrawGrid()
516 {
517   /* draws a grid starting around Nx, Ny squares starting at x,y */
518   int i;
519   float odd = (lineGap & 1)/2.;
520   cairo_t *cr;
521
522   /* get a cairo_t */
523   cr = cairo_create (csBoardWindow);
524
525   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
526   SetPen(cr, lineGap, "#000000", 0);
527
528   /* lines in X */
529   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
530     {
531       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
532       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
533       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
534       cairo_stroke (cr);
535     }
536
537   /* free memory */
538   cairo_destroy (cr);
539
540   return;
541 }
542
543 void
544 DrawBorder (int x, int y, int type, int odd)
545 {
546     cairo_t *cr;
547     char *col;
548
549     switch(type) {
550         case 0: col = "#000000"; break;
551         case 1: col = appData.highlightSquareColor; break;
552         case 2: col = appData.premoveHighlightColor; break;
553     }
554     cr = cairo_create(csBoardWindow);
555     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
556     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
557     SetPen(cr, lineGap, col, 0);
558     cairo_stroke(cr);
559     cairo_destroy(cr);
560     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
561 }
562
563 static int
564 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
565 {
566     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
567     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
568     *x0 = 0; *y0 = 0;
569     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
570     if(textureW[kind] < W*squareSize)
571         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
572     else
573         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
574     if(textureH[kind] < H*squareSize)
575         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
576     else
577         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
578     return 1;
579 }
580
581 void
582 DrawLogo (Option *opt, void *logo)
583 {
584     cairo_surface_t *img;
585     cairo_t *cr;
586     int w, h;
587
588     if(!logo || !opt) return;
589     img = cairo_image_surface_create_from_png (logo);
590     w = cairo_image_surface_get_width (img);
591     h = cairo_image_surface_get_height (img);
592     cr = cairo_create(DRAWABLE(opt));
593     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
594     cairo_set_source_surface (cr, img, 0, 0);
595     cairo_paint (cr);
596     cairo_destroy (cr);
597     cairo_surface_destroy (img);
598     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
599 }
600
601 static void
602 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
603 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
604     int x0, y0;
605     cairo_t *cr;
606
607     cr = cairo_create (dest);
608
609     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
610             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
611             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
612             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
613             cairo_fill (cr);
614             cairo_destroy (cr);
615     } else { // evenly colored squares
616         char *col;
617         switch (color) {
618           case 0: col = appData.darkSquareColor; break;
619           case 1: col = appData.lightSquareColor; break;
620           case 2: col = "#000000"; break;
621         }
622         SetPen(cr, 2.0, col, 0);
623         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
624         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
625         cairo_fill (cr);
626         cairo_destroy (cr);
627     }
628 }
629
630 static void
631 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
632 {
633     int kind, p = piece;
634     cairo_t *cr;
635
636     if ((int)piece < (int) BlackPawn) {
637         kind = 0;
638     } else {
639         kind = 1;
640         piece -= BlackPawn;
641     }
642     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
643     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
644     cr = cairo_create (dest);
645     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
646     cairo_paint(cr);
647     cairo_destroy (cr);
648 }
649
650 void
651 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
652 {
653         cairo_t *cr;
654
655         cr = cairo_create(cs);
656         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
657         if(appData.monoMode) {
658             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
659             cairo_stroke_preserve(cr);
660             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
661         } else {
662             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
663         }
664         cairo_fill(cr);
665
666         cairo_destroy(cr);
667 }
668
669 void
670 DrawDot (int marker, int x, int y, int r)
671 { // used for atomic captures; no need to draw on backup
672   DoDrawDot(csBoardWindow, marker, x, y, r);
673   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
674 }
675
676 void
677 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
678 {   // basic front-end board-draw function: takes care of everything that can be in square:
679     // piece, background, coordinate/count, marker dot
680     cairo_t *cr;
681
682     if (piece == EmptySquare) {
683         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
684     } else {
685         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
686     }
687
688     if(align) { // square carries inscription (coord or piece count)
689         int xx = x, yy = y;
690         cairo_text_extents_t te;
691
692         cr = cairo_create (csBoardWindow);
693         cairo_select_font_face (cr, "Sans",
694                     CAIRO_FONT_SLANT_NORMAL,
695                     CAIRO_FONT_WEIGHT_BOLD);
696
697         cairo_set_font_size (cr, squareSize/4);
698         // calculate where it goes
699         cairo_text_extents (cr, string, &te);
700
701         if (align == 1) {
702             xx += squareSize - te.width - te.x_bearing - 1;
703             yy += squareSize - te.height - te.y_bearing - 1;
704         } else if (align == 2) {
705             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
706         } else if (align == 3) {
707             xx += squareSize - te.width -te.x_bearing - 1;
708             yy += -te.y_bearing + 3;
709         } else if (align == 4) {
710             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
711         }
712
713         cairo_move_to (cr, xx-1, yy);
714         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
715         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
716         cairo_show_text (cr, string);
717         cairo_destroy (cr);
718     }
719
720     if(marker) { // print fat marker dot, if requested
721         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
722     }
723 }
724
725 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
726
727 /*      Masks for XPM pieces. Black and white pieces can have
728         different shapes, but in the interest of retaining my
729         sanity pieces must have the same outline on both light
730         and dark squares, and all pieces must use the same
731         background square colors/images.                */
732
733 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
734
735 static void
736 InitAnimState (AnimNr anr)
737 {
738     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
739     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
740     c_animBufs[anr+4] = csBoardWindow;
741     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
742     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
743 }
744
745 void
746 CreateAnimVars ()
747 {
748   InitAnimState(Game);
749   InitAnimState(Player);
750 }
751
752 static void
753 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
754 {
755   static cairo_t *pieceSource;
756   pieceSource = cairo_create (dest);
757   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
758   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
759   else cairo_paint(pieceSource);
760   cairo_destroy (pieceSource);
761 }
762
763 void
764 InsertPiece (AnimNr anr, ChessSquare piece)
765 {
766     CairoOverlayPiece(piece, c_animBufs[anr]);
767 }
768
769 void
770 DrawBlank (AnimNr anr, int x, int y, int startColor)
771 {
772     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
773 }
774
775 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
776                  int srcX, int srcY, int width, int height, int destX, int destY)
777 {
778         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
779         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
780         cairo_rectangle (cr, destX, destY, width, height);
781         cairo_fill (cr);
782         cairo_destroy (cr);
783         if(c_animBufs[anr+destBuf] == csBoardWindow)
784             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
785 }
786
787 void
788 SetDragPiece (AnimNr anr, ChessSquare piece)
789 {
790 }
791
792 /* [AS] Arrow highlighting support */
793
794 void
795 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
796 {
797     cairo_t *cr;
798     int i;
799     cr = cairo_create (cs);
800     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
801     for (i=0;i<nr;i++) {
802         cairo_line_to(cr, arrow[i].x, arrow[i].y);
803     }
804     if(appData.monoMode) { // should we always outline arrow?
805         cairo_line_to(cr, arrow[0].x, arrow[0].y);
806         SetPen(cr, 2, "#000000", 0);
807         cairo_stroke_preserve(cr);
808     }
809     SetPen(cr, 2, appData.highlightSquareColor, 0);
810     cairo_fill(cr);
811
812     /* free memory */
813     cairo_destroy (cr);
814 }
815
816 void
817 DrawPolygon (Pnt arrow[], int nr)
818 {
819     DoDrawPolygon(csBoardWindow, arrow, nr);
820 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
821 }
822
823