Add -trueColors option
[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 #else
84 #include "bitmaps/bitmaps.h"
85 #endif
86
87 #include "frontend.h"
88 #include "backend.h"
89 #include "xevalgraph.h"
90 #include "board.h"
91 #include "menus.h"
92 #include "dialogs.h"
93 #include "gettext.h"
94 #include "draw.h"
95
96
97 #ifdef __EMX__
98 #ifndef HAVE_USLEEP
99 #define HAVE_USLEEP
100 #endif
101 #define usleep(t)   _sleep2(((t)+500)/1000)
102 #endif
103
104 #ifdef ENABLE_NLS
105 # define  _(s) gettext (s)
106 # define N_(s) gettext_noop (s)
107 #else
108 # define  _(s) (s)
109 # define N_(s)  s
110 #endif
111
112 #define SOLID 0
113 #define OUTLINE 1
114 Boolean cairoAnimate;
115 Option *currBoard;
116 cairo_surface_t *csBoardWindow;
117 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
118 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
119 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
120 static cairo_surface_t *pngBoardBitmap[2];
121 int useTexture, textureW[2], textureH[2];
122
123 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
124 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
125
126 #define White(piece) ((int)(piece) < (int)BlackPawn)
127
128 struct {
129   int x1, x2, y1, y2;
130 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
131
132 static int dual = 0;
133
134 void
135 SwitchWindow ()
136 {
137     dual = !dual;
138     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
139     csBoardWindow = DRAWABLE(currBoard);
140 }
141
142 #define BoardSize int
143 void
144 InitDrawingSizes (BoardSize boardSize, int flags)
145 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
146     int boardWidth, boardHeight;
147     int i;
148     static int oldWidth, oldHeight;
149     static VariantClass oldVariant;
150     static int oldMono = -1, oldTwoBoards = 0;
151     extern Widget formWidget;
152
153     if(!formWidget) return;
154
155     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
156     oldTwoBoards = twoBoards;
157
158     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
159     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
160     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
161
162   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
163
164     oldWidth = boardWidth; oldHeight = boardHeight;
165     CreateGrid();
166
167     /*
168      * Inhibit shell resizing.
169      */
170     ResizeBoardWindow(boardWidth, boardHeight, 0);
171
172     DelayedDrag();
173   }
174
175     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
176     // (only for xpm)
177
178   if(gameInfo.variant != oldVariant) { // and only if variant changed
179
180     for(i=0; i<2; i++) {
181         int p;
182         for(p=0; p<=(int)WhiteKing; p++)
183            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
184         if(gameInfo.variant == VariantShogi) {
185            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
186            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
187            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
188            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
189            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
190         }
191 #ifdef GOTHIC
192         if(gameInfo.variant == VariantGothic) {
193            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
194         }
195 #endif
196         if(gameInfo.variant == VariantSChess) {
197            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
198            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
199         }
200     }
201     oldMono = -10; // kludge to force recreation of animation masks
202     oldVariant = gameInfo.variant;
203   }
204   CreateAnimVars();
205   oldMono = appData.monoMode;
206 }
207
208 static void
209 CreatePNGBoard (char *s, int kind)
210 {
211     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
212     if(strstr(s, ".png")) {
213         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
214         if(img) {
215             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
216             textureW[kind] = cairo_image_surface_get_width (img);
217             textureH[kind] = cairo_image_surface_get_height (img);
218         }
219     }
220 }
221
222 char *pngPieceNames[] = // must be in same order as internal piece encoding
223 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
224   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
225   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
226 };
227
228 cairo_surface_t *
229 ConvertPixmap (int color, int piece)
230 {
231   int i, j, stride, f, colcode[10], w, b;
232   char ch[10];
233   cairo_surface_t *res;
234   XpmPieces *p = builtInXpms + 10;
235   char **pixels = p->xpm[piece % BlackPawn][2*color];
236   int *buf;
237   sscanf(pixels[0], "%*d %*d %d", &f);
238   sscanf(appData.whitePieceColor+1, "%x", &w);
239   sscanf(appData.blackPieceColor+1, "%x", &b);
240   res = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->size, p->size);
241   stride = cairo_image_surface_get_stride(res);
242   buf = (int *) cairo_image_surface_get_data(res);
243   for(i=0; i<f; i++) {
244     ch[i] = pixels[i+1][0];
245     colcode[i] = 0;
246     if(strstr(pixels[i+1], "black")) colcode[i] = 0xFF000000; // 0xFF000000 + (color ? b : 0);
247     if(strstr(pixels[i+1], "white")) colcode[i] = 0xFFFFFFCC; // 0xFF000000 + w;
248   }
249   for(i=0; i<p->size; i++) {
250     for(j=0; j<p->size; j++) {
251       char c = pixels[i+f+1][j];
252       int k;
253       for(k=0; ch[k] != c && k < f; k++);
254       buf[i*p->size + j] = colcode[k];
255     }
256   }
257   cairo_surface_mark_dirty(res);
258   return res;
259 }
260
261 static void
262 ScaleOnePiece (char *name, int color, int piece)
263 {
264   float w, h;
265   char buf[MSG_SIZ];
266   RsvgHandle *svg=NULL;
267   RsvgDimensionData svg_dimensions;
268   GError **svgerror=NULL;
269   cairo_surface_t *img, *cs;
270   cairo_t *cr;
271
272   g_type_init ();
273
274   if(*appData.svgDirectory) { // try to freshly render svg pieces first, always from file, to supply the source bitmap
275     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", appData.svgDirectory, color ? "Black" : "White", pngPieceNames[piece]);
276
277     if(svg = rsvg_handle_new_from_file(buf,svgerror)) {
278
279       rsvg_handle_get_dimensions(svg, &svg_dimensions);
280       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
281
282       cr = cairo_create(img);
283       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
284       rsvg_handle_render_cairo(svg, cr);
285       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
286         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
287         pngPieceImages[color][piece] = img;
288       }
289       cairo_destroy(cr);
290
291       rsvg_handle_close (svg,NULL);
292     }
293   }
294
295   if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
296     if(!*appData.pngDirectory) img = ConvertPixmap(color, piece); else {
297       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pngDirectory, color ? "Black" : "White", pngPieceNames[piece]);
298       img = cairo_image_surface_create_from_png (buf);
299       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) img = ConvertPixmap(color, piece);
300     }
301   }
302   pngPieceImages[color][piece] = img;
303
304
305   // create new bitmap to hold scaled piece image (and remove any old)
306   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
307   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
308   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
309
310   // scaled copying of the raw png image
311   cr = cairo_create(cs);
312   w = cairo_image_surface_get_width (img);
313   h = cairo_image_surface_get_height (img);
314   cairo_scale(cr, squareSize/w, squareSize/h);
315   cairo_set_source_surface (cr, img, 0, 0);
316   cairo_paint (cr);
317   cairo_destroy (cr);
318
319   if(!appData.trueColors || !*appData.pngDirectory && !appData.svgDirectory) { // operate on bitmap to color it (king-size hack...)
320     int stride = cairo_image_surface_get_stride(cs)/4;
321     int *buf = (int *) cairo_image_surface_get_data(cs);
322     int i, j, p;
323     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
324     cairo_surface_flush(cs);
325     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
326         int r, a;
327         float f;
328         unsigned int c = buf[i*stride + j];
329         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
330         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
331         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
332         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
333         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
334     }
335     cairo_surface_mark_dirty(cs);
336   }
337 }
338
339 void
340 CreatePNGPieces ()
341 {
342   int p;
343
344   for(p=0; pngPieceNames[p]; p++) {
345     ScaleOnePiece(pngPieceNames[p], 0, p);
346     ScaleOnePiece(pngPieceNames[p], 1, p);
347   }
348 }
349
350 void
351 CreateAnyPieces ()
352 {   // [HGM] taken out of main
353     CreatePNGPieces();
354     CreatePNGBoard(appData.liteBackTextureFile, 1);
355     CreatePNGBoard(appData.darkBackTextureFile, 0);
356 }
357
358 void
359 InitDrawingParams (int reloadPieces)
360 {
361     int i, p;
362     MakeColors();
363     if(reloadPieces)
364     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
365         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
366         pngPieceImages[i][p] = NULL;
367     }
368     CreateAnyPieces();
369 }
370
371 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
372
373 float
374 Color (char *col, int n)
375 {
376   int c;
377   sscanf(col, "#%x", &c);
378   c = c >> 4*n & 255;
379   return c/255.;
380 }
381
382 void
383 SetPen (cairo_t *cr, float w, char *col, int dash)
384 {
385   static const double dotted[] = {4.0, 4.0};
386   static int len  = sizeof(dotted) / sizeof(dotted[0]);
387   cairo_set_line_width (cr, w);
388   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
389   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
390 }
391
392 void DrawSeekAxis( int x, int y, int xTo, int yTo )
393 {
394     cairo_t *cr;
395
396     /* get a cairo_t */
397     cr = cairo_create (csBoardWindow);
398
399     cairo_move_to (cr, x, y);
400     cairo_line_to(cr, xTo, yTo );
401
402     SetPen(cr, 2, "#000000", 0);
403     cairo_stroke(cr);
404
405     /* free memory */
406     cairo_destroy (cr);
407     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
408 }
409
410 void DrawSeekBackground( int left, int top, int right, int bottom )
411 {
412     cairo_t *cr = cairo_create (csBoardWindow);
413
414     cairo_rectangle (cr, left, top, right-left, bottom-top);
415
416     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
417     cairo_fill(cr);
418
419     /* free memory */
420     cairo_destroy (cr);
421     GraphExpose(currBoard, left, top, right-left, bottom-top);
422 }
423
424 void DrawSeekText(char *buf, int x, int y)
425 {
426     cairo_t *cr = cairo_create (csBoardWindow);
427
428     cairo_select_font_face (cr, "Sans",
429                             CAIRO_FONT_SLANT_NORMAL,
430                             CAIRO_FONT_WEIGHT_NORMAL);
431
432     cairo_set_font_size (cr, 12.0);
433
434     cairo_move_to (cr, x, y+4);
435     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
436     cairo_show_text( cr, buf);
437
438     /* free memory */
439     cairo_destroy (cr);
440     GraphExpose(currBoard, x-5, y-10, 60, 15);
441 }
442
443 void DrawSeekDot(int x, int y, int colorNr)
444 {
445     cairo_t *cr = cairo_create (csBoardWindow);
446     int square = colorNr & 0x80;
447     colorNr &= 0x7F;
448
449     if(square)
450         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
451     else
452         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
453
454     SetPen(cr, 2, "#000000", 0);
455     cairo_stroke_preserve(cr);
456     switch (colorNr) {
457       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
458       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
459       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
460     }
461     cairo_fill(cr);
462
463     /* free memory */
464     cairo_destroy (cr);
465     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
466 }
467
468 void
469 InitDrawingHandle (Option *opt)
470 {
471     csBoardWindow = DRAWABLE(opt);
472 }
473
474 void
475 CreateGrid ()
476 {
477     int i, j;
478
479     if (lineGap == 0) return;
480
481     /* [HR] Split this into 2 loops for non-square boards. */
482
483     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
484         gridSegments[i].x1 = 0;
485         gridSegments[i].x2 =
486           lineGap + BOARD_WIDTH * (squareSize + lineGap);
487         gridSegments[i].y1 = gridSegments[i].y2
488           = lineGap / 2 + (i * (squareSize + lineGap));
489     }
490
491     for (j = 0; j < BOARD_WIDTH + 1; j++) {
492         gridSegments[j + i].y1 = 0;
493         gridSegments[j + i].y2 =
494           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
495         gridSegments[j + i].x1 = gridSegments[j + i].x2
496           = lineGap / 2 + (j * (squareSize + lineGap));
497     }
498 }
499
500 void
501 DrawGrid()
502 {
503   /* draws a grid starting around Nx, Ny squares starting at x,y */
504   int i;
505   cairo_t *cr;
506
507   /* get a cairo_t */
508   cr = cairo_create (csBoardWindow);
509
510   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
511   SetPen(cr, lineGap, "#000000", 0);
512
513   /* lines in X */
514   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
515     {
516       cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
517       cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
518       cairo_stroke (cr);
519     }
520
521   /* free memory */
522   cairo_destroy (cr);
523
524   return;
525 }
526
527 void
528 DrawBorder (int x, int y, int type)
529 {
530     cairo_t *cr;
531     char *col;
532
533     switch(type) {
534         case 0: col = "#000000"; break;
535         case 1: col = appData.highlightSquareColor; break;
536         case 2: col = appData.premoveHighlightColor; break;
537     }
538     cr = cairo_create(csBoardWindow);
539     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
540     cairo_rectangle(cr, x, y, squareSize+lineGap, squareSize+lineGap);
541     SetPen(cr, lineGap, col, 0);
542     cairo_stroke(cr);
543     cairo_destroy(cr);
544     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap, squareSize+2*lineGap);
545 }
546
547 static int
548 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
549 {
550     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
551     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
552     *x0 = 0; *y0 = 0;
553     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
554     if(textureW[kind] < W*squareSize)
555         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
556     else
557         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
558     if(textureH[kind] < H*squareSize)
559         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
560     else
561         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
562     return 1;
563 }
564
565 void
566 DrawLogo (Option *opt, void *logo)
567 {
568     cairo_surface_t *img;
569     cairo_t *cr;
570     int w, h;
571
572     if(!logo || !opt) return;
573     img = cairo_image_surface_create_from_png (logo);
574     w = cairo_image_surface_get_width (img);
575     h = cairo_image_surface_get_height (img);
576     cr = cairo_create(DRAWABLE(opt));
577     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
578     cairo_set_source_surface (cr, img, 0, 0);
579     cairo_paint (cr);
580     cairo_destroy (cr);
581     cairo_surface_destroy (img);
582     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
583 }
584
585 static void
586 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
587 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
588     int x0, y0;
589     cairo_t *cr;
590
591     cr = cairo_create (dest);
592
593     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
594             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
595             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
596             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
597             cairo_fill (cr);
598             cairo_destroy (cr);
599     } else { // evenly colored squares
600         char *col;
601         switch (color) {
602           case 0: col = appData.darkSquareColor; break;
603           case 1: col = appData.lightSquareColor; break;
604           case 2: col = "#000000"; break;
605         }
606         SetPen(cr, 2.0, col, 0);
607         cairo_rectangle (cr, x, y, squareSize, squareSize);
608         cairo_fill (cr);
609         cairo_destroy (cr);
610     }
611 }
612
613 static void
614 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
615 {
616     int kind, p = piece;
617     cairo_t *cr;
618
619     if ((int)piece < (int) BlackPawn) {
620         kind = 0;
621     } else {
622         kind = 1;
623         piece -= BlackPawn;
624     }
625     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
626     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
627     cr = cairo_create (dest);
628     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
629     cairo_paint(cr);
630     cairo_destroy (cr);
631 }
632
633 void
634 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
635 {
636         cairo_t *cr;
637
638         cr = cairo_create(cs);
639         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
640         if(appData.monoMode) {
641             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
642             cairo_stroke_preserve(cr);
643             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
644         } else {
645             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
646         }
647         cairo_fill(cr);
648
649         cairo_destroy(cr);
650 }
651
652 void
653 DrawDot (int marker, int x, int y, int r)
654 { // used for atomic captures; no need to draw on backup
655   DoDrawDot(csBoardWindow, marker, x, y, 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