65.9K
CodeProject 正在变化。 阅读更多。
Home

隐写术 21 - 可穿戴图灵机

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2017年2月15日

CPOL

3分钟阅读

viewsIcon

9723

downloadIcon

106

代码和纱线的乐趣。

引言

您可能还记得在学校学过的,图灵机是一个计算的数学模型。 它定义了一个抽象的机器,使用无限磁带上的字符工作。 理论上,一个模拟图灵机的系统能够支持计算机可以完成的所有任务。

磁带上的每个单元格只能写入一次。 对于第二次写入访问,您可以将使用的磁带复制到一个新的片段。 由于每个计算都可以用二进制完成,所以可写字符的字母表可以限制为{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);
}

关注点

到目前为止,我们所做的只是编码和解码。 我们能够用字符填充我们蓬松的图灵机的磁带。

这足以在穿着羊毛围巾的明眼人身上隐藏文字,这可能是你将秘密从阿拉斯加走私到西伯利亚的唯一机会。

下一步可能是编写编织机程序,使其逐行处理计算。 我希望你喜欢这篇文章,并仔细观察其他人的纺织品。

© . All rights reserved.