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 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>
62 #else /* not STDC_HEADERS */
63 extern char *getenv();
66 # else /* not HAVE_STRING_H */
68 # endif /* not HAVE_STRING_H */
69 #endif /* not STDC_HEADERS */
76 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
80 #include "pixmaps/pixmaps.h"
82 #include "bitmaps/bitmaps.h"
87 #include "xevalgraph.h"
99 #define usleep(t) _sleep2(((t)+500)/1000)
103 # define _(s) gettext (s)
104 # define N_(s) gettext_noop (s)
112 Boolean cairoAnimate;
113 static cairo_surface_t *csBoardWindow, *csBoardBackup, *csDualBoard;
114 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn]; // scaled pieces as used
115 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
116 static cairo_surface_t *pngBoardBitmap[2];
117 int useTexture, textureW[2], textureH[2];
119 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
120 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
122 #define White(piece) ((int)(piece) < (int)BlackPawn)
126 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
133 cairo_surface_t *cstmp = csBoardWindow;
134 csBoardWindow = csDualBoard;
137 csBoardWindow = GetOutputSurface(&dualOptions[3], 0, 0);
146 // delete surfaces after size becomes invalid, so they will be recreated
147 if(csBoardWindow) cairo_surface_destroy(csBoardWindow);
148 if(csBoardBackup) cairo_surface_destroy(csBoardBackup);
149 if(csDualBoard) cairo_surface_destroy(csDualBoard);
150 csBoardWindow = csBoardBackup = csDualBoard = NULL;
153 #define BoardSize int
155 InitDrawingSizes (BoardSize boardSize, int flags)
156 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
157 int boardWidth, boardHeight;
159 static int oldWidth, oldHeight;
160 static VariantClass oldVariant;
161 static int oldMono = -1, oldTwoBoards = 0;
162 extern Widget formWidget;
164 if(!formWidget) return;
166 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
167 oldTwoBoards = twoBoards;
169 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
170 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
171 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
173 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
175 oldWidth = boardWidth; oldHeight = boardHeight;
180 * Inhibit shell resizing.
182 ResizeBoardWindow(boardWidth, boardHeight, 0);
187 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
190 if(gameInfo.variant != oldVariant) { // and only if variant changed
194 for(p=0; p<=(int)WhiteKing; p++)
195 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
196 if(gameInfo.variant == VariantShogi) {
197 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteKing+1];
198 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
199 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
200 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
201 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
204 if(gameInfo.variant == VariantGothic) {
205 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
208 if(gameInfo.variant == VariantSChess) {
209 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
210 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
213 oldMono = -10; // kludge to force recreation of animation masks
214 oldVariant = gameInfo.variant;
217 oldMono = appData.monoMode;
221 CreatePNGBoard (char *s, int kind)
223 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
224 if(strstr(s, ".png")) {
225 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
227 useTexture |= kind + 1; pngBoardBitmap[kind] = img;
228 textureW[kind] = cairo_image_surface_get_width (img);
229 textureH[kind] = cairo_image_surface_get_height (img);
234 char *pngPieceNames[] = // must be in same order as internal piece encoding
235 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
236 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "King",
237 "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
241 ScaleOnePiece (char *name, int color, int piece)
245 cairo_surface_t *img, *cs;
247 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4]; // png 256 x 256 images
249 if((img = pngPieceImages[color][piece]) == NULL) { // if PNG file for this piece was not yet read, read it now and store it
250 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pngDirectory, color ? "Black" : "White", pngPieceNames[piece]);
251 pngPieceImages[color][piece] = img = cairo_image_surface_create_from_png (buf);
252 w = cairo_image_surface_get_width (img);
253 h = cairo_image_surface_get_height (img);
254 if(w != 64 || h != 64) { printf("Bad png size %dx%d in %s\n", w, h, buf); exit(1); }
256 // create new bitmap to hold scaled piece image (and remove any old)
257 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
258 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
259 if(piece <= WhiteKing) pngPieceBitmaps[color][piece] = cs;
260 // scaled copying of the raw png image
261 cr = cairo_create(cs);
262 cairo_scale(cr, squareSize/64., squareSize/64.);
263 cairo_set_source_surface (cr, img, 0, 0);
273 for(p=0; pngPieceNames[p]; p++) {
274 ScaleOnePiece(pngPieceNames[p], 0, p);
275 ScaleOnePiece(pngPieceNames[p], 1, p);
281 { // [HGM] taken out of main
282 if (appData.pngDirectory[0] != NULLCHAR) {
285 CreatePNGBoard(appData.liteBackTextureFile, 1);
286 CreatePNGBoard(appData.darkBackTextureFile, 0);
296 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
299 Color (char *col, int n)
302 sscanf(col, "#%x", &c);
308 SetPen (cairo_t *cr, float w, char *col, int dash)
310 static const double dotted[] = {4.0, 4.0};
311 static int len = sizeof(dotted) / sizeof(dotted[0]);
312 cairo_set_line_width (cr, w);
313 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
314 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
317 void DrawSeekAxis( int x, int y, int xTo, int yTo )
322 cr = cairo_create (csBoardWindow);
324 cairo_move_to (cr, x, y);
325 cairo_line_to(cr, xTo, yTo );
327 SetPen(cr, 2, "#000000", 0);
334 void DrawSeekBackground( int left, int top, int right, int bottom )
336 cairo_t *cr = cairo_create (csBoardWindow);
338 cairo_rectangle (cr, left, top, right-left, bottom-top);
340 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
347 void DrawSeekText(char *buf, int x, int y)
349 cairo_t *cr = cairo_create (csBoardWindow);
351 cairo_select_font_face (cr, "Sans",
352 CAIRO_FONT_SLANT_NORMAL,
353 CAIRO_FONT_WEIGHT_NORMAL);
355 cairo_set_font_size (cr, 12.0);
357 cairo_move_to (cr, x, y+4);
358 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
359 cairo_show_text( cr, buf);
365 void DrawSeekDot(int x, int y, int colorNr)
367 cairo_t *cr = cairo_create (csBoardWindow);
368 int square = colorNr & 0x80;
372 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
374 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
376 SetPen(cr, 2, "#000000", 0);
377 cairo_stroke_preserve(cr);
379 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
380 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
381 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
392 int boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
393 int boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
395 csBoardWindow = GetOutputSurface(&mainOptions[W_BOARD], 0, 0);
396 csBoardBackup = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, boardWidth, boardHeight);
410 if (lineGap == 0) return;
412 /* [HR] Split this into 2 loops for non-square boards. */
414 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
415 gridSegments[i].x1 = 0;
417 lineGap + BOARD_WIDTH * (squareSize + lineGap);
418 gridSegments[i].y1 = gridSegments[i].y2
419 = lineGap / 2 + (i * (squareSize + lineGap));
422 for (j = 0; j < BOARD_WIDTH + 1; j++) {
423 gridSegments[j + i].y1 = 0;
424 gridSegments[j + i].y2 =
425 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
426 gridSegments[j + i].x1 = gridSegments[j + i].x2
427 = lineGap / 2 + (j * (squareSize + lineGap));
432 DoDrawGrid(cairo_surface_t *cs)
434 /* draws a grid starting around Nx, Ny squares starting at x,y */
440 cr = cairo_create (cs);
442 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
443 SetPen(cr, lineGap, "#000000", 0);
446 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
448 cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
449 cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
462 DoDrawGrid(csBoardWindow);
463 if(!dual) DoDrawGrid(csBoardBackup);
467 DoDrawBorder (cairo_surface_t *cs, int x, int y, int type)
474 case 0: col = "#000000"; break;
475 case 1: col = appData.highlightSquareColor; break;
476 case 2: col = appData.premoveHighlightColor; break;
478 cr = cairo_create(cs);
479 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
480 cairo_rectangle(cr, x, y, squareSize+lineGap, squareSize+lineGap);
481 SetPen(cr, lineGap, col, 0);
486 DrawBorder (int x, int y, int type)
488 DoDrawBorder(csBoardWindow, x, y, type);
489 if(!dual) DoDrawBorder(csBoardBackup, x, y, type);
493 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
495 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
496 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
498 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
499 if(textureW[kind] < W*squareSize)
500 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
502 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
503 if(textureH[kind] < H*squareSize)
504 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
506 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
511 DrawLogo (void *handle, void *logo)
513 cairo_surface_t *img, *cs;
517 if(!logo || !handle) return;
518 cs = GetOutputSurface(handle, appData.logoSize, appData.logoSize/2);
519 img = cairo_image_surface_create_from_png (logo);
520 w = cairo_image_surface_get_width (img);
521 h = cairo_image_surface_get_height (img);
522 cr = cairo_create(cs);
523 cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
524 cairo_set_source_surface (cr, img, 0, 0);
527 cairo_surface_destroy (img);
528 cairo_surface_destroy (cs);
532 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
533 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
537 cr = cairo_create (dest);
539 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
540 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
541 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
542 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
545 } else { // evenly colored squares
548 case 0: col = appData.darkSquareColor; break;
549 case 1: col = appData.lightSquareColor; break;
550 case 2: col = "#000000"; break;
552 SetPen(cr, 2.0, col, 0);
553 cairo_rectangle (cr, x, y, squareSize, squareSize);
560 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
565 if ((int)piece < (int) BlackPawn) {
571 if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
572 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
573 cr = cairo_create (dest);
574 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
580 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
584 cr = cairo_create(cs);
585 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
586 if(appData.monoMode) {
587 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
588 cairo_stroke_preserve(cr);
589 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
591 SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
599 DrawDot (int marker, int x, int y, int r)
600 { // used for atomic captures; no need to draw on backup
602 DoDrawDot(csBoardWindow, marker, x, y, r);
606 DoDrawOneSquare (cairo_surface_t *dest, int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
607 { // basic front-end board-draw function: takes care of everything that can be in square:
608 // piece, background, coordinate/count, marker dot
611 if (piece == EmptySquare) {
612 BlankSquare(dest, x, y, square_color, piece, 1);
614 pngDrawPiece(dest, piece, square_color, x, y);
617 if(align) { // square carries inscription (coord or piece count)
619 cairo_text_extents_t te;
621 cr = cairo_create (dest);
622 cairo_select_font_face (cr, "Sans",
623 CAIRO_FONT_SLANT_NORMAL,
624 CAIRO_FONT_WEIGHT_BOLD);
626 cairo_set_font_size (cr, squareSize/4);
627 // calculate where it goes
628 cairo_text_extents (cr, string, &te);
631 xx += squareSize - te.width - te.x_bearing - 1;
632 yy += squareSize - te.height - te.y_bearing - 1;
633 } else if (align == 2) {
634 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
635 } else if (align == 3) {
636 xx += squareSize - te.width -te.x_bearing - 1;
637 yy += -te.y_bearing + 3;
638 } else if (align == 4) {
639 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
642 cairo_move_to (cr, xx-1, yy);
643 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
644 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
645 cairo_show_text (cr, string);
649 if(marker) { // print fat marker dot, if requested
650 DoDrawDot(dest, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
655 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
658 DoDrawOneSquare (csBoardWindow, x, y, piece, square_color, marker, string, align);
660 DoDrawOneSquare (csBoardBackup, x, y, piece, square_color, marker, string, align);
663 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
665 /* Masks for XPM pieces. Black and white pieces can have
666 different shapes, but in the interest of retaining my
667 sanity pieces must have the same outline on both light
668 and dark squares, and all pieces must use the same
669 background square colors/images. */
671 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
674 InitAnimState (AnimNr anr)
676 DrawSeekOpen(); // set cs to board widget
677 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
678 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
679 c_animBufs[anr+4] = csBoardWindow;
680 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
681 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
688 InitAnimState(Player);
692 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
694 static cairo_t *pieceSource;
695 pieceSource = cairo_create (dest);
696 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
697 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
698 else cairo_paint(pieceSource);
699 cairo_destroy (pieceSource);
703 InsertPiece (AnimNr anr, ChessSquare piece)
705 CairoOverlayPiece(piece, c_animBufs[anr]);
709 DrawBlank (AnimNr anr, int x, int y, int startColor)
711 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
714 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
715 int srcX, int srcY, int width, int height, int destX, int destY)
717 cairo_t *cr;// = cairo_create (c_animBufs[anr+destBuf]);
718 cr = cairo_create (c_animBufs[anr+destBuf]);
719 if(c_animBufs[anr+srcBuf] == csBoardWindow)
720 cairo_set_source_surface (cr, csBoardBackup, destX - srcX, destY - srcY);
722 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
723 cairo_rectangle (cr, destX, destY, width, height);
726 if(c_animBufs[anr+destBuf] == csBoardWindow) {
727 cr = cairo_create (csBoardBackup); // also draw to backup
728 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
729 cairo_rectangle (cr, destX, destY, width, height);
736 SetDragPiece (AnimNr anr, ChessSquare piece)
740 /* [AS] Arrow highlighting support */
743 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
747 cr = cairo_create (cs);
748 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
750 cairo_line_to(cr, arrow[i].x, arrow[i].y);
752 if(appData.monoMode) { // should we always outline arrow?
753 cairo_line_to(cr, arrow[0].x, arrow[0].y);
754 SetPen(cr, 2, "#000000", 0);
755 cairo_stroke_preserve(cr);
757 SetPen(cr, 2, appData.highlightSquareColor, 0);
765 DrawPolygon (Pnt arrow[], int nr)
767 DoDrawPolygon(csBoardWindow, arrow, nr);
768 if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);