画像生成

 gnu-SmalltalkCaptchaのような事をしたいのです。けど、今のところ画像を生成するようなメソッドやパッケージなどは用意されて無いようです。SqueakにはSW2Captchaというすごく便利そうなパッケージがある(Morphicで画像を生成しているらしい?)gstでもTcl/Tkの力を借りれば出来なくもないかな と思ったけど、そういえば、以前の記事で書いたようなエラーが出るのでCGIで利用するのは難しい。あと、 system: も結構便利そうだ。

Smalltalk system: 'tclsh auth.tcl'

こんな感じでCGIプログラムの中で認証用画像を生成させることが出来る。けど、もっとこう何かないかなぁ・・・と考えていたら、おぉ、そうだ CCall という機能がある。CCallとはCで書かれたモジュールを動的にリンクさせて、モジュール内の関数をgstから呼び出せるというものです。*1(リンクするのはDLDというクラスとそのメソッド達の仕事)これはSqueakの名前付きプリミティブというものに似た様なもんなんだろうか? ならSqueakだとVMMakerというオリジナルプラグインが作れる強力なツールがある。gstにはそんなもの無いので、こりゃあ面倒くさいかなぁと思ったのですが、調べてみると CCall という仕組みは結構シンプルそうだったので、とりあえず簡単なテストモジュールを作ってみた。
 このモジュールでは GD library を利用させていただきました。GDライブラリはすごく便利ですごく多機能。けど、今回はほんのほんの一部の機能しか使ってません。

#include <stdio.h>
#include "gstpub.h"
#include "gd.h"

static VMProxy *vmProxy;

int imageString(char *string, char *fontname, unsigned char *fg, unsigned char *bg, double ptsize)
{
  gdImagePtr image;
  int background, foreground;
  int brect[8];
  int x, y;
  char *err;
  FILE *out;

  err = gdImageStringFT(NULL, &brect[0], 0, fontname, ptsize, 0.0, 0, 0, string);
  if (err) {return 1;}

  x = brect[2] - brect[6] + 6;
  y = brect[3] - brect[7] + 6;
  image = gdImageCreate(x, y);

  background = gdImageColorResolve(image, bg[0], bg[1], bg[2]);
  foreground = gdImageColorResolve(image, fg[0], fg[1], fg[2]);

  x = 3 - brect[6];
  y = 3 - brect[7];
  err = gdImageStringFT(image, &brect[0], foreground, fontname, ptsize, 0.0, x, y, string);
  if (err) {return 1;}

  out = fopen("auth.png", "wb");
  gdImagePng(image, out);

  fclose(out);

  gdImageDestroy(image);

  return 0;
}

void gst_initModule(VMProxy * proxy)
{
  vmProxy = proxy;
  vmProxy->defineCFunc("imageString", imageString);
}

 グダグダですみません。テスト用なのでお許しください。 brect 回りのマジックナンバーですが、 brect の各要素には文字列の大きさにぴったり合わせた四角形の数値が入ってます。各要素の値と何やら足したり引いたりしているのは、パディングの数値を指定しているみたいなものです。それで最終的な画像の大きさ(x, y)を得ています。そして、CCallで重要なのは gst_initModule() です。ここで関数名を登録しておかないとgstから呼び出すことが出来ません。とりあえず、グダグダながらも出来たので、次はコンパイルです。gstから利用するには共有ライブラリのほうが良いらしい。

gcc -shared -Wl,-soname,gd.so.1 -lgd -lpng -lz -ljpeg -lfreetype -ldl -lm -o GD-1.0.so gd.o

これで完成したGD-1.0.soを、 lib/smalltalk に置いておく。そして、gstから呼び出しテストをしたらエラーが出たので、GD.laファイルを手動で作る。

# The name that we can dlopen(3).
dlname='GD-1.0.so'

# Names of this library.
library_names='GD-1.0.so GD.so'

# Libraries that this one depends upon.
dependency_libs=' -lgd -lpng -lz -ljpeg -lfreetype -ldl -lm'

# Version information for GD.
current=0
age=0
revision=0

# Is this an already installed library?
installed=yes

# Should we warn about portability when linking against -modules?
shouldnotlink=yes

# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''

# Directory that this library needs to be installed in:
libdir='GST_DIR/lib/smalltalk'

このファイルも lib/smalltalk に置いておく。そして再び呼び出しテスト。の前に簡単なGDクラスとメソッドを作っておく。

Object subclass: #GD
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Image'!
GD comment: 'Use GD library'!

!GD class methodsFor: 'testing'!

imageString: aString font: fontPath foreGround: fg backGround: bg size: size
    <cCall: 'imageString' returning: #int args: #(#string #string #byteArray #byteArray #double)>
!!

そして呼び出す。

| authChars authString fontPath |

"最初にモジュールのロード"
DLD addModule: 'GD'.

"次にfilein"
FileStream fileIn: 'GST_DIR/share/smalltalk/GD/GD.st'.

"4桁の認証用文字列を作る"
fontPath := '/usr/share/fonts/truetype/msttcorefonts/Comic_Sans_MS_Bold.ttf'.
authChars := '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
authString := String new: 4.

1 to: 4 do: [:each |
    authString at: each put: (authChars at: (Random between: 1 and: authChars size))
].

"imageString()の呼び出し"
GD imageString: authString font: fontPath foreGround: #[255 255 255] backGround: #[0 0 0] size: 40.

無事呼び出せました。^^ ちなみに packages.xml に次の様な記述をしておいて、

<package>
  <name>GD</name>
  <filein>GD.st</filein>
  <module>GD</module>
  <directory>GD</directory>
</package>

そしてプログラムで

PackageLoader fileInPackage: 'GD'.

とすれば自動的に DLD addModule: をしてくれるみたいですが自分はまだ試していません。

このプログラムで出来た画像→
GD libraryの色々な機能を使えばもっと複雑な感じの画像を生成する事は出来るのですが、今日はもう時間切れ。

追記
 gstのページでCモジュールの作り方がちゃんと解説されていました(var.3.0対応)gst-package を使えばもっと楽に作れるのですね、知らなかったorz

Creating and distributing packages | GNU Smalltalk

さらに追記
パオロさんがgst3.0対応の記事を書いてくださいました。ありがとうございます。
CAPTCHA, the simplest gst external module

*1:追記: こうゆう機能の事を FFI (Foreign Function Interface) というらしいです。知らなかった^^;