隐写术 21 - 可穿戴图灵机





5.00/5 (4投票s)
代码和纱线的乐趣。
引言
您可能还记得在学校学过的,图灵机是一个计算的数学模型。 它定义了一个抽象的机器,使用无限磁带上的字符工作。 理论上,一个模拟图灵机的系统能够支持计算机可以完成的所有任务。
磁带上的每个单元格只能写入一次。 对于第二次写入访问,您可以将使用的磁带复制到一个新的片段。 由于每个计算都可以用二进制完成,所以可写字符的字母表可以限制为{0, 1},虽然更大的字母表可能有利于节省磁带。 在本文中,我们将使用{0, 1, 2, 3}来以4为基数编码所有内容。
模拟无限可写磁带的一种廉价且简单的方法是一团纱线。 您可以通过编织不同的针法来写入字符。 如果您需要覆盖已使用的位置,只需开始一个新行; 这就像倒带并复制图灵机的磁带。
本文解释了如何将任何内容编码为针法,渲染编织图表,并再次解码完成的纱线磁带。 它使用Apache Batik SVG工具包来绘制图案。
您可能想将图案输入编织机,将其变成一个真正的硬件图灵机。 您也可以手工编织它们,仅仅通过让您的信使戴一顶温暖的帽子来传递秘密信息。
应用程序
该Java应用程序接受文本,将字符转换为以4为基数的数字组,并将这些数字转换为一行针法
- 0 = 编织
- 1 = 反编
- 2 = 绕线,编织
- 3 = 绕线,反编
屏幕截图显示了从文本 "Hi CP!" 生成的编织图表。 只有第一行包含信息。 第二行用于减少数字“2”和“3”产生的多余针法。 所有后续的行只是重复,使代码看起来像装饰。
这样的一对行在布料中看起来会很奇怪。 但是通常,针织的东西有装饰图案,如果你经常重复,一切看起来都像一个图案。 因此,可以随意重复代码行,直到结果看起来相当不显眼。
编码和解码
这个小演示只使用拉丁文本。 由于一个字符是一个介于0和255之间的值,它对应于0和3之间的四个数字。 这些数字被连接成一个长字符串,然后发送到图形帧。
private void encode(String clearText){
ByteBuffer bytes = Charset.forName("ISO-8859-15").encode(clearText);
StringBuilder resultBuilder = new StringBuilder(clearText.length() * 4);
// convert each character
while(bytes.hasRemaining()){
int currentValue = bytes.get();
String currentWord = Integer.toString(currentValue, 4);
// pad the number to a length of 4
while(currentWord.length() < 4){
currentWord = "0" + currentWord;
}
resultBuilder.append(currentWord);
}
ChartFrame frame = new ChartFrame();
frame.drawChart(clearText, resultBuilder.toString());
}
当手动阅读一块布料时,您可以记下您识别出的针法的数字...
... 然后用你找到的针法行调用解码器。 它们将被解码为原始文本
decode
方法将数字链转换回原始消息。private String decode(String encodedText){
int n = 0;
StringBuilder resultBuilder = new StringBuilder(encodedText);
// translate blockwise to characters
while(n < resultBuilder.length()){
String currentWord = resultBuilder.substring(n, n+4);
int currentValue = Integer.parseInt(currentWord, 4);
String currentClearChar;
// decode or ignore
if(currentValue < 256){
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{(byte)currentValue});
currentClearChar = Charset.forName("ISO-8859-15").decode(buffer).toString();
}else{
currentClearChar = "?";
}
// compress 4 digits to 1 character
resultBuilder.replace(n, n+4, currentClearChar);
n++;
}
return resultBuilder.toString();
}
渲染图表
对于每个数字,都必须绘制相应的针法。 我使用了Craft Yarn Council的针织图表符号,这些符号很容易用几何形状来渲染。
图表由三个部分组成:列号,编码有效负载的奇数行,固定针法数量的偶数行。
public void drawChart(String title, String base4Digits){
titleText = title;
// prepare SVG document
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
SVGDocument doc = (SVGDocument) impl.createDocument(svgNS, "svg", null);
SVGGraphics2D g = new SVGGraphics2D(doc);
g.setFont(new Font("sans serif", Font.PLAIN, 10));
// calculate chart width
countColumns = getCountCols(base4Digits);
countRows = (int)(200 / boxSize);
svgCanvas.setSize((2+countColumns)*boxSize, countRows*boxSize);
// fill background
g.setPaint(Color.darkGray);
g.fillRect(0, 0, svgCanvas.getWidth(), svgCanvas.getHeight());
// draw odd coding rows, even fixing rows
for(int y = 0; y < (countRows-2); y+=2){
// code symbols may add stiches
drawOddRow(y, base4Digits, g);
// fix excess stitches by knitting two together
drawEvenRow(y+1, base4Digits, g);
}
// draw column numbers
for(int x=0; x < countColumns; x++){
drawColNumber(x+1, countRows-1, g);
}
// prepare and show frame
titleText = titleText + " - " + "start with " + (base4Digits.length()+2) + " stitches";
g.getRoot(doc.getDocumentElement());
svgCanvas.setSVGDocument(doc);
svgCanvas.getParent().setPreferredSize(svgCanvas.getSize());
pack();
setVisible(true);
}
绘制一行很简单:在每一端放置一个编织符号,然后用符号填充空间
private void drawOddRow(int y, String base4Digits, SVGGraphics2D g){
// label and left edge
drawRowNumber(0, y, g);
drawKnit(1, y, g);
// append one or two symbols per digit
int x = 2;
for(int n=0; n < base4Digits.length(); n++){
char currentChar = base4Digits.charAt(n);
/* draw box
0 = knit
1 = purl
2 = yarn over, knit
3 = yarn over, purl
*/
switch(currentChar){
case '0': drawKnit(x, y, g); break;
case '1': drawPurl(x, y, g); break;
case '2': drawYarnOver(x, y, g);
drawKnit(++x, y, g);
break;
case '3': drawYarnOver(x, y, g);
drawPurl(++x, y, g);
break;
}
x++;
}
// right edge
drawKnit(x, y, g);
}
图表符号是基本形状。 以下是一些例子
private void drawBox(int col, int row, SVGGraphics2D g){
int x = col*boxSize;
int y = row*boxSize;
Rectangle2D.Double box = new Rectangle2D.Double(x, y, boxSize, boxSize);
g.fill(box);
g.setColor(Color.black);
g.draw(box);
}
private void drawColNumber(int col, int row, SVGGraphics2D g){
g.setColor(Color.black);
g.setPaint(Color.cyan);
drawBox(col, row, g);
g.drawString(String.valueOf(col),
(col*boxSize)+boxPadding,
(row*boxSize)+boxSize-boxPadding);
}
private void drawYarnOver(int col, int row, SVGGraphics2D g){
g.setColor(Color.black);
g.setPaint(Color.white);
int x = col*boxSize;
int y = row*boxSize;
int height = boxSize-(boxPadding*2);
Ellipse2D circle = new Ellipse2D.Double(
x+boxPadding, y+boxPadding,
height, height);
drawBox(col, row, g);
g.draw(circle);
}
关注点
到目前为止,我们所做的只是编码和解码。 我们能够用字符填充我们蓬松的图灵机的磁带。
这足以在穿着羊毛围巾的明眼人身上隐藏文字,这可能是你将秘密从阿拉斯加走私到西伯利亚的唯一机会。
下一步可能是编写编织机程序,使其逐行处理计算。 我希望你喜欢这篇文章,并仔细观察其他人的纺织品。