パワポでQRコード生成

社長:私も図形を書くのはパワポ一辺倒になってしましました。いや、25年前には X Window に idraw というのがありましてねー、あれはとても良く出来ていて、私も日本語対応した okdraw というのを作ったり…

開発:パワポでQRコードを生成して編集できると便利という話ですね。アドインとか作れば良いのかな。

基盤:誰か作ってそうですね。調べてみます。

*** 数分後 ***

基盤:とりあえずQR4Officeというフリーのアドインがあったので、インストールして使ってみましたが、こんな感じです。

基盤:実用的には十分と思いますが、イマイチぱわぽの機能を使った遊びができない残念感はあります。

社長:背景無しに出来ないのがイタイですね。まあ本来の目的を考えれば当然なんでしょうけど。電子署名に添えるハンコっぽく使いたいので。署名情報自体をハンコにするって洒落てますよね。ところで朱肉の色って、RGBで表すとどうなるのかな?

開発:色はJIS規格で決められてるようです。

朱肉の規格 JIS S 6020-1992 (Cinnabar seal-ink pads) 日本工業規格 JIS S 6020-1992
朱肉
SHUNIKU (Cinnabar seal-ink pads)
1. 適用範囲 この規格は、通常押印に使用する朱肉(以下、朱肉という。)について規定する。

社長:あぁ、seal-ink て表現するか。チャイナバーってのは何?

開発:chinabar じゃなくて cinnabar です。朱色のことですね。朱色の原料「辰砂(しんしゃ=cinnabar)」に由来していて、「辰砂:別名に賢者の石」とあります[Wikipedia]。

社長:賢者の石!その単語大好き。

開発:マンセル値とやらで 7.5R 5/14、RGBにすると 217, 66, 54 だそうです。つまりHTML・CSS的には、color=#D94236 ですね。おや?でも cinnabar でググると色んな色が出てきます。

開発:カラーサイト.com によると、cinnabarは #E15A28 だとあります。これはかなり明るいですね。朱肉にも色んな明度や彩度のバリエーションがありますしね。

開発:そもそも色の見え方は相対的なので、色の表現には sRGB と AdobeRGB とか、色んな体系があるということのようです。当社イメージカラー候補の Royalblue ではこんな感じです。

Royalblue色見本 [color-sample.com]

社長:色には非常にヒッジョーに興味があります。これはいずれ、生成するPNGのパレットを決めるのに勉強しないといけないですね。でも、とりあえず欲しいのはハンコの朱肉です。

基盤:JIS規格の朱肉ですが、QR4Officeで指定するとこうなりますね。

社長:なるほど。うーん、でもリアルで見る朱肉の色って、もっとオレンジっぽくてケバいイメージなんだけどなー。うちの社印に付いてきたやつだって。安物だからだろうか?たしかにこの色のほうが、高級感がある。というか、電子的な書類に押してあるハンコってこういう色のがあるな。JIS準拠ってわけか。

開発:なんにしても、そもそもパワポは透明画像を吐くのが嫌いみたいだから、やはりQRハンコはGoで生成するのが良いみたいですね。やってみます。

社長:それにしても、いい色って本当に素晴らしいですね。これまでウェブサイトにしろパワポにしろ、テキトーに自作した色を使ってきましたが、そういうちゃんとした体系で作ると良いのだということを、初めて知りました。特にウェブサイト作成については、そういうサポート機能があるとスゴく良いですね。WordPressとかにもプラグインしたら良いと思います。

基盤:たぶん、既にあると思いますが。

社長:そもそも、パワポにしろウェブ作成ツールにしろ何にしろ、デフォでもっとちゃんとした色の作成、というか色見本を提供してくれてたら、世の中の色気違いは減ると思うんだよね。Terminalの前後色設定にしたって、これまで納得できる色に調整するのにずいぶん手間をかけたものです。もっとカンタンにもっとキモチ良く!これ、うちの社是だな。

2020-0520 SatoxITS

公式ツイッターはじめました

社長:我が社にも「公式」ツイッターってのがあるとかっこいいと思う。

営業:それは助かります。

基盤:調べてみます。

*** 数分の間 ***

基盤:試しに作ってみました。こんな感じです。

社長:おー、いい感じですね。これが本チャンでいいんじゃないかな。

広報:アイコンは昨日試作したロゴですね。

経理:この背景の山の写真、あのクソエディタの900円分を取り返してますね。

開発:我々もアカウント作りたいな。

社長:仮想人格がアカウントを作ってよいのだろうか?

法務:さあ… 各部署の公式ツイッターという事なら問題なさそうな。

基盤:これで気がついんたんですが、Opera ってサイドバー非表示にすると上に赤黄緑丸が出るんですね。考えてみればなるほどです。

社長:やっぱ Opera だよね。

goでQRコード生成(終了)

開発:PNGの生成がgoでサクッとできちゃうことがわかったので、QRコードの生成を考え始めました。規格としては、JIS X 510:2018です。ざっと眺めたのですが、これは結構骨かなと。

社長:おおー。規格協会がタダで見せてくれるPDFの質もずいぶんまともになったね。以前は劣化したFaxのようだったものですが。

開発:115ページあるし、なんだか気がノリません。原理を説明している仕様書としては面白いと思うのですが、これをただ実装するのって、ジューシーじゃないというか、遊べる隙間が無いと言うか、人生をロスする気がします。

社長:まあうちはとりあえず、ありきたりのQRコードを生成するだけのユーザだからね。ひょっとして、go のライブラリにあったりしないかな。

基盤:調べてみます。

*** 数分経過 ***

基盤:ありました。barcode というパッケージですね。ライセンスはMIT。Githubで配られているんですが、ここってGoの開発者向けサイトの一環みたいで、今年2020年1月のGoの公式ブログからリンクされてます。で、そのページにある利用例がこれです。

package main

import (
	"image/png"
	"os"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/qr"
)

func main() {
	// Create the barcode
	qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)

	// Scale the barcode to 200x200 pixels
	qrCode, _ = barcode.Scale(qrCode, 200, 200)

	// create the output file
	file, _ := os.Create("qrcode.png")
	defer file.Close()

	// encode the barcode as png
	png.Encode(file, qrCode)
}

で、これを走らせると、こういうPNGができました。

社長:おお、iPhoneのカメラをかざすと「Hello World」と認識されます。

基盤:「http://its-more.jp」で作ると、こんな感じ。見た目にも情報量が増えてますね。

ついでに、空文字列だとこんな感じ。これがミニマムでしょうか。

逆に長い文字列、ここではこのプログラムの LICENSE ファイルの600文字くらいを食わせてみましたが、こんな感じです。

それと、こんなふうにボカしても認識できますね。

開発:おおー、素晴らしい! これは助かりました。美しい人生よ、限りない喜びよ。

社長:この胸のトキメキをあなたに。やっぱりあったんですね。まあ、そんなこともあろうかと思って png-go.com というドメインは取って置きました。ピンゴ!(笑)

基盤:必要なのは QR にちなんだドメイン名ですよね。

営業:何かの商売になりますかね?

経理:それって、いつもの安かろうもんのXSOnamae じゃなくて N. Sol. で取ったのはなぜですか?

社長:それがXSOがこのドメインについては何かトラブってましてね、なんか失敗する。で、NSOにいったらさくっと取れまして。XSOの表示の3倍以上の値段でしたねー。さすがドメイン商法の家元!(笑)

経理:… なぜか使われた口座も当社メインバンクじゃなくて、ジャパネット系でした。

社長:それがねー、メインバンクのお上品銀行は、なんか海外送金がダメなんですよ。箱入りなんでしょうかねぇ。2行作っといてよかったです。

2020-0519 SatoxITS

仮想虚無感サーバ

開発:PNGの生成の件は、Goを使えば自分では何もやる事がないということで、終了しました。

社長:ありがたい事だけど、昔の自分の苦労は何だったんだろうって虚無感を感じます。本格的に製品開発にノメる前に、何か面白い事をしたいですね。面白いサーバコンテンツ。

開発:それで考えたのですが、まあこれも昔からありがちな話だとは思いますが、自分では何も個別コンテンツというかリソースを持たないサーバというのはどうかなと。

社長:クッキーだけでやるということですか?

開発:いえ、リソースとしては、クライアント側のローカルファイルとか、サーバも使います。言ってみれば、URLのビューだけを提供するようなサーバです。ブラウザに対するコンサル的なサジェスチョンみたいな。

社長:何かの処理は提供するわけですね。

開発:まずは、PNGの生成ですかね。これは壁紙とかファビコンとか各種のアイコンとかクリッかぶるに使えるかなと。あとはやっぱり、クッキーの編集機能。簡単なエディタになると思いますが、まあフォームでいいのかなと。

社長:そういえば、ブラウザもむかしはCookieの中身を見たり編集できたものだけど、最近のはそうなってないような。

開発:なぜなんでしょうね。あれは実際、かなり困ります。

goによるpngの生成(終了)

さて、pngの容れ物はわかったので、中身を詰めてみよう。イメージデータを詰めるには、、、どうも zlib 形式での圧縮(deflate)は必須らしい。なんでだろう。

同一マシンの中で揮発性で小さめの画像を飛ばすだけなので、圧縮・伸長のオーバヘッドを避けたいのに。マシン内なら楽勝で10Gbps出るのに、Zlibかませたら遅くなる。そもそもCRCもいらない。あれもかなり重い。100Mbpsくらいに落ちてしまう感じだ。たかだか4GHzのCPUで処理してるんだし。そう言えば、zlib の圧縮のパラメータで、圧縮無しってあったっけか…

まあ、せっかく go がPNG用のエンコーダを用意してくれてるんだから、まずはそれを使ってみよう。再び Packege png のページを見る。たぶんこの Encodeという関数だろう。引数はどうなっているのか … 「image.Image」。なんだろう、これ。Rectangle …。…。…。えーっ、つまりこれでこれでごにょごにょイメージを作ってやって、Package png に渡してやると、PNG 形式にしてくれるって事? そうなんですか。

なーんだ

なーんだぁ!

まあ、そういうものが無い方がおかしいと思った。でもまあいい、10年の時の流れを実体験しながら1日で飛び越えられたから。PNGのフォーマットも勉強できたし、何かの役に立つかも知れない。

png Packageによる png の生成

それで、それってどう使うのって、これが Encode の Example ですか。

package main

import (
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
)

func main() {
	const width, height = 256, 256

	// Create a colored image of the given width and height.
	img := image.NewNRGBA(image.Rect(0, 0, width, height))

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			img.Set(x, y, color.NRGBA{
				R: uint8((x + y) & 255),
				G: uint8((x + y) << 1 & 255),
				B: uint8((x + y) << 2 & 255),
				A: 255,
			})
		}
	}

	f, err := os.Create("image.png")
	if err != nil {
		log.Fatal(err)
	}

	if err := png.Encode(f, img); err != nil {
		f.Close()
		log.Fatal(err)
	}

	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}

New して Set したら Encode して終了ですか。そうですかー。どれどれ。go run go。出来た。

ステキ。いやはや極楽極楽。いい世の中になったものたろうだなー :-)。もう道具は全て揃ってるって感じ。

自分で作ったのも、青春の思い出としてアーカイブしておこう(笑)。これはこれで、楽しかった。やはり、がっちりした有名な仕様書を読みながら実装するのはワクワクする。しかし、単にバイト列を直線的にバッファに書いていくのをGo言語で効率的にやる方法がまだわからない。まあ、string じゃなくて byte というのでやるんだろう…

/*
<base href=https://www.w3.org/TR/PNG/>
	2020-0518-03 pnggen/0.0.3 (PNG generator) by ITS more :-)
	Reference: PNG Ed.2 Spec.

*/ package main import ( "os" "fmt" "hash/crc32" ) func myid() string { return "pnggen/0.0.3 (PNG Generator)" } func hton32b(hint int)(string){ buf := make([]byte, 4) buf[0] = byte(hint >> 24) buf[1] = byte(hint >> 16) buf[2] = byte(hint >> 8) buf[3] = byte(hint) return string(buf) } func putNetInt32(file *os.File, hint int) int { file.Write([]byte(hton32b(hint))) return 4 } // 5.3 Chunk layout Chunk := Length+Type+Data+CRC func putChunk(file *os.File, ctype string, cdata string) int { crc := crc32.ChecksumIEEE([]byte(ctype+cdata)) // heavy if large olen := putNetInt32(file,len(cdata)) // length of data file.Write([]byte(ctype)) // chunk type file.Write([]byte(cdata)) // chunk data olen += putNetInt32(file,int(crc)) // CRC of type plus data return olen + len(ctype) + len(cdata) } func pnggen() { outfile := "x3.png" genw := 64; genh := 64; fmt.Fprintf(os.Stderr,"-- %s --\n",myid()) file, _ := os.Create(outfile) defer file.Close() //---- Signature -- 5.2 PNG signagure PNGsignature := "\x89PNG\r\n\x1A\n" file.Write([]byte(PNGsignature)) olen := len(PNGsignature) //---- IHDR -- 11.2.2.2 IHDR Image header ihdr := hton32b(genw) // Width ihdr += hton32b(genh) // Hight ihdr += string(1) // Bit depth, 1 for monocrome ihdr += string(0) // Color type, 0 for Grayscalse ihdr += string(0) // Compression method, 0 for deflate/inflate ihdr += string(0) // Filter method, 0 for adaptive ihdr += string(0) // Interlace method, 0 for no interlace olen += putChunk(file,"IHDR",ihdr) //---- PLTE -- 11.2.2.3 PLTE Pallete olen += putChunk(file,"PLTE",""); //---- IDAT -- 11.2.2.4 IDAT Image data olen += putChunk(file,"IDAT","") //---- IEND -- 11.2.2.5 IEND Image trailer olen += putChunk(file,"IEND","") fmt.Fprintf(os.Stderr,"-- created %s (%d x %d) %d bytes\n", outfile,genw,genh,olen) } func main() { pnggen() } //

goによるpngの生成

サーバで画像を生成して配信したい。まずは静止画で、アイコン、グラフ、QRコード等。高々100KB程度の画像を想定している。要するに、どのピクセルを何色にするか、だけのデータなのだから、簡単にできるに違いない。

PNG選定までの経緯

静止画ならpngが形式がシンプルで良かった記憶がある。機能的に大きな問題がなければPNGで行きたい気分。

静止画、フォーマットでぐぐると、このブログが先頭にくる。比較対象としてあげられている JPEG, GIF, PNG, TIFF, BMP。生成する画像は写真では無いので、JPEGは必要無い。ただの一枚の静止画なのでTIFFである必要は無い。ブラウザで表示することが目的なので、やはりPNGかな。基本的には、非常に近距離で、小さめの画像をやりとりするのが目的なので、圧縮の機能も必要ないというかOFFにできると良い。

Wiki をみても、PNGが適当と思う。PNGが出てきた経緯からして、それが普及した現在、GIFに戻る必要はないのでは。ひとつだけ気になるのはGIFアニメ。でも、アニメーションPNGというのがあるそうで、これも試してみたら、現在の主要5ブラウザで全てで表示できた。

全て問題なし。PNGが良いと思う。Wiki の PNG の項を見たら、だいたいそう言うメリットがまとめられている。

実装言語としてのGo

それで仕様書は何だったろうとたぐると、大元は RFC 2083 だった。

Goでさくっと実装したい。仕様をざっと見て、共通ライブラリ的なもので、自作の線はないなと思うのは zlib。でもこれは go にパッケージがあるようなのでOK。CRC32も問題ない。そもそも可逆圧縮なんだし、フォーマットと圧縮は直交しているはずだ。

ん、まてよ、そもそもGo には PNG自体のライブラリがあるのではないか?やはり、Package png というのがあった。エンコーディングについては、これに任せれば良いらしい。その中で参照されているPNGの仕様は、W3Cのものだった[https://www.w3.org/TR/PNG/]。2003年版とあるので、いい感じに枯れてるのじゃあるまいか。

PNGのフォーマット

Go で PNG で行こう。実際、もともとその一択だったと思う。準拠する仕様は W3C の 2003 年版すなわち、ISO/IEC 15948:2003 とする。

Portable Network Graphics (PNG) Specification (Second Edition)

Information technology — Computer graphics and image processing — Portable Network Graphics (PNG): Functional specification. ISO/IEC 15948:2003 (E)

W3C Recommendation 10 November 2003

https://www.w3.org/TR/PNG/

仕様書をざっと眺める。PDFにしてみても40〜50ページ程度だから、コンパクトな規格だ。画像が入っているのが IETF RFCとは違うところだな(笑)。目次や Term & definitions の章がしっかりしているのは ISO の血の匂いがする。いろいろ書いてあるけど、とりあえず必要な情報って数ページ程度ではあるまいか?

やりたいこことは PNG 形式を作る事、つまり 4.7 PNG datastream を作る事だ。ヘッダの後は、チャンクの連続だと書いてある。必須(クリティカル)のチャンクは、ヘッダ、パレット、データ、終端の4種類か。

それぞれの定義は 11.2.2,3,4,5 にある。2ページ程度の仕様だ。

だが、値を十進で表記するのやめて欲しい。ISO流なのか?なぜ「73 72 68 82 というオクテット列はASCIIコードとして解釈すると偶然ではなく"IHDR"という文字列になります」と一言、言えないのだろう?
まあいい。感じはわかった。いい感じだ。さくっと行けそうな気がする。

トライアル実装(1)

さっそく何か作ってみよう。うーん、Cだと簡単に書けるのだが … とりあえずCで書こうか … つくり初めてみたが何か変だ。無意識に mkgif.c という名前でプログラムを作っていた(笑)

やはりGoで書こう。… … … こんな感じになった。

Pnggen/0.0.1 ソースコード
//
// 2020-0518-01 pnggen/0.0.1 (PNG generator) by ITS more :-)
// Reference: https://www.w3.org/TR/PNG/
//
package main
import (
        "os"
        "fmt"
)
func hton32b(s string, i int)(string){
        s += string(0xFF & (i >> 24))
        s += string(0xFF & (i >> 16))
        s += string(0xFF & (i >> 8))
        s += string(0xFF & (i))
        return s
}
func putNetInt32(file *os.File, length int){
        lengb := make([]byte, 4)
        lengs := string(lengb)
        lengs = ""
        lengs = hton32b(lengs,length)
        file.Write([]byte(lengs))
}
func main() {
        outfile := "x.png"
        fmt.Fprintf(os.Stderr,"-- pnggen/0.0.1 --\n") 
        file, _ := os.Create(outfile)
        defer file.Close()
        
        PNGsignature := "\x89PNG\r\n\x1A\n" // 5.2 PNG signature
        file.Write([]byte(PNGsignature))
        
        // 5.3 Chunk fields := Length, Chunk Type, Chunk Data, CRC
        ckdbuf := make([]byte, 256) // 5.3 CHUNK DATA buffer
        chunkdtop := string(ckdbuf)
        chunkdtop = ""
        
        // 11.2.2 IHDR Image header
        IHDRtype := "IHDR" // IHDR chunk type
        // IHDR chunk data
        chunkd := hton32b(chunkdtop,64) // Width
        chunkd = hton32b(chunkd,64) // Hight 
        chunkd += string(1) // Bit depth, 1 for monocrome
        chunkd += string(0) // Color type, 0 for Grayscalse
        chunkd += string(0) // Compression method, 0 for deflate/inflate 
        chunkd += string(0) // Filter method, 0 for adaptive
        chunkd += string(0) // Interlace method, 0 for no interlace
        // IHDR CRC
        // to be done
        
        putNetInt32(file,len(chunkd)) // Length
        file.Write([]byte(IHDRtype))
        file.Write([]byte(chunkd))

        fmt.Fprintf(os.Stderr,"-- created %s (%d x %d)\n",outfile,64,64) 
}

プログラムから出力したPNGファイルの中身を確認する。

% go run pnggen.go 
-- pnggen/0.0.1 --
-- created x.png (64 x 64)
% file x.png
x.png: PNG image data, 64 x 64, 1-bit grayscale, non-interlaced
% od -c -t d1 x.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
         -119  80  78  71  13  10  26  10   0   0   0  13  73  72  68  82
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0            
            0   0   0  64   0   0   0  64   1   0   0   0   0    

とりあえず file コマンドは、これが 64 x 64 のPNGだと認識してくれている。CRCはまだつけてないけど、チェックしてないんだな。では Preview で開いてみよう。

おっしゃるとおり、ダメージを受けております。ブラウザは何と言うだろう?

Firefoxは怒った。が、他のブラウザたちはノールックでスルーパス。わりといい加減なんだな。

どうやら必須エリアはそろってないと怒られるようなので、空のパレットと空のイメージデータを追加して、IENDで終端してみる。で、プレビューで見てみると…

できたー!苦節6時間(笑)半分以上は、Go言語の string 型との闘いだった。まだよくわからない。そもそもなぜ、printf / scanf でナマの整数値の読み書き、特にネットワークバイトオーダとの変換ができないのか不思議だ。なぜたかが hton / ntoh するために、面倒なことをしなければならないのか??

ともあれ、最小のPNGファイルは、65バイトであることがわかった。

% go run pnggen.go
-- pnggen/0.0.2 (PNG Generator) --
CRC=82124C73
CRC=4BA88955
CRC=35AF061E
CRC=AE426082
-- created x2.png (64 x 64)

% od -c x2.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0 202 022   L
0000040    s  \0  \0  \0  \0   P   L   T   E   K 250 211   U  \0  \0  \0
0000060   \0   I   D   A   T   5 257 006 036  \0  \0  \0  \0 256   B   `
0000100  202   

あれ?IENDが出てないや。出力し忘れた。Previewのチェックも中途半端だな。出力しよう。ということで、最小のPNGファイルは、69バイトであった。

% od -c x2.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0 202 022   L
0000040    s  \0  \0  \0  \0   P   L   T   E   K 250 211   U  \0  \0  \0
0000060   \0   I   D   A   T   5 257 006 036  \0  \0  \0  \0   I   E   N
0000100    D 256   B   ` 202                                            
0000105
% od -t x1 x2.png
0000000    89  50  4e  47  0d  0a  1a  0a  00  00  00  0d  49  48  44  52
0000020    00  00  00  40  00  00  00  40  01  00  00  00  00  82  12  4c
0000040    73  00  00  00  00  50  4c  54  45  4b  a8  89  55  00  00  00
0000060    00  49  44  41  54  35  af  06  1e  00  00  00  00  49  45  4e
0000100    44  ae  42  60  82                                            
0000105

data URI にしてみよう。まずはbase64にする。

% delegated -FenMime -b x2.png  
iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQAAAACCEkxzAAAAAFBMVEVLqIlVAAAAAElEQVQ1
rwYeAAAAAElFTkSuQmCC

data URI はこれで良いはずだ。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQAAAACCEkxzAAAAAFBMVEVLqIlVAAAAAElEQVQ1
rwYeAAAAAElFTkSuQmCC

OperaのURL窓に食わせて表示、ダウンロード。Preview で表示。問題なし。みんなで記念撮影しよう。

ミニマムPNGファイルできました。

あれ?そういや zlib かけてないんだけど、通ったじゃん。自動判別してるのかな?あ、圧縮するのはデータだけなのかな。とりあえず現状を保存。

Pnggen/0.0.2 (PNG Generator) ソースコード
//
// 2020-0518-02 pnggen/0.0.2 (PNG generator) by ITS more :-)
// Reference: https://www.w3.org/TR/PNG/
//
package main
import (
        "os"
        "fmt"
	"hash/crc32"
)
func myid() string {
	return "pnggen/0.0.2 (PNG Generator)"
}
func hton32b(i uint32)(string){
	buf := make([]byte, 4)
	buf[0] = byte(0xFF & (i >> 24))
	buf[1] = byte(0xFF & (i >> 16))
	buf[2] = byte(0xFF & (i >>  8))
	buf[3] = byte(0xFF & (i))
	return string(buf)
}
func putNetInt32(file *os.File, i32 uint32){
        intb := make([]byte, 4)
        ints := string(intb)
        ints = ""
        ints = hton32b(i32)
        file.Write([]byte(ints))
}
func main() {
        outfile := "x2.png"
	genw := 64;
	genh := 64;
        fmt.Fprintf(os.Stderr,"-- %s --\n",myid()) 
        file, _ := os.Create(outfile)
        defer file.Close()
        
        PNGsignature := "\x89PNG\r\n\x1A\n" // 5.2 PNG signature
        file.Write([]byte(PNGsignature))
        
        // 5.3 Chunk fields := Length, (Chunk Type, Chunk Data), CRC
        ckdbuf := make([]byte, 256) // 5.3 CHUNK DATA buffer
        chunkd := string(ckdbuf)
        
	//--------------------------------- IHDR
        // 11.2.2 IHDR Image header
	// https://www.w3.org/TR/PNG/#11IHDR
        IHDRtype := "IHDR" // IHDR chunk type
	chunkd = IHDRtype;
        chunkd += hton32b(64) // Width
        chunkd += hton32b(64) // Hight 
        chunkd += string(1) // Bit depth, 1 for monocrome
        chunkd += string(0) // Color type, 0 for Grayscalse
        chunkd += string(0) // Compression method, 0 for deflate/inflate 
        chunkd += string(0) // Filter method, 0 for adaptive
        chunkd += string(0) // Interlace method, 0 for no interlace

        // 5.3 CRC - for chunk type and chunk data
	crc := crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- PLTE
	// 11.2.2.3 PLTE Pallete
	// https://www.w3.org/TR/PNG/#11PLTE
	PLTEtype := "PLTE"
	chunkd = PLTEtype;
	// empty pallete

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- IDAT
	// 11.2.2.4 IDAT Image data
	IDATtype := "IDAT"
	chunkd = IDATtype;
	// empty data

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- IEND
	// 11.2.2.4 IEND Image trailer
	IENDtype := "IEND"
	chunkd = IENDtype;

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,0) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

        fmt.Fprintf(os.Stderr,"-- created %s (%d x %d)\n",outfile,genw,genh) 
}

2020-0518 SatoxITS

Go言語はじめました

これからはプログラムはGoで書こう、そう思ってはいたのですが、環境整備に手間をとられてHello Worldやっただけで止まっていました。DeleGateサーバの経費節減計画も一段落、delegate.orgも無事復活できて和んでいたところ、Goの事を思い出したので、本日決行です。

インストール

まずはインストールですが、Goのサイトからパッケージをダウンロードしてインストール。クリックするだけ超絶簡単。ただ、コマンドラインからgoと打っても、無いといわれます。

% which go
go not found
% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

それでTerminalを起動し直してみると、自動的にパスが追加されて通りました。ドキュメントを見たらそうしろと書いてありました。

% which go
/usr/local/go/bin/go
% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin

うーん、このパスの追加はどういう仕掛けでできてるんでしょうか?すごく興味がありますが、それは置いておき。

性能測定

それでさっそくHello Worldですが(笑)、実行がかなりもっさりしています。

% time go run hello.go
Hello World!!
go run hello.go  0.16s user 0.10s system 113% cpu 0.230 total

0.2秒?インタープリタが重いんでしょうか?コンパイルしてみます。

% time go build hello.go
go build hello.go  0.06s user 0.07s system 141% cpu 0.095 total
% time ./hello
Hello World!!
./hello  0.00s user 0.00s system 77% cpu 0.004 total

4ミリ秒と出ました。妥当な時間です。

XcodeでGo

と思ったら、GoがXcodeのプロジェクトの選択肢にありません。はて?とりあえずスキップ。

GoでHTTPサーバ

とりあえずやってみたいのは HTTP サーバです。こんな感じでどうでしょう?

// 2020-0517-01 Orange Pekoe HTTP Server v0.0.1 by ITS more :-)
package main
import ( "net" )
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {   
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
func serv(conn net.Conn) {
        req := make([]byte, 1024)
        conn.Read(req)
        res := "HTTP/1.0 200 Ok\r\n\r\nHello! (^-^)/ ITS more"
        conn.Write([]byte(res))
        conn.Close()
}
Orange Pekoe HTTP Server v0.0.1

動きました。簡単すぎてけしからんですね(笑)

サーバ公開

さっそく公開しましょう(^-^)。delegate9.org に持って行って実行です。Ubuntu で sudo apt install golang-go。しかるのちに go run pekoe.go。Azure の Networking で 47401 を開通させて、アクセスしてみます。

Orange Pekoe HTTP Server working on delegate9.org

問題ないですね。昔の事を思うと、簡単過ぎて悲しくなります。

エコーサーバ

このままサクサク進むと思っていたところ、落とし穴に落ちました。最近の?ブラウザって、text/plain を自分では表示しないのがデフォなんですね。Go言語をまるで知らないので、何かの書き間違いなのだろうかと苦しみました。とりあえず text/html にして <plaintext> を突っ込みました。

// 2020-0517-02 Orange Pekoe HTTP Server v0.0.2 by ITS more :-)
package main
import ( "net" )
func serv(conn net.Conn) {
        qbuf := make([]byte, 1024)
        qlen, _ := conn.Read(qbuf)
        qstr := string(qbuf[:qlen])

        resh := "HTTP/1.0 200 Ok\r\n"
        resh += "Content-Type: text/html\r\n"
        resh += "\r\n"
        resh += "Your request:<plaintext>"
        conn.Write([]byte(resh))
        conn.Write([]byte(qstr))
        conn.Write([]byte("--end--\r\n"))
        conn.Close()
}
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
リクエストのエコーできました。

やれやれ。

プログラムを送り込んで実行する

これ、この危険な遊びは、一番やってみたかった事です。Goのデモサイトでもやってますし。たぶん eval とか出来るはずです。… … やはり Eval() でした。

// 2020-0517-03 Orange Pekoe HTTP Server v0.0.3 by ITS more :-)
package main
import (
        "net"
        "fmt"
        "strings"
        "strconv"
        "go/token"
        "go/types"
)
func myid()(string){
        return "OPS/0.0.3 (Orange Pekoe)"
}
func rhdr(rcode int, ctype string, rmsg string)(string){
        return strings.Join([]string{
                "HTTP/1.0 " + strconv.Itoa(rcode) + " "+rmsg,
                "Content-Type: " + ctype,
                "Server: " + myid(),
        "",""},"\r\n")
}
func resp(conn net.Conn, str string){
        conn.Write([]byte(str))
}
func eval(conn net.Conn, xgocode string){
        gocode := udec(xgocode)
        rbuf := make([]byte, 1024)
        rstr := string(rbuf)
        fset := token.NewFileSet()
        rval, _ := types.Eval(fset,nil,token.NoPos,gocode)
        rstr = fmt.Sprintf("%d",rval.Value)
        rmsg := rhdr(200,"text/html","OK")
        rmsg += "Go-Code:<pre>\t" + henc(gocode) + "</pre>"
        rmsg += "Evaluated:<pre>\t"
        resp(conn,rmsg + henc(rstr) + "</pre>\r\n")
        resp(conn,"<i>Powered by "+myid()+"</i>\r\n")
}
func echo(conn net.Conn, qstr string){
        rmsg := rhdr(200,"text/html","OK")
        resp(conn,rmsg)
        resp(conn,"<HR>Your Request<HR><pre>")
        resp(conn,henc(qstr) + "</pre>\r\n")
        resp(conn,"<HR>My Response<HR><pre>")
        resp(conn,henc(rmsg) + "</pre>\r\n")
        resp(conn,"<HR>\r\n")
}
func henc(str string)(string){
        return strings.Replace(str,"<","<",-1)
}
func udec(str string)(string){
        return strings.Replace(str,"%3C","<",-1)
}
func serv(conn net.Conn) {
        qbuf := make([]byte, 1024)
        qlen, _ := conn.Read(qbuf)
        qstr := string(qbuf[:qlen])
        qarg := strings.Split(qstr," ")
        url := qarg[1]

        if( strings.HasPrefix(url,"/eval?") ){
                eval(conn,strings.Split(url,"?")[1])
        }else{
                echo(conn,qstr)
        }
        conn.Close()
}
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
Remote calc. 🙂

動いた。

オブジェクトのサイズは

Go言語も少し覚えたし、めでたしめでたしです。というか、C言語ユーザとしては、全く違和感の無い言語ですね。それで、バイナリのサイズはどのくらいでしょうか?

% go build pekoe.go
% ls -l pekoe
-rwx------  1 sato  staff  4601840 May 17 17:17 pekoe
% size pekoe
__TEXT	__DATA	__OBJC	others  	dec
3256320	283720	0	17012720	20552760

おっとびっくり!DeleGateより少しでかい… ネットワーク系がでかいんでしょうか?ちなみに Hello World は…

% ls -l hello
-rwx------  1 sato  staff  2169960 May 17 17:24 hello
% size hello
__TEXT	__DATA	__OBJC	others  	dec
1462272	266632	0	16895080	18623984
% nm -n pekoe | more
...
0000000001001000 t _go.buildid ## seems offset 16MB

それだけでもこれですか… Evalを使ってたらデカくなるだろうとは思いますが。いったい何が入っているのでしょう?

% nm -n hello
X-D

組み込み系コンパイラなら、参照しない関数はざっくり切ってくれるのですが。セキュリティ上もそのほうが良いと思われます。きっと、ビルドのオプションにあるのでしょう。

実行時のメモリ消費は

とりあえず大人しく待ち受け状態でですが、コンパイル版(ネイティブコード)の場合、ps で見るとこんな様子です:

      SZ    RSS    TIME CMD
 4976972   1808 0:00.01 ./pekoe

一方、インタープリタ?版の場合:

      SZ    RSS TIME    CMD
 5016516  14232 0:00.24 go run pekoe.go
 4977996   2580 0:00.00 /var/folders/bw/8dlt3xks44727rb0y662kw_m0000gn/T/go-build753928546/b001/exe/pekoe

評価

これまで何十年、ほぼC言語一筋だったプログラマから見て、Goの記法はとても自然で、ほとんど違和感を感じません。言語、ツール、ライブラリのドキュメントはちゃんとしてます。とても素直でさっぱりとした言語と環境だと思われ、なんとなくこんな感じかなという勘が、だいたい当たります。とても気持ちが良い。辛い思いをしたWin32とか組み込み系 🙁 の世界とはえらい違いです。

私が今必要としているHTTPサーバ機能は、実際のところ、今日作ってみたこれ程度のものなので、それが100行程度で書け、好きなようにイジりまわせるというのは素晴らしいです。性能的にも問題なさそうだし。どこへ行ってもネイティブで動くし … 

もう、Goって最高!

私の歴史が今日、動きました 🙂

2020-0517 SatoxITS

ぱちもん.shop

経理:昨日もまた、XSOnamaeからたくさん請求書が届いてましたが。

社長:そう、おそらく我が社設立以来最大のヒット作「pachimon.shop」をゲットしました。

広報:わかりやすいですね。イメージが湧いてきました。

開発:どういうコンセプトでしょうか?

基盤:よく売れ残ってましたね。

営業:それ、売れますかね?

社長:いや、当社の倫理からして、転売が目的ということはありません。うちの自社製品の産直売り場にしようと思っているのです。

営業:(ため息)

経理:ではこの「pachimonotaro.com」というのは何ですか?

佐藤:それはね、ヒトとして、もののあはれをしれば自然に出てきてしまうもの、抑えることができないものなのです。かの本居宣長も、さかのぼれば紀貫之から、そう言ってますから。

開発:別の言い方をすると、オヤジギャグのことですね。