Генерация QR-кода в формате файла машинной вышивки Tajima DST

от автора


Введение

На сегодняшний день QR-коды (quick-response) широко используются в различных сферах. Структура QR-кода была разработана в Японии Масахиро Хара.

Хочу поделиться с читателями «Хабрахабра» способом формирования QR-кода в формате машинной вышивки Tajima DST. Данный метод позволяет исключить ручные операции по формированию QR-кода и последующего преобразования полученной картинки в дизайн машинной вышивки. Если у вас или ваших знакомых есть вышивальная машина, то загрузив полученный файл в память машины и выполнив вышивку можно получить следующее:


Проблема

Ни в одной программе для работы с машинной вышивкой нет функции формирования QR-кода. Чтобы получить файл вышивки QR-кода необходимо сначала получить картинку QR-кода в любом позволяющем это сделать on-line сервисе, затем преобразовать её в блоки стежков средствами редактора машинной вышивки. Для одного или двух QR-кодов такой подход приемлем. Для формирования неограниченного количества QR-кодов необходимо исключить ручные операции.

Решение

Сформируем самостоятельно QR-код с помощью сторонней библиотеки, затем по полученной матрице для каждой колонки построим набор стежков для непрерывно идущих ячеек матрицы. Для качества QR-кода опорные квадраты сформируем отдельно, для этого разделим матрицу на шесть областей:

Вышивка протестирована на вышивальной машине модели Brother NV 90E.

Описание входных данных

Входная строка может быть в формате электронной визитной карточки (VCARD), может содержать информацию о географическом положении (GEO). Также это может быть просто текст или строка URL.

Входные данные в виде VCARD:

BEGIN:VCARD VERSION:3.0 FN:к.м.н., пр. Василий Иванович Квакин N:Квакин;Василий;Иванович;пр.,к.м.н. ORG:Рога и Копыта URL:http://ru.wikipedia.org/Вася_Квакин EMAIL;TYPE=INTERNET:vasya.kvakin@example.com END:VCARD 

Полное описание формата VCARD здесь.
Входные данные в виде географических координат, первая координата — долгота, вторая — широта:

GEO:30.31616, 59.95015

Описание приложения

Приложение написано на C#. Используется библиотека MessagingToolkit.QRCode, позволяющая создавать QR-код по входящей информационной строке. Библиотека устанавливается пакетом с nuget.org через консоль менеджера пакетов:

PM> Install-Package MessagingToolkit.QRCode

Матрица QR-кода формируется в виде двумерного массива логических значений.

Получив матрицу QR-кода, перейдём к следующему шагу – формированию списка линий для формирования по ним последовательностей стежков.
Будем считать линию набором последовательно идущих ячеек QR-кода без пропусков. Линии могут быть как вертикальные так горизонтальные в случае опорных рамок QR-кода. Набор линий используется для формирования блоков стежков.

Три опорных прямоугольника располагаются по углам QR-code. Разделим матрицу на 6 областей. Первая область — это левый верхний прямоугольник, стежки которого формируются в первую очередь. Стежки для прямоугольника формируются последовательно для всех его сторон, а не вертикальными колонками как в общем случае. Затем формируются стежки для линий находящихся между опорными левым верхним и левым нижним опорными прямоугольниками. Стежки для нечетных колонок формируются сверху вниз. Для четных колонок стежки формируются снизу вверх. Такая последовательность стежков исключает длинные переходы нити снизу вверх и наоборот. Четвёртая область — самая большая область, формируется аналогично второй. Пятая область – опорный прямоугольник, находящийся в правом верхнем углу. Шестая область – завершающая, стежки для колонок в ней формируются также: нечётные сверху вниз, чётные снизу вверх.

Класс QRCodeCreator

Класс использует пространство имён MessagingToolkit.QRCode.Codec для формирования матрицы QR-кода в следующем методе:

using System.Text; using MessagingToolkit.QRCode.Codec;  namespace EmbroideryFile.QRCode {     internal class QRCodeCreator     {         public bool[][] GetQRCodeMatrix(string DataToEncode)         {             if (string.IsNullOrEmpty(DataToEncode))   return new bool[1][];             QRCodeEncoder qrCodeEncoder = new QRCodeEncoder();             qrCodeEncoder.CharacterSet = "UTF8";             qrCodeEncoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE;             qrCodeEncoder.QRCodeScale = 1;             qrCodeEncoder.QRCodeVersion = -1;             qrCodeEncoder.QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.L;             return qrCodeEncoder.CalQrcode(Encoding.UTF8.GetBytes(DataToEncode));         }     } } 

CharacterSet устанавливаем UTF8, для возможности кодирования символов кириллицы.
Свойству QRCodeErrorCorrect присваиваем значение QRCodeEncoder.ERROR_CORRECTION.L — низкий уровень избыточности при кодировании.
Считаем, что излишняя избыточность данных при чтении не нужна.
Если в файле входной строке присутствуют символы кириллицы, то файл должен быть обязательно сохранён в кодировке UTF8.
Экземпляр этого класса создаётся в конструкторе класса QRCodeStitcher.

Класс QRCodeStitcher

Формирование всех видов блоков стежков реализовано в этом классе.Это обеспечивается следующими этапами:

  1. Формирование списка непрерывных линий для каждой из 6-ти областей;
  2. Генерация стежков для каждой области по списку линий.

Для формирования списка вертикальных линий по матрице QR-кода выполняем проход по ячейкам вертикальных колонок и при пустой текущей ячейке добавляем текущую линию в результирущий список. Исключение составляют опорные квадраты расположенные по краям QR-кода. Каждый элемент списка содержит данные о начальной точке, конечной точке линии, её длине, а также признак попадания самой нижней ячейки линии в последнюю строку матрицы QR-кода.

При формировании стежков для вертикальных линий перемещения по осям ординат и абсцисс имеют фиксированные значения: dX = 25; dY = 2; Размер ячейки QR-кода также зафиксирован: cellSize = 25 единиц. Единицы измерения здесь 0.1 мм.
Модель данных линии представлена в виде следующей структуры:

 public struct Line {     public Coords Dot1 { get; set; }     public Coords Dot2 { get; set; }     public int Length { get; set; }     public bool Lowest { get; set; }  } 

Следующий метод формирует блоки стежков для всех 6-ти описанных ранее областей QR-кода:

private List<List<Coords>> GenerateQRCodeStitchesBoxed() {  var blocks = new List<List<Coords>>();  int rectSize = GetRectSize();  // левый верхний прямоугольник  blocks.AddRange(GetRectangleSatin(0, 0, rectSize - 1, rectSize - 1));  // левый верхний квадрат  blocks.Add(GenerateBoxStitchBlock(2, 2, rectSize - 4));  // область между верхним и нижним прямоугольниками  blocks.AddRange(GetSatinStitches(GetLaneList(0, rectSize + 1, rectSize, _dimension - rectSize - 1)));  // левый нижний прямоугольник  blocks.AddRange(GetRectangleSatin(0, _dimension - rectSize, rectSize - 1, _dimension - 1));  // левый нижний внутренний квадрат  blocks.Add(GenerateBoxStitchBlock(2, _dimension - rectSize + 2, rectSize - 4));  // средняя область  blocks.AddRange(GetSatinStitches(GetLaneList(rectSize + 1, 0, _dimension - rectSize - 1,  _dimension - 1)));  // правый верхний прямоугольник  blocks.AddRange(GetRectangleSatin(_dimension - rectSize, 0, _dimension - 1, rectSize - 1));  // правый верхний внутренний квадрат  blocks.Add(GenerateBoxStitchBlock(_dimension - rectSize + 2, 2, rectSize - 4));  // область под правым верхним прямоугольником  blocks.AddRange(GetSatinStitches(GetLaneList(_dimension - rectSize, rectSize + 1, _dimension - 1,  _dimension - 1)));  return blocks; } 

Метод GetRectangleSatin() создаёт блоки для квадратов стежков по координатам крайних ячеек:

IEnumerable<List<Coords>> GetRectangleSatin(int x1, int y1, int x2, int y2) {      int LeftX = (x1 > x2) ? x2 : x1;      int TopY = (y1 > y2) ? y2 : y1;      int RightX = (x1 < x2) ? x2 : x1;      var BottomY = (y1 < y2) ? y2 : y1;      int length = RightX - LeftX;      var rect = new List<List<Coords>>();      rect.Add(GenerateVerticalColumnStitchBlock(LeftX, TopY, length));      rect.Add(GenerateHorizonColumnStitchBlock(LeftX, BottomY, length));      rect.Add(ReverseCoords(GenerateVerticalColumnStitchBlock(RightX, TopY + 1, length)));      rect.Add(ReverseCoords(GenerateHorizonColumnStitchBlock(LeftX + 1, TopY, length)));      return rect; } 

Следующий метод создаёт для генерации внутреннего квадрата опорных областей QR-кода:

/// <summary> /// Создаёт список стежков для заполненного квадрата /// </summary> /// <param name="cellHorizonPos">Горизонтальная позиция верхней левой ячейки квадрата </param> /// <param name="cellVerticalPos">Вертикальная позиция верхней левой ячейки квадрата </param> /// <param name="boxSize">Размер квадрата</param> /// <returns>Список координат</returns> private List<Coords> GenerateBoxStitchBlock(int cellHorizonPos, int cellVerticalPos, int boxSize) {    var block = new List<Coords>();    int y = 0; int x = 0;    int startX = cellHorizonPos * _cellSize;    int startY = cellVerticalPos * _cellSize;    block.Add(new Coords { X = startX, Y = startY });    while (y < _cellSize * boxSize)    {         while (x < _cellSize * boxSize - _dX)         {            x = x + _dX;            block.Add(new Coords{ X = startX + x, Y = startY + y });         }         x = boxSize * _cellSize;         block.Add(new Coords { X = startX + x, Y = startY + y });         y = y + _dY;         while (x > _dX)         {             x = x - _dX;             block.Add(new Coords { X = startX + x, Y = startY + y });         }         x = 0;         block.Add(new Coords { X = startX + x, Y = startY + y });         y = y + _dY;    }    return block; }  

Блоки стежков для последовательности вертикальных линий формируются в следующем методе:

/// <summary> /// Формирует список блоков стежков по списку непрерывных вертикальных линий /// </summary> private List<List<Coords>> GetSatinStitches(List<Line> lanes) {   List<List<Coords>> blockList = new List<List<Coords>>();   foreach (var lane in lanes)   {                  List<Coords> satin = null;       if (((lane.Length == 1) && ((lane.Dot1.X % 2) == 0)) ||            ((lane.Length > 1) && (lane.Dot2.Y > lane.Dot1.Y)))             satin = GenerateVerticalColumnStitchBlock(lane.Dot1.X, lane.Dot1.Y, lane.Length);        else                                satin = ReverseCoords(GenerateVerticalColumnStitchBlock(lane.Dot2.X, lane.Dot2.Y, lane.Length));               blockList.Add(satin);   }   return blockList; } 

Список линий формируется для областей 2, 4, 6 в следующем методе. Проверка завершения линии выполняется в методах ConsumeRelativeCellDown() и ConsumeRelativeCellUp().

/// <summary> /// Возвращает список вертикальных линий для указанной по угловым ячейкам области  /// </summary> /// <param name="x1">X координата крайней ячейки области</param> /// <param name="y1">Y координата крайней ячейки области</param> /// <param name="x2">X координата крайней ячейки области</param> /// <param name="y2">Y координата крайней ячейки области</param> /// <returns></returns> private List<Line> GetLaneList(int x1, int y1, int x2, int y2) {             try             {                 if (_lines != null) _lines.Clear();                 if (y1 > y2)                 {                     _topY = y2;                     _bottomY = y1;                 }                 else                 {                     _topY = y1;                     _bottomY = y2;                 }                 if (x1 > x2)                 {                     _leftX = x2;                     _rightX = x1;                 }                 else                 {                     _leftX = x1;                     _rightX = x2;                 }                 for (int j = _leftX; j <= _rightX; j = j + 2) //X                 {                     _state = false;                     for (int i = _topY; i <= _bottomY; i++) // Y                                    {                         ConsumeRelativeCellDown(j, i);                     }                     if (j >= _rightX) break;                     _state = false;                     for (int i = _bottomY; i >= _topY; i--) // Y                                    {                         ConsumeRelativeCellUp(j + 1, i);                     }                 }                 return _lines;             }             catch (Exception ex)             {                 Trace.WriteLine(string.Format("GetLineList(): {0}",ex));                 throw;            } } 

Метод ConsumeRelativeCellDown() вызывается при формировании списка линий для чётной колонки QR-кода.

/// <summary>  /// Проверка прерывания текущей линии при проходе сверху вниз  /// </summary>  /// <param name="j"></param>  /// <param name="i"></param>  void ConsumeRelativeCellDown(int j, int i) {             if (_cells[j][i] == true)             {                 // начало линии в верхней строке области                 if ((i == _topY))                 {                     _dot1 = new Coords() { X = j, Y = i };                                         _curLane.Dot1 = _dot1;                     _laneLen = 1;                     _state = true;                 }                 else if ((_state == false))                 {                     // одиночная ячейка внизу матрицы                     if (i == _bottomY)                     {                         _dot1 = new Coords() { X = j, Y = i };                         _curLane.Dot1 = _dot1;                         _dot2 = new Coords() { X = j, Y = i };                         _curLane.Dot2 = _dot2;                         _curLane.Length = 1;                         _curLane.Lowest = true;                         _endLaneFlag = true;                     }                     // начало линии                     else                     {                         _dot1 = new Coords() { X = j, Y = i };                                                 _curLane.Dot1 = _dot1;                         _laneLen = 1;                         _state = true;                     }                 }                 else if ((i == _bottomY))                 {                     //   конец линии внизу                     _dot2 = new Coords() { X = j, Y = i };                     _curLane.Dot2 = _dot2;                     _curLane.Length = ++_laneLen;                     _curLane.Lowest = true;                     _endLaneFlag = true;                 }  // линия продолжается                 else                 {                     _laneLen++;                 }             }             // конец линии, не крайняя ячейка             else if (_state == true)             {                 _dot2 = new Coords() { X = j, Y = i - 1 };                 _curLane.Dot2 = _dot2;                 _curLane.Length = _laneLen;                 _state = false;                 _endLaneFlag = true;             }             if (_endLaneFlag == true)             {                 _lines.Add(_curLane);                 _endLaneFlag = false;             } } 

Метод ConsumeRelativeCellUp() вызывается при формировании списка линий для нечётной колонки QR-кода.

void ConsumeRelativeCellUp(int j, int i) {             if (_cells[j][i] == true)             {                 // начало линии внизу                 if ((i == _bottomY))                 {                      _dot1 = new Coords { X = j, Y = i };                     _curLane.Dot1 = _dot1;                     _laneLen = 1;                     _state = true;                 }                 else if ((_state == false))                 {                     // одинокая ячейка                     if (i == _topY)                     {                         _dot1 = new Coords { X = j, Y = i };                         _curLane.Dot1 = _dot1;                         _dot2 = new Coords { X = j, Y = i };                         _curLane.Dot2 = _dot2;                         _curLane.Length = 1;                         _curLane.Lowest = true;                         _endLaneFlag = true;                     }                     // начало линии                     else                     {                         _dot1 = new Coords { X = j, Y = i };                         _curLane.Dot1 = _dot1;                         _laneLen = 1;                         _state = true;                     }                 }                 else if ((i == _topY))                 {                     //   end of lane at the top                     _dot2 = new Coords { X = j, Y = i };                     _curLane.Dot2 = _dot2;                     _curLane.Length = ++_laneLen;                     _curLane.Lowest = true;                     _endLaneFlag = true;                 }  // линия продолжается                 else                 {                     _laneLen++;                 }             }             // конец линии, не крайняя строка             else if (_state)             {                 _dot2 = new Coords { X = j, Y = i + 1 };                 _curLane.Dot2 = _dot2;                 _curLane.Length = _laneLen;                 _state = false;                 _endLaneFlag = true;             }             if (_endLaneFlag)             {                 _lines.Add(_curLane);                 _endLaneFlag = false;             }  } 

Чётные колонки вышиваются сверху вниз, нечётные снизу вверх, это позволяет исключить длинные стежки перемещения нити при переходе к следующей колонке ячеек QR-кода. Следующий код реализует логику добавления стежков в линию:

/// <summary>         /// Формирование стежков вертикальной линии из соответсвующей позиции         /// </summary>         /// <param name="cellHorizonPos">абсцисса верхней ячейки линии</param>         /// <param name="cellVerticalPos">ордината </param>         /// <param name="length"></param>         private List<Coords> GenerateVerticalColumnStitchBlock(int cellHorizonPos, int cellVerticalPos, int length)         {             var block = new List<Coords>();             int curX, curY;             int columnLength = _cellSize * length;             int startX = cellHorizonPos * _cellSize;             int startY = cellVerticalPos * _cellSize;             block.Add(new Coords { X = startX + _cellSize, Y = startY });             for (curY = 0; curY < columnLength; curY = curY + _dY)             {                 for (curX = (curY == 0) ? 0 : _dX; (curX < _cellSize) && (curY < columnLength); curX = curX + _dX)                 {                     block.Add(new Coords { X = startX + curX, Y = startY + curY });                     curY = curY + _dY;                 }                 int edgedX = _cellSize - (curX - _dX);                 int edgedY = edgedX * _dY / _dX;                 curX = _cellSize;                 curY = curY + edgedY - _dY;                 block.Add(new Coords { X = startX + curX, Y = startY + curY });                 curY = curY + _dY;                 for (curX = _cellSize - _dX; (curX > 0) && (curY < columnLength); curX = curX - _dX)                 {                     block.Add(new Coords { X = startX + curX, Y = startY + curY });                     curY = curY + _dY;                 }                 edgedX = curX + _dX;                 edgedY = edgedX * _dY / _dX;                 curY = curY + edgedY - _dY;                 block.Add(new Coords { X = startX, Y = startY + curY });             }             curX = _cellSize;             curY = columnLength;             block.Add(new Coords { X = startX + curX, Y = startY + curY });             return block;         } 

Класс QrcodeDst

В конструкторе класса создаются экземпляры классов DstFile и QrCodeStitcher.

public QrcodeDst()  {       _dst = new DstFile();       _stitchGen = new QrCodeStitcher();  }  

Класс имеет следующий метод установки свойства:

public QRCodeStitchInfo QrStitchInfo {    set { _stitchGen.Info = value; } } 

В классе QrcodeDst реализован метод FillStreamWithDst(Stream stream) выполняющий сохранение QR-кода в формате машинной вышивки Tajima DST.Метод GetQRCodeStitchBlocks() обеспечивает формирование блоков стежков для вышивки в виде списка списков координат с дополнительной информацией является ли первый стежок стежком перехода или останова. Свойство QrStitchInfo класса QrcodeDst предназначено для получения входной информации в виде строки, для хранения матрицы QR-кода.

Метод класса DstFile WriteStitchesToDstStream() принимает в качестве параметров список блоков координат и экземпляр Stream для записи в него данных стежков в формате машинной вышивки.

Следующий фрагмент кода читает данные для кодирования из файла и использует экземпляр QrcodeDst для сохранения последовательностей стежков QR-кода в файл машинной вышивки:

var  qrcodeGen = new QrcodeDst();  using (var inputStreamReader = new StreamReader(fileName)) {     var text = inputStreamReader.ReadToEnd();     using (Stream outStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))     {       if (qrcodeGen != null)       {           qrcodeGen.QrStitchInfo = new QRCodeStitchInfo {QrCodeText = text};           qrcodeGen.FillStream(outStream);       }     } } 

Формат файла для сохранения вышивки описан в следующем параграфе.

Формат DST файла

Для преобразование координат стежков в байты использовано описание формата DST файла отсюда. Последовательность стежков хранится в виде кодированных смещений относительно предыдущего стежка. То есть в файле хранятся команды на перемещение нити, с указанием типа стежка.

Возможные типы стежков:
• Обычный
• Переход
• Останов

Стежок останова, позволяет сменить нить, если это делается вручную.
DST файл имеет заголовок, данные стежков начинаются с 512-ого байта при нумерации байтов
с нуля.
Стежок кодируется тремя байтами:

Номер бита 7 6 5 4 3 2 1 0
Байт 1 y+1 y-1 y+9 y-9 x-9 x+9 x-1 x+1
Байт 2 y+3 y-3 y+27 y-27 x-27 x+27 x-3 x+3
Байт 3 переход останов y+81 y-81 x-81 x+81 Установлен всегда Установлен всегда

Биты перехода и останова могут быть установлены одновременно. Это необходимо при длинном переходе и одновременной смене нити.
DST файл обязательно должен заканчиваться тремя байтами: 00 00 F3.

Ниже приведён код возвращающий байты стежка по значениям перемещения нити относительно предыдущей позиции:

  byte[] encode_record(int x, int y, DstStitchType stitchType)         {             byte b0, b1, b2;             b0 = b1 = b2 = 0;             byte[] b = new byte[3];             // следующие значение преобразовать невозможно >+121 or < -121.             if (x >= +41) { b2 += setbit(2); x -= 81; };             if (x <= -41) { b2 += setbit(3); x += 81; };             if (x >= +14) { b1 += setbit(2); x -= 27; };             if (x <= -14) { b1 += setbit(3); x += 27; };             if (x >= +5) { b0 += setbit(2); x -= 9; };             if (x <= -5) { b0 += setbit(3); x += 9; };             if (x >= +2) { b1 += setbit(0); x -= 3; };             if (x <= -2) { b1 += setbit(1); x += 3; };             if (x >= +1) { b0 += setbit(0); x -= 1; };             if (x <= -1) { b0 += setbit(1); x += 1; };             if (x != 0)             {                throw;             };             if (y >= +41) { b2 += setbit(5); y -= 81; };             if (y <= -41) { b2 += setbit(4); y += 81; };             if (y >= +14) { b1 += setbit(5); y -= 27; };             if (y <= -14) { b1 += setbit(4); y += 27; };             if (y >= +5) { b0 += setbit(5); y -= 9; };             if (y <= -5) { b0 += setbit(4); y += 9; };             if (y >= +2) { b1 += setbit(7); y -= 3; };             if (y <= -2) { b1 += setbit(6); y += 3; };             if (y >= +1) { b0 += setbit(7); y -= 1; };             if (y <= -1) { b0 += setbit(6); y += 1; };             if (y != 0)             {                throw;             };             switch (stitchType)             {                 case DstStitchType.NORMAL:                     b2 += (byte)3;                     break;                 case DstStitchType.END:                     b2 = (byte)243;                     b0 = b1 = (byte)0;                     break;                 case DstStitchType.JUMP:                     b2 += (byte)131;                     break;                 case DstStitchType.STOP:                     b2 += (byte)195;                     break;                 default:                     b2 += 3;                     break;             };             b[0] = b0; b[1] = b1; b[2] = b2;             return b;         } 

Формирования машинной вышивки QR-кода можно посмотреть по ссылке.

QR-code geolocation

Скачать исходный код формирования машинной вышивки QR-кода можно скачать по следующей ссылке.

Загрузить консольное приложение формирующее файл вышивки можно по сылке.

В папке с приложением находятся необходимые библиотеки, исполняемый файл и текстовый файл содержащий информацию для кодирования.

Для запуска приложения наберите следующее в командной строке:

qrcodegen.exe test.asc

Приложение формирует файл с расширением .DST в папке с приложением. Возможно формирование векторного файла SVG и растрового файла PNG. Файл может быть открыть в программе для редактирования машинной вышивки, например http://florianisoftware.com.

Ссылки по теме

Site of Nathan Crawford – Код с этого сайта использован как основа для формирования PNG файла машинной вышивки.
Rudolf´s Homepage Описание формата Taijama DST
Embroidermodder site — Embroidermodder бесплатный инструмент для работы дизайнами машинной вышивки

ссылка на оригинал статьи http://habrahabr.ru/post/257763/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *