みんなでプロ生ちゃんになる

(みんなで誰かになるブックマークレット・ジェネレーター)

キモいタイトルですが、・・・Twitterのサイトで全員一緒にプロ生ちゃんになるためのブックマークレットを作ることができます。 まあ、プロ生ちゃんでなくても、友利奈緒でも、うまるちゃんでも、福山雅治でも、好きな名前で構いません。 何故このようなものを作ったのか自分でもわかりません。

※ ウェブブラウザでのみ動作します。動作確認は、Windows 8.1/Chrome,IE11 です。

1.下の欄に好きな名前を入力します。仮にここでは「プロ生ちゃん」とします。

2.下の欄の文字を選択し(トリプルクリックが早いですね)、クリップボードにコピーします。
javascript:var a=document.querySelector("li#user-dropdown img.avatar").getAttribute("data-user-id"),b="a.ProfileHeaderCard-nameLink",h="div.DashboardProfileCard-name>a",f="プロ生ちゃん",d=["a .fullname","a.js-user-profile-link>b","div .QuoteTweet-fullname","li.ScrollBump a.js-user-profile-link","p.social-proof a.js-user-profile-link","div .ProfileNameTruncated-link","div.dashboard p.metadata a.js-nav",h,b],g=document.querySelector(b)?document.querySelector(b).innerText:document.querySelector(h)?document.querySelector(h).innerText:"",c=function(){var e=document.querySelectorAll(d.join(",")),t=document.querySelectorAll(".ProfileUserList a, .ProfileCard a, .DashboardProfileCard-content a, a.js-tooltip, img.js-user-profile-link"),r=document.querySelectorAll(".ScrollBump-title span");e&&[].forEach.call(e,function(e){e.innerText=f}),t&&[].forEach.call(t,function(e){e.setAttribute("data-original-title",f),e.setAttribute("original-title",f),e.setAttribute("title",f),e.setAttribute("alt",f)}),r&&[].forEach.call(r,function(e){e.innerHTML=e.innerHTML.replace(g,f)})};setInterval("c()",100),document.title=document.title.replace(g,f),c();
3.どこか適当なウェブサイトをブックマークに保存します。
4.保存したブックマークを編集し、URL欄にさきほどコピーした文字を貼り付けて保存します。ブックマークの名前は自由に決めてください。

f:id:takemaruhirai:20150928214546p:plainf:id:takemaruhirai:20150928214839p:plain

5.Twitterサイトでブックマークをクリックして、「プロ生ちゃん」で満たされた画面を堪能します。

f:id:takemaruhirai:20150928220947p:plain

アニメは終了してしまいましたが、もちろん「友利奈緒」でも構いません。「友利奈緒」にすると、かなりウザい感じが楽しめます。お好みでどうぞ。

f:id:takemaruhirai:20150928221936p:plain

適当に楽しんでいただければ幸いです。同様のサービスがすでにあるかもしれませんが、これはそんな大層なものではありません。飽きたらさっさとブックマークを捨てましょう。

Ubuntu 14.04 で OpenVPN

はじめに

f:id:takemaruhirai:20150527120840p:plain

この記事は、OpenVPNを使用して外部から内部ネットワークにアクセスする仕組みを構築する手順をメモしています。いろいろ他のサイトを参考にしています。

Business VPN | Next-Gen VPN | OpenVPN

ところで

公式サイトに行くと VPN Solution という項目があって、ここから OpenVPN Access Serverという管理ツールをダウンロードできるのですが、GUIが使えたりしていろいろ便利そうではあるのですが、有料製品のようです。今回は、あくまで無料でVPNサーバーを構築する予定ですので、こちらではなく。必要なドキュメント等は、Communityのほうにあります。

f:id:takemaruhirai:20150527120959p:plain

前提となる環境

今回、サーバーには、Ubuntu 14.04 LTS Server を使用しています。これは、すでにインストールされ、内部ネットワーク環境に置かれているものとします。 内部環境のネットワークアドレスは 192,168.1.0/24 とし、ゲートウェイ192.168.1.1VPNサーバーのアドレスは 192.168.1.10 としています。 外部のクライアントPCから、内部のAPサーバーに アドレス 192.168.1.20 でアクセスできるところまでが目標です。

f:id:takemaruhirai:20150527150813p:plain

iPhoneからも接続することを考えていますので、接続方式は ブリッジ ではなく NAT で、仮想ネットワークドライバは TAP ではなく TUN を使用します。

作業前に

以下の作業のほとんどは、root権限で行う必要があります。Ubuntuの場合、普段は sudo で実行しますが、面倒なのであらかじめ root 権限を取得して作業しました。

$ sudo -i

OpenVPNUbuntu にインストールする

まずは、レポジトリの情報を最新にアップデートします。

# apt-get update

そして、パッケージをインストールします。 *1

# apt-get install openvpn easy-rsa libssl-dev openssl

サーバー証明書を発行する

サーバー用に、公開鍵(.crt)、秘密鍵(.key)、証明書署名要求(.csr)、DHパラメータ(.pem)を作成します。

証明書作成用のディレクトリを作成

証明書発行用のディレクトリが必要になります。Ubuntuには専用のコマンド make-cadir が用意されているようなのでこれを使います。場所は、通常 /etc/openvpn の下に、easy-rsa ディレクトリを作成するようです。作成後、そのディレクトリに移動します。

# make-cadir /etc/openvpn/easy-rsa
# cd /etc/openvpn/easy-rsa
/etc/openvpn
│
└─ east-rsa       ←

Ubuntu Manpage: make-cadir - create certificates dir

f:id:takemaruhirai:20150527160828p:plain

証明書作成用の環境変数を設定する

移動先の easy-rsa ディレクトリに、vars というファイルがあります。このファイルの中に、必要な情報を入力していきます。

# vi ./vars
export KEY_COUNTRY="JP"                 # 国
export KEY_PROVINCE="Tokyo"             # 都道府県
export KEY_CITY="Shinjuku"              # 市区町村
export KEY_ORG="My Company Co.Ltd."     # 組織
export KEY_EMAIL="admin@my-company.com" # メールアドレス
export KEY_OU="Sales"                   # 組織単位

変更した varsファイルを読み込みます。

# . ./vars

サーバー証明書を作成する

以下のコマンド(実行可能ファイル)を順に実行してください。

# ./clean-all
# ./build-dh
# ./pkitool --initca
# ./pkitool --server server

keysディレクトリに移動し、TLS認証鍵を作成します。

# cd ./keys
# openvpn --genkey --secret

以上の作業で、keysディレクトリに以下の鍵ファイルが作成されていると思います。

/etc/openvpn
│
└─ east-rsa
    │
    └─ keys
       ca.crt       ←
       ca.key       ←
       server.crt   ←
       server.csr   ←
       server.key   ←
       dh2048.pem   ←
       ta.key       ←

これらのファイルを /etc/openvpn ディレクトリの下へコピーします。

# cp ca.crt server.crt server.keydh1024.pem ta.key /etc/openvpn
/etc/openvpn
│   ca.crt       ←
│   server.crt   ←
│   server.key   ←
│   dh2048.pem   ←
│   ta.key       ←
│
└─ east-rsa
    │
    └─ keys
       ca.crt
       ca.key
       server.crt
       server.csr
       server.key
       dh2048.pem    
       ta.key

OpenVPNを設定する

設定ファイルの作成

OpenVPNの設定を環境に合わせて変更していきます。設定ファイルは、まだ作成されていません。/usr/share/doc/openvpn にサンプル設定ファイルがありますので、以下のコマンドを実行してこれを server.conf として /etc/openvpn にコピーします。

# gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz > /etc/openvpn/server.conf
/etc/openvpn
│   ca.crt
│   server.crt
│   server.key
│   dh2048.pem    
│   ta.key
│   server.conf   ←
│
└─ east-rsa
    │
    └─ keys

この server.conf が設定ファイルになりますので、これを編集します。

# vi /etc/openvpn/server.conf

VPN通信の設定(規定値のまま)

f:id:takemaruhirai:20150527174357p:plain

ポート番号とプロトコルを指定します。必要がなければ、規定値のままで構いません。クライアント側のルーターで既定のポート番号が制限されている場合などに、tcp/443を使用することなどが可能とのことです。

port 1194
;proto tcp
proto udp

接続用の仮想ネットワークデバイスを指定します。今回の例では、iPhoneからの接続を考慮して TUN を選択しています。規定で TUN となっているので、変更は必要ありません。

;dev tap
dev tun

VPN通信に使用するネットワークアドレスを指定します。クライアント・サーバー間はこのアドレスで通信が行われ、サーバーで内部ネットワークアドレスに変換されます。今回の場合、規定値をそのまま使用しています。後の手順で、内部ネットワーク側のルーター設定でこのアドレスを指定する必要があります。

server 10.8.0.0 255.255.255.0

証明書の指定(規定値のまま)

  1. で作成した証明書を指定します。今回の場合、とくに変更は必要ありません。
ca ca.crt
cert server.crt
key server.key

DHパラメータの指定

  1. で作成した dh2048.pem を指定します。今回の場合、以下のように変更する必要があります*2
;dh dh1024.pem
dh dh2048.pem

アクセス先となる内部ネットワークの設定

外部からアクセスしたいネットワークのアドレスを入力します。push "route ..." の箇所を変更してください。今回の例 192.168.1.0/24 の場合は、以下のように指定します。

push "route 192.168.1.0 255.255.255.0"

次に、push "redirect-gateway ..."の箇所のコメントアウトを外して有効化します。これを有効にすることで、WebブラウジングDNS参照など、VPN ClientのすべてのトラフィックVPNサーバー経由にするそうです。

push "redirect-gateway def1 bypass-dhcp"

そして、DNSの設定を変更します。push "dhcp-option DNS..."の箇所を変更します。今回の例では、DNSサーバーとして192.168.1.1を指定します。

push "dhcp-option DNS 192.168.1.1"

TLSの設定

ファイルの末尾に以下の情報を追加します。

mode server
tls-server

TLS暗号化を有効化します。

tls-auth ta.key 0

通信暗号化の設定

サーバー・クライアント間で用いる暗号化の方式を指定します。ここで指定した方式を、クライアント側からも指定する必要があります。ここが、サーバーとクライアントで異なっていると、クライアントからの接続時にエラーとなります。

cipher AES-256-CBC

サービス実行ユーザーの変更

規定では、OpenVPN は root ユーザーとして動作するため、これを nobody:nogroup に変更し、セキュリティを高めておきます。

user nobody
group nogroup

その他のオプション

クライアント間通信の有効化

client-to-client

複数台のクライアントで、同じ証明書を使い回す(非推奨)。

;duplicate-cn

VPN通信の圧縮。今回、これを有効にするとiPhoneからの接続がうまくいかなかったため、コメントアウトしました。

;comp-lzo

設定の反映

server.conf を保存したら、openvpnサービスを再起動します。

# service openvpn restart

参考サイト

内部ネットワークの他のサーバーにアクセスするための設定をする

以上の設定のみで手順を進めると、クライアント側からOpenVPNサーバー自体(図の192.168.1.10あるいは10.8.0.1)にはアクセス可能となりますが、内部ネットワークの他のサーバー等(イントラネット上の業務アプリケーションや共有フォルダなど)にアクセスしようとしても、パケットが届かないためエラーとなってしまいます。そこで、OpenVPNサーバー上で、クライアントから 10.8.0.0/24 経由で来たパケットを 192.168.1.0/24 に転送する必要があります。

f:id:takemaruhirai:20150527180912p:plain

フォワーディングの有効化

まず、IPフォワーディングを有効にします。カーネルパラメータを変更しますので、sysctl.conf を開きます。

# vi /etc/sysctl.conf

そして、以下の行のコメントアウトを解除して保存します。

net.ipv4.ip_forward = 1

設定を即座に反映するために、以下のコマンドを実行します。

# sysctl -p

ルーティング設定の追加

そして、フォワーディングの設定をします。他のサイトでは ufw を使用した例が多く示されていますが、ここでは iptables を利用しています。なお、iptablesは、すべてACCEPTとなっており、とくにフィルタリング等は行われていない(ファイアウォール等は別途設けられている)ケースを想定しています。

まず、iptables の設定を永続化するためのツールを導入します。

# apt-get install iptables-persistent

次に、iptables に以下の設定を追加します。ifconfig コマンドで、物理ネットワークデバイスのデバイス名を確認しておいてください。

# ifconfig

eth0   Link encap:Ethernet  HWaddr 00:0B:CD:1C:18:5A
       inet addr:192.168.1.10  Bcast:192.168.1.255  Mask:255.255.255.0
       ...

lo     Link encap:Local Loopback
       inet addr:127.0.0.1  Mask:255.0.0.0
       ...

tun0   Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
       inet addr:10.8.0.1  P-t-P:10.8.0.2  Mask:255.255.255.255
       ...

そのうえで、以下のコマンドを入力してください。今回は、仮想ネットワークデバイスが tun0(10.8.0.0/24)、物理ネットワークデバイス eth0(192.168.1.0/24)であるとします。

# iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

設定を保存します。

# service iptables-persistent save

参考サイト

ルーターを設定する

OpenVPNサーバー上での設定は以上ですが、内部ネットワークのゲートウェイとなっているルーター機器で、いくつか設定を行います。

ポート開放を許可する

外部から udp/1194へのアクセスを許可します。ルーターフィルタリングなどを使用します。

f:id:takemaruhirai:20150527194141p:plain

NTT OG410Xiの例

f:id:takemaruhirai:20150527193535p:plain

規定で、何もフィルタリングされていない場合は空欄。

外部からの udp/1194 宛てのパケットを VPNサーバーに送る

次に、外部のVPNクライアントからのアクセスを、VPNサーバーを経由するように設定します。ルーター静的IPマスカレードを使用します。

f:id:takemaruhirai:20150527223200p:plain

NTT OG410Xiの例

f:id:takemaruhirai:20150527193614p:plain

内部からの 10.8.0.0/24 宛てのパケットを VPNサーバーに送る

そして、内部から、10.8.0.0/24 に宛てたパケットがすべて VPNサーバーを経由するように設定します。ルータールーティングなどを使用します。

f:id:takemaruhirai:20150527193855p:plain

NTT OG410Xiの例

f:id:takemaruhirai:20150527193555p:plain

参考サイト

クライアント証明書の作成

以上で、サーバー用の設定は完了です。あとは、外部から接続するクライアント端末ごとに、証明書を発行します。この証明書を用いて、各クライアントが内部ネットワークにアクセスします。ここでは、仮にアクセス先の グローバルIPアドレスが 123.456.78.9 であるとしています。

f:id:takemaruhirai:20150527204000p:plain

(ログアウトしていれば再度OpenVPNサーバーにログインし、)2. で作成した /etc/openvpn/easy-rsa ディレクトリに移動します。

# cd /etc/openvpn/easy-rsa
/etc/openvpn
│
└─ east-rsa       ←

作成済みの環境変数を読み込みます。

# . ./vars

作成したいクライアントの識別名を任意に命名します。ここでは、 client1 とします。以下のコマンドを実行してください。

# ./pkitool client1

もし、パスワードを設定したい場合には、--passオプションを使用します。証明書作成中にパスワードを聞かれますので、入力してください(確認を含めて2回)。ただし、4文字以上でないと怒られます。

# ./pkitool --pass client1

これで、keysディレクトリの中に、クライアント用の証明書が作成されているはずです。

/etc/openvpn
│
└─ east-rsa
    │
    └─ keys
       ca.crt
       ca.key
       server.crt
       server.csr
       server.key
       dh2048.pem    
       ta.key
       client1.crt  ←
       client1.csr  ←
       client1.key  ←

作成されたファイルのうち、client1.crtclient1.keyca.crtta.key を何らかの手段でクライアント端末に送ります。セキュリティに配慮して、できるだけ安全な方法で受け渡しをしてください。

/etc/openvpn
│
└─ east-rsa
    │
    └─ keys
       ca.crt       ←
       ca.key
       server.crt
       server.csr
       server.key
       dh2048.pem    
       ta.key       ←
       client1.crt  ←
       client1.csr
       client1.key  ←

VPNクライアントの設定(Windows

アプリケーションの入手

プラスシステムズ株式会社の提供するサイトから、vpnux Clientをダウンロードし、手順に従ってインストールします。*3こちらのソフトウェアの使用が、公式サイトでも推奨されているようです。

www.plum-systems.co.jp

設定

インストールが完了したら、vpnux Client を起動し、メニューの [プロファイル] → [追加]で設定画面を開きます。

f:id:takemaruhirai:20150527221517p:plain

f:id:takemaruhirai:20150527221533p:plain

上図のように設定をしたら、[保存]します。作成したプロファイルが、一覧画面に表示されたら完了です。

f:id:takemaruhirai:20150527221759p:plain

接続

起動時の画面から、作成済みのプロファイルを選択し、[接続]をクリックするだけです。非常に簡単です。

f:id:takemaruhirai:20150527221840p:plain

VPNクライアントの設定(iPhone

ovpnファイルの作成

新規に、テキストファイルを作成します。拡張子は .ovpnとしてください。ファイル名は仮に client1.ovpnとします。ファイルの内容は、以下のようなものになります。remoteの値には、接続先のグローバルIPアドレス、もしくはホスト名を入力します。 cipherの値を、2. で server.conf に指定した値と揃えることに注意してください。

tls-client
key-direction 1
remote 123.456.78.9
cipher AES-256-CBC
port 1194
proto udp
<ca>
-----      ca.crt の BEGIN CERTIFICATE ~ END CERTIFICATE までをペースト -----
</ca>
<cert>
----- client1.crt の BEGIN CERTIFICATE ~ END CERTIFICATE までをペースト -----
</cert>
<key>
----- client1.key の BEGIN ENCRYPTED PRIVATE KEY ~ END ENCRYPTED PRIVATE KEY までをペースト -----
</key>
<tls-auth>
-----      ta.key の BEGIN OpenVPN Static key V1 ~ END OpenVPN Static key V1 までをペースト -----
</tls-auth>

上記のようなファイルを作成したら、<ca><cert><key><tls-auth>各タグの中に該当する証明書(上記コメントのとおり)の鍵文字列を張り付けて保存します。

アプリの入手

iPhoneから App Store にアクセスし、OpenVPN Connect という無料アプリをダウンロードします。

OpenVPN Connect

OpenVPN Connect

設定のインポート

このアプリに、iTunes等の手段で、上記で作成した client1.ovpnを渡します。セキュアかどうか微妙なところですが、iPhoneのメールクライアントやファイラー等から .ovpnファイルを OpenVPN Connectで開くこともできます。

f:id:takemaruhirai:20150527212216p:plain

アプリでファイルを開くと、以下のような画面が表示されますので、+ボタンをタップします。これで、設定が完了します。

f:id:takemaruhirai:20150527224721p:plain

接続の開始

VPN接続を開始するには、下記の場所をタップしてください。

f:id:takemaruhirai:20150527215121p:plain

ステータスが Connected に変われば、接続完了です。

f:id:takemaruhirai:20150527215218p:plain

設定に問題がなければ、Safari等のブラウザやSMBに対応したファイラー等で、内部ネットワーク環境にアクセスできるはずです。下のアプリで、内部ネットワーク上の共有フォルダにアクセスることができました。

FE File Explorer: File Manager

FE File Explorer: File Manager

  • Skyjos Co., Ltd.
  • 仕事効率化
  • 無料

参考サイト

トラブルシューティング

もし、ステータスが接続済になっているにも関わらず、内部ネットワークにアクセスできない場合は、server.conf もしくは クライアントソフトウェア(あるいは client1.ovpn)の設定が間違っているか、ルーティングが正しく行われていないことが考えられます。

クライアント側の接続ログは、以下の場所から確認できますので、参考にしてください。

OpenVPN Connectの場合

f:id:takemaruhirai:20150527215232p:plain

サーバー側のログは、server.conf で何も設定を変更していなければ、システムログを確認します。

# view /var/log/syslog

以下に、私の引っかかったポイントをいくつか挙げておきます。

  • OpenVPNサーバーで、正しくIPフォワーディングの設定が出来ているか。とくに、sysctl.conf を変更し、反映したか。

    • VPNサーバー自体(192.168.1.10)にはアクセスできるが、他のサーバー(192.168.1.20)にはアクセスできない状態の場合、これが該当する可能性が高いです。
  • クライアントとサーバーで、暗号化方式を揃えたか。

    • サーバーで cipherAES-256-CBC としたなら、クライアント側でも同じにする必要があります。
  • 内部ネットワークからのレスポンスが正しく OpenVPNサーバーに戻ってくるか

    • ルーター 192.168.1.1 で、10.8.0.0/24 宛てのパケットを正しくルーティングする必要があります。

*1: 大抵の情報で上記のようになっていますが、 こちらのサイトだと、 libssl-devとopensslは指定していませんね。要調査。

*2:違いは、生成される鍵の長さだそうです。

*3:インストールせず、ポータブルな使い方もできるようです。

俳句プログラミング投稿作品について

f:id:takemaruhirai:20150308035618p:plain

ブログをつくってみました。いろいろ燻っているので、IT関連のテーマでこちらに書いていこうと思います。

まずは、「プロ生ちゃん 俳句プログラミング プチコンテスト」に応募したコードの解説から。ちょっと分かりにくかったかな、という反省を込めて。コンテストの詳細は、以下のとおりです。楽しそうだったので、なんとなくノリで参加してしまいました。

応募作品概要

応募作品は、こんな感じです。

ソースコードはこちらで公開しています。

プログラミング生放送のサイトでも取り上げて頂いています。

何をやっているのか

作品の説明に日本語をUTF-8で表すと3バイトなので、RGBのカラーコードから変換と書いたのですが、これだけだと何をやっているのか、いまひとつ伝わりにくいですね。なので、ここでもう少し詳細に説明しようと思います。

UTF-8では、日本語は3バイト

ウェブではすっかり標準化した文字コード UTF-8 ですが、Windows標準のShift_JIS等と違い、日本語に使われる漢字や仮名は2バイトではなく3バイト(24ビット)のコードで表されます。

UTF-8 - Wikipedia

たとえば、「あ」という文字をエンコードすると「%E3%81%82」になります。つまり、E38182という3バイトの16進数で表現可能ということです。

f:id:takemaruhirai:20150307222948p:plain

【みんなの知識 ちょっと便利帳】URLエンコード/デコードツール = UTF-8

カラーコードも3バイト

ところで、ウェブやPC上で扱われる「色」は、通常RGBつまり、赤・緑・青の光の三原色で表されます。とくに一般的なトゥルーカラーでは、赤・緑・青それぞれの要素が10進数で0~255の1バイト(8ビット)で表されます。3つの色要素を合わせて3バイト(24ビット)です。

RGB - Wikipedia, フルカラー - Wikipedia

たとえば、CSSなどで文字色や背景色を赤にしたい場合、#FF0000と表記しますが、これはそのRGBを16進数で表現したものです。

f:id:takemaruhirai:20150307224202p:plain

WEBカラーピッカー(Color Picker)

日本語を色に置き換えた画像を用意する

つまり、

  • 日本語は3バイトのコードで表現できる
  • 色は3バイトのコードで表現できる

ということです。ここで、「文字」⇒「3バイトのコード」⇒「色」と変換できるということを思いつきました(逆ではありません)。

では、早速、俳句を色に直してみます。例として芭蕉の句である

さまざまの 事思ひ出す 桜かな

なんてどうでしょう? 末尾に作者名も付けましょう。

文字 コード 文字 コード 文字 コード
E58FA4 E38284 E38284
E6B1A0 E38284 E38284
E38284 E38284 E38284
E38284 E38284
E38284 E38284 E88AAD
E38284 E89589

おお!!見事に桜の雰囲気を表すピンク色のイメージとなりました。ついでに空白文字「 」をコード化すると、%20 となります。

文字 コード
000020

それでは、これらの文字(色)を各所に散りばめた画像を作ってみます。せっかくなので、桜をイメージした画像にしました。プロ生ちゃんにも入ってもらいました。

f:id:takemaruhirai:20150307232928p:plain

呼び出したい文字(色)の座標をコード化する

さて、次は、上で用意した画像から俳句を抽出するのですが、各文字に対応する色は、桜の花びらとしてランダムな位置に散りばめてしまいました。なので、必要とする色が画像のどこにあるのかを座標を使って指定する必要があります。

たとえば、最初の「さ」という文字(%E5%8F%A4)は、画像の左上を原点とした座標上で x=450px, y=40px の位置にある花びらの色(#E58FA4)で表されます。つまり、「さ」という文字を、450, 040 という2つの数値で置き換えることができます。

f:id:takemaruhirai:20150307235937p:plain

同様にして、俳句を構成する文字列をすべて座標に置き換えると、

文字 座標 文字 座標 文字 座標
(450, 040) (320, 060) (122, 044)
(260, 040) (030, 030) (234, 122)
(060, 170) (160, 160) (170, 120)
(240, 020) (180, 180)
(044, 090) (444, 144) (270, 070)
(030, 130) (260, 100)
文字 座標
(026, 192)

というようになります。プログラム中でこの座標を利用して画像のピクセルデータを取得し、そのカラーコードを解析すれば、該当する文字が復元可能なはずです。

JavaScriptで画像解析を行うためには、HTML5 Canvasの機能を利用します。ごく簡単に述べると、以下のメソッドを利用して、(x, y) の座標から 任意の [width, height] のピクセルデータを配列として取得できるのです。

var id = context.getImageData(x, y, w, h);

取得した配列は、以下のような1ピクセル分のデータの繰り返しになっています。

添字 0 1 2 3
赤(R) 緑(G) 青(B) アルファ

配列の各要素の値は10進数ですが、次の方法で16進数に変換することができます。

var dec = 10;
var hex = dec.toString(16);

JavaScriptによる画像解析の方法は、wata_htn様の記事に分かりやすく説明されています。


[3][HTML][canvas]canvasで画像解析 - wataメモ

座標コードも画像に埋め込む

これで、「座標」⇒「色」⇒「文字」という順で復元が可能になりました。この座標データを、数値の配列などの形でプログラムコードの中に保持すれば、その値を走査することで元の俳句の文字列が取得できるという算段です。

しかし、それでは、「結局のところコード中に俳句と等価なデータを持っている」ことになり、なんだか片手落ちな気がしました。できれば、俳句データはすべて画像が保持しており、(理論上は)コードを変更しなくても画像を差し替えれば別の俳句が表示されるようにしたかったのです。その無意味なこだわりのため、座標データも画像中に埋め込んでしまいました。

座標データは、画像の y=0 の行に埋め込みます。桜の画像の左上部分を拡大すると、下のように1ピクセルごとの色が並んでいます。

f:id:takemaruhirai:20150308005628p:plain

この色の並びを左から順にカラーコード、さらに座標に該当する文字に直すと、以下のようになっています。

ピクセル カラーコード 座標 文字
R 225 450
G 020 040
B 130 260
A 255
R 020 040
G 030 060
B 085 170
A 255
R 120 240
G 010 020
B 022 044
A 255
R 045 090
G 013 026 (空白)
B 096 192
A 255

この調子で最後まで走査します。

ただし、やむを得ずいくつかの制約を設けてあります。

  • 画像のサイズが 256×256 を越えているため、プログラムでは実際にはRGBの各値×2を実際の座標値として解釈しています。そのため座標は偶数でなければなりません。
  • アルファ値は意図通りにデータとして拾い難いので、座標として採用せずスキップしています。
  • 値が0の場合は無視します。x=0 あるいは y=0 の位置は、文字情報として使わないことにします。

こうして、走査したピクセルデータを基にプログラムで「座標」⇒「色」⇒「文字」と復元すれば、もとの俳句を得ることができます。あとは、得られた文字列を適当に加工して、Canvas 上に配置すればいいだけです。

JavaScriptコード解説

canvas の準備

まず変数の宣言、そしてHTML5 Canvasを準備します。

var haiku, id;
var canvas = document.createElement("canvas");
var ctx    = canvas.getContext('2d');

次に、Imageオブジェクトで画像をCanvasに読み込みます。

var img = new Image();

// JSFiddleに画像を添付するのは難しそうなので、画像は Dropbox から読み込む
img.crossOrigin = "Anonymous";
img.src  = "https://dl.dropboxusercontent.com/u/7521936/pronamachan_sakura.png";

// 画像が完全に読み込まれたら、画像をCanvasに取り込む
img.onload = function(){

    canvas.width  = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);
    document.body.appendChild(canvas);
    
    // 以降の処理
    
};  

座標⇒色⇒文字の変換

Canvasに画像が読み込まれたら、先ほど登場した getImageData メソッドを使って、画像の先頭1行のピクセルデータを取得します。

id = ctx.getImageData(0,0,img.width,1).data;

目的である色情報は、取得した ImageData オブジェクトの data プロパティに格納されています。ただし、この配列は Uint8ClampedArray という特殊な配列であるため、map, reduce等で扱いやすいように普通の配列に変換しています。

haiku = Array(id.length).join(" ").split("")

さらに、値×2を座標として解釈する制約のため、すべての要素の値を2倍しています。

.map(function(e,i){ return id[i] * 2 })

アルファ値は座標値として採用しないため、4番目、8番目、・・・の要素を除外します。また、値が 0 となった場合も切り捨てます。

.filter(function(e,i){ return i % 4 < 3 && e > 0 })

この配列を [ [x1,y1], [x2,y2], ... ] という形の二次元配列に直しています。

.reduce(function(a,b){

    if (a[a.length-1].length > 1) a.push([]);
    a[a.length-1].push(b);
    return a;

}, [[]])

最後に、この二次元配列を座標と解釈し、各座標にあるピクセル色情報を取得、そのカラーコードをデコードしてUTF-8の文字列に変換します。

.map(function(e){

    // 座標 (e[0], e[1]) の1ピクセル分のデータを取得
    var coord = ctx.getImageData(e[0], e[1], 1, 1).data;

    try {
        //           R         G         B
        var code = [ coord[0], coord[1], coord[2] ]

            // の値を 16進数 に変換したうえで、UTF-8としてデコード可能な形式に変換
            .reduce(function(a,b){
                return a || b+0>0? a + "%"+b.toString(16) : a;
            },  "");

 
        // 各要素を、文字と解釈してデコード
        return decodeURIComponent(code);
    }

    // 文字と解釈できないコードは無視
    catch (e) {
        return null;
    }
});

Canvas上に俳句を表示

こうして得られた文字列は、俳句として「一句め 二句め 三句め 作者」という形になっているものとします。最後に、この文字列を空白文字で分割し、位置を適度に調整してCanvas上に時間差で配置しています。

haiku.forEach(function(s, i){ setTimeout(function(){

    ctx.font = "bold 30px '游明朝','ヒラギノ明朝 ProN W3','HG明朝'";
    ctx.fillText(
        haiku[i],
        20 + 80 * Math.pow(i, 1.45),           // x座標
        i==haiku.length-1 ? 180 : 35 + i * 40  // y座標
    );

}, 1000 + i * 1200)});

これで、画像から俳句を読みって文字として表示するプログラムが出来ました。

f:id:takemaruhirai:20150308035618p:plain

もう一句!

mapやreduceやfilterといったメソッドの引数である無名関数を外出しし、わざわざ複雑なオブジェクト内に定義して、名前ををつけてみました。こうすることで無理矢理、コードの中に別の俳句をゴリ押しすることができました。

花の雲 鐘は上野か 浅草か

ちなみに、こちらも芭蕉の詠んだがテーマの俳句です。


haiku = Array(id.length).join(" ").split("")
        
    .map   ( Hana.No.Kumo              )
    .filter( Kane.Wa                   )
    .reduce( Ueno.Ka, Asakusa.Ka       )                
    .map   (              Matsuo.Basho ).join("").split(" ");

コードの可読性からすると、完全に蛇足でしかないのですが・・・

最後に

桜をRGBに変換すると桜色っぽくなるよ!

(ネタバレ)

仰るとおりです(笑)

どういうことかというと、そうですね、たとえば「古池や 蛙飛び込む 水の音」を変換してみましょうか。まずは、「古池や」から・・・

文字 コード
E58FA4
E6B1A0
E38284

ああ、残念ながらどれもピンクっぽいですね(笑)。 どう頑張っても、蛙や池のイメージである青や緑は出てきません。 そんなわけで、必然的にテーマは「」となった訳でした(梅でもよかったのですが)。