2010年09月05日 05:30 [Edit]

perl & javascript - PNGにテキストを埋め込む

いっそこんな利用法はどうか。

404 Blog Not Found:Ajax - データ交換 via PNG
使いどころが限定的すぎる。

Demo 0

アイコンの中にエンコーダーのソースコードを埋め込んである。

$('#demo0 input').click(function(){
    $('#demo0dst').text(showTextInPNG($('#demo0img').get(0)));
});

Perlでテキストを取り出すソースコードは以下のとおり。

#!/usr/bin/env perl
use strict;
use warnings;
use Imager;
use autodie;

my $img = Imager->new();
$img->read( file => shift ) or die $img->errstr;
$img->write( data => \my $pnm, type => 'pnm' ) or die $img->errstr;
my $hdr = do {
    $pnm =~ s/((?:[^\n]+\n){4})//;
    $1;
};
my @pnm = map { ord } split //, $pnm;
my $txt = '';
for my $i (0..+@pnm / 3){
    my $ord = (($pnm[3*$i]   & 0b00000111)<<5)
            + (($pnm[3*$i+1] & 0b00000011)<<3)
            +  ($pnm[3*$i+2] & 0b00000111);
    last if ! $ord;
    $txt .= chr $ord;
}
print $txt;

どうやってるの?

要するに、24bitカラーの元画像を16bit(RGB=565)に減色した上で、開いた8bitの中に1 byteのテキストを埋め込んでいる。ピクセル数以下のバイト数の任意のテキストが埋め込める。テキストを埋め込んだ部分は仮に元の色が#000000だとしても高々#070307にしかならず、見ての通り人の目ではほとんど区別がつかない。

Demo 1

Get Image from URL:
Size:
0x0=0bytes
Text: 0
and
$('#demo0 input').click(function(){
    $('#demo0dst').text(showTextInPNG($('#demo0img').get(0)));
});

$('#demo1get').click(function(){
  $.get('http://api.dan.co.jp/datauri/' + $('#demo1url').val(), function(txt){
    $('#demo1img').attr('src', txt)
  })
});

$('#demo1img').load(function(){
  $('#demo1w').text(this.width);
  $('#demo1h').text(this.height);
  $('#demo1s').text(this.height*this.width);
});

$('#demo1txt').keyup(function(){
  var text = this.value,
      chars = text.length,
      bytes = Base64.utob(text).length;
  $('#demo1st').text([chars, 'chars,', bytes, 'bytes'].join(' '));
}).trigger('keyup');

$('#demo1embed').click(function(){
  $('#demo1timg').attr('src', 
    hideTextInPNG($('#demo1img').get(0), $('#demo1txt').val())
  );
});

$('#demo1extract').click(function(){
  $('#demo1dst').text(showTextInPNG($('#demo1timg').get(0)));
});

JavaScriptによる実装

/*
 * You need base64.js.
 * cf: http://blog.livedoor.jp/dankogai/archives/51067688.html
*/

showTextInPNG = function(node) {
    var el  = document.createElement("canvas"),
        ctx = el.getContext("2d"),
        w   = el.width  = el.style.width  = node.width,
        h   = el.height = el.style.height = node.height;
    ctx.drawImage(node, 0, 0);
    var d = ctx.getImageData(0, 0, w, h).data,
        a = [];
    for(var i = 0, l = d.length; i < l; i += 4) {
        var c = ((d[i]   & 0x7) << 5)
              + ((d[i+1] & 0x3) << 3)
              +  (d[i+2] & 0x7);
        if (!c) break;
        a.push(String.fromCharCode(c));
    }
    return Base64.btou(a.join(''));
};

hideTextInPNG = function(node, txt) {
    var el  = document.createElement("canvas"),
        ctx = el.getContext("2d"),
        w   = el.width  = el.style.width  = node.width,
        h   = el.height = el.style.height = node.height,
        bin = Base64.utob(txt);
    ctx.drawImage(node, 0, 0);
    var imd = ctx.getImageData(0, 0, w, h),
        d = imd.data,
        a = [];
    for(var i = 0, l = d.length; i < l; i += 4) {
        var ord = bin.charCodeAt(i >> 2);
        d[i]   = (d[i]   & 0xf8) + ((ord & 0xf8) >> 5);
        d[i+1] = (d[i+1] & 0xfc) + ((ord & 0x18) >> 3);
        d[i+2] = (d[i+2] & 0xf8) +  (ord & 0x07);
    }
    ctx.putImageData(imd, 0, 0);
    return el.toDataURL('image/png');
};

See Also:

Dan the Steganographer


この記事へのトラックバックURL