2 * draw.c -- drawing routines for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
15 * Permission to use, copy, modify, and distribute this software and its
16 * documentation for any purpose and without fee is hereby granted,
17 * provided that the above copyright notice appear in all copies and that
18 * both that copyright notice and this permission notice appear in
19 * supporting documentation, and that the name of Digital not be
20 * used in advertising or publicity pertaining to distribution of the
21 * software without specific, written prior permission.
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
36 * GNU XBoard is free software: you can redistribute it and/or modify
37 * it under the terms of the GNU General Public License as published by
38 * the Free Software Foundation, either version 3 of the License, or (at
39 * your option) any later version.
41 * GNU XBoard is distributed in the hope that it will be useful, but
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 * General Public License for more details.
46 * You should have received a copy of the GNU General Public License
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
68 # else /* not HAVE_STRING_H */
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
83 #include "evalgraph.h"
92 #define usleep(t) _sleep2(((t)+500)/1000)
96 # define _(s) gettext (s)
97 # define N_(s) gettext_noop (s)
105 Boolean cairoAnimate;
107 cairo_surface_t *csBoardWindow;
108 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4]; // png 256 x 256 images
109 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn]; // scaled pieces as used
110 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
111 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
112 static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2];
113 int useTexture, textureW[2], textureH[2];
115 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
116 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
118 #define White(piece) ((int)(piece) < (int)BlackPawn)
120 char svgDir[MSG_SIZ] = SVGDIR;
122 char *crWhite = "#FFFFB0";
123 char *crBlack = "#AD5D3D";
127 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
130 SwitchWindow (int main)
132 currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
133 csBoardWindow = DRAWABLE(currBoard);
137 SelectPieces(VariantClass v)
142 for(p=0; p<=(int)WhiteKing; p++)
143 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
144 if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi
145 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
146 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
147 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+3];
148 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+4];
149 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
150 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
153 if(v == VariantGothic) {
154 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
157 if(v == VariantSChess) {
158 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
159 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
161 if(v == VariantChuChess) {
162 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
164 if(v == VariantChu) {
165 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteClaw];
166 pngPieceBitmaps[i][(int)WhiteClaw] = pngPieceBitmaps2[i][(int)WhiteNightrider];
167 pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteHorned];
168 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteStag];
169 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteEagle];
170 pngPieceBitmaps[i][(int)WhiteHorned] = pngPieceBitmaps2[i][(int)WhiteUnicorn];
171 pngPieceBitmaps[i][(int)WhiteStag] = pngPieceBitmaps2[i][(int)WhiteSilver];
172 pngPieceBitmaps[i][(int)WhiteEagle] = pngPieceBitmaps2[i][(int)WhiteFalcon];
177 #define BoardSize int
179 InitDrawingSizes (BoardSize boardSize, int flags)
180 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
181 int boardWidth, boardHeight;
182 static int oldWidth, oldHeight;
183 static VariantClass oldVariant;
184 static int oldTwoBoards = 0, oldNrOfFiles = 0;
186 if(!mainOptions[W_BOARD].handle) return;
188 if(boardSize == -2 && gameInfo.variant != oldVariant
189 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
190 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH; // keep total width fixed
191 if(appData.overrideLineGap < 0) lineGap = squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4;
192 squareSize -= lineGap;
196 oldNrOfFiles = BOARD_WIDTH;
198 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
199 oldTwoBoards = twoBoards;
201 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
202 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
203 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
205 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
207 oldWidth = boardWidth; oldHeight = boardHeight;
209 CreateAnyPieces(0); // redo texture scaling
212 * Inhibit shell resizing.
214 ResizeBoardWindow(boardWidth, boardHeight, 0);
219 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
222 if(gameInfo.variant != oldVariant) { // and only if variant changed
224 SelectPieces(gameInfo.variant);
226 oldVariant = gameInfo.variant;
232 ExposeRedraw (Option *graph, int x, int y, int w, int h)
233 { // copy a selected part of the buffer bitmap to the display
234 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
235 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
236 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
237 cairo_rectangle(cr, x, y, w, h);
243 CreatePNGBoard (char *s, int kind)
246 static float n[2] = { 1., 1. };
247 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
248 textureW[kind] = 0; // prevents bitmap from being used if not succesfully loaded
249 if(strstr(s, ".png")) {
250 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
254 if(pngOriginalBoardBitmap[kind]) cairo_surface_destroy(pngOriginalBoardBitmap[kind]);
255 if(n[kind] != 1.) cairo_surface_destroy(pngBoardBitmap[kind]);
256 useTexture |= kind + 1; pngOriginalBoardBitmap[kind] = img;
257 w = textureW[kind] = cairo_image_surface_get_width (img);
258 h = textureH[kind] = cairo_image_surface_get_height (img);
260 while((q = strchr(p+1, '-'))) p = q; // find last '-'
261 if(strlen(p) < 11 && sscanf(p, "-%dx%d.pn%c", &f, &r, &c) == 3 && c == 'g') {
262 if(f == 0 || r == 0) f = BOARD_WIDTH, r = BOARD_HEIGHT; // 0x0 means 'fits any', so make it fit
263 textureW[kind] = (w*BOARD_WIDTH)/f; // sync cutting locations with square pattern
264 textureH[kind] = (h*BOARD_HEIGHT)/r;
265 n[kind] = r*squareSize/h; // scale to make it fit exactly vertically
267 if((p = strstr(s, "xq")) && (p == s || p[-1] == '/')) { // assume full-board image for Xiangqi
268 while(0.8*squareSize*BOARD_WIDTH > n[kind]*w || 0.8*squareSize*BOARD_HEIGHT > n[kind]*h) n[kind]++;
270 while(squareSize > n[kind]*w || squareSize > n[kind]*h) n[kind]++;
272 if(n[kind] == 1.) pngBoardBitmap[kind] = img; else {
273 // create scaled-up copy of the raw png image when it was too small
274 cairo_surface_t *cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, n[kind]*w, n[kind]*h);
275 cairo_t *cr = cairo_create(cs);
276 pngBoardBitmap[kind] = cs; textureW[kind] *= n[kind]; textureH[kind] *= n[kind];
277 // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
278 cairo_scale(cr, n[kind], n[kind]);
279 cairo_set_source_surface (cr, img, 0, 0);
287 char *pngPieceNames[] = // must be in same order as internal piece encoding
288 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
289 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Crown", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
290 "GoldPawn", "Claw", "PromoHorse", "PromoDragon", "GoldLance", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
291 "Dolphin", "Sword", "Leopard", "HSword", "GoldSilver", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
292 "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
295 char *backupPiece[] = { "Princess", NULL, NULL, NULL, NULL, NULL, NULL,
296 NULL, NULL, NULL, NULL, NULL, NULL, "King", "Queen", "Lion" }; // pieces that map on other when not kanji
299 LoadSVG (char *dir, int color, int piece, int retry)
302 RsvgHandle *svg=svgPieces[color][piece];
303 RsvgDimensionData svg_dimensions;
304 GError *svgerror=NULL;
305 cairo_surface_t *img;
307 char *name = (retry ? backupPiece[piece - WhiteGrasshopper] : pngPieceNames[piece]);
309 if(!name) return NULL;
311 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", name);
313 if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
315 rsvg_handle_get_dimensions(svg, &svg_dimensions);
316 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
318 cr = cairo_create(img);
319 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
320 rsvg_handle_render_cairo(svg, cr);
321 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
322 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
323 pngPieceImages[color][piece] = img;
329 if(!retry && piece >= WhiteGrasshopper && piece <= WhiteNothing) // pieces that are only different in kanji sets
330 return LoadSVG(dir, color, piece, 1);
332 g_error_free(svgerror);
337 ScaleOnePiece (int color, int piece)
341 cairo_surface_t *img, *cs;
346 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
348 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
349 if(*appData.pieceDirectory) { // user specified piece directory
350 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
351 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
352 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
353 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
354 } else pngPieceImages[color][piece] = img;
358 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
359 static int warned = 0;
360 if(!(svgPieces[color][piece] = LoadSVG(svgDir, color, piece, 0)) && !warned) { // try to fall back on installed svg
361 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
362 printf("%s\n", msg); // give up
363 DisplayError(msg, 0);
364 warned = 1; // prevent error message being repeated for each piece type
368 img = pngPieceImages[color][piece];
370 // create new bitmap to hold scaled piece image (and remove any old)
371 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
372 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
376 // scaled copying of the raw png image
377 cr = cairo_create(cs);
378 w = cairo_image_surface_get_width (img);
379 h = cairo_image_surface_get_height (img);
380 cairo_scale(cr, squareSize/w, squareSize/h);
381 cairo_set_source_surface (cr, img, 0, 0);
385 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
386 int stride = cairo_image_surface_get_stride(cs)/4;
387 int *buf = (int *) cairo_image_surface_get_data(cs);
389 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
390 cairo_surface_flush(cs);
391 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
394 unsigned int c = buf[i*stride + j];
395 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
396 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
397 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
398 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
399 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
400 if(appData.monoMode) {
401 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
402 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
403 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
406 cairo_surface_mark_dirty(cs);
415 for(p=0; pngPieceNames[p]; p++) {
419 SelectPieces(gameInfo.variant);
423 CreateAnyPieces (int p)
424 { // [HGM] taken out of main
425 if(p) CreatePNGPieces();
426 CreatePNGBoard(appData.liteBackTextureFile, 1);
427 CreatePNGBoard(appData.darkBackTextureFile, 0);
431 InitDrawingParams (int reloadPieces)
435 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
436 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
437 pngPieceImages[i][p] = NULL;
438 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
439 svgPieces[i][p] = NULL;
444 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
447 Color (char *col, int n)
450 sscanf(col, "#%x", &c);
456 SetPen (cairo_t *cr, float w, char *col, int dash)
458 static const double dotted[] = {4.0, 4.0};
459 static int len = sizeof(dotted) / sizeof(dotted[0]);
460 cairo_set_line_width (cr, w);
461 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
462 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
465 void DrawSeekAxis( int x, int y, int xTo, int yTo )
470 cr = cairo_create (csBoardWindow);
472 cairo_move_to (cr, x, y);
473 cairo_line_to(cr, xTo, yTo );
475 SetPen(cr, 2, "#000000", 0);
480 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
483 void DrawSeekBackground( int left, int top, int right, int bottom )
485 cairo_t *cr = cairo_create (csBoardWindow);
487 cairo_rectangle (cr, left, top, right-left, bottom-top);
489 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
494 GraphExpose(currBoard, left, top, right-left, bottom-top);
497 void DrawSeekText(char *buf, int x, int y)
499 cairo_t *cr = cairo_create (csBoardWindow);
501 cairo_select_font_face (cr, "Sans",
502 CAIRO_FONT_SLANT_NORMAL,
503 CAIRO_FONT_WEIGHT_NORMAL);
505 cairo_set_font_size (cr, 12.0);
507 cairo_move_to (cr, x, y+4);
508 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
509 cairo_show_text( cr, buf);
513 GraphExpose(currBoard, x-5, y-10, 60, 15);
516 void DrawSeekDot(int x, int y, int colorNr)
518 cairo_t *cr = cairo_create (csBoardWindow);
519 int square = colorNr & 0x80;
523 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
525 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
527 SetPen(cr, 2, "#000000", 0);
528 cairo_stroke_preserve(cr);
530 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
531 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
532 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
538 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
542 InitDrawingHandle (Option *opt)
544 csBoardWindow = DRAWABLE(opt);
552 if (lineGap == 0) return;
554 /* [HR] Split this into 2 loops for non-square boards. */
556 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
557 gridSegments[i].x1 = 0;
559 lineGap + BOARD_WIDTH * (squareSize + lineGap);
560 gridSegments[i].y1 = gridSegments[i].y2
561 = lineGap / 2 + (i * (squareSize + lineGap));
564 for (j = 0; j < BOARD_WIDTH + 1; j++) {
565 gridSegments[j + i].y1 = 0;
566 gridSegments[j + i].y2 =
567 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
568 gridSegments[j + i].x1 = gridSegments[j + i].x2
569 = lineGap / 2 + (j * (squareSize + lineGap));
576 /* draws a grid starting around Nx, Ny squares starting at x,y */
578 float odd = (lineGap & 1)/2.;
582 cr = cairo_create (csBoardWindow);
584 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
585 SetPen(cr, lineGap, "#000000", 0);
588 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
590 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
591 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
592 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
603 DrawBorder (int x, int y, int type, int odd)
609 case 0: col = "#000000"; break;
610 case 1: col = appData.highlightSquareColor; break;
611 case 2: col = appData.premoveHighlightColor; break;
612 default: col = "#808080"; break; // cannot happen
614 cr = cairo_create(csBoardWindow);
615 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
616 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
617 SetPen(cr, lineGap, col, 0);
620 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
624 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
626 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
627 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
629 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
630 if(textureW[kind] < W*squareSize)
631 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
633 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
634 if(textureH[kind] < H*squareSize)
635 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
637 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
642 DrawLogo (Option *opt, void *logo)
644 cairo_surface_t *img;
648 if(!logo || !opt) return;
649 img = cairo_image_surface_create_from_png (logo);
650 w = cairo_image_surface_get_width (img);
651 h = cairo_image_surface_get_height (img);
652 cr = cairo_create(DRAWABLE(opt));
653 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
654 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
655 cairo_set_source_surface (cr, img, 0, 0);
658 cairo_surface_destroy (img);
659 GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
663 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
664 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
668 cr = cairo_create (dest);
670 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
671 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
672 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
673 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
676 } else { // evenly colored squares
679 case 0: col = appData.darkSquareColor; break;
680 case 1: col = appData.lightSquareColor; break;
681 case 2: col = "#000000"; break;
682 default: col = "#808080"; break; // cannot happen
684 SetPen(cr, 2.0, col, 0);
685 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
686 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
693 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
698 if ((int)piece < (int) BlackPawn) {
704 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
705 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
706 cr = cairo_create (dest);
707 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
712 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
715 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
719 cr = cairo_create(cs);
720 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
721 if(appData.monoMode) {
722 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
723 cairo_stroke_preserve(cr);
724 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
726 SetPen(cr, 2, markerColor[marker-1], 0);
734 DrawDot (int marker, int x, int y, int r)
735 { // used for atomic captures; no need to draw on backup
736 DoDrawDot(csBoardWindow, marker, x, y, r);
737 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
741 DrawText (char *string, int x, int y, int align)
744 cairo_text_extents_t te;
747 cr = cairo_create (csBoardWindow);
748 cairo_select_font_face (cr, "Sans",
749 CAIRO_FONT_SLANT_NORMAL,
750 CAIRO_FONT_WEIGHT_BOLD);
752 cairo_set_font_size (cr, align < 0 ? 2*squareSize/3 : squareSize/4);
753 // calculate where it goes
754 cairo_text_extents (cr, string, &te);
757 xx += squareSize - te.width - te.x_bearing - 1;
758 yy += squareSize - te.height - te.y_bearing - 1;
759 } else if (align == 2) {
760 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
761 } else if (align == 3) {
762 xx += squareSize - te.width -te.x_bearing - 1;
763 yy += -te.y_bearing + 3;
764 } else if (align == 4) {
765 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
766 } else if (align < 0) {
767 xx += squareSize/2 - te.width/2, yy += 9*squareSize/16 - te.y_bearing/2;
770 cairo_move_to (cr, xx-1, yy);
771 if(align == -2) cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); else
772 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
773 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
774 cairo_show_text (cr, string);
779 InscribeKanji (ChessSquare piece, int x, int y)
781 char *p, *q, buf[10];
783 if(piece == EmptySquare) return;
784 if(piece >= BlackPawn) piece = BLACK_TO_WHITE piece;
785 p = appData.inscriptions;
787 while(piece > WhitePawn) {
788 if(*p++ == NULLCHAR) {
789 if(n != WhiteKing) return;
794 while((*p & 0xC0) == 0x80) p++; // skip UTF-8 continuation bytes
798 for(q=buf; (*++q & 0xC0) == 0x80;);
800 DrawText(buf, x, y, n > WhiteLion ? -2 : -1);
804 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
805 { // basic front-end board-draw function: takes care of everything that can be in square:
806 // piece, background, coordinate/count, marker dot
808 if (piece == EmptySquare) {
809 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
811 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
812 if(appData.inscriptions[0]) InscribeKanji(piece, x, y);
815 if(align) { // square carries inscription (coord or piece count)
816 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
817 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
820 if(marker) { // print fat marker dot, if requested
821 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
825 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
827 /* Masks for XPM pieces. Black and white pieces can have
828 different shapes, but in the interest of retaining my
829 sanity pieces must have the same outline on both light
830 and dark squares, and all pieces must use the same
831 background square colors/images. */
833 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
836 InitAnimState (AnimNr anr)
838 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
839 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
840 c_animBufs[anr+4] = csBoardWindow;
841 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
842 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
849 InitAnimState(Player);
853 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
855 static cairo_t *pieceSource;
856 pieceSource = cairo_create (dest);
857 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
858 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
859 else cairo_paint(pieceSource);
860 cairo_destroy (pieceSource);
864 InsertPiece (AnimNr anr, ChessSquare piece)
866 CairoOverlayPiece(piece, c_animBufs[anr]);
870 DrawBlank (AnimNr anr, int x, int y, int startColor)
872 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
875 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
876 int srcX, int srcY, int width, int height, int destX, int destY)
879 c_animBufs[anr+4] = csBoardWindow;
880 cr = cairo_create (c_animBufs[anr+destBuf]);
881 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
882 cairo_rectangle (cr, destX, destY, width, height);
885 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
886 GraphExpose(currBoard, destX, destY, width, height);
890 SetDragPiece (AnimNr anr, ChessSquare piece)
894 /* [AS] Arrow highlighting support */
897 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
901 cr = cairo_create (cs);
902 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
904 cairo_line_to(cr, arrow[i].x, arrow[i].y);
906 if(appData.monoMode) { // should we always outline arrow?
907 cairo_line_to(cr, arrow[0].x, arrow[0].y);
908 SetPen(cr, 2, "#000000", 0);
909 cairo_stroke_preserve(cr);
911 SetPen(cr, 2, appData.highlightSquareColor, 0);
919 DrawPolygon (Pnt arrow[], int nr)
921 DoDrawPolygon(csBoardWindow, arrow, nr);
922 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
925 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
928 ChoosePen(cairo_t *cr, int i)
932 SetPen(cr, 1.0, "#000000", 0);
935 SetPen(cr, 1.0, "#A0A0A0", 1);
938 SetPen(cr, 1.0, "#0000FF", 1);
941 SetPen(cr, 3.0, crWhite, 0);
944 SetPen(cr, 3.0, crBlack, 0);
947 SetPen(cr, 3.0, "#E0E0F0", 0);
952 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
954 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
956 static int curX, curY;
958 if(penType != PEN_NONE) {
959 cairo_t *cr = cairo_create(DRAWABLE(disp));
960 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
961 cairo_move_to (cr, curX, curY);
962 cairo_line_to (cr, x,y);
963 ChoosePen(cr, penType);
968 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
972 // front-end wrapper for drawing functions to do rectangles
974 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
978 cr = cairo_create (DRAWABLE(disp));
979 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
980 cairo_rectangle (cr, left, top, right-left, bottom-top);
983 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
984 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
985 case 2: ChoosePen(cr, PEN_BACKGD); break;
991 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
992 ChoosePen(cr, PEN_BLACK);
999 // front-end wrapper for putting text in graph
1001 DrawEvalText (char *buf, int cbBuf, int y)
1003 // the magic constants 8 and 5 should really be derived from the font size somehow
1004 cairo_text_extents_t extents;
1005 cairo_t *cr = cairo_create(DRAWABLE(disp));
1007 /* GTK-TODO this has to go into the font-selection */
1008 cairo_select_font_face (cr, "Sans",
1009 CAIRO_FONT_SLANT_NORMAL,
1010 CAIRO_FONT_WEIGHT_NORMAL);
1011 cairo_set_font_size (cr, 12.0);
1014 cairo_text_extents (cr, buf, &extents);
1016 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
1017 cairo_text_path (cr, buf);
1018 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
1019 cairo_fill_preserve (cr);
1020 cairo_set_source_rgb (cr, 0, 1.0, 0);
1021 cairo_set_line_width (cr, 0.1);