[Из песочницы] Генерация QR-кода в формате файла машинной вышивки Tajima DST

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

eab9c0e2b0904789a0464c86d8d28b0a.png

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

Вышивка протестирована на вышивальной машине модели 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 Формирование всех видов блоков стежков реализовано в этом классе.Это обеспечивается следующими этапами: Формирование списка непрерывных линий для каждой из 6-ти областей; Генерация стежков для каждой области по списку линий. Для формирования списка вертикальных линий по матрице 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> GenerateQRCodeStitchesBoxed () { var blocks = new List>(); 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> 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>(); 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-кода: ///

/// Создаёт список стежков для заполненного квадрата /// /// Горизонтальная позиция верхней левой ячейки квадрата /// Вертикальная позиция верхней левой ячейки квадрата /// Размер квадрата /// Список координат private List GenerateBoxStitchBlock (int cellHorizonPos, int cellVerticalPos, int boxSize) { var block = new List(); 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; } Блоки стежков для последовательности вертикальных линий формируются в следующем методе: /// /// Формирует список блоков стежков по списку непрерывных вертикальных линий /// private List> GetSatinStitches (List lanes) { List> blockList = new List>(); foreach (var lane in lanes) { List 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 (). /// /// Возвращает список вертикальных линий для указанной по угловым ячейкам области /// /// X координата крайней ячейки области /// Y координата крайней ячейки области /// X координата крайней ячейки области /// Y координата крайней ячейки области /// private List 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-кода. /// /// Проверка прерывания текущей линии при проходе сверху вниз /// /// /// 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-кода. Следующий код реализует логику добавления стежков в линию: ///

/// Формирование стежков вертикальной линии из соответсвующей позиции /// /// абсцисса верхней ячейки линии /// ордината /// private List GenerateVerticalColumnStitchBlock (int cellHorizonPos, int cellVerticalPos, int length) { var block = new List(); 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 бесплатный инструмент для работы дизайнами машинной вышивки

© Habrahabr.ru