Создание шахматной доски в формате SVG, PNG и GIF
Самый гибкий способ превратить строку в изображение — использовать URL-адрес, который знает, какое изображение возвращать. Надеясь, что простой способ предоставления шахматных изображений будет жизнеспособным, я решил посмотреть, как быстро дешевый сервер сможет преобразовать строку FEN любой шахматной позиции в легковесное изображение.
Основы шахматной нотации
Стандартной нотацией для хранения информации о местоположении является нотация Форсайта-Эдвардса. Строка FEN выглядит как rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
, за которым следует пробел и другая информация, не имеющая отношения к созданию образа платы.
- Строчные буквы представляют собой черные фигуры, а прописные буквы — белые фигуры.
- Буквы обозначают ожидаемые фигуры, за исключением n, который является конем.
- Числа обозначают количество последовательных пустых мест.
/
разделяет строки.
Анализ этих строк относительно прост, требуется лишь небольшая работа по обработке чисел. Следите за текущим столбцом, переходя к следующему столбцу после каждой части и перемещая заданное количество столбцов при достижении целого числа. Перейдите к следующей строке, когда столбец больше 7, пока не будут заполнены все 8 строк.
let row = 0, col = 0;
for (var i=0; i<fen.length; i++) {
switch (fen[i]) {
case 'P': case 'N': case 'B':
case 'R': case 'Q': case 'K':
case 'p': case 'n': case 'b':
case 'r': case 'q': case 'k': {
//the piece is fen[i], row is row, col is col
//turn that info into a graphic
col++;
if (col > 7) {
col = 0;
row++;
}
break;
}
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': {
//move the given number of cols
col += parseInt(fen[i]);
if (col > 7) {
col = 0;
row++;
}
}
//skip over other chars like /
}
if (row > 7) { break;}
}
Типы изображений
Существует несколько вариантов типа создаваемого изображения. По умолчанию статические изображения будут в формате SVG, поскольку они легкие, очень простые в создании и прекрасно масштабируются до любого размера.
Многие приложения социальных сетей не отображают SVG, поэтому для совместимости важен вариант PNG. Наконец, я хочу создать GIF-файлы, чтобы можно было анимировать несколько движений.
Создание SVG
Для создания SVG требуется только манипулирование строками, поэтому любая внутренняя настройка выполняется легко. Все, что нам нужно, это пустая доска и дорожки для каждой детали. Затем каждый раз, когда мы попадаем в фрагмент при прохождении строки FEN, просто добавляем соответствующие пути и переводим их в правильную строку и столбец.
case 'r': case 'n': case 'b': case 'q': case 'k': case 'p': {
svg += `<g transform="translate(${col*45} ${row*45})">${pieces[fen[i]]}</g>`;
//add code here to update row, col as above
break;
}
case 'R': case 'N': case 'B': case 'Q': case 'K': case 'P': {
svg += `<g transform="translate(${col*45} ${row*45})" class="w">${this.pieces[this.fen[i].toLowerCase()]}</g>`;
//each square is 45 units wide/tall
break;
}
Любая коллекция фрагментов SVG (я предполагаю, что при необходимости подойдут растровые изображения) может работать, заключая каждый фрагмент в группу, которая преобразуется, чтобы вписаться в желаемый квадрат.
Пустую доску можно просто использовать в качестве фона в виде шахматной доски, но вы также можете добавить рамку с метками сетки или чем-то еще. Если вы знаете размер каждого квадрата, добавить кусочки сверху в нужных местах не составит труда.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="-9 -9 378 378">
<defs>
<pattern id="bg" x="0" y="0" width="90" height="90" patternUnits="userSpaceOnUse">
<rect fill="rgb(190,190,190)" x="0" y="0" width="90" height="90"/>
<rect fill="rgb(150,100,75)" x="45" y="0" width="45" height="45"/>
<rect fill="rgb(150,100,75)" x="0" y="45" width="45" height="45"/>
</pattern>
</defs>
<rect x="-9" y="-9" width="100%" height="100%" fill="black"/>
<rect x="0" y="0" width="360" height="360" fill="url(#bg)" fill-opacity="0.75"></rect>
<!--Add pieces here-->
</svg>
Здесь нет никаких зависимостей, поэтому этот алгоритм может работать как на стороне клиента, так и на стороне сервера, а создание изображения занимает менее миллисекунды при сжатии примерно до 2 КБ. Для рабочего сервера я использовал OpenResty (расширение Nginx) с Lua, но процесс тот же.
Создание PNG
Существует множество способов конвертировать SVG в PNG с помощью встроенного средства визуализации браузера или такого инструмента, как rsvg-convert. Однако самый быстрый подход — предварительно отрисовать массивы пикселей для каждого фрагмента, а затем соединить их вместе во время выполнения.
Для простоты я буду генерировать PNG с помощью JavaScript/Node и библиотеки изображений Sharp. Любая библиотека, которая может конвертировать массивы пикселей в файлы изображений, сделает этот процесс довольно простым.
Всего по 6 частей каждого цвета, каждая из которых должна быть на темном и светлом фоне, всего 24 варианта.
Я создал изображения каждой части размером 80 на 80 пикселей, чтобы они поместились в квадраты размером 90 пикселей с небольшим отступом. Я разделяю каждое изображение на 80 Uint8Arrays, по одному на каждую строку, и создаю карту этих массивов, которую могу вызывать при необходимости.
//each square is 90px and I only want to replace the center 80x80 so offset by 5
//the board is 720px wide and each pixel uses 3 cells (R,G,B)
var minRow = 5 + 90 * row;
var minCol = 5 + 90 * col;
for (var rr = 0; rr < 80; rr++) {
//arr is a Uint8Array that starts as a blank board.
//inputPNGs finds the correct row for the correct piece
//then insert that array at the correct starting index with arr.set()
arr.set(inputPNGs[p][rr], (rr + minRow) * 720 * 3 + minCol * 3);
}
Когда сервер запускается, я использую Sharp для создания массивов для доски и каждой детали. Затем каждый раз, когда мне нужно новое изображение платы, Sharp генерирует PNG из сгенерированного Uint8Array. Также можно создавать файлы JPG, WEBP или любой другой распространенный формат, а также изменять размер и другие настройки.
Весь процесс занимает всего несколько миллисекунд, но для достижения максимальной скорости и минимального размера файла я применил более сложный процесс с Rust/Actix для рабочего сервера.
Анимированные GIF-файлы
Что касается GIF-файлов, я требую, чтобы пользователи создавали их в браузере, а затем загружали на GIF-сервер. Я не думаю, что существует эффективный метод создания GIF-файлов по запросу на стороне сервера по сравнению со статическими изображениями.
С помощью путей SVG можно создать действительно красивый переход между позициями. Создайте путь, который начинается с начальной фигуры, перемещается по доске к следующей позиции, а затем заканчивается фигурой в новом месте. За счет анимации того, какая часть этого пути видна в любой момент, создается красивое плавное движение.
Чтобы правильно выбрать время для плавной анимации, требуется немного вычислений, но после этой работы библиотеки gif.js и gifsicle могут вывести довольно легкий GIF-файл для загрузки и обмена.
Первый шаг — сделать несколько снимков через определенные промежутки времени. Нам нужно сгенерировать соответствующий SVG для каждого кадра, отобразить его в холсте, а затем преобразовать холст в PNG.
Когда у нас будет массив изображений, следуйте документации gif.js
, чтобы создать GIF. Эти GIF-файлы немного великоваты, но gifsicle
значительно уменьшит их размер. При использовании сжатия «-O3» и всего 32 цветов (достаточно для наших шахматных изображений) размер уменьшается как минимум на 80%.
Веб-компонент
Хотя для этого требуется немного JavaScript, есть еще один простой способ добавить изображение доски на веб-страницу — с помощью специального веб-компонента. Поскольку у меня уже была функция для генерации SVG, мне просто нужно было добавить некоторый шаблон веб-компонента.
Библиотека сжимается до размера около 3 КБ и позволяет создавать или обновлять несколько изображений плат без дополнительных запросов. Другим преимуществом является возможность стилизовать границу, изменить цвет квадратов и даже добавить несколько стрелок.
<chess-board fen="rnbqkbnr/8/8/8/8/8/8/RNBQKBNR" arrows="a1a5 b1c3,g8f6" dark="blue" light="gray" border="12 black white"></chess-board>
<chess-board fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" light="orange" border="8 black black"></chess-board>
<script src="https://rnbqkbnr.com/chess-board.js"></script>
Вывод
Если вам нужно изображение шахматной позиции, просто вставьте или введите строку FEN (с информацией о рокировке и т. д. или без нее) после домена https://rnbqkbnr.com везде, где принимаются URL-адреса: браузеры, многие приложения и веб-сайты или внутри элемента HTML img
. Если вам нужен PNG, просто добавьте в конец .png
.