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];
193 #define BoardSize int
195 InitDrawingSizes (BoardSize boardSize, int flags)
196 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
197 int boardWidth, boardHeight;
198 static int oldWidth, oldHeight;
199 static VariantClass oldVariant;
200 static int oldTwoBoards = 0, oldNrOfFiles = 0;
202 if(!mainOptions[W_BOARD].handle) return;
204 if(boardSize == -2 && gameInfo.variant != oldVariant
205 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
206 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
207 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
208 squareSize -= lineGap;
209 CreatePNGPieces(appData.pieceDirectory);
212 oldNrOfFiles = BOARD_WIDTH;
214 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
215 oldTwoBoards = twoBoards;
217 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
218 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
219 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
221 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
223 oldWidth = boardWidth; oldHeight = boardHeight;
225 CreateAnyPieces(0); // redo texture scaling
228 * Inhibit shell resizing.
230 ResizeBoardWindow(boardWidth, boardHeight, 0);
235 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
238 if(gameInfo.variant != oldVariant) { // and only if variant changed
240 SelectPieces(gameInfo.variant);
242 oldVariant = gameInfo.variant;
248 ExposeRedraw (Option *graph, int x, int y, int w, int h)
249 { // copy a selected part of the buffer bitmap to the display
250 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
251 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
252 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
253 cairo_rectangle(cr, x, y, w, h);
258 static int modV[2], modH[2];
261 CreatePNGBoard (char *s, int kind)
264 static float n[2] = { 1., 1. };
265 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
266 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
267 if(strstr(s, ".png")) {
268 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
269 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
272 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
273 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
274 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
275 w = textureW[kind] = cairo_image_surface_get_width (img);
276 h = textureH[kind] = cairo_image_surface_get_height (img);
277 transparency[kind] = cairo_image_surface_get_format (img) == CAIRO_FORMAT_ARGB32;
278 n[kind] = 1.; modV[kind] = modH[kind] = -1;
279 while((q = strchr(p+1, '-'))) p = q; // find last '-'
280 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
281 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
282 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
283 textureH[kind] = (h*BOARD_HEIGHT)/r;
284 n[kind] = (r*squareSize + 0.99)/h; // scale to make it fit exactly vertically
285 modV[kind] = r; modH[kind] = f;
287 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
288 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
290 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
292 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
293 // create scaled-up copy of the raw png image when it was too small
294 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
295 cairo_t *cr = cairo_create(cs);
296 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
297 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
298 cairo_scale(cr, n[kind], n[kind]);
299 cairo_set_source_surface (cr, img, 0, 0);
307 char *pngPieceNames[] = // must be in same order as internal piece encoding
308 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
309 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
310 "Sword", "Zebra", "Camel", "Tower", "Wolf", "Hat", "Duck", "Lance", "Dragon", "Gnu", "Cub",
311 "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw",
312 "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right",
313 "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King",
317 char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk")
318 "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
319 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Chancellor", NULL,
320 NULL, "Knight", NULL, "Commoner", NULL, NULL, NULL, "Canon", NULL, NULL, NULL,
321 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion", "Elephant"
325 LoadSVG (char *dir, int color, int piece, int retry)
328 RsvgHandle *svg=svgPieces[color][piece];
329 RsvgDimensionData svg_dimensions;
330 GError *svgerror=NULL;
331 cairo_surface_t *img;
333 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
335 if(!name) return NULL;
337 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
340 svg = rsvg_handle_new_from_file(buf, &svgerror);
341 if(!svg) { // failed! If -pid name starts with "sub_" we try to load the piece from the parent directory
343 safeStrCpy(buf, dir, MSG_SIZ);
344 while((q = strchr(p, '/'))) p = q + 1;
345 if(!strncmp(p, "sub_", 4)) {
346 if(p == buf) safeStrCpy(buf, ".", MSG_SIZ); else p[-1] = NULLCHAR; // strip last directory off path
347 return LoadSVG(buf, color, piece, retry);
350 if(!svg && *appData.inscriptions) { // if there is no piece-specific SVG, but we make inscriptions, try general background
351 snprintf(buf, MSG_SIZ, "%s/%sTile.svg", dir, color ? "Black" : "White");
352 svg = rsvg_handle_new_from_file(buf, &svgerror);
357 rsvg_handle_get_dimensions(svg, &svg_dimensions);
358 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
360 cr = cairo_create(img);
361 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
362 rsvg_handle_render_cairo(svg, cr);
363 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
364 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
365 pngPieceImages[color][piece] = img;
371 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteDrunk) // pieces that are only different in kanji sets
372 return LoadSVG(dir, color, piece, 1);
374 g_error_free(svgerror);
378 static void Wint(FILE *f, int n)
379 { // write 32-bit int, lsb first
380 fprintf(f, "%c%c%c%c", n&255, n>>8&255, n>>16&255, n>>24&255);
384 SaveWindowsBitmap(ChessSquare piece, int color, int *data, int stride, int w, int h, int bpp)
386 int i, v, line = (w*bpp + 3)>>2, size = line*4*h;
389 snprintf(buf, 100, "bmaps/piece%d_%d%c.bmp", piece, w, color ? 's': 'o');
390 if(piece >= appData.bmpSave && piece != WhiteKing) return;
391 f = fopen(buf, "wb");
394 Wint(f, size+54); Wint(f, 0); Wint(f, 54); Wint(f, 40); // file size, reserved, image offset, header size
395 Wint(f, w); Wint(f, h); Wint(f, 8*bpp << 16 | 1); // width, height, planes & bits/pix
396 Wint(f, 0); // compression
397 Wint(f, size); Wint(f, 3780); Wint(f, 3780); Wint(f, 0); Wint(f, 0); // image size, pix/m (H and V), color-table size (2x)
398 for(v=h-1; v>=0; v--) {
400 int pix = data[stride*v+i], r=pix>>16&255, g=pix>>8&255, b=pix&255, a=pix>>24&255;
401 r = r*a/255; b = b*a/255; g = g*a/255;
402 if(bpp == 4) fprintf(f, "%c%c%c%c", b, g, r, a); else fprintf(f, "%c%c%c", b, g, r);
404 i *= bpp; while(i++ < line*4) fprintf(f, "%c", 0); // padding
407 if(appData.bmpSave && piece == WhiteKing && color) exit(0);
411 ScaleOnePiece (int color, int piece, char *pieceDir)
415 cairo_surface_t *img, *cs;
420 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
422 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
423 if(*pieceDir) { // user specified piece directory
424 snprintf(buf, MSG_SIZ, "%s/%s%s.png", pieceDir, color ? "Black" : "White", pngPieceNames[piece]);
425 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
426 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
427 svgPieces[color][piece] = LoadSVG(pieceDir, color, piece, 0); // so try if he has svg there
428 } else pngPieceImages[color][piece] = img;
432 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
433 static int warned = 0;
434 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) // try to fall back on installed svg
435 && !warned && strcmp(pngPieceNames[piece], "Tile")) { // but do not complain about missing 'Tile'
436 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
437 printf("%s (%s)\n", msg, pngPieceNames[piece]); // give up
438 DisplayError(msg, 0);
439 warned = 1; // prevent error message being repeated for each piece type
443 img = pngPieceImages[color][piece];
445 // create new bitmap to hold scaled piece image (and remove any old)
446 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
447 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
451 // scaled copying of the raw png image
452 cr = cairo_create(cs);
453 w = cairo_image_surface_get_width (img);
454 h = cairo_image_surface_get_height (img);
455 cairo_scale(cr, squareSize/w, squareSize/h);
456 cairo_set_source_surface (cr, img, 0, 0);
460 if(!appData.trueColors || !*pieceDir) { // operate on bitmap to color it (king-size hack...)
461 int stride = cairo_image_surface_get_stride(cs)/4;
462 int *buf = (int *) cairo_image_surface_get_data(cs);
464 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
465 cairo_surface_flush(cs);
466 SaveWindowsBitmap(piece, color, buf, stride, squareSize, squareSize, 4);
467 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
470 unsigned int c = buf[i*stride + j];
471 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
472 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
473 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
474 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
475 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
476 if(appData.monoMode) {
477 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
478 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
479 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
482 cairo_surface_mark_dirty(cs);
487 CreatePNGPieces (char *pieceDir)
490 for(p=0; pngPieceNames[p]; p++) {
491 ScaleOnePiece(0, p, pieceDir);
492 ScaleOnePiece(1, p, pieceDir);
494 SelectPieces(gameInfo.variant);
498 CreateAnyPieces (int p)
499 { // [HGM] taken out of main
500 if(p) CreatePNGPieces(appData.pieceDirectory);
501 CreatePNGBoard(appData.liteBackTextureFile, 1);
502 CreatePNGBoard(appData.darkBackTextureFile, 0);
509 for(i=0; i<2; i++) for(p=0; p<BlackPawn; p++) {
510 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
511 pngPieceImages[i][p] = NULL;
512 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
513 svgPieces[i][p] = NULL;
518 InitDrawingParams (int reloadPieces)
520 if(reloadPieces) ClearPieces();
525 Preview (int n, char *s)
527 static Boolean changed[4];
530 case 0: // restore true setting
531 if(changed[3]) ClearPieces();
532 CreateAnyPieces(changed[3]); // recomputes textures and (optionally) pieces
533 for(n=0; n<4; n++) changed[n] = FALSE;
537 CreatePNGBoard(s, n-1);
544 DrawPosition(TRUE, NULL);
547 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
550 Color (char *col, int n)
553 sscanf(col, "#%x", &c);
559 SetPen (cairo_t *cr, float w, char *col, int dash)
561 static const double dotted[] = {4.0, 4.0};
562 static int len = sizeof(dotted) / sizeof(dotted[0]);
563 cairo_set_line_width (cr, w);
564 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
565 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
568 void DrawSeekAxis( int x, int y, int xTo, int yTo )
573 cr = cairo_create (CsBoardWindow(currBoard));
575 cairo_move_to (cr, x, y);
576 cairo_line_to(cr, xTo, yTo );
578 SetPen(cr, 2, "#000000", 0);
583 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
586 void DrawSeekBackground( int left, int top, int right, int bottom )
588 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
590 cairo_rectangle (cr, left, top, right-left, bottom-top);
592 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
597 GraphExpose(currBoard, left, top, right-left, bottom-top);
600 void DrawSeekText(char *buf, int x, int y)
602 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
604 cairo_select_font_face (cr, "Sans",
605 CAIRO_FONT_SLANT_NORMAL,
606 CAIRO_FONT_WEIGHT_NORMAL);
608 cairo_set_font_size (cr, 12.0);
610 cairo_move_to (cr, x, y+4);
611 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
612 cairo_show_text( cr, buf);
616 GraphExpose(currBoard, x-5, y-10, 60, 15);
619 void DrawSeekDot(int x, int y, int colorNr)
621 cairo_t *cr = cairo_create (CsBoardWindow(currBoard));
622 int square = colorNr & 0x80;
626 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
628 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
630 SetPen(cr, 2, "#000000", 0);
631 cairo_stroke_preserve(cr);
633 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
634 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
635 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
641 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
645 InitDrawingHandle (Option *opt)
647 // CsBoardWindow = DRAWABLE(opt);
656 if (lineGap == 0) return;
658 /* [HR] Split this into 2 loops for non-square boards. */
660 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
661 gridSegments[i].x1 = 0;
663 lineGap + BOARD_WIDTH * (squareSize + lineGap);
664 gridSegments[i].y1 = gridSegments[i].y2
665 = lineGap / 2 + (i * (squareSize + lineGap));
668 for (j = 0; j < BOARD_WIDTH + 1; j++) {
669 gridSegments[j + i].y1 = 0;
670 gridSegments[j + i].y2 =
671 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
672 gridSegments[j + i].x1 = gridSegments[j + i].x2
673 = lineGap / 2 + (j * (squareSize + lineGap));
680 /* draws a grid starting around Nx, Ny squares starting at x,y */
682 float odd = (lineGap & 1)/2.;
686 cr = cairo_create (CsBoardWindow(currBoard));
688 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
689 SetPen(cr, lineGap, "#000000", 0);
692 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
694 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
695 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
696 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
707 DrawBorder (int x, int y, int type, int odd)
713 case 0: col = "#000000"; break;
714 case 1: col = appData.highlightSquareColor; break;
715 case 2: col = appData.premoveHighlightColor; break;
716 default: col = "#808080"; break; // cannot happen
718 cr = cairo_create(CsBoardWindow(currBoard));
719 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
720 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
721 SetPen(cr, lineGap, col, 0);
724 // GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
728 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
730 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
731 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
733 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
734 if(modV[kind] > 0) nx %= modH[kind], ny %= modV[kind]; // tile fixed-format board periodically to extend it
735 if(textureW[kind] < W*squareSize)
736 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
738 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
739 if(textureH[kind] < H*squareSize)
740 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
742 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
747 DrawLogo (Option *opt, void *logo)
749 cairo_surface_t *img;
754 cr = cairo_create(CsBoardWindow(opt));
755 cairo_rectangle (cr, 0, 0, opt->max, opt->value);
756 cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0);
757 cairo_fill(cr); // paint background in case logo does not exist
759 img = cairo_image_surface_create_from_png (logo);
760 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
761 w = cairo_image_surface_get_width (img);
762 h = cairo_image_surface_get_height (img);
763 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
764 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
765 cairo_set_source_surface (cr, img, 0, 0);
768 cairo_surface_destroy (img);
771 GraphExpose(opt, 0, 0, opt->max, opt->value);
775 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
776 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
777 int x0, y0, texture = (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color);
780 cr = cairo_create (dest);
782 if(!texture || transparency[color]) // draw color also (as background) when texture could be transparent
783 { // evenly colored squares
786 case 0: col = appData.darkSquareColor; break;
787 case 1: col = appData.lightSquareColor; break;
788 case 2: col = "#000000"; break;
789 default: col = "#808080"; break; // cannot happen
791 SetPen(cr, 2.0, col, 0);
792 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
793 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
797 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
798 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
799 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
806 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
811 if ((int)piece < (int) BlackPawn) {
817 if(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra;
818 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
819 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
820 cr = cairo_create (dest);
821 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
826 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
829 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
833 cr = cairo_create(cs);
834 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
835 if(appData.monoMode) {
836 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
837 cairo_stroke_preserve(cr);
838 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
840 SetPen(cr, 2, markerColor[marker-1], 0);
848 DrawDot (int marker, int x, int y, int r)
849 { // used for atomic captures; no need to draw on backup
850 DoDrawDot(CsBoardWindow(currBoard), marker, x, y, r);
851 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
855 DrawUnicode (cairo_surface_t *canvas, char *string, int x, int y, char id, int flip, int size, int vpos)
857 // cairo_text_extents_t te;
861 PangoFontDescription *desc;
863 char fontName[MSG_SIZ];
865 cr = cairo_create (canvas);
866 layout = pango_cairo_create_layout(cr);
867 pango_layout_set_text(layout, string, -1);
868 snprintf(fontName, MSG_SIZ, "Sans Normal %dpx", size*squareSize/64);
869 desc = pango_font_description_from_string(fontName);
870 pango_layout_set_font_description(layout, desc);
871 pango_font_description_free(desc);
872 pango_layout_get_pixel_extents(layout, NULL, &r);
873 cairo_translate(cr, x + squareSize/2 - s*r.width/2, y + (32+vpos*s)*squareSize/64 - s*r.height/2);
874 if(s < 0) cairo_rotate(cr, G_PI);
875 cairo_set_source_rgb(cr, (id == '+' ? 1.0 : 0.0), 0.0, 0.0);
876 pango_cairo_update_layout(cr, layout);
877 pango_cairo_show_layout(cr, layout);
878 g_object_unref(layout);
883 DrawText (char *string, int x, int y, int align)
886 cairo_text_extents_t te;
889 cr = cairo_create (CsBoardWindow(currBoard));
890 cairo_select_font_face (cr, "Sans",
891 CAIRO_FONT_SLANT_NORMAL,
892 CAIRO_FONT_WEIGHT_BOLD);
894 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
895 // calculate where it goes
896 cairo_text_extents (cr, string, &te);
899 xx += squareSize - te.width - te.x_bearing - 1;
900 yy += squareSize - te.height - te.y_bearing - 1;
901 } else if (align == 2) {
902 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
903 } else if (align == 3) {
904 xx += squareSize - te.width -te.x_bearing - 1;
905 yy += -te.y_bearing + 3;
906 } else if (align == 4) {
907 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
910 cairo_move_to (cr, xx-1, yy);
911 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
912 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
913 cairo_show_text (cr, string);
918 InscribeKanji (cairo_surface_t *canvas, ChessSquare piece, int x, int y)
920 char *p, *q, buf[20], nr = 1;
921 int i, n, size = 40, flip = appData.upsideDown && flipView == (piece < BlackPawn);
922 if(piece == EmptySquare) return;
923 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
924 p = appData.inscriptions;
925 if(*p > '0' && *p < '3') nr = *p++ - '0'; // nr of kanji per piece
927 while(piece > WhitePawn) {
928 if(*p == '/') p++, piece = n - WhitePBishop; // secondary series
929 if(*p++ == NULLCHAR) {
930 if(n != WhiteKing) return;
935 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
936 if(*q != '.' && ++i < nr) continue; // yet more kanji for the current piece
940 for(q=buf; (*++q & 0xC0) == 0x80;); // skip first unicode
943 while((*++p & 0xC0) == 0x80) {} // skip second unicode
944 *p = NULLCHAR; size = 30; i = 16;
945 DrawUnicode(canvas, q, x, y, PieceToChar(n), flip, size, -10);
948 DrawUnicode(canvas, buf, x, y, PieceToChar(n), flip, size, i);
952 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
953 { // basic front-end board-draw function: takes care of everything that can be in square:
954 // piece, background, coordinate/count, marker dot
956 if (piece == EmptySquare) {
957 BlankSquare(CsBoardWindow(currBoard), x, y, square_color, piece, 1);
959 pngDrawPiece(CsBoardWindow(currBoard), piece, square_color, x, y);
960 if(appData.inscriptions[0]) InscribeKanji(CsBoardWindow(currBoard), piece, x, y);
963 if(align) { // square carries inscription (coord or piece count)
964 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
965 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
968 if(marker) { // print fat marker dot, if requested
969 DoDrawDot(CsBoardWindow(currBoard), marker, x + squareSize/4, y+squareSize/4, squareSize/2);
973 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
975 /* Masks for XPM pieces. Black and white pieces can have
976 different shapes, but in the interest of retaining my
977 sanity pieces must have the same outline on both light
978 and dark squares, and all pieces must use the same
979 background square colors/images. */
981 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
984 InitAnimState (AnimNr anr)
986 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
987 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
988 c_animBufs[anr+4] = CsBoardWindow(currBoard);
989 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
990 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
997 InitAnimState(Player);
1001 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
1003 static cairo_t *pieceSource;
1004 pieceSource = cairo_create (dest);
1005 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
1006 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
1007 else cairo_paint(pieceSource);
1008 cairo_destroy (pieceSource);
1009 if(appData.inscriptions[0]) InscribeKanji(dest, piece, 0, 0);
1013 InsertPiece (AnimNr anr, ChessSquare piece)
1015 CairoOverlayPiece(piece, c_animBufs[anr]);
1019 DrawBlank (AnimNr anr, int x, int y, int startColor)
1021 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
1024 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
1025 int srcX, int srcY, int width, int height, int destX, int destY)
1028 c_animBufs[anr+4] = CsBoardWindow(currBoard);
1029 cr = cairo_create (c_animBufs[anr+destBuf]);
1030 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
1031 cairo_rectangle (cr, destX, destY, width, height);
1034 if(c_animBufs[anr+destBuf] == CsBoardWindow(currBoard)) // suspect that GTK needs this!
1035 GraphExpose(currBoard, destX, destY, width, height);
1039 SetDragPiece (AnimNr anr, ChessSquare piece)
1043 /* [AS] Arrow highlighting support */
1046 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
1050 cr = cairo_create (cs);
1051 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
1052 for (i=0;i<nr;i++) {
1053 cairo_line_to(cr, arrow[i].x, arrow[i].y);
1055 if(appData.monoMode) { // should we always outline arrow?
1056 cairo_line_to(cr, arrow[0].x, arrow[0].y);
1057 SetPen(cr, 2, "#000000", 0);
1058 cairo_stroke_preserve(cr);
1060 SetPen(cr, 2, appData.highlightSquareColor, 0);
1068 DrawPolygon (Pnt arrow[], int nr)
1070 DoDrawPolygon(CsBoardWindow(currBoard), arrow, nr);
1071 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
1074 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
1077 ChoosePen(cairo_t *cr, int i)
1081 SetPen(cr, 1.0, "#000000", 0);
1084 SetPen(cr, 1.0, "#A0A0A0", 1);
1086 case PEN_BLUEDOTTED:
1087 SetPen(cr, 1.0, "#0000FF", 1);
1090 SetPen(cr, 3.0, crWhite, 0);
1093 SetPen(cr, 3.0, crBlack, 0);
1096 SetPen(cr, 3.0, "#E0E0F0", 0);
1101 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
1103 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
1105 static int curX, curY;
1107 if(penType != PEN_NONE) {
1108 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1109 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1110 cairo_move_to (cr, curX, curY);
1111 cairo_line_to (cr, x,y);
1112 ChoosePen(cr, penType);
1117 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
1121 // front-end wrapper for drawing functions to do rectangles
1123 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
1127 cr = cairo_create (CsBoardWindow(disp));
1128 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1129 cairo_rectangle (cr, left, top, right-left, bottom-top);
1132 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
1133 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
1134 case 2: ChoosePen(cr, PEN_BACKGD); break;
1140 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
1141 ChoosePen(cr, PEN_BLACK);
1148 // front-end wrapper for putting text in graph
1150 DrawEvalText (char *buf, int cbBuf, int y)
1152 // the magic constants 8 and 5 should really be derived from the font size somehow
1153 cairo_text_extents_t extents;
1154 cairo_t *cr = cairo_create(CsBoardWindow(disp));
1156 /* GTK-TODO this has to go into the font-selection */
1157 cairo_select_font_face (cr, "Sans",
1158 CAIRO_FONT_SLANT_NORMAL,
1159 CAIRO_FONT_WEIGHT_NORMAL);
1160 cairo_set_font_size (cr, 12.0);
1163 cairo_text_extents (cr, buf, &extents);
1165 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1166 cairo_text_path (cr, buf);
1167 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1168 cairo_fill_preserve (cr);
1169 cairo_set_source_rgb (cr, 0, 1.0, 0);
1170 cairo_set_line_width (cr, 0.1);