概要

  • PNG に文字列を埋め込む。
  • HTML5 の Canvas を使って PNG 画像に文字列を埋め込んだり、取り出したりする。
  • 文字列を埋め込む際に、不透明度を 0 にしておけば見た目で分からなくなると思ったんだが、そう上手くいかなかったわ。
  • ゴミデータが表示されるのに対処。(割り算を int だと思い込んでたわ, 2011.03.04)
  • ヘッダとして「EmbPng」が埋め込まれてるときだけ取り出し処理をするようにした。(2011.03.04)
  • 本来はiTXtチャンクに埋め込むべきなんだろうけど、JavaScriptからバイナリをいじれるのかな? Base64をデコードしてやればいけるんだろか。

ソース

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Content-Style-Type" content="text/css">
<link rel="stylesheet" type="text/css" href="take.css">
<title>PNGへの文字列の埋め込み</title>
</head>
<body onload="loadImage()">
<h1>PNGへの文字列の埋め込み</h1>
<div><a id="aCanvas" href=""><canvas id="cvsImg" width="300" height="300"></canvas></a></div>
<div><textarea id="txtSrc" cols="60" rows="8"></textarea></div>

<h2>埋め込み</h2>
<p>
文字列を埋め込んだ後、画像をクリックすると保存ダイアログが出るので拡張子を「png」に変えて保存すること。<br>
<button id="btnEmbed" onclick="embed();">埋め込み</button>
</p>

<h2>取り出し</h2>
<p>
アップロードする画像ファイルはこのスクリプトのあるフォルダの「data」に予め入れておくこと。<br>
<input id="fInput" name="fInput" type="file"> <button id="btnLoad" onclick="loadImage();">読み込み</button>
</p>

<h2>セキュリティ設定</h2>
<p>ローカルファイルを読み込んで加工しようとするとセキュリティ違反となることがある。<br>
設定で許可することができるが、セキュリティを弱めることになるので使用には注意すること。</p>
<dl>
<dt>Firefoxの場合</dt><dd>「about:config」で「security.fileuri.strict_origin_policy」を「false」に設定する。</dd>
<dt>Google Chromeの場合</dt><dd>起動オプションに「--allow-file-access-from-files」を付加する。</dd>
</dl>

<h2>雑感</h2>
<ul>
<li><p>文字列を埋め込むときに不透明度を0にして見た目で分からなくなる予定だったが、そう上手くいかなかったわ。</p></li>
<li><p>Firefox 3.6.13 だと色が変わらないけど、Opera 10.63 だと色が変わっちゃうなぁ。(--;)</p></li>
</ul>

<h2>リンク</h2>
<ul>
<li><p><a href="http://www.html5.jp/">HTML5.JP</a> / <a href="http://www.html5.jp/canvas/">Canvas</a> / <a href="http://www.html5.jp/canvas/how6.html">画像を組み込む</a> / <a href="http://www.html5.jp/canvas/ref/method/getImageData.html">getImageData</a></p></li>
<li><p><a href="http://blog.livedoor.jp/silverlight2_games/">silverlight3でgamesのブログ</a> / <a href="http://blog.livedoor.jp/silverlight2_games/archives/1216100.html">JavaScript を PNG画像化</a></p></li>
<li><p><a href="http://d.hatena.ne.jp/edvakf/">by edvakf in hatena</a> / <a href="http://d.hatena.ne.jp/edvakf/20091009/1255052759">「HTML5のcanvasで作る画像フィルター」は自分ならこう書く</a></p></li>
<li><p><a href="http://d.hatena.ne.jp/chiheisen/">地平線に行く</a> / <a href="http://d.hatena.ne.jp/chiheisen/20100815/1281885412">canvas の getImageDataが少しめんどくさい</a></p></li>
<li><p><a href="http://www.webtoolkit.info/">Webtoolkit.info</a> / <a href="http://www.webtoolkit.info/javascript-utf8.html">Javascript UTF-8</a></p></li>
</ul>
<script type="text/javascript" src="embedPNG.js"></script>
<script type="text/javascript" src="webtoolkit.utf8.js"></script>
</body>
</html>

embedPNG.js

/*
	PNGへの文字列の埋め込み
*/

// Opera対応 http://d.hatena.ne.jp/edvakf/20091009/1255052759
if (window.CanvasRenderingContext2D && !CanvasRenderingContext2D.prototype.createImageData && window.ImageData) {
	CanvasRenderingContext2D.prototype.createImageData = function(w,h){return new ImageData(w,h) };
}

var _myID = 'EmbPng';

function embed(){
	var txtSrc = Utf8.encode(document.getElementById('txtSrc').value);
	var pSrc = 0;
	var Src = [];
	for( var i=0; i<_myID.length; ++i ){
		Src[ pSrc++ ] = _myID.charCodeAt( i );
	}
	var lenTxt = txtSrc.length;
	Src[ pSrc++ ] = lenTxt % 256;
	Src[ pSrc++ ] = Math.floor(lenTxt / 256) % 256;
	Src[ pSrc++ ] = Math.floor(lenTxt / 256) / 256;
	lenTxt += pSrc;
	for( var i=0; i<txtSrc.length; ++i ){
		Src[ pSrc++ ] = txtSrc.charCodeAt( i );
	}
	for( var i=0; i<4; ++i ){
		Src[ Src.length ] = 0;
	}
	var ctx = document.getElementById('cvsImg').getContext('2d');
	ctx.width = 300;
	ctx.height = 300;
	var img = ctx.getImageData(0, 0, ctx.width, ctx.height);
	var rgba = img.data;
	var pImg = 0;
	for( pSrc=0; pSrc < lenTxt && pImg < ctx.width * ctx.height * 4; ){
		rgba[ pImg++ ] = Src[ pSrc++ ];	// R
		rgba[ pImg++ ] = Src[ pSrc++ ];	// G
		rgba[ pImg++ ] = Src[ pSrc++ ];	// B
		rgba[ pImg++ ] = 255;			// A
	}
	img.data = rgba;
	ctx.putImageData(img, 0, 0);

	var cvs = document.getElementById('cvsImg');
	var aCvs = document.getElementById('aCanvas');
	aCvs.href = cvs.toDataURL().replace('image/png', 'application/octet-stream');
};

function pickout(){
	var LF = String.fromCharCode(10);
	var ctx = document.getElementById('cvsImg').getContext('2d');
	var img = ctx.getImageData(0, 0, ctx.width, ctx.height);
	var rgba = img.data;
	var pImg = 0;
	var key = '';
	for( var i=0; i<3; ++i ){
		key += String.fromCharCode( rgba[ pImg++ ] );
	}
	pImg++;
	for( var i=0; i<3; ++i ){
		key += String.fromCharCode( rgba[ pImg++ ] );
	}
	pImg++;
	if ( key != _myID ){
		return;
	}
	var lenTxt = 0;
	for( var i=0; i<3; ++i ){
		lenTxt += rgba[ pImg++ ] * Math.pow( 256, i )
	}
	pImg++;
	var pSrc = 0;
	var Src = [];
	for( ; pSrc < lenTxt && pImg < ctx.width * ctx.height * 4; ){
		Src[ pSrc++ ] = rgba[ pImg++ ];	// R
		Src[ pSrc++ ] = rgba[ pImg++ ];	// G
		Src[ pSrc++ ] = rgba[ pImg++ ];	// B
		pImg++;							// A
	}
	var txtDst = '';
	var txtDebug = LF + lenTxt + LF;
	for( var i=0; i < lenTxt; ++i ){
		txtDst += String.fromCharCode( Src[ i ] );
		txtDebug += ('0' + Src[ i ].toString(16)).slice(-2) + ' ';
	}
	document.getElementById('txtSrc').value = Utf8.decode( txtDst ); // + txtDebug;
};

function loadImage(){
	var fname = document.getElementById('fInput').value || 'base.png';
	fname.match( /([^\\\/\:]+)$/ );
	fname = 'data/' + RegExp.$1;
//	alert(fname);
	var cvs = document.getElementById('cvsImg');
	if ( ! cvs || ! cvs.getContext ) { return false; }
	var ctx = cvs.getContext('2d');
	ctx.width = 300;
	ctx.height = 300;
	var img = ctx.createImageData(ctx.width, ctx.height);
	var rgba = img.data;
	for( var i=0; i<rgba.length; ++i ){
		rgba[ i ] = 0x00;
	}
	img.data = rgba;
	ctx.putImageData(img, 0, 0);

	/* Imageオブジェクトを生成 */
	var img = new Image();
	img.src = fname + "?" + new Date().getTime();
	/* 画像が読み込まれるのを待ってから処理を続行 */
	img.onload = function(){
		ctx.drawImage(img, 0, 0);
		pickout();
	}
};

// EOF

リンク