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