Prevent odd-width line shift in length direction
[xboard.git] / draw.c
1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #include "config.h"
53
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60
61 #if STDC_HEADERS
62 # include <stdlib.h>
63 # include <string.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
66 # if HAVE_STRING_H
67 #  include <string.h>
68 # else /* not HAVE_STRING_H */
69 #  include <strings.h>
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
72
73 #if ENABLE_NLS
74 #include <locale.h>
75 #endif
76
77
78 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
79 #include "common.h"
80
81 #include "frontend.h"
82 #include "backend.h"
83 #include "xevalgraph.h"
84 #include "board.h"
85 #include "menus.h"
86 #include "dialogs.h"
87 #include "gettext.h"
88 #include "draw.h"
89
90
91 #ifdef __EMX__
92 #ifndef HAVE_USLEEP
93 #define HAVE_USLEEP
94 #endif
95 #define usleep(t)   _sleep2(((t)+500)/1000)
96 #endif
97
98 #ifdef ENABLE_NLS
99 # define  _(s) gettext (s)
100 # define N_(s) gettext_noop (s)
101 #else
102 # define  _(s) (s)
103 # define N_(s)  s
104 #endif
105
106 #define SOLID 0
107 #define OUTLINE 1
108 Boolean cairoAnimate;
109 Option *currBoard;
110 cairo_surface_t *csBoardWindow;
111 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4];   // png 256 x 256 images
112 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn];    // scaled pieces as used
113 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
114 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
115 static cairo_surface_t *pngBoardBitmap[2];
116 int useTexture, textureW[2], textureH[2];
117
118 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
119 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
120
121 #define White(piece) ((int)(piece) < (int)BlackPawn)
122
123 struct {
124   int x1, x2, y1, y2;
125 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
126
127 static int dual = 0;
128
129 void
130 SwitchWindow ()
131 {
132     dual = !dual;
133     currBoard = (dual ? &mainOptions[W_BOARD] : &dualOptions[3]);
134     csBoardWindow = DRAWABLE(currBoard);
135 }
136
137 #define BoardSize int
138 void
139 InitDrawingSizes (BoardSize boardSize, int flags)
140 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
141     int boardWidth, boardHeight;
142     int i;
143     static int oldWidth, oldHeight;
144     static VariantClass oldVariant;
145     static int oldMono = -1, oldTwoBoards = 0;
146     extern Widget formWidget;
147
148     if(!formWidget) return;
149
150     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
151     oldTwoBoards = twoBoards;
152
153     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
154     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
155     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
156
157   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
158
159     oldWidth = boardWidth; oldHeight = boardHeight;
160     CreateGrid();
161
162     /*
163      * Inhibit shell resizing.
164      */
165     ResizeBoardWindow(boardWidth, boardHeight, 0);
166
167     DelayedDrag();
168   }
169
170     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
171     // (only for xpm)
172
173   if(gameInfo.variant != oldVariant) { // and only if variant changed
174
175     for(i=0; i<2; i++) {
176         int p;
177         for(p=0; p<=(int)WhiteKing; p++)
178            pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
179         if(gameInfo.variant == VariantShogi) {
180            pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
181            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
182            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
183            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
184            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
185         }
186 #ifdef GOTHIC
187         if(gameInfo.variant == VariantGothic) {
188            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
189         }
190 #endif
191         if(gameInfo.variant == VariantSChess) {
192            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
193            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
194         }
195     }
196     oldMono = -10; // kludge to force recreation of animation masks
197     oldVariant = gameInfo.variant;
198   }
199   CreateAnimVars();
200   oldMono = appData.monoMode;
201 }
202
203 void
204 ExposeRedraw (Option *graph, int x, int y, int w, int h)
205 {   // copy a selected part of the buffer bitmap to the display
206     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
207     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
208     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
209     cairo_rectangle(cr, x, y, w, h);
210     cairo_fill(cr);
211     cairo_destroy(cr);
212 }
213
214 static void
215 CreatePNGBoard (char *s, int kind)
216 {
217     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
218     if(strstr(s, ".png")) {
219         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
220         if(img) {
221             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
222             textureW[kind] = cairo_image_surface_get_width (img);
223             textureH[kind] = cairo_image_surface_get_height (img);
224         }
225     }
226 }
227
228 char *pngPieceNames[] = // must be in same order as internal piece encoding
229 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner", 
230   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King", 
231   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
232 };
233
234 RsvgHandle *
235 LoadSVG (char *dir, int color, int piece)
236 {
237     char buf[MSG_SIZ];
238   RsvgHandle *svg=svgPieces[color][piece];
239   RsvgDimensionData svg_dimensions;
240   GError **svgerror=NULL;
241   cairo_surface_t *img;
242   cairo_t *cr;
243
244     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
245
246     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf,svgerror))) {
247
248       rsvg_handle_get_dimensions(svg, &svg_dimensions);
249       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
250
251       cr = cairo_create(img);
252       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
253       rsvg_handle_render_cairo(svg, cr);
254       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
255         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
256         pngPieceImages[color][piece] = img;
257       }
258       cairo_destroy(cr);
259
260       return svg;
261     }
262     return NULL;
263 }
264
265 static void
266 ScaleOnePiece (int color, int piece)
267 {
268   float w, h;
269   char buf[MSG_SIZ];
270   cairo_surface_t *img, *cs;
271   cairo_t *cr;
272
273   g_type_init ();
274
275   if(!svgPieces[color][piece]) { // try to freshly render cached svg pieces first, to supply the source bitmap
276     svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
277   }
278
279   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
280     if(*appData.pieceDirectory) { // user specified piece directory
281       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
282       pngPieceImages[color][piece] = img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
283       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
284         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
285       }
286     }
287   }
288
289   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
290     static int warned = 0;
291     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
292       char *msg = _("No default pieces installed\nSelect your own -pieceImageDirectory");
293       printf("%s\n", msg); // give up
294       DisplayError(msg, 0);
295       warned = 1; // prevent error message being repeated for each piece type
296     }
297   }
298
299   img = pngPieceImages[color][piece];
300
301   // create new bitmap to hold scaled piece image (and remove any old)
302   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
303   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
304   if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
305
306   if(!img) return;
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.pieceDirectory) { // 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         if(appData.monoMode) {
333             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
334             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
335             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
336         }
337     }
338     cairo_surface_mark_dirty(cs);
339   }
340 }
341
342 void
343 CreatePNGPieces ()
344 {
345   int p;
346
347   for(p=0; pngPieceNames[p]; p++) {
348     ScaleOnePiece(0, p);
349     ScaleOnePiece(1, p);
350   }
351 }
352
353 void
354 CreateAnyPieces ()
355 {   // [HGM] taken out of main
356     CreatePNGPieces();
357     CreatePNGBoard(appData.liteBackTextureFile, 1);
358     CreatePNGBoard(appData.darkBackTextureFile, 0);
359 }
360
361 void
362 InitDrawingParams (int reloadPieces)
363 {
364     int i, p;
365     if(reloadPieces)
366     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
367         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
368         pngPieceImages[i][p] = NULL;
369         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
370         svgPieces[i][p] = NULL;
371     }
372     CreateAnyPieces();
373 }
374
375 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
376
377 float
378 Color (char *col, int n)
379 {
380   int c;
381   sscanf(col, "#%x", &c);
382   c = c >> 4*n & 255;
383   return c/255.;
384 }
385
386 void
387 SetPen (cairo_t *cr, float w, char *col, int dash)
388 {
389   static const double dotted[] = {4.0, 4.0};
390   static int len  = sizeof(dotted) / sizeof(dotted[0]);
391   cairo_set_line_width (cr, w);
392   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
393   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
394 }
395
396 void DrawSeekAxis( int x, int y, int xTo, int yTo )
397 {
398     cairo_t *cr;
399
400     /* get a cairo_t */
401     cr = cairo_create (csBoardWindow);
402
403     cairo_move_to (cr, x, y);
404     cairo_line_to(cr, xTo, yTo );
405
406     SetPen(cr, 2, "#000000", 0);
407     cairo_stroke(cr);
408
409     /* free memory */
410     cairo_destroy (cr);
411     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
412 }
413
414 void DrawSeekBackground( int left, int top, int right, int bottom )
415 {
416     cairo_t *cr = cairo_create (csBoardWindow);
417
418     cairo_rectangle (cr, left, top, right-left, bottom-top);
419
420     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
421     cairo_fill(cr);
422
423     /* free memory */
424     cairo_destroy (cr);
425     GraphExpose(currBoard, left, top, right-left, bottom-top);
426 }
427
428 void DrawSeekText(char *buf, int x, int y)
429 {
430     cairo_t *cr = cairo_create (csBoardWindow);
431
432     cairo_select_font_face (cr, "Sans",
433                             CAIRO_FONT_SLANT_NORMAL,
434                             CAIRO_FONT_WEIGHT_NORMAL);
435
436     cairo_set_font_size (cr, 12.0);
437
438     cairo_move_to (cr, x, y+4);
439     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
440     cairo_show_text( cr, buf);
441
442     /* free memory */
443     cairo_destroy (cr);
444     GraphExpose(currBoard, x-5, y-10, 60, 15);
445 }
446
447 void DrawSeekDot(int x, int y, int colorNr)
448 {
449     cairo_t *cr = cairo_create (csBoardWindow);
450     int square = colorNr & 0x80;
451     colorNr &= 0x7F;
452
453     if(square)
454         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
455     else
456         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
457
458     SetPen(cr, 2, "#000000", 0);
459     cairo_stroke_preserve(cr);
460     switch (colorNr) {
461       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
462       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
463       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
464     }
465     cairo_fill(cr);
466
467     /* free memory */
468     cairo_destroy (cr);
469     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
470 }
471
472 void
473 InitDrawingHandle (Option *opt)
474 {
475     csBoardWindow = DRAWABLE(opt);
476 }
477
478 void
479 CreateGrid ()
480 {
481     int i, j;
482
483     if (lineGap == 0) return;
484
485     /* [HR] Split this into 2 loops for non-square boards. */
486
487     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
488         gridSegments[i].x1 = 0;
489         gridSegments[i].x2 =
490           lineGap + BOARD_WIDTH * (squareSize + lineGap);
491         gridSegments[i].y1 = gridSegments[i].y2
492           = lineGap / 2 + (i * (squareSize + lineGap));
493     }
494
495     for (j = 0; j < BOARD_WIDTH + 1; j++) {
496         gridSegments[j + i].y1 = 0;
497         gridSegments[j + i].y2 =
498           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
499         gridSegments[j + i].x1 = gridSegments[j + i].x2
500           = lineGap / 2 + (j * (squareSize + lineGap));
501     }
502 }
503
504 void
505 DrawGrid()
506 {
507   /* draws a grid starting around Nx, Ny squares starting at x,y */
508   int i;
509   float odd = (lineGap & 1)/2.;
510   cairo_t *cr;
511
512   /* get a cairo_t */
513   cr = cairo_create (csBoardWindow);
514
515   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
516   SetPen(cr, lineGap, "#000000", 0);
517
518   /* lines in X */
519   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
520     {
521       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
522       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
523       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
524       cairo_stroke (cr);
525     }
526
527   /* free memory */
528   cairo_destroy (cr);
529
530   return;
531 }
532
533 void
534 DrawBorder (int x, int y, int type, int odd)
535 {
536     cairo_t *cr;
537     char *col;
538
539     switch(type) {
540         case 0: col = "#000000"; break;
541         case 1: col = appData.highlightSquareColor; break;
542         case 2: col = appData.premoveHighlightColor; break;
543     }
544     cr = cairo_create(csBoardWindow);
545     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
546     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
547     SetPen(cr, lineGap, col, 0);
548     cairo_stroke(cr);
549     cairo_destroy(cr);
550     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
551 }
552
553 static int
554 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
555 {
556     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
557     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
558     *x0 = 0; *y0 = 0;
559     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
560     if(textureW[kind] < W*squareSize)
561         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
562     else
563         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
564     if(textureH[kind] < H*squareSize)
565         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
566     else
567         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
568     return 1;
569 }
570
571 void
572 DrawLogo (Option *opt, void *logo)
573 {
574     cairo_surface_t *img;
575     cairo_t *cr;
576     int w, h;
577
578     if(!logo || !opt) return;
579     img = cairo_image_surface_create_from_png (logo);
580     w = cairo_image_surface_get_width (img);
581     h = cairo_image_surface_get_height (img);
582     cr = cairo_create(DRAWABLE(opt));
583     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
584     cairo_set_source_surface (cr, img, 0, 0);
585     cairo_paint (cr);
586     cairo_destroy (cr);
587     cairo_surface_destroy (img);
588     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
589 }
590
591 static void
592 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
593 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
594     int x0, y0;
595     cairo_t *cr;
596
597     cr = cairo_create (dest);
598
599     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
600             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
601             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
602             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
603             cairo_fill (cr);
604             cairo_destroy (cr);
605     } else { // evenly colored squares
606         char *col;
607         switch (color) {
608           case 0: col = appData.darkSquareColor; break;
609           case 1: col = appData.lightSquareColor; break;
610           case 2: col = "#000000"; break;
611         }
612         SetPen(cr, 2.0, col, 0);
613         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
614         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
615         cairo_fill (cr);
616         cairo_destroy (cr);
617     }
618 }
619
620 static void
621 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
622 {
623     int kind, p = piece;
624     cairo_t *cr;
625
626     if ((int)piece < (int) BlackPawn) {
627         kind = 0;
628     } else {
629         kind = 1;
630         piece -= BlackPawn;
631     }
632     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
633     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
634     cr = cairo_create (dest);
635     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
636     cairo_paint(cr);
637     cairo_destroy (cr);
638 }
639
640 void
641 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
642 {
643         cairo_t *cr;
644
645         cr = cairo_create(cs);
646         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
647         if(appData.monoMode) {
648             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
649             cairo_stroke_preserve(cr);
650             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
651         } else {
652             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
653         }
654         cairo_fill(cr);
655
656         cairo_destroy(cr);
657 }
658
659 void
660 DrawDot (int marker, int x, int y, int r)
661 { // used for atomic captures; no need to draw on backup
662   DoDrawDot(csBoardWindow, marker, x, y, r);
663   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
664 }
665
666 void
667 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
668 {   // basic front-end board-draw function: takes care of everything that can be in square:
669     // piece, background, coordinate/count, marker dot
670     cairo_t *cr;
671
672     if (piece == EmptySquare) {
673         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
674     } else {
675         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
676     }
677
678     if(align) { // square carries inscription (coord or piece count)
679         int xx = x, yy = y;
680         cairo_text_extents_t te;
681
682         cr = cairo_create (csBoardWindow);
683         cairo_select_font_face (cr, "Sans",
684                     CAIRO_FONT_SLANT_NORMAL,
685                     CAIRO_FONT_WEIGHT_BOLD);
686
687         cairo_set_font_size (cr, squareSize/4);
688         // calculate where it goes
689         cairo_text_extents (cr, string, &te);
690
691         if (align == 1) {
692             xx += squareSize - te.width - te.x_bearing - 1;
693             yy += squareSize - te.height - te.y_bearing - 1;
694         } else if (align == 2) {
695             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
696         } else if (align == 3) {
697             xx += squareSize - te.width -te.x_bearing - 1;
698             yy += -te.y_bearing + 3;
699         } else if (align == 4) {
700             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
701         }
702
703         cairo_move_to (cr, xx-1, yy);
704         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
705         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
706         cairo_show_text (cr, string);
707         cairo_destroy (cr);
708     }
709
710     if(marker) { // print fat marker dot, if requested
711         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
712     }
713 }
714
715 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
716
717 /*      Masks for XPM pieces. Black and white pieces can have
718         different shapes, but in the interest of retaining my
719         sanity pieces must have the same outline on both light
720         and dark squares, and all pieces must use the same
721         background square colors/images.                */
722
723 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
724
725 static void
726 InitAnimState (AnimNr anr)
727 {
728     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
729     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
730     c_animBufs[anr+4] = csBoardWindow;
731     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
732     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
733 }
734
735 void
736 CreateAnimVars ()
737 {
738   InitAnimState(Game);
739   InitAnimState(Player);
740 }
741
742 static void
743 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
744 {
745   static cairo_t *pieceSource;
746   pieceSource = cairo_create (dest);
747   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
748   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
749   else cairo_paint(pieceSource);
750   cairo_destroy (pieceSource);
751 }
752
753 void
754 InsertPiece (AnimNr anr, ChessSquare piece)
755 {
756     CairoOverlayPiece(piece, c_animBufs[anr]);
757 }
758
759 void
760 DrawBlank (AnimNr anr, int x, int y, int startColor)
761 {
762     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
763 }
764
765 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
766                  int srcX, int srcY, int width, int height, int destX, int destY)
767 {
768         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
769         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
770         cairo_rectangle (cr, destX, destY, width, height);
771         cairo_fill (cr);
772         cairo_destroy (cr);
773         if(c_animBufs[anr+destBuf] == csBoardWindow)
774             GraphExpose(currBoard, destX, destY, squareSize, squareSize);
775 }
776
777 void
778 SetDragPiece (AnimNr anr, ChessSquare piece)
779 {
780 }
781
782 /* [AS] Arrow highlighting support */
783
784 void
785 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
786 {
787     cairo_t *cr;
788     int i;
789     cr = cairo_create (cs);
790     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
791     for (i=0;i<nr;i++) {
792         cairo_line_to(cr, arrow[i].x, arrow[i].y);
793     }
794     if(appData.monoMode) { // should we always outline arrow?
795         cairo_line_to(cr, arrow[0].x, arrow[0].y);
796         SetPen(cr, 2, "#000000", 0);
797         cairo_stroke_preserve(cr);
798     }
799     SetPen(cr, 2, appData.highlightSquareColor, 0);
800     cairo_fill(cr);
801
802     /* free memory */
803     cairo_destroy (cr);
804 }
805
806 void
807 DrawPolygon (Pnt arrow[], int nr)
808 {
809     DoDrawPolygon(csBoardWindow, arrow, nr);
810 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
811 }
812
813