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;
223 CreatePNGPieces(appData.pieceDirectory);
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], transparency[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 transparency[kind] = cairo_image_surface_get_format (img) == CAIRO_FORMAT_ARGB32;
292 n[kind] = 1.; modV[kind] = modH[kind] = -1;
293 while((q = strchr(p+1, '-'))) p = q; // find last '-'
294 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
295 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
296 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
297 textureH[kind] = (h*BOARD_HEIGHT)/r;
298 n[kind] = (r*squareSize + 0.99)/h; // scale to make it fit exactly vertically
299 modV[kind] = r; modH[kind] = f;
301 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
302 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
304 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
306 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
307 // create scaled-up copy of the raw png image when it was too small
308 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
309 cairo_t *cr = cairo_create(cs);
310 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
311 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
312 cairo_scale(cr, n[kind], n[kind]);
313 cairo_set_source_surface (cr, img, 0, 0);
321 char *pngPieceNames[] = // must be in same order as internal piece encoding
322 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
323 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
324 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
325 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
326 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
327 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
331 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
332 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
333 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
334 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
335 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
339 LoadSVG (char *dir, int color, int piece, int retry)
342 RsvgHandle *svg=svgPieces[color][piece];
343 RsvgDimensionData svg_dimensions;
344 GError *svgerror=NULL;
345 cairo_surface_t *img;
347 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
349 if(!name) return NULL;
351 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
354 svg = rsvg_handle_new_from_file(buf, &svgerror);
355 if(!svg) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
357 safeStrCpy(buf, dir, MSG_SIZ);
358 while((q = strchr(p, '/'))) p = q + 1;
359 if(!strncmp(p, "sub_", 4)) {
360 if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
361 return LoadSVG(buf, color, piece, retry);
364 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
365 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
366 svg = rsvg_handle_new_from_file(buf, &svgerror);
371 rsvg_handle_get_dimensions(svg, &svg_dimensions);
372 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
374 cr = cairo_create(img);
375 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
376 rsvg_handle_render_cairo(svg, cr);
377 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
378 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
379 pngPieceImages[color][piece] = img;
385 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
386 return LoadSVG(dir, color, piece, 1);
388 g_error_free(svgerror);
393 ScaleOnePiece (int color, int piece, char *pieceDir)
397 cairo_surface_t *img, *cs;
402 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
404 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
405 if(*pieceDir) { // user specified piece directory
406 snprintf(buf, MSG_SIZ, "%s/%s%s.png", pieceDir, color ? "Black" : "White", pngPieceNames[piece]);
407 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
408 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
409 svgPieces[color][piece] = LoadSVG(pieceDir, color, piece, 0); // so try if he has svg there
410 } else pngPieceImages[color][piece] = img;
414 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
415 static int warned = 0;
416 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
417 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
418 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
419 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
420 DisplayError(msg, 0);
421 warned = 1; // prevent error message being repeated for each piece type
425 img = pngPieceImages[color][piece];
427 // create new bitmap to hold scaled piece image (and remove any old)
428 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
429 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
433 // scaled copying of the raw png image
434 cr = cairo_create(cs);
435 w = cairo_image_surface_get_width (img);
436 h = cairo_image_surface_get_height (img);
437 cairo_scale(cr, squareSize/w, squareSize/h);
438 cairo_set_source_surface (cr, img, 0, 0);
442 if(!appData.trueColors || !*pieceDir) { // operate on bitmap to color it (king-size hack...)
443 int stride = cairo_image_surface_get_stride(cs)/4;
444 int *buf = (int *) cairo_image_surface_get_data(cs);
446 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
447 cairo_surface_flush(cs);
448 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
451 unsigned int c = buf[i*stride + j];
452 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
453 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
454 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
455 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
456 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
457 if(appData.monoMode) {
458 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
459 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
460 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
463 cairo_surface_mark_dirty(cs);
468 CreatePNGPieces (char *pieceDir)
471 for(p=0; pngPieceNames[p]; p++) {
472 ScaleOnePiece(0, p, pieceDir);
473 ScaleOnePiece(1, p, pieceDir);
475 SelectPieces(gameInfo.variant);
479 CreateAnyPieces (int p)
480 { // [HGM] taken out of main
481 if(p) CreatePNGPieces(appData.pieceDirectory);
482 CreatePNGBoard(appData.liteBackTextureFile, 1);
483 CreatePNGBoard(appData.darkBackTextureFile, 0);
490 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
491 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
492 pngPieceImages[i][p] = NULL;
493 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
494 svgPieces[i][p] = NULL;
499 InitDrawingParams (int reloadPieces)
501 if(reloadPieces) ClearPieces();
506 Preview (int n, char *s)
508 static Boolean changed[4];
511 case 0: // restore true setting
512 if(changed[3]) ClearPieces();
513 CreateAnyPieces(changed[3]); // recomputes textures and (optionally) pieces
514 for(n=0; n<4; n++) changed[n] = FALSE;
518 CreatePNGBoard(s, n-1);
525 DrawPosition(TRUE, NULL);
528 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
531 Color (char *col, int n)
534 sscanf(col, "#%x", &c);
540 SetPen (cairo_t *cr, float w, char *col, int dash)
542 static const double dotted[] = {4.0, 4.0};
543 static int len = sizeof(dotted) / sizeof(dotted[0]);
544 cairo_set_line_width (cr, w);
545 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
546 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
549 void DrawSeekAxis( int x, int y, int xTo, int yTo )
554 cr = cairo_create (CsBoardWindow(currBoard));
556 cairo_move_to (cr, x, y);
557 cairo_line_to(cr, xTo, yTo );
559 SetPen(cr, 2, "#000000", 0);
564 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
567 void DrawSeekBackground( int left, int top, int right, int bottom )
569 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
571 cairo_rectangle (cr, left, top, right-left, bottom-top);
573 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
578 GraphExpose(currBoard, left, top, right-left, bottom-top);
581 void DrawSeekText(char *buf, int x, int y)
583 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
585 cairo_select_font_face (cr, "Sans",
586 CAIRO_FONT_SLANT_NORMAL,
587 CAIRO_FONT_WEIGHT_NORMAL);
589 cairo_set_font_size (cr, 12.0);
591 cairo_move_to (cr, x, y+4);
592 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
593 cairo_show_text( cr, buf);
597 GraphExpose(currBoard, x-5, y-10, 60, 15);
600 void DrawSeekDot(int x, int y, int colorNr)
602 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
603 int square = colorNr & 0x80;
607 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
609 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
611 SetPen(cr, 2, "#000000", 0);
612 cairo_stroke_preserve(cr);
614 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
615 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
616 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
622 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
626 InitDrawingHandle (Option *opt)
628 // CsBoardWindow = DRAWABLE(opt);
637 if (lineGap == 0) return;
639 /* [HR] Split this into 2 loops for non-square boards. */
641 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
642 gridSegments[i].x1 = 0;
644 lineGap + BOARD_WIDTH * (squareSize + lineGap);
645 gridSegments[i].y1 = gridSegments[i].y2
646 = lineGap / 2 + (i * (squareSize + lineGap));
649 for (j = 0; j < BOARD_WIDTH + 1; j++) {
650 gridSegments[j + i].y1 = 0;
651 gridSegments[j + i].y2 =
652 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
653 gridSegments[j + i].x1 = gridSegments[j + i].x2
654 = lineGap / 2 + (j * (squareSize + lineGap));
661 /* draws a grid starting around Nx, Ny squares starting at x,y */
663 float odd = (lineGap & 1)/2.;
667 cr = cairo_create (CsBoardWindow(currBoard));
669 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
670 SetPen(cr, lineGap, "#000000", 0);
673 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
675 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
676 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
677 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
688 DrawBorder (int x, int y, int type, int odd)
694 case 0: col = "#000000"; break;
695 case 1: col = appData.highlightSquareColor; break;
696 case 2: col = appData.premoveHighlightColor; break;
697 default: col = "#808080"; break; // cannot happen
699 cr = cairo_create(CsBoardWindow(currBoard));
700 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
701 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
702 SetPen(cr, lineGap, col, 0);
705 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
709 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
711 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
712 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
714 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
715 if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
716 if(textureW[kind] < W*squareSize)
717 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
719 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
720 if(textureH[kind] < H*squareSize)
721 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
723 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
728 DrawLogo (Option *opt, void *logo)
730 cairo_surface_t *img;
735 cr = cairo_create(CsBoardWindow(opt));
736 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
737 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
738 cairo_fill(cr); // paint background in case logo does not exist
740 img = cairo_image_surface_create_from_png (logo);
741 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
742 w = cairo_image_surface_get_width (img);
743 h = cairo_image_surface_get_height (img);
744 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
745 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
746 cairo_set_source_surface (cr, img, 0, 0);
749 cairo_surface_destroy (img);
752 GraphExpose(opt, 0, 0, opt->max, opt->value);
756 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
757 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
758 int x0, y0, texture = (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color);
761 cr = cairo_create (dest);
763 if(!texture || transparency[color]) // draw color also (as background) when texture could be transparent
764 { // evenly colored squares
767 case 0: col = appData.darkSquareColor; break;
768 case 1: col = appData.lightSquareColor; break;
769 case 2: col = "#000000"; break;
770 default: col = "#808080"; break; // cannot happen
772 SetPen(cr, 2.0, col, 0);
773 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
774 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
778 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
779 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
780 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
787 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
792 if ((int)piece < (int) BlackPawn) {
798 if(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra;
799 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
800 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
801 cr = cairo_create (dest);
802 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
807 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
810 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
814 cr = cairo_create(cs);
815 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
816 if(appData.monoMode) {
817 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
818 cairo_stroke_preserve(cr);
819 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
821 SetPen(cr, 2, markerColor[marker-1], 0);
829 DrawDot (int marker, int x, int y, int r)
830 { // used for atomic captures; no need to draw on backup
831 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
832 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
836 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip)
838 // cairo_text_extents_t te;
842 PangoFontDescription *desc;
844 char fontName[MSG_SIZ];
846 cr = cairo_create (canvas);
847 layout = pango_cairo_create_layout(cr);
848 pango_layout_set_text(layout, string, -1);
849 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", 5*squareSize/8);
850 desc = pango_font_description_from_string(fontName);
851 pango_layout_set_font_description(layout, desc);
852 pango_font_description_free(desc);
853 pango_layout_get_pixel_extents(layout, NULL, &r);
854 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (8+s)*squareSize/16 - s*r.height/2);
855 if(s < 0) cairo_rotate(cr, G_PI);
856 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
857 pango_cairo_update_layout(cr, layout);
858 pango_cairo_show_layout(cr, layout);
859 g_object_unref(layout);
864 DrawText (char *string, int x, int y, int align)
867 cairo_text_extents_t te;
870 cr = cairo_create (CsBoardWindow(currBoard));
871 cairo_select_font_face (cr, "Sans",
872 CAIRO_FONT_SLANT_NORMAL,
873 CAIRO_FONT_WEIGHT_BOLD);
875 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
876 // calculate where it goes
877 cairo_text_extents (cr, string, &te);
880 xx += squareSize - te.width - te.x_bearing - 1;
881 yy += squareSize - te.height - te.y_bearing - 1;
882 } else if (align == 2) {
883 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
884 } else if (align == 3) {
885 xx += squareSize - te.width -te.x_bearing - 1;
886 yy += -te.y_bearing + 3;
887 } else if (align == 4) {
888 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
891 cairo_move_to (cr, xx-1, yy);
892 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
893 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
894 cairo_show_text (cr, string);
899 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
901 char *p, *q, buf[10];
902 int n, flip = appData.upsideDown && flipView == (piece < BlackPawn);
903 if(piece == EmptySquare) return;
904 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
905 p = appData.inscriptions;
907 while(piece > WhitePawn) {
908 if(*p == '/') p++, piece = n - WhitePBishop; // secondary series
909 if(*p++ == NULLCHAR) {
910 if(n != WhiteKing) return;
915 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
919 for(q=buf; (*++q & 0xC0) == 0x80;);
921 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip);
925 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
926 { // basic front-end board-draw function: takes care of everything that can be in square:
927 // piece, background, coordinate/count, marker dot
929 if (piece == EmptySquare) {
930 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
932 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
933 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
936 if(align) { // square carries inscription (coord or piece count)
937 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
938 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
941 if(marker) { // print fat marker dot, if requested
942 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
946 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
948 /* Masks for XPM pieces. Black and white pieces can have
949 different shapes, but in the interest of retaining my
950 sanity pieces must have the same outline on both light
951 and dark squares, and all pieces must use the same
952 background square colors/images. */
954 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
957 InitAnimState (AnimNr anr)
959 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
960 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
961 c_animBufs[anr+4] = CsBoardWindow(currBoard);
962 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
963 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
970 InitAnimState(Player);
974 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
976 static cairo_t *pieceSource;
977 pieceSource = cairo_create (dest);
978 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
979 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
980 else cairo_paint(pieceSource);
981 cairo_destroy (pieceSource);
982 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
986 InsertPiece (AnimNr anr, ChessSquare piece)
988 CairoOverlayPiece(piece, c_animBufs[anr]);
992 DrawBlank (AnimNr anr, int x, int y, int startColor)
994 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
997 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
998 int srcX, int srcY, int width, int height, int destX, int destY)
1001 c_animBufs[anr+4] = CsBoardWindow(currBoard);
1002 cr = cairo_create (c_animBufs[anr+destBuf]);
1003 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
1004 cairo_rectangle (cr, destX, destY, width, height);
1007 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1008 GraphExpose(currBoard, destX, destY, width, height);
1012 SetDragPiece (AnimNr anr, ChessSquare piece)
1016 /* [AS] Arrow highlighting support */
1019 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1023 cr = cairo_create (cs);
1024 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1025 for (i=0;i<nr;i++) {
1026 cairo_line_to(cr, arrow[i].x, arrow[i].y);
1028 if(appData.monoMode) { // should we always outline arrow?
1029 cairo_line_to(cr, arrow[0].x, arrow[0].y);
1030 SetPen(cr, 2, "#000000", 0);
1031 cairo_stroke_preserve(cr);
1033 SetPen(cr, 2, appData.highlightSquareColor, 0);
1041 DrawPolygon (Pnt arrow[], int nr)
1043 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1044 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1047 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1050 ChoosePen(cairo_t *cr, int i)
1054 SetPen(cr, 1.0, "#000000", 0);
1057 SetPen(cr, 1.0, "#A0A0A0", 1);
1059 case PEN_BLUEDOTTED:
1060 SetPen(cr, 1.0, "#0000FF", 1);
1063 SetPen(cr, 3.0, crWhite, 0);
1066 SetPen(cr, 3.0, crBlack, 0);
1069 SetPen(cr, 3.0, "#E0E0F0", 0);
1074 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1076 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1078 static int curX, curY;
1080 if(penType != PEN_NONE) {
1081 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1082 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1083 cairo_move_to (cr, curX, curY);
1084 cairo_line_to (cr, x,y);
1085 ChoosePen(cr, penType);
1090 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1094 // front-end wrapper for drawing functions to do rectangles
1096 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1100 cr = cairo_create (CsBoardWindow(disp));
1101 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1102 cairo_rectangle (cr, left, top, right-left, bottom-top);
1105 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1106 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1107 case 2: ChoosePen(cr, PEN_BACKGD); break;
1113 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1114 ChoosePen(cr, PEN_BLACK);
1121 // front-end wrapper for putting text in graph
1123 DrawEvalText (char *buf, int cbBuf, int y)
1125 // the magic constants 8 and 5 should really be derived from the font size somehow
1126 cairo_text_extents_t extents;
1127 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1129 /* GTK-TODO this has to go into the font-selection */
1130 cairo_select_font_face (cr, "Sans",
1131 CAIRO_FONT_SLANT_NORMAL,
1132 CAIRO_FONT_WEIGHT_NORMAL);
1133 cairo_set_font_size (cr, 12.0);
1136 cairo_text_extents (cr, buf, &extents);
1138 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1139 cairo_text_path (cr, buf);
1140 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1141 cairo_fill_preserve (cr);
1142 cairo_set_source_rgb (cr, 0, 1.0, 0);
1143 cairo_set_line_width (cr, 0.1);