Cache svg handles
[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 RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
119 static cairo_surface_t *pngBoardBitmap[2];
120 int useTexture, textureW[2], textureH[2];
121
122 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
123 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
124
125 #define White(piece) ((int)(piece) < (int)BlackPawn)
126
127 struct {
128   int x1, x2, y1, y2;
129 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
130
131 static int dual = 0;
132
133 void
134 SwitchWindow ()
135 {
136     dual = !dual;
137     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
138     csBoardWindow = DRAWABLE(currBoard);
139 }
140
141 #define BoardSize int
142 void
143 InitDrawingSizes (BoardSize boardSize, int flags)
144 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
145     int boardWidth, boardHeight;
146     int i;
147     static int oldWidth, oldHeight;
148     static VariantClass oldVariant;
149     static int oldMono = -1, oldTwoBoards = 0;
150     extern Widget formWidget;
151
152     if(!formWidget) return;
153
154     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
155     oldTwoBoards = twoBoards;
156
157     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
158     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
159     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
160
161   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
162
163     oldWidth = boardWidth; oldHeight = boardHeight;
164     CreateGrid();
165
166     /*
167      * Inhibit shell resizing.
168      */
169     ResizeBoardWindow(boardWidth, boardHeight, 0);
170
171     DelayedDrag();
172   }
173
174     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
175     // (only for xpm)
176
177   if(gameInfo.variant != oldVariant) { // and only if variant changed
178
179     for(i=0; i<2; i++) {
180         int p;
181         for(p=0; p<=(int)WhiteKing; p++)
182            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
183         if(gameInfo.variant == VariantShogi) {
184            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
185            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
186            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
187            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
188            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
189         }
190 #ifdef GOTHIC
191         if(gameInfo.variant == VariantGothic) {
192            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
193         }
194 #endif
195         if(gameInfo.variant == VariantSChess) {
196            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
197            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
198         }
199     }
200     oldMono = -10; // kludge to force recreation of animation masks
201     oldVariant = gameInfo.variant;
202   }
203   CreateAnimVars();
204   oldMono = appData.monoMode;
205 }
206
207 static void
208 CreatePNGBoard (char *s, int kind)
209 {
210     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
211     if(strstr(s, ".png")) {
212         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
213         if(img) {
214             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
215             textureW[kind] = cairo_image_surface_get_width (img);
216             textureH[kind] = cairo_image_surface_get_height (img);
217         }
218     }
219 }
220
221 char *pngPieceNames[] = // must be in same order as internal piece encoding
222 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
223   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
224   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
225 };
226
227 cairo_surface_t *
228 ConvertPixmap (int color, int piece)
229 {
230   int i, j, stride, f, colcode[10], w, b;
231   char ch[10];
232   cairo_surface_t *res;
233   XpmPieces *p = builtInXpms + 10;
234   char **pixels = p->xpm[piece % BlackPawn][2*color];
235   int *buf;
236   sscanf(pixels[0], "%*d %*d %d", &f);
237   sscanf(appData.whitePieceColor+1, "%x", &w);
238   sscanf(appData.blackPieceColor+1, "%x", &b);
239   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
240   stride = cairo_image_surface_get_stride(res);
241   buf = (int *) cairo_image_surface_get_data(res);
242   for(i=0; i<f; i++) {
243     ch[i] = pixels[i+1][0];
244     colcode[i] = 0;
245     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
246     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
247   }
248   for(i=0; i<p->size; i++) {
249     for(j=0; j<p->size; j++) {
250       char c = pixels[i+f+1][j];
251       int k;
252       for(k=0; ch[k] != c && k < f; k++);
253       buf[i*p->size + j] = colcode[k];
254     }
255   }
256   cairo_surface_mark_dirty(res);
257   return res;
258 }
259
260 RsvgHandle *
261 LoadSVG (char *dir, int color, int piece)
262 {
263     char buf[MSG_SIZ];
264   RsvgHandle *svg=NULL;
265   RsvgDimensionData svg_dimensions;
266   GError **svgerror=NULL;
267   cairo_surface_t *img;
268   cairo_t *cr;
269
270     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
271
272     if(svg = rsvg_handle_new_from_file(buf,svgerror)) {
273
274       rsvg_handle_get_dimensions(svg, &svg_dimensions);
275       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
276
277       cr = cairo_create(img);
278       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
279       rsvg_handle_render_cairo(svg, cr);
280       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
281         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
282         pngPieceImages[color][piece] = img;
283       }
284       cairo_destroy(cr);
285
286       return svg;
287     }
288     return NULL;
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(!svgPieces[color][piece] && *appData.svgDirectory) { // try to freshly render svg pieces first, always from file, to supply the source bitmap
302     svgPieces[color][piece] = 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         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       cairo_move_to (cr, gridSegments[i].x1 + odd, gridSegments[i].y1 + odd);
530       cairo_line_to (cr, gridSegments[i].x2 + odd, gridSegments[i].y2 + 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 = cairo_create (c_animBufs[anr+destBuf]);
776         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
777         cairo_rectangle (cr, destX, destY, width, height);
778         cairo_fill (cr);
779         cairo_destroy (cr);
780         if(c_animBufs[anr+destBuf] == csBoardWindow)
781             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
782 }
783
784 void
785 SetDragPiece (AnimNr anr, ChessSquare piece)
786 {
787 }
788
789 /* [AS] Arrow highlighting support */
790
791 void
792 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
793 {
794     cairo_t *cr;
795     int i;
796     cr = cairo_create (cs);
797     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
798     for (i=0;i<nr;i++) {
799         cairo_line_to(cr, arrow[i].x, arrow[i].y);
800     }
801     if(appData.monoMode) { // should we always outline arrow?
802         cairo_line_to(cr, arrow[0].x, arrow[0].y);
803         SetPen(cr, 2, "#000000", 0);
804         cairo_stroke_preserve(cr);
805     }
806     SetPen(cr, 2, appData.highlightSquareColor, 0);
807     cairo_fill(cr);
808
809     /* free memory */
810     cairo_destroy (cr);
811 }
812
813 void
814 DrawPolygon (Pnt arrow[], int nr)
815 {
816     DoDrawPolygon(csBoardWindow, arrow, nr);
817 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
818 }
819
820