Introduction
Nowadays everybody knows about QR-codes. The QR-code structure was invented in Japan by Masahiro Hara. This article describes the process of converting QR-code matrix into embroidery design format (Taijama DST).
Problem
There is no default function to create Qr-code in embroidery software so far. It is only possible to generate a raster picture in any on-line service, which provides a Qr-code image. And then generate stitches on that picture in an embroidery program. It takes not much time for 3 or 5 QR-codes, although for larger quantity of QR-codes it takes a lot of time.
Solution
To automate QR-code generation is used external library. And then for six areas of QR-code column by column stitches are generated and finaly are saved in a file. Deviding in six areas is shown on folowing picture:
Console application which demonstates QR-code generation can be downloaded by the following link:
In the root folder of the application are an executable file, dll and a text file test.asc. To start the program from command prompt just type the following:
qrcodegen.exe test.asc
The application generates the new file with .DST extension. File can be opened with any embroidery design program. Input string can contains visit card format (VCARD) ,geolocation data (GEO), URL link or just simple text.
Example of VCARD format:
BEGIN:VCARD
VERSION:3.0
FN:John Doo
N:Jonh
ORG:Company name
URL:http:
EMAIL;TYPE=INTERNET:vasya.kvakin@example.com
END:VCARD
Input data in case of geographic coordinates, should contain longitude and latitude devided by comma separator and go after word GEO with colon:
GEO:30.31616, 59.95015
If you have access to an embroidery machine you be able to stitch this QR-code on a T-shirt or any clothes you choose:
Background
The code is written in C#. It uses MessagingToolkit.QRCode
library to create QR-code matrix from a string. Library is available for installation from nuget:
PM> Install-Package MessagingToolkit.QRCode
Input string could be a representation of VCARD, GEO coordinates or just an URL as noticed earlier.
QR-code matrix is created as two dimensional jugged boolean array.
At this point we have a boolean matrix of QR-Code and the next step is generating a list of lines. Lines are vertical series of cells without gaps.
These lines are used to create blocks of stitches, so stitches are composed in blocks. Blocks are vertical or horizontal bars, they are set of stitches.
The three distinctive squares are located at the corners of the QR code. Therefore QR-code is divided into six areas. The first area is the left top square, so it is generated first. Then the second area is generated - vertical bars between the previous area and the the square at the bottom. The third area is the left bottom square. Then column by column, the fourth area is generated until the top right square is reached. The top right distinctive square is generated as the first and second square. Finally the sixth area is generated column by column.
Code description
First thing to do is to generate QR-code matrix.
Class QRCodeCreator
This class uses MessagingToolkit.QRCode.Codec
namespace to generate a matrix of QR-code:
using System.Text;
using MessagingToolkit.QRCode.Codec;
namespace EmbroideryFile.QRCode
{
internal class QRCodeCreator
{
public bool[][] GetQRCodeMatrix(string DataToEncode)
{
if (string.IsNullOrEmpty(DataToEncode))
return null;
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 is set to UTF8 to have ability to encode non latin characters. Encoding redundancy is set to low level (QRCodeEncoder.ERROR_CORRECTION.L) into property QRCodeErrorCorrect. It supposes QR-code will not be damaged, so hi level of redundacy is excesive. This class is aggregated in class QRCodeStitcher
.
Class QRCodeStitcher
All stitch blocks creation is implemented in this class. It proceeds in two steps:
-
Prepare a list of vertical lines without gaps from cells for each of six areas of QR-code matrix;
-
Create stitch blocks for each continuous line.
To achieve first step it goes throught columns of matrix cells and when next empty cell is detectect then the current line is finished and is added to the result list. So from the boolean matrix a list of continuous lines is created. Each line contains information about the start point, end point, length of line and a flag of touching the bottom of QR-code.
For second step it goes through each line cell by cell and add zigzagified stitches to each stitch block result.
In a line bar step in Y and X axis and cell size are hard coded: dX
= 25; dY
= 2; cellSize
= 25; The units here is 0.1 mm.
Class QRCodeStitcher
has class QRCodeCreater
as a member _qrCode
. When property Info
of type QRCodeStitchInfo
is set then in method SetInfo
property Matrix
of _info
memeber is calculated by GetQrCodeMatrix
method.
Line has the following structure:
public struct Line
{
public Coords Dot1 { get; set; }
public Coords Dot2 { get; set; }
public int Length { get; set; }
public bool Lowest { get; set; }
}
This method generates all stitch blocks for all six areas:
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;
}
Method GenerateBoxStitchBlock is used to generate inner box of distinctive squares:
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;
}
Method GetRectangleSatin creates blocks of stitches for distinctive rectangle.
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;
}
Zigzaggified columns are formed by following method:
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;
}
Even columns are stitched from top to bottom, odd columns are stitched from bottom to top.
Method GenerateVerticalColoumnStitchBlock
creates list of stitches for line downwards.
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;
}
Method GetLaneList returns a list of continuous lines for specified by coordinates of corners area.
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)
{
_state = false;
for (int i = _topY; i <= _bottomY; i++)
{
ConsumeRelativeCellDown(j, i);
}
if (j >= _rightX) break;
_state = false;
for (int i = _bottomY; i >= _topY; i--)
{
ConsumeRelativeCellUp(j + 1, i);
}
}
return _lines;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("GetLineList(): {0}",ex));
throw;
}
}
Method ConsumeRelativeCellDown is used for even lines on the way from the top cell to the bottom cell:
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;
}
}
Method ConsumeRelativeCellUp is used for odd lines on the way from the bottom cell to the top cell:
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))
{
_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;
}
}
In the following video the whole process of stitching is shown: QR-code stitching video
Class QrcodeDst
Class aggregates DstFile and QrCodeStitcher instances. DstFile instance is used to save stitches in DST file. Blocks of stitches are generated in QrCodeStitcher instance.
public QrcodeDst()
{
_dst = new DstFile();
_stitchGen = new QrCodeStitcher();
}
Class has the following property setter:
public QRCodeStitchInfo QrStitchInfo
{
set { _stitchGen.Info = value; }
}
In class QrcodeDst
is implemented method FillStreamWithDst(Stream stream)
to save generated design of QR-code in Tajima DST format.
Following code demonstrate how to use QrcodeDst instance to read text from a file and save it in DST format:
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);
}
}
}
To save stitches into an embroidery Tajima DST format there is a desription of Tajima DST file on a stage. Next paragraph describes it shortly.
Tajima DST file format
Conversion from a list of stitches to DST file bytes is based on format description from here. In a file we have a sequence of stitches as comands of needle movements. Stiches are stored as offsets from previous stitch positon. For each stitch the type of comand is also stored. Types of stitch are:
DST file has a header. Stitch data starts at the 512 byte position with numeration from zero.
Stitch is coded with three bytes:
Bit no | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
Byte 1 | y+1 | y-1 | y+9 | y-9 | x-9 | x+9 | x-1 | x+1 | |
Byte 2 | y+3 | y-3 | y+27 | y-27 | x-27 | x+27 | x-3 | x+3 | |
Byte 3 | jump | stop | y+81 | y-81 | x-81 | x+81 | always on | always on |
DST file ends with three bytes: x00 00 F3
Following method encode stitch to bytes with type information:
byte[] encode_record(int x, int y, DstStitchType stitchType)
{
byte b0, b1, b2;
b0 = b1 = b2 = 0;
byte[] b = new byte[3];
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;
}
By this link it is possible to generate QR-Code embroidery online:
To see source code of generating QR-code embroidery in .DST format follow the link here.
Related links