DevGang
Авторизоваться

Создание шахматной доски в формате 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.  

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу