2 * draw.c -- drawing routines for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * The following terms apply to Digital Equipment Corporation's copyright
13 * ------------------------------------------------------------------------
16 * Permission to use, copy, modify, and distribute this software and its
17 * documentation for any purpose and without fee is hereby granted,
18 * provided that the above copyright notice appear in all copies and that
19 * both that copyright notice and this permission notice appear in
20 * supporting documentation, and that the name of Digital not be
21 * used in advertising or publicity pertaining to distribution of the
22 * software without specific, written prior permission.
24 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31 * ------------------------------------------------------------------------
33 * The following terms apply to the enhanced version of XBoard
34 * distributed by the Free Software Foundation:
35 * ------------------------------------------------------------------------
37 * GNU XBoard is free software: you can redistribute it and/or modify
38 * it under the terms of the GNU General Public License as published by
39 * the Free Software Foundation, either version 3 of the License, or (at
40 * your option) any later version.
42 * GNU XBoard is distributed in the hope that it will be useful, but
43 * WITHOUT ANY WARRANTY; without even the implied warranty of
44 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 * General Public License for more details.
47 * You should have received a copy of the GNU General Public License
48 * along with this program. If not, see http://www.gnu.org/licenses/. *
50 *------------------------------------------------------------------------
51 ** See the file ChangeLog for a revision history. */
57 #include <cairo/cairo.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60 #include <pango/pangocairo.h>
65 #else /* not STDC_HEADERS */
66 extern char *getenv();
69 # else /* not HAVE_STRING_H */
71 # endif /* not HAVE_STRING_H */
72 #endif /* not STDC_HEADERS */
84 #include "evalgraph.h"
93 #define usleep(t) _sleep2(((t)+500)/1000)
97 # define _(s) gettext (s)
98 # define N_(s) gettext_noop (s)
106 Boolean cairoAnimate;
108 cairo_surface_t *csBoardWindow;
109 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn]; // png 256 x 256 images
110 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn]; // scaled pieces as used
111 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn]; // scaled pieces in store
112 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
113 static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2];
114 int useTexture, textureW[2], textureH[2];
116 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
117 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
119 #define White(piece) ((int)(piece) < (int)BlackPawn)
121 char svgDir[MSG_SIZ] = SVGDIR;
123 char *crWhite = "#FFFFB0";
124 char *crBlack = "#AD5D3D";
128 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
131 SwitchWindow (int main)
133 currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
134 csBoardWindow = DRAWABLE(currBoard);
138 SelectPieces(VariantClass v)
143 for(p=0; p<=(int)WhiteKing; p++)
144 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
145 if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
146 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
147 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhitePKnight];
148 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhitePLance];
149 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhitePSilver];
150 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
151 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
154 if(v == VariantGothic) {
155 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
158 if(v == VariantSChess) {
159 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
160 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
162 if(v == VariantChuChess) {
163 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
165 if(v == VariantChu) {
166 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
167 pngPieceBitmaps[i][(int)WhiteClaw] = pngPieceBitmaps2[i][(int)WhiteNightrider];
168 pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
169 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteSword];
170 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteDagger];
171 pngPieceBitmaps[i][(int)WhiteCat] = pngPieceBitmaps2[i][(int)WhiteUnicorn];
172 pngPieceBitmaps[i][(int)WhiteSword] = pngPieceBitmaps2[i][(int)WhiteSilver];
173 pngPieceBitmaps[i][(int)WhiteDagger] = pngPieceBitmaps2[i][(int)WhiteFalcon];
174 pngPieceBitmaps[i][(int)WhiteMan] = pngPieceBitmaps2[i][(int)WhiteCopper];
175 pngPieceBitmaps[i][(int)WhiteCopper] = pngPieceBitmaps2[i][(int)WhiteMan];
176 pngPieceBitmaps[i][(int)WhiteAxe] = pngPieceBitmaps2[i][(int)WhiteCannon];
177 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteAxe];
182 #define BoardSize int
184 InitDrawingSizes (BoardSize boardSize, int flags)
185 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
186 int boardWidth, boardHeight;
187 static int oldWidth, oldHeight;
188 static VariantClass oldVariant;
189 static int oldTwoBoards = 0, oldNrOfFiles = 0;
191 if(!mainOptions[W_BOARD].handle) return;
193 if(boardSize == -2 && gameInfo.variant != oldVariant
194 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
195 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
196 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
197 squareSize -= lineGap;
201 oldNrOfFiles = BOARD_WIDTH;
203 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
204 oldTwoBoards = twoBoards;
206 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
207 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
208 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
210 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
212 oldWidth = boardWidth; oldHeight = boardHeight;
214 CreateAnyPieces(0); // redo texture scaling
217 * Inhibit shell resizing.
219 ResizeBoardWindow(boardWidth, boardHeight, 0);
224 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
227 if(gameInfo.variant != oldVariant) { // and only if variant changed
229 SelectPieces(gameInfo.variant);
231 oldVariant = gameInfo.variant;
237 ExposeRedraw (Option *graph, int x, int y, int w, int h)
238 { // copy a selected part of the buffer bitmap to the display
239 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
240 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
241 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
242 cairo_rectangle(cr, x, y, w, h);
248 CreatePNGBoard (char *s, int kind)
251 static float n[2] = { 1., 1. };
252 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
253 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
254 if(strstr(s, ".png")) {
255 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
256 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
259 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
260 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
261 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
262 w = textureW[kind] = cairo_image_surface_get_width (img);
263 h = textureH[kind] = cairo_image_surface_get_height (img);
265 while((q = strchr(p+1, '-'))) p = q; // find last '-'
266 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
267 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
268 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
269 textureH[kind] = (h*BOARD_HEIGHT)/r;
270 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
272 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
273 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
275 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
277 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
278 // create scaled-up copy of the raw png image when it was too small
279 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
280 cairo_t *cr = cairo_create(cs);
281 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
282 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
283 cairo_scale(cr, n[kind], n[kind]);
284 cairo_set_source_surface (cr, img, 0, 0);
292 char *pngPieceNames[] = // must be in same order as internal piece encoding
293 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
294 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
295 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
296 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
297 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
298 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
302 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
303 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
304 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
305 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
306 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
310 LoadSVG (char *dir, int color, int piece, int retry)
313 RsvgHandle *svg=svgPieces[color][piece];
314 RsvgDimensionData svg_dimensions;
315 GError *svgerror=NULL;
316 cairo_surface_t *img;
318 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
320 if(!name) return NULL;
322 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
325 svg = rsvg_handle_new_from_file(buf, &svgerror);
326 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
327 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
328 svg = rsvg_handle_new_from_file(buf, &svgerror);
333 rsvg_handle_get_dimensions(svg, &svg_dimensions);
334 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
336 cr = cairo_create(img);
337 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
338 rsvg_handle_render_cairo(svg, cr);
339 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
340 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
341 pngPieceImages[color][piece] = img;
347 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
348 return LoadSVG(dir, color, piece, 1);
350 g_error_free(svgerror);
355 ScaleOnePiece (int color, int piece)
359 cairo_surface_t *img, *cs;
364 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
366 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
367 if(*appData.pieceDirectory) { // user specified piece directory
368 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
369 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
370 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
371 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
372 } else pngPieceImages[color][piece] = img;
376 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
377 static int warned = 0;
378 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
379 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
380 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
381 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
382 DisplayError(msg, 0);
383 warned = 1; // prevent error message being repeated for each piece type
387 img = pngPieceImages[color][piece];
389 // create new bitmap to hold scaled piece image (and remove any old)
390 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
391 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
395 // scaled copying of the raw png image
396 cr = cairo_create(cs);
397 w = cairo_image_surface_get_width (img);
398 h = cairo_image_surface_get_height (img);
399 cairo_scale(cr, squareSize/w, squareSize/h);
400 cairo_set_source_surface (cr, img, 0, 0);
404 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
405 int stride = cairo_image_surface_get_stride(cs)/4;
406 int *buf = (int *) cairo_image_surface_get_data(cs);
408 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
409 cairo_surface_flush(cs);
410 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
413 unsigned int c = buf[i*stride + j];
414 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
415 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
416 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
417 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
418 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
419 if(appData.monoMode) {
420 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
421 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
422 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
425 cairo_surface_mark_dirty(cs);
434 for(p=0; pngPieceNames[p]; p++) {
438 SelectPieces(gameInfo.variant);
442 CreateAnyPieces (int p)
443 { // [HGM] taken out of main
444 if(p) CreatePNGPieces();
445 CreatePNGBoard(appData.liteBackTextureFile, 1);
446 CreatePNGBoard(appData.darkBackTextureFile, 0);
450 InitDrawingParams (int reloadPieces)
454 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
455 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
456 pngPieceImages[i][p] = NULL;
457 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
458 svgPieces[i][p] = NULL;
463 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
466 Color (char *col, int n)
469 sscanf(col, "#%x", &c);
475 SetPen (cairo_t *cr, float w, char *col, int dash)
477 static const double dotted[] = {4.0, 4.0};
478 static int len = sizeof(dotted) / sizeof(dotted[0]);
479 cairo_set_line_width (cr, w);
480 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
481 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
484 void DrawSeekAxis( int x, int y, int xTo, int yTo )
489 cr = cairo_create (csBoardWindow);
491 cairo_move_to (cr, x, y);
492 cairo_line_to(cr, xTo, yTo );
494 SetPen(cr, 2, "#000000", 0);
499 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
502 void DrawSeekBackground( int left, int top, int right, int bottom )
504 cairo_t *cr = cairo_create (csBoardWindow);
506 cairo_rectangle (cr, left, top, right-left, bottom-top);
508 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
513 GraphExpose(currBoard, left, top, right-left, bottom-top);
516 void DrawSeekText(char *buf, int x, int y)
518 cairo_t *cr = cairo_create (csBoardWindow);
520 cairo_select_font_face (cr, "Sans",
521 CAIRO_FONT_SLANT_NORMAL,
522 CAIRO_FONT_WEIGHT_NORMAL);
524 cairo_set_font_size (cr, 12.0);
526 cairo_move_to (cr, x, y+4);
527 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
528 cairo_show_text( cr, buf);
532 GraphExpose(currBoard, x-5, y-10, 60, 15);
535 void DrawSeekDot(int x, int y, int colorNr)
537 cairo_t *cr = cairo_create (csBoardWindow);
538 int square = colorNr & 0x80;
542 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
544 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
546 SetPen(cr, 2, "#000000", 0);
547 cairo_stroke_preserve(cr);
549 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
550 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
551 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
557 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
561 InitDrawingHandle (Option *opt)
563 csBoardWindow = DRAWABLE(opt);
571 if (lineGap == 0) return;
573 /* [HR] Split this into 2 loops for non-square boards. */
575 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
576 gridSegments[i].x1 = 0;
578 lineGap + BOARD_WIDTH * (squareSize + lineGap);
579 gridSegments[i].y1 = gridSegments[i].y2
580 = lineGap / 2 + (i * (squareSize + lineGap));
583 for (j = 0; j < BOARD_WIDTH + 1; j++) {
584 gridSegments[j + i].y1 = 0;
585 gridSegments[j + i].y2 =
586 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
587 gridSegments[j + i].x1 = gridSegments[j + i].x2
588 = lineGap / 2 + (j * (squareSize + lineGap));
595 /* draws a grid starting around Nx, Ny squares starting at x,y */
597 float odd = (lineGap & 1)/2.;
601 cr = cairo_create (csBoardWindow);
603 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
604 SetPen(cr, lineGap, "#000000", 0);
607 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
609 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
610 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
611 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
622 DrawBorder (int x, int y, int type, int odd)
628 case 0: col = "#000000"; break;
629 case 1: col = appData.highlightSquareColor; break;
630 case 2: col = appData.premoveHighlightColor; break;
631 default: col = "#808080"; break; // cannot happen
633 cr = cairo_create(csBoardWindow);
634 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
635 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
636 SetPen(cr, lineGap, col, 0);
639 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
643 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
645 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
646 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
648 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
649 if(textureW[kind] < W*squareSize)
650 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
652 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
653 if(textureH[kind] < H*squareSize)
654 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
656 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
661 DrawLogo (Option *opt, void *logo)
663 cairo_surface_t *img;
668 cr = cairo_create(DRAWABLE(opt));
669 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
670 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
671 cairo_fill(cr); // paint background in case logo does not exist
673 img = cairo_image_surface_create_from_png (logo);
674 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
675 w = cairo_image_surface_get_width (img);
676 h = cairo_image_surface_get_height (img);
677 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
678 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
679 cairo_set_source_surface (cr, img, 0, 0);
682 cairo_surface_destroy (img);
685 GraphExpose(opt, 0, 0, opt->max, opt->value);
689 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
690 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
694 cr = cairo_create (dest);
696 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
697 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
698 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
699 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
702 } else { // evenly colored squares
705 case 0: col = appData.darkSquareColor; break;
706 case 1: col = appData.lightSquareColor; break;
707 case 2: col = "#000000"; break;
708 default: col = "#808080"; break; // cannot happen
710 SetPen(cr, 2.0, col, 0);
711 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
712 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
719 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
724 if ((int)piece < (int) BlackPawn) {
730 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
731 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
732 cr = cairo_create (dest);
733 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
738 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
741 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
745 cr = cairo_create(cs);
746 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
747 if(appData.monoMode) {
748 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
749 cairo_stroke_preserve(cr);
750 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
752 SetPen(cr, 2, markerColor[marker-1], 0);
760 DrawDot (int marker, int x, int y, int r)
761 { // used for atomic captures; no need to draw on backup
762 DoDrawDot(csBoardWindow, marker, x, y, r);
763 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
767 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
769 // cairo_text_extents_t te;
773 PangoFontDescription *desc;
775 char fontName[MSG_SIZ];
777 cr = cairo_create (canvas);
778 layout = pango_cairo_create_layout(cr);
779 pango_layout_set_text(layout, string, -1);
780 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
781 desc = pango_font_description_from_string(fontName);
782 pango_layout_set_font_description(layout, desc);
783 pango_font_description_free(desc);
784 pango_layout_get_pixel_extents(layout, NULL, &r);
785 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
786 if(s < 0) cairo_rotate(cr, G_PI);
787 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
788 pango_cairo_update_layout(cr, layout);
789 pango_cairo_show_layout(cr, layout);
790 g_object_unref(layout);
795 DrawText (char *string, int x, int y, int align)
798 cairo_text_extents_t te;
801 cr = cairo_create (csBoardWindow);
802 cairo_select_font_face (cr, "Sans",
803 CAIRO_FONT_SLANT_NORMAL,
804 CAIRO_FONT_WEIGHT_BOLD);
806 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
807 // calculate where it goes
808 cairo_text_extents (cr, string, &te);
811 xx += squareSize - te.width - te.x_bearing - 1;
812 yy += squareSize - te.height - te.y_bearing - 1;
813 } else if (align == 2) {
814 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
815 } else if (align == 3) {
816 xx += squareSize - te.width -te.x_bearing - 1;
817 yy += -te.y_bearing + 3;
818 } else if (align == 4) {
819 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
822 cairo_move_to (cr, xx-1, yy);
823 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
824 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
825 cairo_show_text (cr, string);
830 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
832 char *p, *q, buf[10];
833 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
834 if(piece == EmptySquare) return;
835 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
836 p = appData.inscriptions;
838 while(piece > WhitePawn) {
839 if(*p++ == NULLCHAR) {
840 if(n != WhiteKing) return;
845 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
849 for(q=buf; (*++q & 0xC0) == 0x80;);
851 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
855 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
856 { // basic front-end board-draw function: takes care of everything that can be in square:
857 // piece, background, coordinate/count, marker dot
859 if (piece == EmptySquare) {
860 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
862 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
863 if(appData.inscriptions[0]) InscribeKanji(csBoardWindow, piece, x, y);
866 if(align) { // square carries inscription (coord or piece count)
867 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
868 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
871 if(marker) { // print fat marker dot, if requested
872 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
876 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
878 /* Masks for XPM pieces. Black and white pieces can have
879 different shapes, but in the interest of retaining my
880 sanity pieces must have the same outline on both light
881 and dark squares, and all pieces must use the same
882 background square colors/images. */
884 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
887 InitAnimState (AnimNr anr)
889 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
890 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
891 c_animBufs[anr+4] = csBoardWindow;
892 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
893 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
900 InitAnimState(Player);
904 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
906 static cairo_t *pieceSource;
907 pieceSource = cairo_create (dest);
908 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
909 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
910 else cairo_paint(pieceSource);
911 cairo_destroy (pieceSource);
912 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
916 InsertPiece (AnimNr anr, ChessSquare piece)
918 CairoOverlayPiece(piece, c_animBufs[anr]);
922 DrawBlank (AnimNr anr, int x, int y, int startColor)
924 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
927 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
928 int srcX, int srcY, int width, int height, int destX, int destY)
931 c_animBufs[anr+4] = csBoardWindow;
932 cr = cairo_create (c_animBufs[anr+destBuf]);
933 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
934 cairo_rectangle (cr, destX, destY, width, height);
937 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
938 GraphExpose(currBoard, destX, destY, width, height);
942 SetDragPiece (AnimNr anr, ChessSquare piece)
946 /* [AS] Arrow highlighting support */
949 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
953 cr = cairo_create (cs);
954 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
956 cairo_line_to(cr, arrow[i].x, arrow[i].y);
958 if(appData.monoMode) { // should we always outline arrow?
959 cairo_line_to(cr, arrow[0].x, arrow[0].y);
960 SetPen(cr, 2, "#000000", 0);
961 cairo_stroke_preserve(cr);
963 SetPen(cr, 2, appData.highlightSquareColor, 0);
971 DrawPolygon (Pnt arrow[], int nr)
973 DoDrawPolygon(csBoardWindow, arrow, nr);
974 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
977 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
980 ChoosePen(cairo_t *cr, int i)
984 SetPen(cr, 1.0, "#000000", 0);
987 SetPen(cr, 1.0, "#A0A0A0", 1);
990 SetPen(cr, 1.0, "#0000FF", 1);
993 SetPen(cr, 3.0, crWhite, 0);
996 SetPen(cr, 3.0, crBlack, 0);
999 SetPen(cr, 3.0, "#E0E0F0", 0);
1004 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1006 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1008 static int curX, curY;
1010 if(penType != PEN_NONE) {
1011 cairo_t *cr = cairo_create(DRAWABLE(disp));
1012 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1013 cairo_move_to (cr, curX, curY);
1014 cairo_line_to (cr, x,y);
1015 ChoosePen(cr, penType);
1020 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1024 // front-end wrapper for drawing functions to do rectangles
1026 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1030 cr = cairo_create (DRAWABLE(disp));
1031 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1032 cairo_rectangle (cr, left, top, right-left, bottom-top);
1035 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1036 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1037 case 2: ChoosePen(cr, PEN_BACKGD); break;
1043 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1044 ChoosePen(cr, PEN_BLACK);
1051 // front-end wrapper for putting text in graph
1053 DrawEvalText (char *buf, int cbBuf, int y)
1055 // the magic constants 8 and 5 should really be derived from the font size somehow
1056 cairo_text_extents_t extents;
1057 cairo_t *cr = cairo_create(DRAWABLE(disp));
1059 /* GTK-TODO this has to go into the font-selection */
1060 cairo_select_font_face (cr, "Sans",
1061 CAIRO_FONT_SLANT_NORMAL,
1062 CAIRO_FONT_WEIGHT_NORMAL);
1063 cairo_set_font_size (cr, 12.0);
1066 cairo_text_extents (cr, buf, &extents);
1068 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1069 cairo_text_path (cr, buf);
1070 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1071 cairo_fill_preserve (cr);
1072 cairo_set_source_rgb (cr, 0, 1.0, 0);
1073 cairo_set_line_width (cr, 0.1);