Revive -flipBlack 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, 2013 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 #include "common.h"
78
79 #include "backend.h"
80 #include "board.h"
81 #include "menus.h"
82 #include "dialogs.h"
83 #include "evalgraph.h"
84 #include "gettext.h"
85 #include "draw.h"
86
87
88 #ifdef __EMX__
89 #ifndef HAVE_USLEEP
90 #define HAVE_USLEEP
91 #endif
92 #define usleep(t)   _sleep2(((t)+500)/1000)
93 #endif
94
95 #ifdef ENABLE_NLS
96 # define  _(s) gettext (s)
97 # define N_(s) gettext_noop (s)
98 #else
99 # define  _(s) (s)
100 # define N_(s)  s
101 #endif
102
103 #define SOLID 0
104 #define OUTLINE 1
105 Boolean cairoAnimate;
106 Option *currBoard;
107 cairo_surface_t *csBoardWindow;
108 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
109 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
110 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
111 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
112 static cairo_surface_t *pngBoardBitmap[2];
113 int useTexture, textureW[2], textureH[2];
114
115 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
116 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
117
118 #define White(piece) ((int)(piece) < (int)BlackPawn)
119
120 char *crWhite = "#FFFFB0";
121 char *crBlack = "#AD5D3D";
122
123 struct {
124   int x1, x2, y1, y2;
125 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
126
127 static int dual = 0;
128
129 void
130 SwitchWindow ()
131 {
132     dual = !dual;
133     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
134     csBoardWindow = DRAWABLE(currBoard);
135 }
136
137 void
138 SelectPieces(VariantClass v)
139 {
140     int i;
141     for(i=0; i<2; i++) {
142         int p;
143         for(p=0; p<=(int)WhiteKing; p++)
144            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
145         if(v == VariantShogi) {
146            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
147            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
148            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
149            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
150            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
151         }
152 #ifdef GOTHIC
153         if(v == VariantGothic) {
154            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
155         }
156 #endif
157         if(v == VariantSChess) {
158            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
159            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
160         }
161     }
162 }
163
164 #define BoardSize int
165 void
166 InitDrawingSizes (BoardSize boardSize, int flags)
167 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
168     int boardWidth, boardHeight;
169     static int oldWidth, oldHeight;
170     static VariantClass oldVariant;
171     static int oldTwoBoards = 0;
172
173     if(!mainOptions[W_BOARD].handle) return;
174
175     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
176     oldTwoBoards = twoBoards;
177
178     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
179     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
180     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
181
182   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
183
184     oldWidth = boardWidth; oldHeight = boardHeight;
185     CreateGrid();
186
187     /*
188      * Inhibit shell resizing.
189      */
190     ResizeBoardWindow(boardWidth, boardHeight, 0);
191
192     DelayedDrag();
193   }
194
195     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
196     // (only for xpm)
197
198   if(gameInfo.variant != oldVariant) { // and only if variant changed
199
200     SelectPieces(gameInfo.variant);
201
202     oldVariant = gameInfo.variant;
203   }
204   CreateAnimVars();
205 }
206
207 void
208 ExposeRedraw (Option *graph, int x, int y, int w, int h)
209 {   // copy a selected part of the buffer bitmap to the display
210     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
211     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
212     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
213     cairo_rectangle(cr, x, y, w, h);
214     cairo_fill(cr);
215     cairo_destroy(cr);
216 }
217
218 static void
219 CreatePNGBoard (char *s, int kind)
220 {
221     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
222     if(strstr(s, ".png")) {
223         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
224         if(img) {
225             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
226             textureW[kind] = cairo_image_surface_get_width (img);
227             textureH[kind] = cairo_image_surface_get_height (img);
228         }
229     }
230 }
231
232 char *pngPieceNames[] = // must be in same order as internal piece encoding
233 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
234   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King",
235   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
236 };
237
238 RsvgHandle *
239 LoadSVG (char *dir, int color, int piece)
240 {
241     char buf[MSG_SIZ];
242   RsvgHandle *svg=svgPieces[color][piece];
243   RsvgDimensionData svg_dimensions;
244   GError *svgerror=NULL;
245   cairo_surface_t *img;
246   cairo_t *cr;
247
248     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
249
250     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
251
252       rsvg_handle_get_dimensions(svg, &svg_dimensions);
253       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
254
255       cr = cairo_create(img);
256       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
257       rsvg_handle_render_cairo(svg, cr);
258       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
259         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
260         pngPieceImages[color][piece] = img;
261       }
262       cairo_destroy(cr);
263
264       return svg;
265     }
266     if(svgerror)
267         g_error_free(svgerror);
268     return NULL;
269 }
270
271 static void
272 ScaleOnePiece (int color, int piece)
273 {
274   float w, h;
275   char buf[MSG_SIZ];
276   cairo_surface_t *img, *cs;
277   cairo_t *cr;
278
279   g_type_init ();
280
281   svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
282
283   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
284     if(*appData.pieceDirectory) { // user specified piece directory
285       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
286       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
287       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
288         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
289       } else pngPieceImages[color][piece] = img;
290     }
291   }
292
293   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
294     static int warned = 0;
295     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
296       char *msg = _("No default pieces installed\nSelect your own -pieceImageDirectory");
297       printf("%s\n", msg); // give up
298       DisplayError(msg, 0);
299       warned = 1; // prevent error message being repeated for each piece type
300     }
301   }
302
303   img = pngPieceImages[color][piece];
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
309   if(!img) return;
310
311   // scaled copying of the raw png image
312   cr = cairo_create(cs);
313   w = cairo_image_surface_get_width (img);
314   h = cairo_image_surface_get_height (img);
315   cairo_scale(cr, squareSize/w, squareSize/h);
316   cairo_set_source_surface (cr, img, 0, 0);
317   cairo_paint (cr);
318   cairo_destroy (cr);
319
320   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
321     int stride = cairo_image_surface_get_stride(cs)/4;
322     int *buf = (int *) cairo_image_surface_get_data(cs);
323     int i, j, p;
324     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
325     cairo_surface_flush(cs);
326     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
327         int r, a;
328         float f;
329         unsigned int c = buf[i*stride + j];
330         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
331         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
332         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
333         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
334         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
335         if(appData.monoMode) {
336             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
337             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
338             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
339         }
340     }
341     cairo_surface_mark_dirty(cs);
342   }
343 }
344
345 void
346 CreatePNGPieces ()
347 {
348   int p;
349
350   for(p=0; pngPieceNames[p]; p++) {
351     ScaleOnePiece(0, p);
352     ScaleOnePiece(1, p);
353   }
354   SelectPieces(gameInfo.variant);
355 }
356
357 void
358 CreateAnyPieces ()
359 {   // [HGM] taken out of main
360     CreatePNGPieces();
361     CreatePNGBoard(appData.liteBackTextureFile, 1);
362     CreatePNGBoard(appData.darkBackTextureFile, 0);
363 }
364
365 void
366 InitDrawingParams (int reloadPieces)
367 {
368     int i, p;
369     if(reloadPieces)
370     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
371         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
372         pngPieceImages[i][p] = NULL;
373         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
374         svgPieces[i][p] = NULL;
375     }
376     CreateAnyPieces();
377 }
378
379 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
380
381 float
382 Color (char *col, int n)
383 {
384   int c;
385   sscanf(col, "#%x", &c);
386   c = c >> 4*n & 255;
387   return c/255.;
388 }
389
390 void
391 SetPen (cairo_t *cr, float w, char *col, int dash)
392 {
393   static const double dotted[] = {4.0, 4.0};
394   static int len  = sizeof(dotted) / sizeof(dotted[0]);
395   cairo_set_line_width (cr, w);
396   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
397   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
398 }
399
400 void DrawSeekAxis( int x, int y, int xTo, int yTo )
401 {
402     cairo_t *cr;
403
404     /* get a cairo_t */
405     cr = cairo_create (csBoardWindow);
406
407     cairo_move_to (cr, x, y);
408     cairo_line_to(cr, xTo, yTo );
409
410     SetPen(cr, 2, "#000000", 0);
411     cairo_stroke(cr);
412
413     /* free memory */
414     cairo_destroy (cr);
415     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
416 }
417
418 void DrawSeekBackground( int left, int top, int right, int bottom )
419 {
420     cairo_t *cr = cairo_create (csBoardWindow);
421
422     cairo_rectangle (cr, left, top, right-left, bottom-top);
423
424     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
425     cairo_fill(cr);
426
427     /* free memory */
428     cairo_destroy (cr);
429     GraphExpose(currBoard, left, top, right-left, bottom-top);
430 }
431
432 void DrawSeekText(char *buf, int x, int y)
433 {
434     cairo_t *cr = cairo_create (csBoardWindow);
435
436     cairo_select_font_face (cr, "Sans",
437                             CAIRO_FONT_SLANT_NORMAL,
438                             CAIRO_FONT_WEIGHT_NORMAL);
439
440     cairo_set_font_size (cr, 12.0);
441
442     cairo_move_to (cr, x, y+4);
443     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
444     cairo_show_text( cr, buf);
445
446     /* free memory */
447     cairo_destroy (cr);
448     GraphExpose(currBoard, x-5, y-10, 60, 15);
449 }
450
451 void DrawSeekDot(int x, int y, int colorNr)
452 {
453     cairo_t *cr = cairo_create (csBoardWindow);
454     int square = colorNr & 0x80;
455     colorNr &= 0x7F;
456
457     if(square)
458         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
459     else
460         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
461
462     SetPen(cr, 2, "#000000", 0);
463     cairo_stroke_preserve(cr);
464     switch (colorNr) {
465       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
466       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
467       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
468     }
469     cairo_fill(cr);
470
471     /* free memory */
472     cairo_destroy (cr);
473     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
474 }
475
476 void
477 InitDrawingHandle (Option *opt)
478 {
479     csBoardWindow = DRAWABLE(opt);
480 }
481
482 void
483 CreateGrid ()
484 {
485     int i, j;
486
487     if (lineGap == 0) return;
488
489     /* [HR] Split this into 2 loops for non-square boards. */
490
491     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
492         gridSegments[i].x1 = 0;
493         gridSegments[i].x2 =
494           lineGap + BOARD_WIDTH * (squareSize + lineGap);
495         gridSegments[i].y1 = gridSegments[i].y2
496           = lineGap / 2 + (i * (squareSize + lineGap));
497     }
498
499     for (j = 0; j < BOARD_WIDTH + 1; j++) {
500         gridSegments[j + i].y1 = 0;
501         gridSegments[j + i].y2 =
502           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
503         gridSegments[j + i].x1 = gridSegments[j + i].x2
504           = lineGap / 2 + (j * (squareSize + lineGap));
505     }
506 }
507
508 void
509 DrawGrid()
510 {
511   /* draws a grid starting around Nx, Ny squares starting at x,y */
512   int i;
513   float odd = (lineGap & 1)/2.;
514   cairo_t *cr;
515
516   /* get a cairo_t */
517   cr = cairo_create (csBoardWindow);
518
519   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
520   SetPen(cr, lineGap, "#000000", 0);
521
522   /* lines in X */
523   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
524     {
525       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
526       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
527       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
528       cairo_stroke (cr);
529     }
530
531   /* free memory */
532   cairo_destroy (cr);
533
534   return;
535 }
536
537 void
538 DrawBorder (int x, int y, int type, int odd)
539 {
540     cairo_t *cr;
541     char *col;
542
543     switch(type) {
544         case 0: col = "#000000"; break;
545         case 1: col = appData.highlightSquareColor; break;
546         case 2: col = appData.premoveHighlightColor; break;
547         default: col = "#808080"; break; // cannot happen
548     }
549     cr = cairo_create(csBoardWindow);
550     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
551     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
552     SetPen(cr, lineGap, col, 0);
553     cairo_stroke(cr);
554     cairo_destroy(cr);
555     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
556 }
557
558 static int
559 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
560 {
561     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
562     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
563     *x0 = 0; *y0 = 0;
564     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
565     if(textureW[kind] < W*squareSize)
566         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
567     else
568         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
569     if(textureH[kind] < H*squareSize)
570         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
571     else
572         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
573     return 1;
574 }
575
576 void
577 DrawLogo (Option *opt, void *logo)
578 {
579     cairo_surface_t *img;
580     cairo_t *cr;
581     int w, h;
582
583     if(!logo || !opt) return;
584     img = cairo_image_surface_create_from_png (logo);
585     w = cairo_image_surface_get_width (img);
586     h = cairo_image_surface_get_height (img);
587     cr = cairo_create(DRAWABLE(opt));
588 //    cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
589     cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
590     cairo_set_source_surface (cr, img, 0, 0);
591     cairo_paint (cr);
592     cairo_destroy (cr);
593     cairo_surface_destroy (img);
594     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
595 }
596
597 static void
598 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
599 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
600     int x0, y0;
601     cairo_t *cr;
602
603     cr = cairo_create (dest);
604
605     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
606             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
607             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
608             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
609             cairo_fill (cr);
610             cairo_destroy (cr);
611     } else { // evenly colored squares
612         char *col = NULL;
613         switch (color) {
614           case 0: col = appData.darkSquareColor; break;
615           case 1: col = appData.lightSquareColor; break;
616           case 2: col = "#000000"; break;
617           default: col = "#808080"; break; // cannot happen
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;
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) kind = 1 - kind; // 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 static void
674 DrawText (char *string, int x, int y, int align)
675 {
676         int xx = x, yy = y;
677         cairo_text_extents_t te;
678         cairo_t *cr;
679
680         cr = cairo_create (csBoardWindow);
681         cairo_select_font_face (cr, "Sans",
682                     CAIRO_FONT_SLANT_NORMAL,
683                     CAIRO_FONT_WEIGHT_BOLD);
684
685         cairo_set_font_size (cr, squareSize/4);
686         // calculate where it goes
687         cairo_text_extents (cr, string, &te);
688
689         if (align == 1) {
690             xx += squareSize - te.width - te.x_bearing - 1;
691             yy += squareSize - te.height - te.y_bearing - 1;
692         } else if (align == 2) {
693             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
694         } else if (align == 3) {
695             xx += squareSize - te.width -te.x_bearing - 1;
696             yy += -te.y_bearing + 3;
697         } else if (align == 4) {
698             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
699         }
700
701         cairo_move_to (cr, xx-1, yy);
702         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
703         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
704         cairo_show_text (cr, string);
705         cairo_destroy (cr);
706 }
707
708 void
709 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
710 {   // basic front-end board-draw function: takes care of everything that can be in square:
711     // piece, background, coordinate/count, marker dot
712
713     if (piece == EmptySquare) {
714         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
715     } else {
716         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
717     }
718
719     if(align) { // square carries inscription (coord or piece count)
720         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
721         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
722     }
723
724     if(marker) { // print fat marker dot, if requested
725         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
726     }
727 }
728
729 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
730
731 /*      Masks for XPM pieces. Black and white pieces can have
732         different shapes, but in the interest of retaining my
733         sanity pieces must have the same outline on both light
734         and dark squares, and all pieces must use the same
735         background square colors/images.                */
736
737 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
738
739 static void
740 InitAnimState (AnimNr anr)
741 {
742     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
743     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
744     c_animBufs[anr+4] = csBoardWindow;
745     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
746     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
747 }
748
749 void
750 CreateAnimVars ()
751 {
752   InitAnimState(Game);
753   InitAnimState(Player);
754 }
755
756 static void
757 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
758 {
759   static cairo_t *pieceSource;
760   pieceSource = cairo_create (dest);
761   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
762   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
763   else cairo_paint(pieceSource);
764   cairo_destroy (pieceSource);
765 }
766
767 void
768 InsertPiece (AnimNr anr, ChessSquare piece)
769 {
770     CairoOverlayPiece(piece, c_animBufs[anr]);
771 }
772
773 void
774 DrawBlank (AnimNr anr, int x, int y, int startColor)
775 {
776     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
777 }
778
779 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
780                  int srcX, int srcY, int width, int height, int destX, int destY)
781 {
782         cairo_t *cr;
783         c_animBufs[anr+4] = csBoardWindow;
784         cr = cairo_create (c_animBufs[anr+destBuf]);
785         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
786         cairo_rectangle (cr, destX, destY, width, height);
787         cairo_fill (cr);
788         cairo_destroy (cr);
789         if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
790             GraphExpose(currBoard, destX, destY, width, height);
791 }
792
793 void
794 SetDragPiece (AnimNr anr, ChessSquare piece)
795 {
796 }
797
798 /* [AS] Arrow highlighting support */
799
800 void
801 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
802 {
803     cairo_t *cr;
804     int i;
805     cr = cairo_create (cs);
806     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
807     for (i=0;i<nr;i++) {
808         cairo_line_to(cr, arrow[i].x, arrow[i].y);
809     }
810     if(appData.monoMode) { // should we always outline arrow?
811         cairo_line_to(cr, arrow[0].x, arrow[0].y);
812         SetPen(cr, 2, "#000000", 0);
813         cairo_stroke_preserve(cr);
814     }
815     SetPen(cr, 2, appData.highlightSquareColor, 0);
816     cairo_fill(cr);
817
818     /* free memory */
819     cairo_destroy (cr);
820 }
821
822 void
823 DrawPolygon (Pnt arrow[], int nr)
824 {
825     DoDrawPolygon(csBoardWindow, arrow, nr);
826 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
827 }
828
829 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
830
831 static void
832 ChoosePen(cairo_t *cr, int i)
833 {
834   switch(i) {
835     case PEN_BLACK:
836       SetPen(cr, 1.0, "#000000", 0);
837       break;
838     case PEN_DOTTED:
839       SetPen(cr, 1.0, "#A0A0A0", 1);
840       break;
841     case PEN_BLUEDOTTED:
842       SetPen(cr, 1.0, "#0000FF", 1);
843       break;
844     case PEN_BOLDWHITE:
845       SetPen(cr, 3.0, crWhite, 0);
846       break;
847     case PEN_BOLDBLACK:
848       SetPen(cr, 3.0, crBlack, 0);
849       break;
850     case PEN_BACKGD:
851       SetPen(cr, 3.0, "#E0E0F0", 0);
852       break;
853   }
854 }
855
856 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
857 void
858 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
859 {
860   static int curX, curY;
861
862   if(penType != PEN_NONE) {
863     cairo_t *cr = cairo_create(DRAWABLE(disp));
864     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
865     cairo_move_to (cr, curX, curY);
866     cairo_line_to (cr, x,y);
867     ChoosePen(cr, penType);
868     cairo_stroke (cr);
869     cairo_destroy (cr);
870   }
871
872   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
873   curX = x; curY = y;
874 }
875
876 // front-end wrapper for drawing functions to do rectangles
877 void
878 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
879 {
880   cairo_t *cr;
881
882   cr = cairo_create (DRAWABLE(disp));
883   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
884   cairo_rectangle (cr, left, top, right-left, bottom-top);
885   switch(side)
886     {
887     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
888     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
889     case 2: ChoosePen(cr, PEN_BACKGD); break;
890     }
891   cairo_fill (cr);
892
893   if(style != FILLED)
894     {
895       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
896       ChoosePen(cr, PEN_BLACK);
897       cairo_stroke (cr);
898     }
899
900   cairo_destroy(cr);
901 }
902
903 // front-end wrapper for putting text in graph
904 void
905 DrawEvalText (char *buf, int cbBuf, int y)
906 {
907     // the magic constants 8 and 5 should really be derived from the font size somehow
908   cairo_text_extents_t extents;
909   cairo_t *cr = cairo_create(DRAWABLE(disp));
910
911   /* GTK-TODO this has to go into the font-selection */
912   cairo_select_font_face (cr, "Sans",
913                           CAIRO_FONT_SLANT_NORMAL,
914                           CAIRO_FONT_WEIGHT_NORMAL);
915   cairo_set_font_size (cr, 12.0);
916
917
918   cairo_text_extents (cr, buf, &extents);
919
920   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
921   cairo_text_path (cr, buf);
922   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
923   cairo_fill_preserve (cr);
924   cairo_set_source_rgb (cr, 0, 1.0, 0);
925   cairo_set_line_width (cr, 0.1);
926   cairo_stroke (cr);
927
928   /* free memory */
929   cairo_destroy (cr);
930 }