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 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
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.
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
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
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.
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.
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/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.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+4]; // 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+4]; // 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)WhiteKing+2];
148 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+3];
149 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+4];
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)WhiteHorned];
169 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteStag];
170 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteEagle];
171 pngPieceBitmaps[i][(int)WhiteHorned] = pngPieceBitmaps2[i][(int)WhiteUnicorn];
172 pngPieceBitmaps[i][(int)WhiteStag] = pngPieceBitmaps2[i][(int)WhiteSilver];
173 pngPieceBitmaps[i][(int)WhiteEagle] = pngPieceBitmaps2[i][(int)WhiteFalcon];
178 #define BoardSize int
180 InitDrawingSizes (BoardSize boardSize, int flags)
181 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
182 int boardWidth, boardHeight;
183 static int oldWidth, oldHeight;
184 static VariantClass oldVariant;
185 static int oldTwoBoards = 0, oldNrOfFiles = 0;
187 if(!mainOptions[W_BOARD].handle) return;
189 if(boardSize == -2 && gameInfo.variant != oldVariant
190 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
191 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
192 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
193 squareSize -= lineGap;
197 oldNrOfFiles = BOARD_WIDTH;
199 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
200 oldTwoBoards = twoBoards;
202 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
203 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
204 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
206 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
208 oldWidth = boardWidth; oldHeight = boardHeight;
210 CreateAnyPieces(0); // redo texture scaling
213 * Inhibit shell resizing.
215 ResizeBoardWindow(boardWidth, boardHeight, 0);
220 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
223 if(gameInfo.variant != oldVariant) { // and only if variant changed
225 SelectPieces(gameInfo.variant);
227 oldVariant = gameInfo.variant;
233 ExposeRedraw (Option *graph, int x, int y, int w, int h)
234 { // copy a selected part of the buffer bitmap to the display
235 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
236 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
237 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
238 cairo_rectangle(cr, x, y, w, h);
244 CreatePNGBoard (char *s, int kind)
247 static float n[2] = { 1., 1. };
248 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
249 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
250 if(strstr(s, ".png")) {
251 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
255 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
256 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
257 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
258 w = textureW[kind] = cairo_image_surface_get_width (img);
259 h = textureH[kind] = cairo_image_surface_get_height (img);
261 while((q = strchr(p+1, '-'))) p = q; // find last '-'
262 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
263 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
264 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
265 textureH[kind] = (h*BOARD_HEIGHT)/r;
266 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
268 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
269 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
271 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
273 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
274 // create scaled-up copy of the raw png image when it was too small
275 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
276 cairo_t *cr = cairo_create(cs);
277 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
278 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
279 cairo_scale(cr, n[kind], n[kind]);
280 cairo_set_source_surface (cr, img, 0, 0);
288 char *pngPieceNames[] = // must be in same order as internal piece encoding
289 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
290 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
291 "Wolf", "Camel", "Zebra", "Wizard", "Lance",
292 "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
293 "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop",
294 "Dragon", "Viking", "Iron", "Copper", "Tower", "King",
295 "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
298 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
299 NULL, NULL, NULL, NULL, NULL,
300 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
303 LoadSVG (char *dir, int color, int piece, int retry)
306 RsvgHandle *svg=svgPieces[color][piece];
307 RsvgDimensionData svg_dimensions;
308 GError *svgerror=NULL;
309 cairo_surface_t *img;
311 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
313 if(!name) return NULL;
315 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
318 svg = rsvg_handle_new_from_file(buf, &svgerror);
319 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
320 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
321 svg = rsvg_handle_new_from_file(buf, &svgerror);
326 rsvg_handle_get_dimensions(svg, &svg_dimensions);
327 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
329 cr = cairo_create(img);
330 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
331 rsvg_handle_render_cairo(svg, cr);
332 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
333 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
334 pngPieceImages[color][piece] = img;
340 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
341 return LoadSVG(dir, color, piece, 1);
343 g_error_free(svgerror);
348 ScaleOnePiece (int color, int piece)
352 cairo_surface_t *img, *cs;
357 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
359 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
360 if(*appData.pieceDirectory) { // user specified piece directory
361 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
362 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
363 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
364 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
365 } else pngPieceImages[color][piece] = img;
369 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
370 static int warned = 0;
371 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
372 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
373 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
374 printf("%s\n", msg); // give up
375 DisplayError(msg, 0);
376 warned = 1; // prevent error message being repeated for each piece type
380 img = pngPieceImages[color][piece];
382 // create new bitmap to hold scaled piece image (and remove any old)
383 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
384 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
388 // scaled copying of the raw png image
389 cr = cairo_create(cs);
390 w = cairo_image_surface_get_width (img);
391 h = cairo_image_surface_get_height (img);
392 cairo_scale(cr, squareSize/w, squareSize/h);
393 cairo_set_source_surface (cr, img, 0, 0);
397 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
398 int stride = cairo_image_surface_get_stride(cs)/4;
399 int *buf = (int *) cairo_image_surface_get_data(cs);
401 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
402 cairo_surface_flush(cs);
403 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
406 unsigned int c = buf[i*stride + j];
407 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
408 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
409 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
410 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
411 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
412 if(appData.monoMode) {
413 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
414 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
415 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
418 cairo_surface_mark_dirty(cs);
427 for(p=0; pngPieceNames[p]; p++) {
431 SelectPieces(gameInfo.variant);
435 CreateAnyPieces (int p)
436 { // [HGM] taken out of main
437 if(p) CreatePNGPieces();
438 CreatePNGBoard(appData.liteBackTextureFile, 1);
439 CreatePNGBoard(appData.darkBackTextureFile, 0);
443 InitDrawingParams (int reloadPieces)
447 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
448 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
449 pngPieceImages[i][p] = NULL;
450 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
451 svgPieces[i][p] = NULL;
456 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
459 Color (char *col, int n)
462 sscanf(col, "#%x", &c);
468 SetPen (cairo_t *cr, float w, char *col, int dash)
470 static const double dotted[] = {4.0, 4.0};
471 static int len = sizeof(dotted) / sizeof(dotted[0]);
472 cairo_set_line_width (cr, w);
473 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
474 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
477 void DrawSeekAxis( int x, int y, int xTo, int yTo )
482 cr = cairo_create (csBoardWindow);
484 cairo_move_to (cr, x, y);
485 cairo_line_to(cr, xTo, yTo );
487 SetPen(cr, 2, "#000000", 0);
492 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
495 void DrawSeekBackground( int left, int top, int right, int bottom )
497 cairo_t *cr = cairo_create (csBoardWindow);
499 cairo_rectangle (cr, left, top, right-left, bottom-top);
501 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
506 GraphExpose(currBoard, left, top, right-left, bottom-top);
509 void DrawSeekText(char *buf, int x, int y)
511 cairo_t *cr = cairo_create (csBoardWindow);
513 cairo_select_font_face (cr, "Sans",
514 CAIRO_FONT_SLANT_NORMAL,
515 CAIRO_FONT_WEIGHT_NORMAL);
517 cairo_set_font_size (cr, 12.0);
519 cairo_move_to (cr, x, y+4);
520 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
521 cairo_show_text( cr, buf);
525 GraphExpose(currBoard, x-5, y-10, 60, 15);
528 void DrawSeekDot(int x, int y, int colorNr)
530 cairo_t *cr = cairo_create (csBoardWindow);
531 int square = colorNr & 0x80;
535 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
537 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
539 SetPen(cr, 2, "#000000", 0);
540 cairo_stroke_preserve(cr);
542 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
543 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
544 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
550 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
554 InitDrawingHandle (Option *opt)
556 csBoardWindow = DRAWABLE(opt);
564 if (lineGap == 0) return;
566 /* [HR] Split this into 2 loops for non-square boards. */
568 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
569 gridSegments[i].x1 = 0;
571 lineGap + BOARD_WIDTH * (squareSize + lineGap);
572 gridSegments[i].y1 = gridSegments[i].y2
573 = lineGap / 2 + (i * (squareSize + lineGap));
576 for (j = 0; j < BOARD_WIDTH + 1; j++) {
577 gridSegments[j + i].y1 = 0;
578 gridSegments[j + i].y2 =
579 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
580 gridSegments[j + i].x1 = gridSegments[j + i].x2
581 = lineGap / 2 + (j * (squareSize + lineGap));
588 /* draws a grid starting around Nx, Ny squares starting at x,y */
590 float odd = (lineGap & 1)/2.;
594 cr = cairo_create (csBoardWindow);
596 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
597 SetPen(cr, lineGap, "#000000", 0);
600 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
602 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
603 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
604 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
615 DrawBorder (int x, int y, int type, int odd)
621 case 0: col = "#000000"; break;
622 case 1: col = appData.highlightSquareColor; break;
623 case 2: col = appData.premoveHighlightColor; break;
624 default: col = "#808080"; break; // cannot happen
626 cr = cairo_create(csBoardWindow);
627 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
628 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
629 SetPen(cr, lineGap, col, 0);
632 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
636 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
638 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
639 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
641 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
642 if(textureW[kind] < W*squareSize)
643 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
645 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
646 if(textureH[kind] < H*squareSize)
647 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
649 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
654 DrawLogo (Option *opt, void *logo)
656 cairo_surface_t *img;
661 cr = cairo_create(DRAWABLE(opt));
662 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
663 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
664 cairo_fill(cr); // paint background in case logo does not exist
666 img = cairo_image_surface_create_from_png (logo);
667 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
668 w = cairo_image_surface_get_width (img);
669 h = cairo_image_surface_get_height (img);
670 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
671 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
672 cairo_set_source_surface (cr, img, 0, 0);
675 cairo_surface_destroy (img);
678 GraphExpose(opt, 0, 0, opt->max, opt->value);
682 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
683 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
687 cr = cairo_create (dest);
689 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
690 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
691 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
692 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
695 } else { // evenly colored squares
698 case 0: col = appData.darkSquareColor; break;
699 case 1: col = appData.lightSquareColor; break;
700 case 2: col = "#000000"; break;
701 default: col = "#808080"; break; // cannot happen
703 SetPen(cr, 2.0, col, 0);
704 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
705 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
712 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
717 if ((int)piece < (int) BlackPawn) {
723 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
724 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
725 cr = cairo_create (dest);
726 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
731 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
734 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
738 cr = cairo_create(cs);
739 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
740 if(appData.monoMode) {
741 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
742 cairo_stroke_preserve(cr);
743 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
745 SetPen(cr, 2, markerColor[marker-1], 0);
753 DrawDot (int marker, int x, int y, int r)
754 { // used for atomic captures; no need to draw on backup
755 DoDrawDot(csBoardWindow, marker, x, y, r);
756 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
760 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
762 // cairo_text_extents_t te;
766 PangoFontDescription *desc;
768 char fontName[MSG_SIZ];
770 cr = cairo_create (canvas);
771 layout = pango_cairo_create_layout(cr);
772 pango_layout_set_text(layout, string, -1);
773 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
774 desc = pango_font_description_from_string(fontName);
775 pango_layout_set_font_description(layout, desc);
776 pango_font_description_free(desc);
777 pango_layout_get_pixel_extents(layout, NULL, &r);
778 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
779 if(s < 0) cairo_rotate(cr, G_PI);
780 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
781 pango_cairo_update_layout(cr, layout);
782 pango_cairo_show_layout(cr, layout);
783 g_object_unref(layout);
788 DrawText (char *string, int x, int y, int align)
791 cairo_text_extents_t te;
794 cr = cairo_create (csBoardWindow);
795 cairo_select_font_face (cr, "Sans",
796 CAIRO_FONT_SLANT_NORMAL,
797 CAIRO_FONT_WEIGHT_BOLD);
799 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
800 // calculate where it goes
801 cairo_text_extents (cr, string, &te);
804 xx += squareSize - te.width - te.x_bearing - 1;
805 yy += squareSize - te.height - te.y_bearing - 1;
806 } else if (align == 2) {
807 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
808 } else if (align == 3) {
809 xx += squareSize - te.width -te.x_bearing - 1;
810 yy += -te.y_bearing + 3;
811 } else if (align == 4) {
812 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
815 cairo_move_to (cr, xx-1, yy);
816 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
817 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
818 cairo_show_text (cr, string);
823 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
825 char *p, *q, buf[10];
826 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
827 if(piece == EmptySquare) return;
828 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
829 p = appData.inscriptions;
831 while(piece > WhitePawn) {
832 if(*p++ == NULLCHAR) {
833 if(n != WhiteKing) return;
838 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
842 for(q=buf; (*++q & 0xC0) == 0x80;);
844 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
848 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
849 { // basic front-end board-draw function: takes care of everything that can be in square:
850 // piece, background, coordinate/count, marker dot
852 if (piece == EmptySquare) {
853 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
855 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
856 if(appData.inscriptions[0]) InscribeKanji(csBoardWindow, piece, x, y);
859 if(align) { // square carries inscription (coord or piece count)
860 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
861 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
864 if(marker) { // print fat marker dot, if requested
865 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
869 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
871 /* Masks for XPM pieces. Black and white pieces can have
872 different shapes, but in the interest of retaining my
873 sanity pieces must have the same outline on both light
874 and dark squares, and all pieces must use the same
875 background square colors/images. */
877 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
880 InitAnimState (AnimNr anr)
882 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
883 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
884 c_animBufs[anr+4] = csBoardWindow;
885 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
886 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
893 InitAnimState(Player);
897 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
899 static cairo_t *pieceSource;
900 pieceSource = cairo_create (dest);
901 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
902 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
903 else cairo_paint(pieceSource);
904 cairo_destroy (pieceSource);
905 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
909 InsertPiece (AnimNr anr, ChessSquare piece)
911 CairoOverlayPiece(piece, c_animBufs[anr]);
915 DrawBlank (AnimNr anr, int x, int y, int startColor)
917 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
920 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
921 int srcX, int srcY, int width, int height, int destX, int destY)
924 c_animBufs[anr+4] = csBoardWindow;
925 cr = cairo_create (c_animBufs[anr+destBuf]);
926 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
927 cairo_rectangle (cr, destX, destY, width, height);
930 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
931 GraphExpose(currBoard, destX, destY, width, height);
935 SetDragPiece (AnimNr anr, ChessSquare piece)
939 /* [AS] Arrow highlighting support */
942 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
946 cr = cairo_create (cs);
947 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
949 cairo_line_to(cr, arrow[i].x, arrow[i].y);
951 if(appData.monoMode) { // should we always outline arrow?
952 cairo_line_to(cr, arrow[0].x, arrow[0].y);
953 SetPen(cr, 2, "#000000", 0);
954 cairo_stroke_preserve(cr);
956 SetPen(cr, 2, appData.highlightSquareColor, 0);
964 DrawPolygon (Pnt arrow[], int nr)
966 DoDrawPolygon(csBoardWindow, arrow, nr);
967 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
970 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
973 ChoosePen(cairo_t *cr, int i)
977 SetPen(cr, 1.0, "#000000", 0);
980 SetPen(cr, 1.0, "#A0A0A0", 1);
983 SetPen(cr, 1.0, "#0000FF", 1);
986 SetPen(cr, 3.0, crWhite, 0);
989 SetPen(cr, 3.0, crBlack, 0);
992 SetPen(cr, 3.0, "#E0E0F0", 0);
997 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
999 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1001 static int curX, curY;
1003 if(penType != PEN_NONE) {
1004 cairo_t *cr = cairo_create(DRAWABLE(disp));
1005 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1006 cairo_move_to (cr, curX, curY);
1007 cairo_line_to (cr, x,y);
1008 ChoosePen(cr, penType);
1013 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1017 // front-end wrapper for drawing functions to do rectangles
1019 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1023 cr = cairo_create (DRAWABLE(disp));
1024 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1025 cairo_rectangle (cr, left, top, right-left, bottom-top);
1028 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1029 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1030 case 2: ChoosePen(cr, PEN_BACKGD); break;
1036 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1037 ChoosePen(cr, PEN_BLACK);
1044 // front-end wrapper for putting text in graph
1046 DrawEvalText (char *buf, int cbBuf, int y)
1048 // the magic constants 8 and 5 should really be derived from the font size somehow
1049 cairo_text_extents_t extents;
1050 cairo_t *cr = cairo_create(DRAWABLE(disp));
1052 /* GTK-TODO this has to go into the font-selection */
1053 cairo_select_font_face (cr, "Sans",
1054 CAIRO_FONT_SLANT_NORMAL,
1055 CAIRO_FONT_WEIGHT_NORMAL);
1056 cairo_set_font_size (cr, 12.0);
1059 cairo_text_extents (cr, buf, &extents);
1061 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1062 cairo_text_path (cr, buf);
1063 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1064 cairo_fill_preserve (cr);
1065 cairo_set_source_rgb (cr, 0, 1.0, 0);
1066 cairo_set_line_width (cr, 0.1);