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);
272 static int modV[2], modH[2];
275 CreatePNGBoard (char *s, int kind)
278 static float n[2] = { 1., 1. };
279 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
280 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
281 if(strstr(s, ".png")) {
282 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
283 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
286 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
287 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
288 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
289 w = textureW[kind] = cairo_image_surface_get_width (img);
290 h = textureH[kind] = cairo_image_surface_get_height (img);
291 n[kind] = 1.; modV[kind] = modH[kind] = -1;
292 while((q = strchr(p+1, '-'))) p = q; // find last '-'
293 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
294 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
295 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
296 textureH[kind] = (h*BOARD_HEIGHT)/r;
297 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
298 modV[kind] = r; modH[kind] = f;
300 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
301 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
303 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
305 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
306 // create scaled-up copy of the raw png image when it was too small
307 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
308 cairo_t *cr = cairo_create(cs);
309 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
310 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
311 cairo_scale(cr, n[kind], n[kind]);
312 cairo_set_source_surface (cr, img, 0, 0);
320 char *pngPieceNames[] = // must be in same order as internal piece encoding
321 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
322 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
323 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
324 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
325 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
326 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
330 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
331 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
332 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
333 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
334 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
338 LoadSVG (char *dir, int color, int piece, int retry)
341 RsvgHandle *svg=svgPieces[color][piece];
342 RsvgDimensionData svg_dimensions;
343 GError *svgerror=NULL;
344 cairo_surface_t *img;
346 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
348 if(!name) return NULL;
350 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
353 svg = rsvg_handle_new_from_file(buf, &svgerror);
354 if(!svg) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
356 safeStrCpy(buf, dir, MSG_SIZ);
357 while((q = strchr(p, '/'))) p = q + 1;
358 if(!strncmp(p, "sub_", 4)) {
359 if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
360 return LoadSVG(buf, color, piece, retry);
363 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
364 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
365 svg = rsvg_handle_new_from_file(buf, &svgerror);
370 rsvg_handle_get_dimensions(svg, &svg_dimensions);
371 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
373 cr = cairo_create(img);
374 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
375 rsvg_handle_render_cairo(svg, cr);
376 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
377 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
378 pngPieceImages[color][piece] = img;
384 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
385 return LoadSVG(dir, color, piece, 1);
387 g_error_free(svgerror);
392 ScaleOnePiece (int color, int piece)
396 cairo_surface_t *img, *cs;
401 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
403 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
404 if(*appData.pieceDirectory) { // user specified piece directory
405 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
406 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
407 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
408 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
409 } else pngPieceImages[color][piece] = img;
413 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
414 static int warned = 0;
415 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
416 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
417 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
418 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
419 DisplayError(msg, 0);
420 warned = 1; // prevent error message being repeated for each piece type
424 img = pngPieceImages[color][piece];
426 // create new bitmap to hold scaled piece image (and remove any old)
427 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
428 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
432 // scaled copying of the raw png image
433 cr = cairo_create(cs);
434 w = cairo_image_surface_get_width (img);
435 h = cairo_image_surface_get_height (img);
436 cairo_scale(cr, squareSize/w, squareSize/h);
437 cairo_set_source_surface (cr, img, 0, 0);
441 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
442 int stride = cairo_image_surface_get_stride(cs)/4;
443 int *buf = (int *) cairo_image_surface_get_data(cs);
445 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
446 cairo_surface_flush(cs);
447 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
450 unsigned int c = buf[i*stride + j];
451 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
452 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
453 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
454 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
455 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
456 if(appData.monoMode) {
457 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
458 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
459 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
462 cairo_surface_mark_dirty(cs);
471 for(p=0; pngPieceNames[p]; p++) {
475 SelectPieces(gameInfo.variant);
479 CreateAnyPieces (int p)
480 { // [HGM] taken out of main
481 if(p) CreatePNGPieces();
482 CreatePNGBoard(appData.liteBackTextureFile, 1);
483 CreatePNGBoard(appData.darkBackTextureFile, 0);
487 InitDrawingParams (int reloadPieces)
491 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
492 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
493 pngPieceImages[i][p] = NULL;
494 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
495 svgPieces[i][p] = NULL;
500 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
503 Color (char *col, int n)
506 sscanf(col, "#%x", &c);
512 SetPen (cairo_t *cr, float w, char *col, int dash)
514 static const double dotted[] = {4.0, 4.0};
515 static int len = sizeof(dotted) / sizeof(dotted[0]);
516 cairo_set_line_width (cr, w);
517 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
518 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
521 void DrawSeekAxis( int x, int y, int xTo, int yTo )
526 cr = cairo_create (CsBoardWindow(currBoard));
528 cairo_move_to (cr, x, y);
529 cairo_line_to(cr, xTo, yTo );
531 SetPen(cr, 2, "#000000", 0);
536 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
539 void DrawSeekBackground( int left, int top, int right, int bottom )
541 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
543 cairo_rectangle (cr, left, top, right-left, bottom-top);
545 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
550 GraphExpose(currBoard, left, top, right-left, bottom-top);
553 void DrawSeekText(char *buf, int x, int y)
555 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
557 cairo_select_font_face (cr, "Sans",
558 CAIRO_FONT_SLANT_NORMAL,
559 CAIRO_FONT_WEIGHT_NORMAL);
561 cairo_set_font_size (cr, 12.0);
563 cairo_move_to (cr, x, y+4);
564 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
565 cairo_show_text( cr, buf);
569 GraphExpose(currBoard, x-5, y-10, 60, 15);
572 void DrawSeekDot(int x, int y, int colorNr)
574 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
575 int square = colorNr & 0x80;
579 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
581 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
583 SetPen(cr, 2, "#000000", 0);
584 cairo_stroke_preserve(cr);
586 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
587 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
588 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
594 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
598 InitDrawingHandle (Option *opt)
600 // CsBoardWindow = DRAWABLE(opt);
609 if (lineGap == 0) return;
611 /* [HR] Split this into 2 loops for non-square boards. */
613 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
614 gridSegments[i].x1 = 0;
616 lineGap + BOARD_WIDTH * (squareSize + lineGap);
617 gridSegments[i].y1 = gridSegments[i].y2
618 = lineGap / 2 + (i * (squareSize + lineGap));
621 for (j = 0; j < BOARD_WIDTH + 1; j++) {
622 gridSegments[j + i].y1 = 0;
623 gridSegments[j + i].y2 =
624 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
625 gridSegments[j + i].x1 = gridSegments[j + i].x2
626 = lineGap / 2 + (j * (squareSize + lineGap));
633 /* draws a grid starting around Nx, Ny squares starting at x,y */
635 float odd = (lineGap & 1)/2.;
639 cr = cairo_create (CsBoardWindow(currBoard));
641 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
642 SetPen(cr, lineGap, "#000000", 0);
645 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
647 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
648 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
649 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
660 DrawBorder (int x, int y, int type, int odd)
666 case 0: col = "#000000"; break;
667 case 1: col = appData.highlightSquareColor; break;
668 case 2: col = appData.premoveHighlightColor; break;
669 default: col = "#808080"; break; // cannot happen
671 cr = cairo_create(CsBoardWindow(currBoard));
672 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
673 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
674 SetPen(cr, lineGap, col, 0);
677 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
681 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
683 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
684 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
686 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
687 if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
688 if(textureW[kind] < W*squareSize)
689 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
691 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
692 if(textureH[kind] < H*squareSize)
693 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
695 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
700 DrawLogo (Option *opt, void *logo)
702 cairo_surface_t *img;
707 cr = cairo_create(CsBoardWindow(opt));
708 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
709 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
710 cairo_fill(cr); // paint background in case logo does not exist
712 img = cairo_image_surface_create_from_png (logo);
713 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
714 w = cairo_image_surface_get_width (img);
715 h = cairo_image_surface_get_height (img);
716 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
717 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
718 cairo_set_source_surface (cr, img, 0, 0);
721 cairo_surface_destroy (img);
724 GraphExpose(opt, 0, 0, opt->max, opt->value);
728 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
729 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
733 cr = cairo_create (dest);
735 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
736 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
737 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
738 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
741 } else { // evenly colored squares
744 case 0: col = appData.darkSquareColor; break;
745 case 1: col = appData.lightSquareColor; break;
746 case 2: col = "#000000"; break;
747 default: col = "#808080"; break; // cannot happen
749 SetPen(cr, 2.0, col, 0);
750 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
751 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
758 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
763 if ((int)piece < (int) BlackPawn) {
769 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
770 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
771 cr = cairo_create (dest);
772 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
777 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
780 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
784 cr = cairo_create(cs);
785 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
786 if(appData.monoMode) {
787 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
788 cairo_stroke_preserve(cr);
789 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
791 SetPen(cr, 2, markerColor[marker-1], 0);
799 DrawDot (int marker, int x, int y, int r)
800 { // used for atomic captures; no need to draw on backup
801 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
802 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
806 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
808 // cairo_text_extents_t te;
812 PangoFontDescription *desc;
814 char fontName[MSG_SIZ];
816 cr = cairo_create (canvas);
817 layout = pango_cairo_create_layout(cr);
818 pango_layout_set_text(layout, string, -1);
819 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
820 desc = pango_font_description_from_string(fontName);
821 pango_layout_set_font_description(layout, desc);
822 pango_font_description_free(desc);
823 pango_layout_get_pixel_extents(layout, NULL, &r);
824 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
825 if(s < 0) cairo_rotate(cr, G_PI);
826 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
827 pango_cairo_update_layout(cr, layout);
828 pango_cairo_show_layout(cr, layout);
829 g_object_unref(layout);
834 DrawText (char *string, int x, int y, int align)
837 cairo_text_extents_t te;
840 cr = cairo_create (CsBoardWindow(currBoard));
841 cairo_select_font_face (cr, "Sans",
842 CAIRO_FONT_SLANT_NORMAL,
843 CAIRO_FONT_WEIGHT_BOLD);
845 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
846 // calculate where it goes
847 cairo_text_extents (cr, string, &te);
850 xx += squareSize - te.width - te.x_bearing - 1;
851 yy += squareSize - te.height - te.y_bearing - 1;
852 } else if (align == 2) {
853 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
854 } else if (align == 3) {
855 xx += squareSize - te.width -te.x_bearing - 1;
856 yy += -te.y_bearing + 3;
857 } else if (align == 4) {
858 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
861 cairo_move_to (cr, xx-1, yy);
862 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
863 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
864 cairo_show_text (cr, string);
869 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
871 char *p, *q, buf[10];
872 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
873 if(piece == EmptySquare) return;
874 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
875 p = appData.inscriptions;
877 while(piece > WhitePawn) {
878 if(*p++ == NULLCHAR) {
879 if(n != WhiteKing) return;
884 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
888 for(q=buf; (*++q & 0xC0) == 0x80;);
890 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
894 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
895 { // basic front-end board-draw function: takes care of everything that can be in square:
896 // piece, background, coordinate/count, marker dot
898 if (piece == EmptySquare) {
899 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
901 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
902 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
905 if(align) { // square carries inscription (coord or piece count)
906 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
907 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
910 if(marker) { // print fat marker dot, if requested
911 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
915 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
917 /* Masks for XPM pieces. Black and white pieces can have
918 different shapes, but in the interest of retaining my
919 sanity pieces must have the same outline on both light
920 and dark squares, and all pieces must use the same
921 background square colors/images. */
923 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
926 InitAnimState (AnimNr anr)
928 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
929 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
930 c_animBufs[anr+4] = CsBoardWindow(currBoard);
931 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
932 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
939 InitAnimState(Player);
943 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
945 static cairo_t *pieceSource;
946 pieceSource = cairo_create (dest);
947 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
948 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
949 else cairo_paint(pieceSource);
950 cairo_destroy (pieceSource);
951 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
955 InsertPiece (AnimNr anr, ChessSquare piece)
957 CairoOverlayPiece(piece, c_animBufs[anr]);
961 DrawBlank (AnimNr anr, int x, int y, int startColor)
963 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
966 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
967 int srcX, int srcY, int width, int height, int destX, int destY)
970 c_animBufs[anr+4] = CsBoardWindow(currBoard);
971 cr = cairo_create (c_animBufs[anr+destBuf]);
972 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
973 cairo_rectangle (cr, destX, destY, width, height);
976 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
977 GraphExpose(currBoard, destX, destY, width, height);
981 SetDragPiece (AnimNr anr, ChessSquare piece)
985 /* [AS] Arrow highlighting support */
988 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
992 cr = cairo_create (cs);
993 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
995 cairo_line_to(cr, arrow[i].x, arrow[i].y);
997 if(appData.monoMode) { // should we always outline arrow?
998 cairo_line_to(cr, arrow[0].x, arrow[0].y);
999 SetPen(cr, 2, "#000000", 0);
1000 cairo_stroke_preserve(cr);
1002 SetPen(cr, 2, appData.highlightSquareColor, 0);
1010 DrawPolygon (Pnt arrow[], int nr)
1012 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1013 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1016 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1019 ChoosePen(cairo_t *cr, int i)
1023 SetPen(cr, 1.0, "#000000", 0);
1026 SetPen(cr, 1.0, "#A0A0A0", 1);
1028 case PEN_BLUEDOTTED:
1029 SetPen(cr, 1.0, "#0000FF", 1);
1032 SetPen(cr, 3.0, crWhite, 0);
1035 SetPen(cr, 3.0, crBlack, 0);
1038 SetPen(cr, 3.0, "#E0E0F0", 0);
1043 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1045 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1047 static int curX, curY;
1049 if(penType != PEN_NONE) {
1050 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1051 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1052 cairo_move_to (cr, curX, curY);
1053 cairo_line_to (cr, x,y);
1054 ChoosePen(cr, penType);
1059 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1063 // front-end wrapper for drawing functions to do rectangles
1065 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1069 cr = cairo_create (CsBoardWindow(disp));
1070 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1071 cairo_rectangle (cr, left, top, right-left, bottom-top);
1074 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1075 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1076 case 2: ChoosePen(cr, PEN_BACKGD); break;
1082 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1083 ChoosePen(cr, PEN_BLACK);
1090 // front-end wrapper for putting text in graph
1092 DrawEvalText (char *buf, int cbBuf, int y)
1094 // the magic constants 8 and 5 should really be derived from the font size somehow
1095 cairo_text_extents_t extents;
1096 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1098 /* GTK-TODO this has to go into the font-selection */
1099 cairo_select_font_face (cr, "Sans",
1100 CAIRO_FONT_SLANT_NORMAL,
1101 CAIRO_FONT_WEIGHT_NORMAL);
1102 cairo_set_font_size (cr, 12.0);
1105 cairo_text_extents (cr, buf, &extents);
1107 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1108 cairo_text_path (cr, buf);
1109 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1110 cairo_fill_preserve (cr);
1111 cairo_set_source_rgb (cr, 0, 1.0, 0);
1112 cairo_set_line_width (cr, 0.1);