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 "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
292 "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
293 "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
296 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
297 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
300 LoadSVG (char *dir, int color, int piece, int retry)
303 RsvgHandle *svg=svgPieces[color][piece];
304 RsvgDimensionData svg_dimensions;
305 GError *svgerror=NULL;
306 cairo_surface_t *img;
308 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
310 if(!name) return NULL;
312 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
314 if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
316 rsvg_handle_get_dimensions(svg, &svg_dimensions);
317 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
319 cr = cairo_create(img);
320 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
321 rsvg_handle_render_cairo(svg, cr);
322 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
323 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
324 pngPieceImages[color][piece] = img;
330 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
331 return LoadSVG(dir, color, piece, 1);
333 g_error_free(svgerror);
338 ScaleOnePiece (int color, int piece)
342 cairo_surface_t *img, *cs;
347 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
349 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
350 if(*appData.pieceDirectory) { // user specified piece directory
351 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
352 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
353 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
354 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
355 } else pngPieceImages[color][piece] = img;
359 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
360 static int warned = 0;
361 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) && !warned) { // try to fall back on installed svg
362 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
363 printf("%s\n", msg); // give up
364 DisplayError(msg, 0);
365 warned = 1; // prevent error message being repeated for each piece type
369 img = pngPieceImages[color][piece];
371 // create new bitmap to hold scaled piece image (and remove any old)
372 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
373 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
377 // scaled copying of the raw png image
378 cr = cairo_create(cs);
379 w = cairo_image_surface_get_width (img);
380 h = cairo_image_surface_get_height (img);
381 cairo_scale(cr, squareSize/w, squareSize/h);
382 cairo_set_source_surface (cr, img, 0, 0);
386 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
387 int stride = cairo_image_surface_get_stride(cs)/4;
388 int *buf = (int *) cairo_image_surface_get_data(cs);
390 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
391 cairo_surface_flush(cs);
392 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
395 unsigned int c = buf[i*stride + j];
396 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
397 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
398 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
399 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
400 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
401 if(appData.monoMode) {
402 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
403 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
404 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
407 cairo_surface_mark_dirty(cs);
416 for(p=0; pngPieceNames[p]; p++) {
420 SelectPieces(gameInfo.variant);
424 CreateAnyPieces (int p)
425 { // [HGM] taken out of main
426 if(p) CreatePNGPieces();
427 CreatePNGBoard(appData.liteBackTextureFile, 1);
428 CreatePNGBoard(appData.darkBackTextureFile, 0);
432 InitDrawingParams (int reloadPieces)
436 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
437 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
438 pngPieceImages[i][p] = NULL;
439 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
440 svgPieces[i][p] = NULL;
445 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
448 Color (char *col, int n)
451 sscanf(col, "#%x", &c);
457 SetPen (cairo_t *cr, float w, char *col, int dash)
459 static const double dotted[] = {4.0, 4.0};
460 static int len = sizeof(dotted) / sizeof(dotted[0]);
461 cairo_set_line_width (cr, w);
462 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
463 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
466 void DrawSeekAxis( int x, int y, int xTo, int yTo )
471 cr = cairo_create (csBoardWindow);
473 cairo_move_to (cr, x, y);
474 cairo_line_to(cr, xTo, yTo );
476 SetPen(cr, 2, "#000000", 0);
481 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
484 void DrawSeekBackground( int left, int top, int right, int bottom )
486 cairo_t *cr = cairo_create (csBoardWindow);
488 cairo_rectangle (cr, left, top, right-left, bottom-top);
490 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
495 GraphExpose(currBoard, left, top, right-left, bottom-top);
498 void DrawSeekText(char *buf, int x, int y)
500 cairo_t *cr = cairo_create (csBoardWindow);
502 cairo_select_font_face (cr, "Sans",
503 CAIRO_FONT_SLANT_NORMAL,
504 CAIRO_FONT_WEIGHT_NORMAL);
506 cairo_set_font_size (cr, 12.0);
508 cairo_move_to (cr, x, y+4);
509 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
510 cairo_show_text( cr, buf);
514 GraphExpose(currBoard, x-5, y-10, 60, 15);
517 void DrawSeekDot(int x, int y, int colorNr)
519 cairo_t *cr = cairo_create (csBoardWindow);
520 int square = colorNr & 0x80;
524 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
526 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
528 SetPen(cr, 2, "#000000", 0);
529 cairo_stroke_preserve(cr);
531 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
532 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
533 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
539 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
543 InitDrawingHandle (Option *opt)
545 csBoardWindow = DRAWABLE(opt);
553 if (lineGap == 0) return;
555 /* [HR] Split this into 2 loops for non-square boards. */
557 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
558 gridSegments[i].x1 = 0;
560 lineGap + BOARD_WIDTH * (squareSize + lineGap);
561 gridSegments[i].y1 = gridSegments[i].y2
562 = lineGap / 2 + (i * (squareSize + lineGap));
565 for (j = 0; j < BOARD_WIDTH + 1; j++) {
566 gridSegments[j + i].y1 = 0;
567 gridSegments[j + i].y2 =
568 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
569 gridSegments[j + i].x1 = gridSegments[j + i].x2
570 = lineGap / 2 + (j * (squareSize + lineGap));
577 /* draws a grid starting around Nx, Ny squares starting at x,y */
579 float odd = (lineGap & 1)/2.;
583 cr = cairo_create (csBoardWindow);
585 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
586 SetPen(cr, lineGap, "#000000", 0);
589 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
591 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
592 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
593 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
604 DrawBorder (int x, int y, int type, int odd)
610 case 0: col = "#000000"; break;
611 case 1: col = appData.highlightSquareColor; break;
612 case 2: col = appData.premoveHighlightColor; break;
613 default: col = "#808080"; break; // cannot happen
615 cr = cairo_create(csBoardWindow);
616 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
617 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
618 SetPen(cr, lineGap, col, 0);
621 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
625 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
627 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
628 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
630 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
631 if(textureW[kind] < W*squareSize)
632 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
634 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
635 if(textureH[kind] < H*squareSize)
636 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
638 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
643 DrawLogo (Option *opt, void *logo)
645 cairo_surface_t *img;
649 if(!logo || !opt) return;
650 img = cairo_image_surface_create_from_png (logo);
651 w = cairo_image_surface_get_width (img);
652 h = cairo_image_surface_get_height (img);
653 cr = cairo_create(DRAWABLE(opt));
654 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
655 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
656 cairo_set_source_surface (cr, img, 0, 0);
659 cairo_surface_destroy (img);
660 GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
664 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
665 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
669 cr = cairo_create (dest);
671 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
672 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
673 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
674 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
677 } else { // evenly colored squares
680 case 0: col = appData.darkSquareColor; break;
681 case 1: col = appData.lightSquareColor; break;
682 case 2: col = "#000000"; break;
683 default: col = "#808080"; break; // cannot happen
685 SetPen(cr, 2.0, col, 0);
686 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
687 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
694 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
699 if ((int)piece < (int) BlackPawn) {
705 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
706 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
707 cr = cairo_create (dest);
708 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
713 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
716 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
720 cr = cairo_create(cs);
721 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
722 if(appData.monoMode) {
723 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
724 cairo_stroke_preserve(cr);
725 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
727 SetPen(cr, 2, markerColor[marker-1], 0);
735 DrawDot (int marker, int x, int y, int r)
736 { // used for atomic captures; no need to draw on backup
737 DoDrawDot(csBoardWindow, marker, x, y, r);
738 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
742 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
744 // cairo_text_extents_t te;
748 PangoFontDescription *desc;
750 char fontName[MSG_SIZ];
752 cr = cairo_create (canvas);
753 layout = pango_cairo_create_layout(cr);
754 pango_layout_set_text(layout, string, -1);
755 snprintf(fontName, MSG_SIZ, "Sans Bold %dpx", 2*squareSize/3);
756 desc = pango_font_description_from_string(fontName);
757 pango_layout_set_font_description(layout, desc);
758 pango_font_description_free(desc);
759 pango_layout_get_pixel_extents(layout, NULL, &r);
760 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
761 if(s < 0) cairo_rotate(cr, G_PI);
762 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
763 pango_cairo_update_layout(cr, layout);
764 pango_cairo_show_layout(cr, layout);
765 g_object_unref(layout);
770 DrawText (char *string, int x, int y, int align)
773 cairo_text_extents_t te;
776 cr = cairo_create (csBoardWindow);
777 cairo_select_font_face (cr, "Sans",
778 CAIRO_FONT_SLANT_NORMAL,
779 CAIRO_FONT_WEIGHT_BOLD);
781 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
782 // calculate where it goes
783 cairo_text_extents (cr, string, &te);
786 xx += squareSize - te.width - te.x_bearing - 1;
787 yy += squareSize - te.height - te.y_bearing - 1;
788 } else if (align == 2) {
789 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
790 } else if (align == 3) {
791 xx += squareSize - te.width -te.x_bearing - 1;
792 yy += -te.y_bearing + 3;
793 } else if (align == 4) {
794 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
797 cairo_move_to (cr, xx-1, yy);
798 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
799 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
800 cairo_show_text (cr, string);
805 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
807 char *p, *q, buf[10];
808 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
809 if(piece == EmptySquare) return;
810 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
811 p = appData.inscriptions;
813 while(piece > WhitePawn) {
814 if(*p++ == NULLCHAR) {
815 if(n != WhiteKing) return;
820 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
824 for(q=buf; (*++q & 0xC0) == 0x80;);
826 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
830 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
831 { // basic front-end board-draw function: takes care of everything that can be in square:
832 // piece, background, coordinate/count, marker dot
834 if (piece == EmptySquare) {
835 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
837 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
838 if(appData.inscriptions[0]) InscribeKanji(csBoardWindow, piece, x, y);
841 if(align) { // square carries inscription (coord or piece count)
842 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
843 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
846 if(marker) { // print fat marker dot, if requested
847 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
851 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
853 /* Masks for XPM pieces. Black and white pieces can have
854 different shapes, but in the interest of retaining my
855 sanity pieces must have the same outline on both light
856 and dark squares, and all pieces must use the same
857 background square colors/images. */
859 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
862 InitAnimState (AnimNr anr)
864 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
865 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
866 c_animBufs[anr+4] = csBoardWindow;
867 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
868 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
875 InitAnimState(Player);
879 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
881 static cairo_t *pieceSource;
882 pieceSource = cairo_create (dest);
883 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
884 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
885 else cairo_paint(pieceSource);
886 cairo_destroy (pieceSource);
887 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
891 InsertPiece (AnimNr anr, ChessSquare piece)
893 CairoOverlayPiece(piece, c_animBufs[anr]);
897 DrawBlank (AnimNr anr, int x, int y, int startColor)
899 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
902 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
903 int srcX, int srcY, int width, int height, int destX, int destY)
906 c_animBufs[anr+4] = csBoardWindow;
907 cr = cairo_create (c_animBufs[anr+destBuf]);
908 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
909 cairo_rectangle (cr, destX, destY, width, height);
912 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
913 GraphExpose(currBoard, destX, destY, width, height);
917 SetDragPiece (AnimNr anr, ChessSquare piece)
921 /* [AS] Arrow highlighting support */
924 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
928 cr = cairo_create (cs);
929 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
931 cairo_line_to(cr, arrow[i].x, arrow[i].y);
933 if(appData.monoMode) { // should we always outline arrow?
934 cairo_line_to(cr, arrow[0].x, arrow[0].y);
935 SetPen(cr, 2, "#000000", 0);
936 cairo_stroke_preserve(cr);
938 SetPen(cr, 2, appData.highlightSquareColor, 0);
946 DrawPolygon (Pnt arrow[], int nr)
948 DoDrawPolygon(csBoardWindow, arrow, nr);
949 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
952 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
955 ChoosePen(cairo_t *cr, int i)
959 SetPen(cr, 1.0, "#000000", 0);
962 SetPen(cr, 1.0, "#A0A0A0", 1);
965 SetPen(cr, 1.0, "#0000FF", 1);
968 SetPen(cr, 3.0, crWhite, 0);
971 SetPen(cr, 3.0, crBlack, 0);
974 SetPen(cr, 3.0, "#E0E0F0", 0);
979 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
981 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
983 static int curX, curY;
985 if(penType != PEN_NONE) {
986 cairo_t *cr = cairo_create(DRAWABLE(disp));
987 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
988 cairo_move_to (cr, curX, curY);
989 cairo_line_to (cr, x,y);
990 ChoosePen(cr, penType);
995 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
999 // front-end wrapper for drawing functions to do rectangles
1001 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1005 cr = cairo_create (DRAWABLE(disp));
1006 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1007 cairo_rectangle (cr, left, top, right-left, bottom-top);
1010 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1011 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1012 case 2: ChoosePen(cr, PEN_BACKGD); break;
1018 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1019 ChoosePen(cr, PEN_BLACK);
1026 // front-end wrapper for putting text in graph
1028 DrawEvalText (char *buf, int cbBuf, int y)
1030 // the magic constants 8 and 5 should really be derived from the font size somehow
1031 cairo_text_extents_t extents;
1032 cairo_t *cr = cairo_create(DRAWABLE(disp));
1034 /* GTK-TODO this has to go into the font-selection */
1035 cairo_select_font_face (cr, "Sans",
1036 CAIRO_FONT_SLANT_NORMAL,
1037 CAIRO_FONT_WEIGHT_NORMAL);
1038 cairo_set_font_size (cr, 12.0);
1041 cairo_text_extents (cr, buf, &extents);
1043 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1044 cairo_text_path (cr, buf);
1045 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1046 cairo_fill_preserve (cr);
1047 cairo_set_source_rgb (cr, 0, 1.0, 0);
1048 cairo_set_line_width (cr, 0.1);