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