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