ChessImager is a Go package that creates images of chess boards based on a FEN string. It is highly configurable, so that you can create chess board images that look exactly the way you want them to look.
ChessImager is somewhat inspired by CJSaylor's repository chessimage.
- The Basics
- Configuration
- Image Context
- Render order
- Border renderer
- Board renderer
- Rank and file renderer
- Highlight renderer
- Piece renderer
- Annotations renderer
- Moves renderer
- Examples
Start by creating an Imager object by calling the chessImager.NewImager() function. With this object, you can render simple chess board images from a FEN string by using the Render() method on the imager object you just created. If you want to create a board where black is on bottom, you must use the RenderInverted() method.
If you want to create more advanced images with move arrows, highlighted squares and/or annotations, you'll need a ImageContext object for each image you want to create. You can create that by using the NewContext() method on the imager object and passing a fen string to it. Add all the moves, highlighted squares and annotations to the context object, and then call the RenderWithContext() method on the imager object, and provide the context object to that method. There is also a method called RenderWithContextInverted(), that will generate an image where black is on bottom.
The purpose of the context object is that you create one imager object at the beginning of your code, and then one context object for each advanced image that you want to generate. Once an advanced image is created, you can discard the context object, and create a new one.
For examples of how to use chessImager, see the Examples section, at the end of this readme.
This is the simple example from the examples section below
Let's look at a simple example of how to use chessImager.
Rendering a chess board image, based on a FEN string, is basically one line of code. Add a few more lines of code to save the image to disk, and you have this code:
// Render simple image
const fen = "b2r3r/k3Rp1p/p2q1np1/Np1P4/3p1Q2/P4PPB/1PP4P/1K6 b - - 1 25"
img, _ := chessImager.NewImager().Render(fen)
// Save image
file, _ := os.Create("/path/to/img.png")
defer fileSimple.Close()
_ = png.Encode(file, img)
This code will generate the following image, using the default styling in config/default.json:
ChessImager uses a configuration JSON file to define the size of the board and colors etc. You can
either use the embedded config/default.json or you can create your own configuration file. If you want to use your own
configuration file, you will need to use the function chessImager.NewImagerFromPath(path)
. See
examples/other/other.go for an example of how to do this.
All colors in the settings file can be specified in one of four different ways, since the hashtag and the alpha values are optional:
Pattern | Example string |
---|---|
#RRGGBBAA | #10E4F3FF |
#RRGGBB | #10E4F3 |
RRGGBBAA | 10E4F3FF |
RRGGBB | 10E4F3 |
If you don't specify the alpha component of the color, FF will be assumed.
If no font is specified in the settings file, the Go Regular TTF font will be used. This font will be used for annotations and the rank and file decorators.
The path must point to the TTF font file. Example:
"font_style": {
"path" : "/home/[username]/roboto.ttf"
}
For simple chess board images, you don't need an image context. You can just use chessImager.NewImager().Render(fen)
and be done with it.
For more advanced chess board images, you will need to create an image Context object, using the chessImager. NewContext()
function. Using this context object, you can add highlighted squares, annotations and moves.
Every new context created, resets the moves, annotations and highlighted squares lists, so it is strongly recommended
to create a new context for each new image that you want to generate. When you are ready to render the image, you
pass along the context object to the chessImager.RenderWithContext()
function.
ChessImager is split up into seven different renderers, that are each responsible for rendering different parts of the chess board. The renderers, and their indexes, are:
Index | Name | Description |
---|---|---|
0 | Border | Renders the border around the chess board |
1 | Board | Renders the actual chess board |
2 | RankAndFile | Renders the rank numbers and file letters |
3 | HighlightedSquares | Renders the highlight squares |
4 | Pieces | Renders the chess pieces |
5 | Annotations | Renders the annotations |
6 | Moves | Renders the moves |
You will not get very interesting images if you change the order of renderer 0 and 1. But all renderers can be moved around to fit your use case.
In the JSON file, you can set the order of the renderers by changing the order list. The default order is the order above.
Name | Type | Description |
---|---|---|
order | integer list | The digits 0 through 6 in any order |
An example would be if you want the pieces to be rendered before the highlighted squares, then you could set the order to be 0, 1, 2, 4, 3, 5, 6 by setting:
{
"order": [0, 1, 2, 4, 3, 5, 6],
}
in the JSON file.
If you don't want to edit the JSON file, you could just specify it with code, like this:
// Change rendering order to : 0, 1, 2, 4, 3, 5, 6
_ = imager.SetOrder([]int{0, 1, 2, 4, 3, 5, 6})
To reset the rendering order, you can either create a new ChessImager object, or just set the order to nil:
// Reset rendering order to default : 0, 1, 2, 3, 4, 5, 6
_ = imager.SetOrder(nil)
Check out examples/advanced/advanced.go to see how to change render order.
The border renderer should always be the first renderer. It clears the image with the border color specified in the JSON file, and therefore will remove everything else that has been rendered so far.
The settings for the border renderer can be found in the border section of the json file.
Name | Type | Description |
---|---|---|
Width | integer | The width of the border in pixels |
Color | string | The color of the border |
{
"border": {
"width": 20,
"color": "#70663EFF"
},
}
The border settings will be ignored when board.type=1
(image)
The board renderer is usually the second renderer. It has a type
field that specifies how the renderer should draw
the board.
If type
=0, then the renderer will draw the board manually using the settings in the default section (under the board section).
If type
=1 then the renderer will draw an image containing a chessboard using the settings in the image section (under the board section).
Name | Type | Description |
---|---|---|
type | integer | 0 = default, 1 = image |
default | - | The settings for a manually drawn chess board |
image | - | The settings for a chess board image |
The settings under board.default are the following:
Name | type | Description |
---|---|---|
size | integer | The size of the board (border not included). Should probably be divisible by 8. |
white | string | The color for the white squares |
black | string | The color for the black squares |
"board": {
"type": 0,
"default": {
"size": 600,
"white": "#FAF3DCFF",
"black": "#E1DBB5FF"
}
}
You can use an image of a chess board as the background if you want to.
Important : If you do use a board image, it is assumed that the border and rank and files are in the image, so the border settings and rank and file settings will be ignored.
The settings under board.image are the following:
Name | type | Description |
---|---|---|
path | string | The path to the board image |
rect | rectangle | A rectangle that specifies where in the image the board is. |
"board": {
"type": 1,
"image": {
"path": "/home/[username]/chessboard.png",
"rect": {"x": 30, "y": 50, "width": 480,"height": 480},
}
}
See examples/other/other.json for an example of how to use a background board image, instead of the default board.
The rank and file renderer renders the file letters A to H and the rank numbers 1 to 8 on the chess board.
Name | Type | Description |
---|---|---|
type | integer | 0 = none, 1 = in border, 2 = in squares |
font_color | string | The color of the font for the rank and file indicators |
font_size | integer | The size of the font for the rank and file indicators |
"rank_and_file": {
"type": 1,
"font_color": "#FAF3DCFF",
"font_size": 16
}
The rank and file settings will be ignored when board.type=1
(image)
The highlight renderer highlights certain squares in a number of different ways. For example, you can highlight the square filling it with a color, or giving it a border of a specific color.
The style of the highlighted square is normally determined by the default highlight style provided in the default. json
file.
Name | Type | Description |
---|---|---|
type | integer | 0 = square, 1 = border, 2 = circle, 3 = filled circle, 4 = cross |
color | string | The highlight color |
width | integer | The width of the border, circle or cross (type=1, 2 or 4) |
factor | float | The size of the circle or cross (type=2, 3 or 4) |
The factor 0.5 means that the circle or cross should be 50% of the width of the square.
You can add a highlighted square by using the method AddHighlight()
on the ImageContext object in
the
style that is specified in the currently used JSON file:
imager := chessImager.NewImager()
ctx := imager.NewContext(fen).AddHighlight("e7")
image := imager.RenderWithContext(ctx)
Another alternative is to use the method AddHighlightWithStyle()
, that allows you to provide some special
styling to this specific square:
imager := chessImager.NewImager()
ctx := imager.NewContext(fen)
hs, _ := ctx.NewHighlightStyle(0, "#88008888", 0, 0)
ctx.AddHighlightWithStyle("e7", hs)
image := imager.RenderWithContext(ctx)
The piece renderer are responsible for drawing the pieces on the board (as specified in the FEN string).
type=0
means that you want to use the embedded pieces. See the embedded pieces section for more information. This is the easiest option, but you should be aware that these images are for personal use only.
type=1
means that you want to provide 12 images, one for each piece. See the images section for more information.
type=2
means that you want to provide an image map that contains all 12 pieces. See the image map section for more information.
Name | Type | Description |
---|---|---|
factor | float | Resize factor for pieces where 1.0 is equal to 100%. Pieces will be scaled up or down using the factor. |
type | integer | 0 = Use embedded pieces, 1 = use an image for each piece, 2 = use an image map |
images | - | Contains 12 paths, one for each piece. |
image_map | - | Contains 1 path, and 12 rectangles. |
The embedded pieces has no configuration, except piece.factor
that applies to all piece renderers.
Important :Note that the default embedded chess pieces are free for personal use only (See : https://clipart-library.com/clip-art/chess-pieces-silhouette-14.htm).
Example of the pieces section where type=0:
"pieces": {
"factor": 1.0,
"type": 0,
},
To use separate images for each piece you will have to provide 12 paths to the image files. The piece field must contain "wp","wb","wn","wr","wq" and "wk" for the white pieces, and "bp","bb","bn","br","bq" and "bk" for the black pieces
Example of the pieces section where type=1:
"pieces": {
"factor": 1.0,
"type": 1,
"images": {
"pieces": [
{"piece": "wp", "path": "/home/per/code/chessImager/test/data/wp.png"},
{"piece": "wb", "path": "/home/per/code/chessImager/test/data/wb.png"},
{"piece": "wn", "path": "/home/per/code/chessImager/test/data/wn.png"},
{"piece": "wr", "path": "/home/per/code/chessImager/test/data/wr.png"},
{"piece": "wq", "path": "/home/per/code/chessImager/test/data/wq.png"},
{"piece": "wk", "path": "/home/per/code/chessImager/test/data/wk.png"},
{"piece": "bp", "path": "/home/per/code/chessImager/test/data/bp.png"},
{"piece": "bb", "path": "/home/per/code/chessImager/test/data/bb.png"},
{"piece": "bn", "path": "/home/per/code/chessImager/test/data/bn.png"},
{"piece": "br", "path": "/home/per/code/chessImager/test/data/br.png"},
{"piece": "bq", "path": "/home/per/code/chessImager/test/data/bq.png"},
{"piece": "bk", "path": "/home/per/code/chessImager/test/data/bk.png"}
]
},
},
To use an image map containing all 12 pieces, you will have to provide a path to the image, and 12 rectangle that specifies where in the image the pieces can be found.
This JSON will pick out the red and yellow pieces out of the following image.
https://opengameart.org/content/colorful-chess-pieces
"pieces": {
"factor": 1.0,
"type": 2,
"image_map": {
"path": "/home/per/code/chessImager/test/data/pieces_colorful.png",
"pieces": [
{"piece": "WP", "rect": {"x": 0, "y": 896, "width": 128,"height": 128}},
{"piece": "WN", "rect": {"x": 128, "y": 896, "width": 128, "height": 128}},
{"piece": "WB", "rect": {"x": 256, "y": 896, "width": 128, "height": 128}},
{"piece": "WR", "rect": {"x": 384,"y": 896, "width": 128, "height": 128}},
{"piece": "WQ", "rect": {"x": 512, "y": 896,"width": 128, "height": 128}},
{"piece": "WK", "rect": {"x": 640, "y": 896, "width": 128,"height": 128}},
{"piece": "BP", "rect": {"x": 0, "y": 640, "width": 128, "height": 128}},
{"piece": "BN", "rect": {"x": 128, "y": 640, "width": 128, "height": 128}},
{"piece": "BB", "rect": {"x": 256,"y": 640, "width": 128, "height": 128}},
{"piece": "BR", "rect": {"x": 384, "y": 640,"width": 128, "height": 128}},
{"piece": "BQ", "rect": {"x": 512, "y": 640, "width": 128,"height": 128}},
{"piece": "BK", "rect": {"x": 640, "y": 640, "width": 128, "height": 128}}
]
}
},
The annotation renderer is responsible for rendering annotations, like !! or ??. You decide how big the annotation circle should be and what text it should contain.
The style of the annotation is normally determined by the default annotation style provided in the config/default.json file.
Name | Type | Description |
---|---|---|
position | integer | 0 = TopLeft, 1 = TopRight, 2 = BottomRight, 3 = BottomLeft, 4 = Middle |
size | integer | The size of the annotation circle |
font_color | string | The font color |
font_size | integer | The font size |
background_color | string | The background color of the annotation circle |
border_color | string | The border color of the annotation circle |
border_width | integer | The width of the border |
You can add an annotation by using the method AddAnnotation()
on the ImageContext object:
imager := chessImager.NewImager()
ctx := imager.NewContext(fen).AddAnnotation("e7", "!!")
image, _ := imager.RenderWithContext(ctx)
Another alternative is to use the method AddAnnotationWithStyle()
, that allows you to provide some special
styling to this specific square:
imager := chessImager.NewImager()
ctx := imager.NewContext(fen)
as, _ := ctx.NewAnnotationStyle(
chessImager.PositionTopLeft,
25, 20, 1,
"E8E57C", "000000", "FFFFFF",
)
ctx.AddAnnotationWithStyle("e7", "11", as)
image, _ := imager.RenderWithContext(ctx)
The moves renderer is responsible for rendering moves, that is, indicating that a piece has moved from one square to another.
The style of the move can be changed in the config/default.json file (or your own version of
that file), or by providing a chessImager.MoveStyle
struct to the AddMoveWithStyle()
method.
Name | Type | Description |
---|---|---|
type | integer | 0 = Dotted, 1 = Arrow |
color | string | The color of the dots or arrow |
color2 | string | The color of the second row of dots or arrow. Used only for castling moves. |
factor | float | The size of the dots or arrow, relative to the square size |
padding | float | How much space should there be between the two arrows in castling moves |
You can add a move by using the method AddMove()
on the ImageContext object, by providing the from
square and the to square.
imager := chessImager.NewImager()
ctx := imager.NewContext(fen).AddMove("e7", "c5")
image, _ := imager.RenderWithContext(ctx)
Another alternative is to use the method AddMoveWithStyle()
, that allows you to provide some special styling to this particular move, on top of the from square and the to square:
imager := chessImager.NewImager()
ctx := imager.NewContext(fen)
// Create a move style, for the move e7-c5
ms, _ := ctx.NewMoveStyle(
chessImager.MoveTypeDots, // Move type
"#9D6B5EFF", // Dot color
"#9D6B5EFF", // Dot color 2
0.2, // Dot size
0, // Padding
)
ctx.AddMoveWithStyle("e7", "c5", ms)
image, _ := imager.RenderWithContext(ctx)
To create a castling move, use one of the following eight variations:
// White king side castling
ctx.AddMove("0-0", "") // or
ctx.AddMove("o-o", "")
// White queen side castling
ctx.AddMove("0-0-0", "") // or
ctx.AddMove("o-o-o", "")
// Black king side castling
ctx.AddMove("", "0-0") // or
ctx.AddMove("", "o-o")
// Black queen side castling
ctx.AddMove("", "0-0-0") // or
ctx.AddMove("", "o-o-o")
The color movestyle.color
is used for the kings arrow or dots, and the color movestyle.color2
is used for the rooks arrow or dots.
See the castling example where the following image is generated:
All the examples below (except the last two) comes from move 25 by Kasparov, playing against Topalov in Wijk aan Zee (Netherlands), in 1999:
https://www.chess.com/games/view/969971
The first example is super easy. Rendering a chess board image, based on a FEN string, is basically one line of code. Add a few more lines of code to save the image to disk, and you have this code:
// Render simple image
const fen = "b2r3r/k3Rp1p/p2q1np1/Np1P4/3p1Q2/P4PPB/1PP4P/1K6 b - - 1 25"
img, _ := chessImager.NewImager().Render(fen)
// Save image
file, _ := os.Create("/path/to/img.png")
defer fileSimple.Close()
_ = png.Encode(file, img)
This code will generate the following image, using the default styling in config/default.json:
Here is a slightly more advanced example, that adds a highlighted square, an annotation and a move. For this we need
to create a ImageContext object, using the imager.NewContext()
method. We also need to use the
imager.RenderWithContext()
method so that we can provide the ImageContext.
This example also uses the styles that are defined in the config/default.json files:
// Create a new imager using embedded default.json settings
imager := chessImager.NewImager()
// Create a new image context
const fen = "b2r3r/k3Rp1p/p2q1np1/Np1P4/3p1Q2/P4PPB/1PP4P/1K6 b - - 1 25"
ctx := imager.NewContext(fen)
// Highlight square e7, annotate square e7 with "!!" and
// show move e1-e7 using the styles specified in default.json.
ctx.AddHighlight("e7").AddAnnotation("e7", "!!").AddMove("e1", "e7")
// Render image
image, _ := imager.RenderWithContext(ctx)
This would generate the following image:
Let's look at an even more advanced example. If you want to add annotations, highlighted squares and moves, using styles other than the ones provided in config/default.json, you can do that by providing the styles objects manually.
For example, let's change a few things from the medium example:
- the highlight color and style (filled circle). See Highlight renderer for more information.
- the annotation position (top left) and border color. See Annotation renderer for more information.
- the move size and color for the next image. See Move renderer for more information.
And for fun, lets change the render order too...
Read more about renderers and their order in the render order section.
// Create a new imager using embedded default.json settings
imager := chessImager.NewImager()
// Set the rendering order
_ = imager.SetOrder([]int{0, 1, 2, 3, 5, 4, 6})
// Create a new image context
const fen = "b2r3r/k3Rp1p/p2q1np1/Np1P4/3p1Q2/P4PPB/1PP4P/1K6 b - - 1 25"
ctx := imager.NewContext(fen)
// Create a highlight style, for the square e7
hs, _ := ctx.NewHighlightStyle(
chessImager.HighlightFilledCircle, // Highlight type
"#88E57C", // Highlight color
4, // Highlight cirle width
0.9 // Highlight factor (not used for this Type)
)
// Create an annotation style, for the square e7
as, _ := ctx.NewAnnotationStyle(
chessImager.PositionTopLeft, // Position top left
25, 20, 1, // Size, font size, border width
"#E8E57C", "#000000", "#FFFFFF", // Background, font, border color
)
// Create a move style, for the move e1-e7
ms, _ := ctx.NewMoveStyle(
chessImager.MoveTypeDots, // Move type
"#9D6B5EFF", // Dot color
"#9D6B5EFF", // Dot color 2
0.2, // Dot size
0, // Padding
)
~~~~
// Highlight the e7 square, annotate e7 as a brilliant move (!!) and
// show move e1-e7.
ctx.AddHighlightWithStyle("e7", hs)
.AddAnnotationWithStyle("e7", "!!", as)
.AddMoveWithStyle("e1", "e7", ms)
// Render the image
image, _ := imager.RenderWithContext(ctx)
This code will generate the following image:
As you can see, the pieces are now rendered after annotations, the annotation lies behind the piece.
In this example we will create our own JSON file and load it using the NewImagerFromPath()
function. In the
examples/other/other.json file, we will also use a board image, examples/other/chessboard.jpg, instead of drawing it manually.
// Create a new imager using your custom JSON file
imager, _ := chessImager.NewImagerFromPath("examples/other.json")
// Highlight the e7 square, annotate e7 as a brilliant move (!!) and
// show move e1-e7.
const fen = "b2r3r/k3Rp1p/p2q1np1/Np1P4/3p1Q2/P4PPB/1PP4P/1K6 b - - 1 25"
ctx := imager.NewContext(fen).AddHighlight("e7").AddAnnotation("e7", "!!").AddMove("e1", "e7")
// Render the image
img, _ := imager.RenderWithContext(ctx)
This code will generate the following image:
Background chess board image comes from here : https://www.supercoloring.com/paper-crafts/printable-green-chess-board-with-pieces-template
In this example we will look at how to create castling moves. For more information about castling, check out the castling section.
// Create a new imager using embedded default.json settings
imager := chessImager.NewImager()
// Create a new image context, and add white king side castling,
// and black queen side castling.
const fen = "2kr4/8/8/8/8/8/8/5RK1 b - - 1 25"
ctx := imager.NewContext(fen).AddMove("0-0", "").AddMove("", "0-0-0")
// Render the image
img, _ := imager.RenderWithContext(ctx)
This code will generate the following image:
In this example we will read a PGN file, and generate a picture for each move. This code can definitely be improved when it comes to handling castling moves better, but for this example it will have to do.
To be able to read and parse a PGN file, we will need the use of a package that can do that for us : gopkg.in/freeeve/pgn.v1
package main
import (
"fmt"
"image/png"
"log"
"os"
"github.com/Hultan/chessImager"
"gopkg.in/freeeve/pgn.v1"
)
func main() {
imager := chessImager.NewImager()
f, err := os.Open("game.pgn")
if err != nil {
log.Fatal(err)
}
ps := pgn.NewPGNScanner(f)
for ps.Next() {
game, err := ps.Scan()
if err != nil {
log.Fatal(err)
}
b := pgn.NewBoard()
i := 1
for _, move := range game.Moves {
_ = b.MakeMove(move)
ctx := imager.NewContext(b.String()).
AddMove(move.From.String(), move.To.String()).
AddHighlight(move.From.String()).
AddHighlight(move.To.String())
img, _ := imager.RenderWithContext(ctx)
file, _ := os.Create(fmt.Sprintf("%d.png", i))
_ = png.Encode(file, img)
_ = file.Close()
i++
}
}
}
This code will generate 85 PNG images, one for each move in the game.