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]; // 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);
139 NewCanvas (Option *graph)
142 int w = graph->max, h = graph->value;
143 if(graph->choice) cairo_surface_destroy((cairo_surface_t *) graph->choice);
144 graph->choice = (char**) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
145 // paint white, to prevent weirdness when people maximize window and drag pieces over space next to board
146 cr = cairo_create ((cairo_surface_t *) graph->choice);
147 cairo_rectangle (cr, 0, 0, w, h);
148 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
151 graph->min &= ~REPLACE;
154 static cairo_surface_t *
155 CsBoardWindow (Option *opt)
156 { // test before every draw event if we need to resize the canvas
157 if(opt->min & REPLACE) NewCanvas(opt);
158 return DRAWABLE(opt);
163 SelectPieces(VariantClass v)
168 for(p=0; p<=(int)WhiteKing; p++)
169 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
170 if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
171 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
172 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhitePKnight];
173 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhitePLance];
174 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhitePSilver];
175 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
176 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
179 if(v == VariantGothic) {
180 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
183 if(v == VariantSChess) {
184 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
185 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
187 if(v == VariantChuChess) {
188 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
190 if(v == VariantChu) {
191 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
192 pngPieceBitmaps[i][(int)WhiteClaw] = pngPieceBitmaps2[i][(int)WhiteNightrider];
193 pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
194 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteSword];
195 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteDagger];
196 pngPieceBitmaps[i][(int)WhiteCat] = pngPieceBitmaps2[i][(int)WhiteUnicorn];
197 pngPieceBitmaps[i][(int)WhiteSword] = pngPieceBitmaps2[i][(int)WhiteSilver];
198 pngPieceBitmaps[i][(int)WhiteDagger] = pngPieceBitmaps2[i][(int)WhiteFalcon];
199 pngPieceBitmaps[i][(int)WhiteMan] = pngPieceBitmaps2[i][(int)WhiteCopper];
200 pngPieceBitmaps[i][(int)WhiteCopper] = pngPieceBitmaps2[i][(int)WhiteMan];
201 pngPieceBitmaps[i][(int)WhiteAxe] = pngPieceBitmaps2[i][(int)WhiteCannon];
202 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteAxe];
207 #define BoardSize int
209 InitDrawingSizes (BoardSize boardSize, int flags)
210 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
211 int boardWidth, boardHeight;
212 static int oldWidth, oldHeight;
213 static VariantClass oldVariant;
214 static int oldTwoBoards = 0, oldNrOfFiles = 0;
216 if(!mainOptions[W_BOARD].handle) return;
218 if(boardSize == -2 && gameInfo.variant != oldVariant
219 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
220 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
221 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
222 squareSize -= lineGap;
226 oldNrOfFiles = BOARD_WIDTH;
228 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
229 oldTwoBoards = twoBoards;
231 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
232 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
233 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
235 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
237 oldWidth = boardWidth; oldHeight = boardHeight;
239 CreateAnyPieces(0); // redo texture scaling
242 * Inhibit shell resizing.
244 ResizeBoardWindow(boardWidth, boardHeight, 0);
249 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
252 if(gameInfo.variant != oldVariant) { // and only if variant changed
254 SelectPieces(gameInfo.variant);
256 oldVariant = gameInfo.variant;
262 ExposeRedraw (Option *graph, int x, int y, int w, int h)
263 { // copy a selected part of the buffer bitmap to the display
264 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
265 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
266 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
267 cairo_rectangle(cr, x, y, w, h);
273 CreatePNGBoard (char *s, int kind)
276 static float n[2] = { 1., 1. };
277 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
278 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
279 if(strstr(s, ".png")) {
280 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
281 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
284 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
285 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
286 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
287 w = textureW[kind] = cairo_image_surface_get_width (img);
288 h = textureH[kind] = cairo_image_surface_get_height (img);
290 while((q = strchr(p+1, '-'))) p = q; // find last '-'
291 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
292 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
293 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
294 textureH[kind] = (h*BOARD_HEIGHT)/r;
295 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
297 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
298 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
300 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
302 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
303 // create scaled-up copy of the raw png image when it was too small
304 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
305 cairo_t *cr = cairo_create(cs);
306 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
307 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
308 cairo_scale(cr, n[kind], n[kind]);
309 cairo_set_source_surface (cr, img, 0, 0);
317 char *pngPieceNames[] = // must be in same order as internal piece encoding
318 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
319 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
320 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
321 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
322 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
323 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
327 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
328 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
329 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
330 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
331 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
335 LoadSVG (char *dir, int color, int piece, int retry)
338 RsvgHandle *svg=svgPieces[color][piece];
339 RsvgDimensionData svg_dimensions;
340 GError *svgerror=NULL;
341 cairo_surface_t *img;
343 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
345 if(!name) return NULL;
347 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
350 svg = rsvg_handle_new_from_file(buf, &svgerror);
351 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
352 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
353 svg = rsvg_handle_new_from_file(buf, &svgerror);
358 rsvg_handle_get_dimensions(svg, &svg_dimensions);
359 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
361 cr = cairo_create(img);
362 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
363 rsvg_handle_render_cairo(svg, cr);
364 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
365 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
366 pngPieceImages[color][piece] = img;
372 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
373 return LoadSVG(dir, color, piece, 1);
375 g_error_free(svgerror);
380 ScaleOnePiece (int color, int piece)
384 cairo_surface_t *img, *cs;
389 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
391 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
392 if(*appData.pieceDirectory) { // user specified piece directory
393 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
394 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
395 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
396 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
397 } else pngPieceImages[color][piece] = img;
401 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
402 static int warned = 0;
403 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
404 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
405 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
406 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
407 DisplayError(msg, 0);
408 warned = 1; // prevent error message being repeated for each piece type
412 img = pngPieceImages[color][piece];
414 // create new bitmap to hold scaled piece image (and remove any old)
415 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
416 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
420 // scaled copying of the raw png image
421 cr = cairo_create(cs);
422 w = cairo_image_surface_get_width (img);
423 h = cairo_image_surface_get_height (img);
424 cairo_scale(cr, squareSize/w, squareSize/h);
425 cairo_set_source_surface (cr, img, 0, 0);
429 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
430 int stride = cairo_image_surface_get_stride(cs)/4;
431 int *buf = (int *) cairo_image_surface_get_data(cs);
433 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
434 cairo_surface_flush(cs);
435 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
438 unsigned int c = buf[i*stride + j];
439 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
440 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
441 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
442 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
443 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
444 if(appData.monoMode) {
445 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
446 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
447 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
450 cairo_surface_mark_dirty(cs);
459 for(p=0; pngPieceNames[p]; p++) {
463 SelectPieces(gameInfo.variant);
467 CreateAnyPieces (int p)
468 { // [HGM] taken out of main
469 if(p) CreatePNGPieces();
470 CreatePNGBoard(appData.liteBackTextureFile, 1);
471 CreatePNGBoard(appData.darkBackTextureFile, 0);
475 InitDrawingParams (int reloadPieces)
479 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
480 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
481 pngPieceImages[i][p] = NULL;
482 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
483 svgPieces[i][p] = NULL;
488 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
491 Color (char *col, int n)
494 sscanf(col, "#%x", &c);
500 SetPen (cairo_t *cr, float w, char *col, int dash)
502 static const double dotted[] = {4.0, 4.0};
503 static int len = sizeof(dotted) / sizeof(dotted[0]);
504 cairo_set_line_width (cr, w);
505 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
506 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
509 void DrawSeekAxis( int x, int y, int xTo, int yTo )
514 cr = cairo_create (CsBoardWindow(currBoard));
516 cairo_move_to (cr, x, y);
517 cairo_line_to(cr, xTo, yTo );
519 SetPen(cr, 2, "#000000", 0);
524 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
527 void DrawSeekBackground( int left, int top, int right, int bottom )
529 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
531 cairo_rectangle (cr, left, top, right-left, bottom-top);
533 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
538 GraphExpose(currBoard, left, top, right-left, bottom-top);
541 void DrawSeekText(char *buf, int x, int y)
543 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
545 cairo_select_font_face (cr, "Sans",
546 CAIRO_FONT_SLANT_NORMAL,
547 CAIRO_FONT_WEIGHT_NORMAL);
549 cairo_set_font_size (cr, 12.0);
551 cairo_move_to (cr, x, y+4);
552 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
553 cairo_show_text( cr, buf);
557 GraphExpose(currBoard, x-5, y-10, 60, 15);
560 void DrawSeekDot(int x, int y, int colorNr)
562 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
563 int square = colorNr & 0x80;
567 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
569 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
571 SetPen(cr, 2, "#000000", 0);
572 cairo_stroke_preserve(cr);
574 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
575 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
576 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
582 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
586 InitDrawingHandle (Option *opt)
588 // CsBoardWindow = DRAWABLE(opt);
597 if (lineGap == 0) return;
599 /* [HR] Split this into 2 loops for non-square boards. */
601 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
602 gridSegments[i].x1 = 0;
604 lineGap + BOARD_WIDTH * (squareSize + lineGap);
605 gridSegments[i].y1 = gridSegments[i].y2
606 = lineGap / 2 + (i * (squareSize + lineGap));
609 for (j = 0; j < BOARD_WIDTH + 1; j++) {
610 gridSegments[j + i].y1 = 0;
611 gridSegments[j + i].y2 =
612 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
613 gridSegments[j + i].x1 = gridSegments[j + i].x2
614 = lineGap / 2 + (j * (squareSize + lineGap));
621 /* draws a grid starting around Nx, Ny squares starting at x,y */
623 float odd = (lineGap & 1)/2.;
627 cr = cairo_create (CsBoardWindow(currBoard));
629 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
630 SetPen(cr, lineGap, "#000000", 0);
633 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
635 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
636 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
637 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
648 DrawBorder (int x, int y, int type, int odd)
654 case 0: col = "#000000"; break;
655 case 1: col = appData.highlightSquareColor; break;
656 case 2: col = appData.premoveHighlightColor; break;
657 default: col = "#808080"; break; // cannot happen
659 cr = cairo_create(CsBoardWindow(currBoard));
660 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
661 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
662 SetPen(cr, lineGap, col, 0);
665 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
669 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
671 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
672 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
674 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
675 if(textureW[kind] < W*squareSize)
676 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
678 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
679 if(textureH[kind] < H*squareSize)
680 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
682 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
687 DrawLogo (Option *opt, void *logo)
689 cairo_surface_t *img;
694 cr = cairo_create(CsBoardWindow(opt));
695 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
696 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
697 cairo_fill(cr); // paint background in case logo does not exist
699 img = cairo_image_surface_create_from_png (logo);
700 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
701 w = cairo_image_surface_get_width (img);
702 h = cairo_image_surface_get_height (img);
703 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
704 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
705 cairo_set_source_surface (cr, img, 0, 0);
708 cairo_surface_destroy (img);
711 GraphExpose(opt, 0, 0, opt->max, opt->value);
715 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
716 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
720 cr = cairo_create (dest);
722 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
723 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
724 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
725 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
728 } else { // evenly colored squares
731 case 0: col = appData.darkSquareColor; break;
732 case 1: col = appData.lightSquareColor; break;
733 case 2: col = "#000000"; break;
734 default: col = "#808080"; break; // cannot happen
736 SetPen(cr, 2.0, col, 0);
737 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
738 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
745 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
750 if ((int)piece < (int) BlackPawn) {
756 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
757 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
758 cr = cairo_create (dest);
759 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
764 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
767 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
771 cr = cairo_create(cs);
772 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
773 if(appData.monoMode) {
774 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
775 cairo_stroke_preserve(cr);
776 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
778 SetPen(cr, 2, markerColor[marker-1], 0);
786 DrawDot (int marker, int x, int y, int r)
787 { // used for atomic captures; no need to draw on backup
788 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
789 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
793 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
795 // cairo_text_extents_t te;
799 PangoFontDescription *desc;
801 char fontName[MSG_SIZ];
803 cr = cairo_create (canvas);
804 layout = pango_cairo_create_layout(cr);
805 pango_layout_set_text(layout, string, -1);
806 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
807 desc = pango_font_description_from_string(fontName);
808 pango_layout_set_font_description(layout, desc);
809 pango_font_description_free(desc);
810 pango_layout_get_pixel_extents(layout, NULL, &r);
811 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
812 if(s < 0) cairo_rotate(cr, G_PI);
813 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
814 pango_cairo_update_layout(cr, layout);
815 pango_cairo_show_layout(cr, layout);
816 g_object_unref(layout);
821 DrawText (char *string, int x, int y, int align)
824 cairo_text_extents_t te;
827 cr = cairo_create (CsBoardWindow(currBoard));
828 cairo_select_font_face (cr, "Sans",
829 CAIRO_FONT_SLANT_NORMAL,
830 CAIRO_FONT_WEIGHT_BOLD);
832 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
833 // calculate where it goes
834 cairo_text_extents (cr, string, &te);
837 xx += squareSize - te.width - te.x_bearing - 1;
838 yy += squareSize - te.height - te.y_bearing - 1;
839 } else if (align == 2) {
840 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
841 } else if (align == 3) {
842 xx += squareSize - te.width -te.x_bearing - 1;
843 yy += -te.y_bearing + 3;
844 } else if (align == 4) {
845 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
848 cairo_move_to (cr, xx-1, yy);
849 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
850 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
851 cairo_show_text (cr, string);
856 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
858 char *p, *q, buf[10];
859 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
860 if(piece == EmptySquare) return;
861 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
862 p = appData.inscriptions;
864 while(piece > WhitePawn) {
865 if(*p++ == NULLCHAR) {
866 if(n != WhiteKing) return;
871 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
875 for(q=buf; (*++q & 0xC0) == 0x80;);
877 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
881 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
882 { // basic front-end board-draw function: takes care of everything that can be in square:
883 // piece, background, coordinate/count, marker dot
885 if (piece == EmptySquare) {
886 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
888 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
889 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
892 if(align) { // square carries inscription (coord or piece count)
893 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
894 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
897 if(marker) { // print fat marker dot, if requested
898 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
902 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
904 /* Masks for XPM pieces. Black and white pieces can have
905 different shapes, but in the interest of retaining my
906 sanity pieces must have the same outline on both light
907 and dark squares, and all pieces must use the same
908 background square colors/images. */
910 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
913 InitAnimState (AnimNr anr)
915 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
916 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
917 c_animBufs[anr+4] = CsBoardWindow(currBoard);
918 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
919 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
926 InitAnimState(Player);
930 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
932 static cairo_t *pieceSource;
933 pieceSource = cairo_create (dest);
934 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
935 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
936 else cairo_paint(pieceSource);
937 cairo_destroy (pieceSource);
938 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
942 InsertPiece (AnimNr anr, ChessSquare piece)
944 CairoOverlayPiece(piece, c_animBufs[anr]);
948 DrawBlank (AnimNr anr, int x, int y, int startColor)
950 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
953 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
954 int srcX, int srcY, int width, int height, int destX, int destY)
957 c_animBufs[anr+4] = CsBoardWindow(currBoard);
958 cr = cairo_create (c_animBufs[anr+destBuf]);
959 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
960 cairo_rectangle (cr, destX, destY, width, height);
963 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
964 GraphExpose(currBoard, destX, destY, width, height);
968 SetDragPiece (AnimNr anr, ChessSquare piece)
972 /* [AS] Arrow highlighting support */
975 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
979 cr = cairo_create (cs);
980 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
982 cairo_line_to(cr, arrow[i].x, arrow[i].y);
984 if(appData.monoMode) { // should we always outline arrow?
985 cairo_line_to(cr, arrow[0].x, arrow[0].y);
986 SetPen(cr, 2, "#000000", 0);
987 cairo_stroke_preserve(cr);
989 SetPen(cr, 2, appData.highlightSquareColor, 0);
997 DrawPolygon (Pnt arrow[], int nr)
999 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1000 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1003 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1006 ChoosePen(cairo_t *cr, int i)
1010 SetPen(cr, 1.0, "#000000", 0);
1013 SetPen(cr, 1.0, "#A0A0A0", 1);
1015 case PEN_BLUEDOTTED:
1016 SetPen(cr, 1.0, "#0000FF", 1);
1019 SetPen(cr, 3.0, crWhite, 0);
1022 SetPen(cr, 3.0, crBlack, 0);
1025 SetPen(cr, 3.0, "#E0E0F0", 0);
1030 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1032 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1034 static int curX, curY;
1036 if(penType != PEN_NONE) {
1037 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1038 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1039 cairo_move_to (cr, curX, curY);
1040 cairo_line_to (cr, x,y);
1041 ChoosePen(cr, penType);
1046 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1050 // front-end wrapper for drawing functions to do rectangles
1052 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1056 cr = cairo_create (CsBoardWindow(disp));
1057 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1058 cairo_rectangle (cr, left, top, right-left, bottom-top);
1061 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1062 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1063 case 2: ChoosePen(cr, PEN_BACKGD); break;
1069 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1070 ChoosePen(cr, PEN_BLACK);
1077 // front-end wrapper for putting text in graph
1079 DrawEvalText (char *buf, int cbBuf, int y)
1081 // the magic constants 8 and 5 should really be derived from the font size somehow
1082 cairo_text_extents_t extents;
1083 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1085 /* GTK-TODO this has to go into the font-selection */
1086 cairo_select_font_face (cr, "Sans",
1087 CAIRO_FONT_SLANT_NORMAL,
1088 CAIRO_FONT_WEIGHT_NORMAL);
1089 cairo_set_font_size (cr, 12.0);
1092 cairo_text_extents (cr, buf, &extents);
1094 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1095 cairo_text_path (cr, buf);
1096 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1097 cairo_fill_preserve (cr);
1098 cairo_set_source_rgb (cr, 0, 1.0, 0);
1099 cairo_set_line_width (cr, 0.1);