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