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