自主制作アニメの話とか… Text_art
  • Visitor No. - : - : -
  • ここは、Delphi(Pascal)によるプログラム講座です。(2009/11/23開始)
  • かなりアバウトな計画
    • 現在の出来具合。プログラムを使いたい人は、このページのドンケツまで進んでください。
  • 長辺250文字を使うと47段階表示で、こんなにきれい(Jpeg)です。
  • このサンプルPDFは、秀丸からDocuCom PDF Driverに出力しています。12/20の段階で、印刷機能をプログラムしました。位置は、自動で中央配置、縦長・横長の自動判定、文字も用紙に対して最大になるよう自動調整で印刷します。文字情報から印刷データを作成するので、画像保存とは比較にならないきれいさで印刷します。
  • 着手から1ヶ月、知らない人でも使えるインターフェイスまでできあがりました。(2009/12/23)

絵を読み込んで、文字の画数を濃淡として表現

  • 一般にテキストアート(text art)・アスキーアート(ascii art)などと言われていますが、パソコンが、パーソナルではなかったころには、グラフィックプリンターなんてないので、ドット絵のようなものか、文字の画数を濃淡として表現するか、違う文字を2度打ちするなどので絵を印刷しました。
  • これ自体は、いくつかのソフトと手作業の技で、できないものではありません。私の場合、半角で構成するなら、横幅は、200として
    • MS-Paintで適当な絵を縦方向に半分に縮め、上下反転したものをBMPで保存。
    • PSPで、グレースケール->16色に減色->24bitに増色->win16色を含む256色に原色->再度グレースケール-->保存。
    • Binary Editor->先頭から色FFFFFF(白)まで削除->保存。
    • 秀丸で欧文字表記設定->200字ごとに改行する->濃淡にあわせて置換->csvで保存。
    • Excelで、非プロポーショナルフォントを選択->うんとズームバック->行間を調整->印刷比率を調整。
  • 以上の手順で作ったサンプルです。文字で構成されているのを確認しやすいように、PDFにしてあります。これ以後もサンプルは、プログラムのチェックで作ったものをPDFでUploadしています。
  • ただ、これは、絵の1ドットを1文字で置き換えているだけなので芸がないと言われれば、その通りと返すしかありません。
  • 前から作ろうと思っていたのですが、今回引き金になったのは、朝日新聞に載ってたこの記事です。
  • ここ
  • 注意していただきたいのは、フローチャートも設計仕様もなく、思いつきで作っているので、仕様がコロコロ変ります。

文字の濃淡を評価する

  • 文字は使う人が決めたいでしょう、多分。家族の名前や住所の漢字を織り込んでみたいというのが普通でしょう。「・」「/」「+」「-」は必須として.....
  • 複雑な形の面積を求めるのに升目を描いて数を数え、中途半端な全部の数÷2で求める小学校の時の方法。
    • 漢字1個の濃淡を占める面積に対する黒の比率で表す。48ポイント以上あれば、「麒麟」のような字もそこそこ妥当な数値を出すのではないでしょうか?
  • まずは、こんなFormを用意しましょう
    • Edit1:調べたい漢字入力、Edit2:結果出力、Image1:漢字記入用、Button1:作業用ボタン。
  • ところで、48ポイントの全角文字が占める縦横はどうして計るのか?(ここまで2009/11/23)
   var x,y:integer;
   begin
     Image1.Canvas.Font.Name:='MS ゴシック';
     Image1.Canvas.Font.Size:=60;
     y:=Image1.Canvas.TextHeight('問');
     x:=Image1.Canvas.TextWidth('問');
     Edit2.Text:=IntToStr(x)+':'+IntToStr(y)
   end;
  • こんな風にButton1にでも記述してボタンをクリックすれば結果が表示されます。
    • Font Sizeをいろいろ変更してみてください。
  • このままButton1に入れておかないで、Form Create時に実行してグローバル変数に格納しましょう。
    • var moji_haba:integer;を先頭の方に記述して、それにさっきの実験でx=yだったので、どっちか1つ確認すればOK。
   procedure TForm1.FormCreate(Sender: TObject);
   begin
     Image1.Canvas.Font.Name:='MS ゴシック';
     Image1.Canvas.Font.Size:=60;
     moji_haba:=Image1.Canvas.TextWidth('問');
   end;
  • では、今度はButton1に文字の占める面積に対する黒の比率を求めてみましょう。
   procedure TForm1.Button1Click(Sender: TObject);
   var x,y,n:integer;
   begin
     Image1.Canvas.Font.Color:=clBlack;
     Image1.Canvas.TextOut(0,0,Edit1.Text);
     n:=0;
     for y:=0 to moji_haba-1 do for x:=0 to moji_haba-1 do
       if Image1.Canvas.Pixels[x,y]=clBlack then n:=n+1;
     Edit2.Text:=IntToStr(round(n/moji_haba/moji_haba*100))+'%';
   end;
  • 麒麟の字でもそれぞれ42%、44%.....半分未満です。(ここまで2009/11/24)
  • 1つ1つ濃淡を調べて並べ替えるのは面倒なので、まずButton1の内容を汎用関数化します。
   private
     { Private 宣言 }
     function noutan(kan:string):integer;
  • functionの1行を宣言部分に書き加えて、中身はほとんどそのまま。
 function TForm1.noutan(kan:string):integer;
 var x,y,n:integer;
 begin
   Image1.Picture.Bitmap.PixelFormat:=pf1bit;
   Image1.Canvas.Font.Color:=clBlack;
   Image1.Canvas.TextOut(0,0,kan);
   n:=0;
   for y:=0 to moji_haba-1 do for x:=0 to moji_haba-1 do
     if Image1.Canvas.Pixels[x,y]=clBlack then n:=n+1;
   noutan:=round(n/moji_haba/moji_haba*100);
 end;
  • で、Button1は以下のように書けばOKです。
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   Edit2.Text:=IntToStr(noutan(Edit1.Text))+'%';
 end;
  • ここまでやったら、また、同じように作動しているか確かめてみましょう。そしたら、次に進みましょう。
  • これをグローバルの定数として追加し、連続して濃淡を算出してみましょう。(ここまで2009/11/27)
  • って書いたんですが、
 const data=' ・一卜了十乙公以中占五卒沢労安串咲欧詰森夷伽鉗蠻庵諭躡鬱籠蠢麟原';
       Cmax=31;
 var   kanji:array[0..100] of string;  BW:array[0..100] of integer;
       moji_haba:integer;
  • グローバルの定数・変数として上記のように設定します。moji_habaなんて、いきなり使っていて、説明をわすれてました。
  • Cmaxは、0から31までの32段階表示です。kanjiは漢字を入れる配列、BWは、濃淡度を入れる配列です。
  • 起動時の設定で、整数の変数xを追加して、漢字データを配列に格納します。
   for x:=0 to length(data) div 2-1 do kanji[x]:=copy(data,x*2+1,2);
  • Memo1を追加しましょう。その上でButton1の内容は、
 procedure TForm1.Button1Click(Sender: TObject);
 var i,j,n,l:integer;  k:string;
 begin
   l:=length(data) div 2-1;
   for i:=0 to l do
   begin
     k:=kanji[i];  BW[i]:=noutan(k);
     Memo1.Lines.Add(IntToStr(i+1)+' '+k+','+IntToStr(BW[i])+'%');
   end;
 end;
  • これで、実行すれば、漢字と濃淡度が順番にでます。
  • ついでに並べ替えてみましょう。良くある総当たり戦方式です。Button1に追加です。さらに、もう一度表示させて並べ替えを確認しましょう。
   for i:=0 to l-1 do {この行から並べ替え}
   begin
     for j:=i to l do
       if BW[i]>BW[j] then
       begin
         k:=kanji[i]; kanji[i]:=kanji[j]; kanji[j]:=k;
         n:=BW[i]; BW[i]:=BW[j]; BW[j]:=n;
       end;
   end;
   for i:=0 to l do {再度表示}
   begin
     k:=kanji[i];  BW[i]:=noutan(k);
     Memo1.Lines.Add(IntToStr(i+1)+' '+k+','+IntToStr(BW[i])+'%');
   end;
  • 漢字の濃淡度出して並べ替えたら、一度試してみたくなるのが人間の心理です。
  • 基本的にはRGBの平均値を求めるのですが、正確に言うと色の三原色は、それぞれの値が255でも同じ明るさにはなりません。B255が最も暗く、次にR255、次がG255ではなく、B255+R255つまりマゼンダがG255より暗いのです。気になる人は、PSPなどのグラフィックツールで先にBW画像に直しておきましょう。
  • 右は明度、左は8bitの頃のカラー番号です。三原色の番号に注目してください。
    • 1+2=3、つまり、青+赤=マゼンダ
    • 1+4=5、つまり、青+緑=水色
    • 2+4=6、つまり、赤+緑=黄色
  • うまくできています。
  • Button1の内容は、起動時に実行するようにすればOKなんですが、ま、実験の繰り返しなので、Button2を用意して、test.bmpなる横幅120くらいの画像を用意しましょう。
 procedure TForm1.Button2Click(Sender: TObject);
 var x,y:integer;  n,g:longint; dam:string;
 begin    Memo1.Lines.Clear;
   Image1.Picture.LoadFromFile('test.bmp');
   for y:=0 to Image1.Height-1 do
   begin
     dam:='';
     for x:=0 to Image1.Width-1 do
     begin
       n:=Image1.Canvas.Pixels[x,y];
       g:=n div $10000;   n:=N mod $10000;
       g:=((g+(n div $100)+(n mod $100)) div 3) div 8;
       dam:=dam+kanji[Cmax-g];
     end;
     Memo1.Lines.Add(dam);
   end;
   Memo1.Lines.SaveToFile('test.csv');
 end;
  • 中ほどの長い行ですが、8で割っているのは、濃淡32段階表示のため、256÷32の結果です。
  • もちろん、ファイル名選択の部分を作ってもいいし、その辺はご自由にどうぞ。
  • 上記最後のサイズテストの実験で、長辺60文字位で作れれば、工夫の度合いが見れるかもしれません。否、失敗がわかるかもしれません。
  • ここまでの状態で、横幅120前後の画像で遊んでみてください。(ここまで2009/11/29)
    • ProgramDown loadは、更新のため下の方を探してください。

さて、これから.....

  • 1画素1文字対応ならこれで終わりになりますが....前述のMilla=Jovovichのもので長辺121のものだと、目の星が見えるが91では、見えないが、歯が見えているのはまだ分かるが、61になると歯もよくわからない状態である。長辺61に設定して、漢字一文字を4区画に分けて、濃度の偏りを調べて適用すれば、長辺121並の画質ににはならないだろうか? 画数の多いものはそんな文字を探してセットにするのは簡単だが、画数の少ないものになると、記号なども使うことになるだろうし、それでも発見できるか分からない。
    • 国構え門構えは線のカクカクが目立つので使わない。濃度から言えば、40段階表示が限界かもしれない。
    • こんな感じで分類して、kanjiの配列を2次元化しましょう。
    • kanji:array[0..100] of string;.....となってたのを
   kanji:array[0..400,0..8] of string;
  • として、同じ濃度のそれぞれ番号順に配列に格納して下準備をしましょう。そのためには、これまで作った部分も大幅に変更が必要になりますね.....また、濃淡を算出しても、人間の目で見て国構えが濃く見えるように、文字が大きいと、必ずしも数値どおりには見えないこともあるので、ある程度自動処理して漢字セットを作るのは、自分で好きな漢字を入れて作りましょう。
  • そんな訳で、Formの空いているところにStringGridを1個追加しましょう。(ここまで2009/11/30)と思ったんですが、やっぱりやめて.......また、別のことを思いつきました。
  • 同じ文字が整然と並んでいるところができてしまうので、これを乱数で選び出すようにしてはどうかと思い、Button3を一時的に追加し、Edit1か2を入力装置にしてMemo1に濃度別に出力、つまり、文字の濃淡を片っ端から調べようというわけです。
 procedure TForm1.Button3Click(Sender: TObject);
 var i,l,n:integer;  dam,K_data:string;
 const F_name='濃淡.txt';
 begin
   if FileExists(F_name)=false then
   begin
     Memo1.Lines.Clear;
     for i:=0 to 60 do Memo1.Lines.Add(IntToStr(i)+',');
     Memo1.Lines.SaveToFile(F_name);
   end;
   //-------------------------------
   Edit2.SelectAll; Edit2.PasteFromClipboard;
   Memo1.Lines.LoadFromFile(F_name);
   K_data:=Edit2.Text;
   l:=length(K_data) div 2;
   for i:=0 to l do
   begin
     dam:=copy(K_data,i*2+1,2);
     n:=noutan(dam);
     Memo1.Lines[n]:=Memo1.Lines[n]+dam;
   end;
   Memo1.Lines.SaveToFile(F_name);
 end;
  • 1bit Color表示の100ポイントで調べています。そして、調査の結果、スペースや記号を含む第1水準漢字で0-45%、部首を含む第2水準で、7-48%、双方とも47%なく、0-46段階の表示で、実験してみようかと思います。加えて、同じ濃淡の漢字を複数の候補から乱数で選び出す.....ちょっと時間がかかるかもしれません。つまり、初期設定から作り直すことにしましょう。
    • こんな感じになります。(2009/12/2)
    • 明朝は別に調べましたが、40%未満になります。
  • これを使って構成してみることになります。ただし、使う文字の種類が増えると、PDFのサイズも鰻登りになります。
 function bunri(dat,c,f:string):string;
   var p,l,cl:integer;
 begin
   l:=length(dat); p:=pos(c,dat); cl:=length(c)-1;
   if f='-' then bunri:=copy(dat,1,p-1);
   if f='+' then bunri:=copy(dat,p+1+cl,l-p-cl);
 end;
  • この関数は、特定の最初にあるc文字の前か後ろを取り出すものです。さらに、
 var   kanji:array[0..100,0..8] of string;  BW:array[0..100] of integer;
       moji_haba, Cmax:integer;
  • グローバルの定数はなくして、変数に変えて、
 function PixToKan(P_noutan,P_katayori:integer):string;
 var l:integer; dam: string;
 begin
   l:=length(kanji[P_noutan,P_katayori]);
   if l=2 then  dam:=kanji[P_noutan,P_katayori]
   else
   begin
     l:=Random(l div 2)*2+1;
     dam:=copy(kanji[P_noutan,P_katayori],l,2);
   end;
   PixToKan:=dam;
 end;
  • P_katayoriは、今のところ0でしか使いませんが、こんな関数を作ります。濃度を与えられると、それにあわせた漢字を候補の中からランダムに選択して返します。
 procedure TForm1.FormCreate(Sender: TObject);
 var x:integer;
 begin
   Memo1.Lines.Clear;
   Memo1.Lines.LoadFromFile('濃淡0.txt');
   Cmax:=Memo1.Lines.Count-1;
   for x:=0 to Cmax do kanji[x,0]:=bunri(Memo1.Lines[x],',','+');
   Memo1.Lines.Clear;
   Randomize;
 end;
  • 初期設定もこんなに変ります。濃淡0.txtのファイルは、サンプルでつけてあります。
 procedure TForm1.Button2Click(Sender: TObject);
 var x,y:integer;  n,g:longint; dam:string;
 begin  
   Memo1.Lines.Clear;
   Image1.Picture.LoadFromFile('test.bmp');
   for y:=0 to Image1.Height-1 do
   begin
     dam:='';
     for x:=0 to Image1.Width-1 do
     begin
       n:=Image1.Canvas.Pixels[x,y];
       g:=n div $10000;   n:=N mod $10000;
       g:=round(((g+(n div $100)+(n mod $100)) div 3)/(256/cmax));
       dam:=dam+PixToKan(Cmax-g,0);
     end;
     Memo1.Lines.Add(dam);
   end;
   Memo1.Lines.SaveToFile('test.csv');
 end;
  • ここは、あまり変りませんね。Pixelsの平均値出す部分は後で、関数にして独立させましょう。
    • Milla Jovovichでサンプル
    • 東野英治郎(水戸黄門)
    • ProgramDown loadは、更新のため下の方を探してください。
    • サンプル見ても分かるように、やっぱ、いろんな文字が混ざっているほうが滑らかに見えます。幅120前後の絵を使って遊んで見ましょう。(2009/12/3)
    • 1文字を4区画に分けて評価します。とりあえず、削除してしまいましたが、起動時設定に次の3行を復活してみます。
  Image1.Canvas.Font.Name:='MS ゴシック';
  Image1.Canvas.Font.Size:=100;
  moji_haba:=Image1.Canvas.TextWidth('問');
  • 4つの区分で濃淡を評価する関数を作り、とりあえず、8桁数字で返すことにします。最終的には、偏り分類の番号で返す予定ですが....
function TForm1.noutan4(kan:string):integer;
  function Q_noutan(x1,y1,hen:integer):integer;
  var x,y,n:integer;
  begin
    n:=0;
    for y:=y1 to y1+hen-1 do for x:=x1 to x1+hen-1 do
      if Image1.Canvas.Pixels[x,y]=clBlack then n:=n+1;
    Q_noutan:=round(n/hen/hen*100);
  end;
var Han_Haba:integer;
begin
  Image1.Picture.Bitmap.PixelFormat:=pf1bit;
  Image1.Canvas.Font.Color:=clBlack;
  Image1.Canvas.TextOut(0,0,kan);
  Han_Haba:=Moji_haba div 2;
  noutan4:=Q_noutan(0,0,Han_Haba)*1000000+Q_noutan(Han_Haba,0,Han_Haba)*10000
           +Q_noutan(0,Han_Haba,Han_Haba)*100+ Q_noutan(Han_Haba,Han_Haba,Han_Haba);
end;
  • 関数の定義の中にもう1つ入っています。ローカル関数と呼びます。Qは、1/4からの頭文字。USS-1701発進の時のエンジン出力は通常の1/4です。
    • ま、そんなこんなで、最初の実験のように、1文字入れては、結果を見るんですが、見た目ほど偏りがないことがわかりました。たとえば、私の姓の旧字体「澤」なんかも思ったほどではなく、左右で10%、上下の上下差は、順に6.5%、14%、上下とも全体では17%になっています。
    • 訐が極端に左上に濃いのですが、ほとんどは、微妙なところです。上のように画数が少なければ偏り指数も5%あればいいほうではないかとなります。
    • そんなことで、偏り指数は、全体の濃度の関数で表すようにしてみます。最後は、人間の目で見て編集することは必要になると思います。また、カナ単位や括弧も有効な材料になってきます。
    • 偏りの判断は、後に画像の時にも使うことを想定して、4つの区画の数字を入力して、0-8のタイプを返すグローバル関数にします。
    • 偏りの判定基準も変更しました。
function Katayori(n1,n2,n3,n4,nt:integer):integer;
var nt_s,K_type,sa12,sa34:integer;
begin
  nt_s:=round(nt*3*(1-nt/7/cmax)) div 2;  { 偏り指数 }
  K_type:=0;
  sa12:=((n1+n3)-(n2+n4))*3; if abs(sa12)<nt_s then sa12:=0;
    if sa12>0 then K_type:=1;
    if sa12<0 then K_type:=2;
  sa34:=((n1+n2)-(n3+n4))*3; if abs(sa34)<nt_s then sa34:=0;
    if abs(sa12)<abs(sa34) then
    begin
      if sa34>0 then K_type:=3;
      if sa34<0 then K_type:=4;
      sa12:=sa34;
    end;
  sa34:=(n1*3-(n2+n3+n4))*2; if abs(sa34)<nt_s then sa34:=0;
    if abs(sa12)<abs(sa34) then
    begin
      if sa34>0 then K_type:=5;
      if sa34<0 then K_type:=8;
      sa12:=sa34;
    end;
  sa34:=(n2*3-(n1+n3+n4))*2; if abs(sa34)<nt_s then sa34:=0;
    if abs(sa12)<abs(sa34) then
    begin
      if sa34>0 then K_type:=6;
      if sa34<0 then K_type:=7;
      sa12:=sa34;
    end;
  sa34:=(n3*3-(n1+n2+n4))*2; if abs(sa34)<nt_s then sa34:=0;
    if abs(sa12)<abs(sa34) then
    begin
      if sa34>0 then K_type:=7;
      if sa34<0 then K_type:=6;
      sa12:=sa34;
    end;
  sa34:=(n4*3-(n1+n2+n3))*2; if abs(sa34)<nt_s then sa34:=0;
    if abs(sa12)<abs(sa34) then
    begin
      if sa34>0 then K_type:=8;
      if sa34<0 then K_type:=5;
      sa12:=sa34;  {<=コンパイルしたら、この行は、いらないって...}
    end;
  Katayori:=K_type;
end;
  • 我ながら非合理的と感心dするできばえの悪さ.....{ 偏り指数 }の数値を調整しながら、分類の具合を確かめます。
 noutan4:=Katayori(Q_noutan(0,0,Han_Haba)
                  ,Q_noutan(Han_Haba,0,Han_Haba)
                  ,Q_noutan(0,Han_Haba,Han_Haba)
                  ,Q_noutan(Han_Haba,Han_Haba,Han_Haba),noutan(kan));
 //-------------------以下はチェック用
 Memo1.Lines.Add(IntToStr(Q_noutan(0,0,Han_Haba))+':'
                +IntToStr(Q_noutan(Han_Haba,0,Han_Haba)));
 Memo1.Lines.Add(IntToStr(Q_noutan(0,Han_Haba,Han_Haba))+':'
                +IntToStr(Q_noutan(Han_Haba,Han_Haba,Han_Haba)));
  • 関数noutan4の戻り値を偏りの型番号に変えて、Memo1に具体的な数値を入れてチェックしてみます。
  • いらないButtonにEdit1に入力された漢字を計算してEdit2に偏り型を表示します。最初の漢字の濃淡度と似たようなものなので自分でプログラムしてもらうことにして、2倍3倍は何かと思うでしょうが、偏りをより正確に出すため、1区画あたりの平均ではなく、いくつかの場合の最小公倍数で比較しているだけです。(2009/12/5)
  • さて、昨日と打って変わってよい天気、元上司の3回忌で知人とお墓参りに行って来ました。昨日までのところで、漢字の偏りを算出して分類して、私の目で、多少修正して、元になる使用漢字リストを作成しました。どうしても該当がないところは、適当なもので代用するか、半角文字2個あわせて作るかになります。リストは修正しやすいようにCSVファイルにしました。使用漢字リストの形式を変更したので、起動時の設定も書き換えることになります。
 Memo1.Lines.LoadFromFile('MSG.csv');
 Cmax:=Memo1.Lines.Count-1;
 for x:=0 to Cmax do
 begin
   dam:=bunri(Memo1.Lines[x],',','+');
   for y:=0 to 7 do
   begin
     kanji[x,y]:=bunri(dam,',','-');
     dam:=bunri(dam,',','+');
   end;
   kanji[x,8]:=dam;
 end;
  • MSゴシックようの1,2水準の漢字の濃度と型分類表もExcelファイルで準備。では、まず、RGB平均値算出を独立関数にします。
function Gray(n:longint):integer;  var g:integer;
begin
  g:=n div $10000;   n:=N mod $10000;
  g:=(g+(n div $100)+(n mod $100)) div 3;
  Gray:=g;
end;
  • ま、これは、そんなでもないんですが、とりあえず、Button5を追加して、まず、4画素を1漢字で表現するプログラムを作りましょう。基本的な部分は、Button2の記述と一緒なので....
procedure TForm1.Button5Click(Sender: TObject);
var x,y,xx,yy,ImgX,ImgY, Gr1,Gr2,Gr3,Gr4,GrT:integer;  g:longint; dam:string;
begin    Memo1.Lines.Clear;
  Image1.Picture.LoadFromFile('test2.bmp');
  ImgX:=Image1.Width div 2;
  ImgY:=Image1.Height div 2;
  for yy:=0 to ImgY-1 do
  begin
    dam:='';
    for xx:=0 to ImgX-1 do
    begin
      x:=xx*2; y:=yy*2;
        Gr1:=Gray(Image1.Canvas.Pixels[x,y]);
        Gr2:=Gray(Image1.Canvas.Pixels[x+1,y]);
        Gr3:=Gray(Image1.Canvas.Pixels[x,y+1]);
        Gr4:=Gray(Image1.Canvas.Pixels[x+1,y+1]);
      GrT:=Katayori(Gr1,Gr2,Gr3,Gr4,(Gr1+Gr2+Gr3+Gr4) div 4);
      g:=round((Gr1+Gr2+Gr3+Gr4)*Cmax/256/4);
      dam:=dam+PixToKan(Cmax-g,GrT);
    end;
    Memo1.Lines.Add(dam);
  end;
  Memo1.Lines.SaveToFile('Test2.txt');
end;
  • って感じですが、全くだめ、そんなはずはないと拡大してみると、偏りが逆になっています。黒の度合いで文字を分類した時の偏り計算なので、暗いほど数字が大きくなりますが、色は、暗いほど数字が小さくなるので、
      GrT:=Katayori(256-Gr1,256-Gr2,256-Gr3,256-Gr4,256-((Gr1+Gr2+Gr3+Gr4) div 4));
  • と直して再度実験。実験にはラムちゃんを使ってみました。
    • 最初に、2x2=4倍の画素のもの。これが元の絵。
    • MS-Paintでサイズを半分にしたもの、MS-Paintでは、Full Colorで縮めると隣り合わせの色との平均を取ったりします。
    • 元のサイズの画像で、1つ飛ばしで漢字に置き換えたもの。これは、問題外。
    • 2x2=4画素の平均値で漢字に置き換えたもの。
    • 偏り判定をしたものということで、数の赤い線のあたりを比較してみると、偏り判定の効果が出ています。
    • PDF file
  • 再びMilla Jovovich(PDF)で、長辺60文字でテスト、目の星は無理だったが歯が見える!
  • これで、色の偏り判定部分ができた。歯だけではなく目の周りや頬のラインなどなめらかになっている。ま、うまくできた方ではないかと思います。(2009/12/6)
    • どっちのファイルもAdobe Readerの「表示(V)」の「ページレイアウト(L)」の「単一ページ(S)」にしてページを進めたり戻したりすると、出来栄えの具合が比較しやすくなります。
    • ProgramDown loadは、更新のため下の方を探してください。
    • 多少のインターフェイスをくっつけて、今この辺の作業状況。使えるのは、ファイルの選択と概観を見る部分だけです。(2009/12/7)
  • たくさん実験してみて理想的な文字数を探ってみました。プログラムも一部変更して、右下文字数と署名を入れてみました。

大きな画像への対応

  • さてこれからの作業は、大きなBitMap Fileを一気に漢字に置き換えるための関数を作ります。
    • 基本的には2x2=4ドットの時と似た様なものです。画像上の開始位置と一区画になるなる単位の1辺のドット数の3つから漢字1個を特定する関数にします。
    • 入れ子になっている関数は1区画をさらに4つに分けた区画のBW平均値を求める関数です。
function TForm1.Kukaku(x,y,d:integer):string;
  function _kukaku(xl1,xl2,yl1,yl2:integer):integer;
  var ix1,iy1,n1:integer;  Ttl:Longint;
  begin
    n1:=0;  Ttl:=0;
    for ix1:=xl1 to xl2 do  for iy1:=yl1 to yl2 do
      begin n1:=n1+1;  Ttl:=Ttl+Gray(Image1.Canvas.Pixels[ix1,iy1]); end;
    _kukaku:=Ttl div n1;
  end;
var md,x2,x3,x4,y2,y3,y4, g,Gr1,Gr2,Gr3,Gr4,GrT:integer;
begin                                               {x->x2, x3,x4}
  md:=d div 2;                              {y->y2     1       2 }
  x2:=x+md-1;  x3:=x+md;  x4:=x+d-1;        {y3-.y4    3       4 }
  y2:=y+md-1;  y3:=y+md;  y4:=y+d-1;
    Gr1:=_kukaku(x,x2,y,y2);
    Gr2:=_kukaku(x3,x4,y,y2);
    Gr3:=_kukaku(x,x2,y3,y4);
    Gr4:=_kukaku(x3,x4,y3,y4);
  GrT:=Katayori(256-Gr1,256-Gr2,256-Gr3,256-Gr4,256-((Gr1+Gr2+Gr3+Gr4) div 4));
  g:=round((Gr1+Gr2+Gr3+Gr4)*Cmax/256/4);
  Kukaku:=PixToKan(Cmax-g,GrT); 
end;
  • ためしにButtonに組み込んでみましょう。
procedure TForm1.Button9Click(Sender: TObject);
const k=4;
var x,y,xx,yy,ImgX,ImgY:integer; dam:string;
begin
  Memo1.Lines.Clear;
  Image1.Picture.LoadFromFile(Edit2.Text);
  ImgX:=Image1.Width div k;
  ImgY:=Image1.Height div k;
  for yy:=0 to ImgY-1 do
  begin
    dam:='';
    for xx:=0 to ImgX-1 do
    begin
      x:=xx*k; y:=yy*k;
      dam:=dam+Kukaku(x,y,k);
    end;
    Memo1.Lines.Add(dam);
  end;
  shomei(ImgX,ImgY);
  Memo1.Lines.SaveToFile(F_Ext(Edit2.Text,'txt'));
end;

7年ぶりの印刷プログラム!

  • そもそも、私は、中途半端なプログラムしか作ってないので、印刷ルーチンを記述したのは、勤務表作成支援ソフトの時以来です。久しぶりなので、ちょっと不安。前回は、10年ほど前に一旦印刷部分を記述したんですが、何も印刷しない問題に出くわして中断して完成したのが7年前のこと。原因は、8bit時代からの習慣で、黒地に白字のwindowsの基本設定だったためです。Delphiでは、printer canvasのデフォルトは、白地にwindowsの基本文字色を字色とします。白地に白字を印刷した訳ですから.....何も出ませんでした。
  • 基本的には、Printer.Canvasに情報を書き込み、実機械に送れば出てきます。出来具合を確かめるのに、Image.Canvasに書き込んで確かめるのと両方を1つの手続きで済ませます。
 procedure TForm1.kijutu(Tcan: TCanvas; Pwidth,Pheight:longint);
  • TcanというTCanvas変数を設定して、Pwidth,Pheight(PはPaper)で書き込む用紙のサイズを入れると、全部自動調整で印刷する方法です。
 kijutu(Image1.Canvas, Image1.Width, Image1.Height);
  • これで、Image1を保存すれば、画像ファイルの完成。
 kijutu(Printer.Canvas, Printer.PageWidth, Printer.PageHeight);
  • これで、印刷用画質のものが完成です。後は、kijutuの中身やら、印刷用紙の向きなどの設定です。(2009/12/13)
  • そんな訳で、UsesのところにPrintersを書き加えます。Unit Fileの空行とばして3つめです。これで、Printerに関する部分を書き始めます。
  • ここで問題点をいくつか......
    • (1) 私は、PDF printer driverを使っていますが、Imageとの間に大きな差があります。PDFに限らず、Printerは、出力すると、データは、お掃除されますが、Imageは、残っていますので、次のを書き込んでも隙間から、前のが見えたりします。かといって、全部真っ白に塗りなおした場合、PDFでは、一旦白い背景を描いてから、背景の上に漢字を表示するので、もたつきます。
    • (2) Font.Sizeは整数なので、微妙な画素数の調整ができません。そこで、画素数を単位とした文字サイズとして、Font.Heightにします。均等幅のMSゴシックは縦横同じなので.....9.5ポイントなんてFont.Sizeでは、できません。
    • (3) 印刷機にセットされている用紙のサイズと向き(縦長か横長か)を知る方法。先に書きましたがPrinter.PageWidth Printer.PageHeight、これを使う前に、向きを決めなければなりません。Printer.Orientationで、値はpoLandscape(風景画つまり、横長)と、poPortrait(肖像画つまり、縦長)のどちらかを代入します。
 procedure TForm1.kijutu(Tcan: TCanvas; Pwidth,Pheight:longint; Prn:boolean);
  • (1)の解決としてPrnで区別することにします。(2009/12/14)
    • booleanとは、論理の真偽のことです。
  • (2)の問題はローカル関数を作って、kijutu内部で算出することにします。Font.Sizeは用紙のサイズで決まりますが、Font.Heightは、用紙に対して与えられた、ドット数で計算してくれます。つまり、私が1年前まで使っていた20年超過の24ピンドットインパクトプリンターは、B5短編に40文字程度ですから、40x24=960ドットです。dpi換算すると...B5短編が約180mm÷25.4mm=7.1インチで、960÷7.1=135、つまり135dpiになります。いまのプリンターは仕様書によると横方向9600dpiになってました。
  function fitChrSize(haba:integer):integer;      const s='問';
  begin
    TCan.Font.Height:=1;
    with Tcan do
    begin
      if TextHeight(s)<haba then
        repeat
          font.Height:=font.Height+1;  
        until haba<TextHeight(s);
      fitChrSize:=font.Height-1;
    end;
  end;
  • (3)は後回しにして....
const yohaku_hi=0.023809524; { 5/210 A4の短編 縁取り5mm }
var yohaku_X, yohaku_Y, moji_X, moji_Y, inji_X, inji_Y,  
  //   余白                文字数             印字区域(x,y)長      
    ix,iy, xx,yy, Pmoji_haba  :integer;
  //変数    座標
  • 縁取りは{ }内の注意書きの通りです。比率で記入しておきます。constはいきなり5/210でもOK。
begin
  if Prn=False then
  begin
    TCan.Brush.Color:=clWhite; TCan.Pen.Color:=clWhite;
    TCan.Rectangle(0,0,Pwidth-1,Pheight-1);
  end;
  TCan.Font.Name:=Memo1.Font.Name;
  moji_X:=length(Memo1.Lines[0]) div 2;
  moji_Y:=Memo1.Lines.Count;
  // 余白計算
  if Pwidth<Pheight
    then begin yohaku_X:=round(Pwidth*yohaku_hi);  yohaku_Y:=yohaku_X; end
    else begin yohaku_Y:=round(Pheight*yohaku_hi); yohaku_X:=yohaku_Y; end;
  inji_X:=Pwidth-yohaku_X*2;
  inji_Y:=Pheight-yohaku_Y*2;
  //  文字幅計算
  if ((moji_X/moji_Y)<(inji_X/inji_Y))
    then Pmoji_haba:=inji_Y div moji_Y
    else Pmoji_haba:=inji_X div moji_X;
  // 余白 再調整
  yohaku_X:=(Pwidth -moji_X*Pmoji_haba) div 2;
  yohaku_Y:=(Pheight-moji_Y*Pmoji_haba) div 2;
  TCan.Font.Height:=fitChrSize(Pmoji_haba);
  TCan.Font.Color:=0;
  //  canvasに書き込み
  for iy:=0 to moji_Y-1 do
  begin
    for ix:=0 to moji_X-1 do
    begin
      xx:=ix*Pmoji_haba+yohaku_X;
      yy:=iy*Pmoji_haba+yohaku_Y;
      TCan.TextOut(xx,yy, copy(Memo1.Lines[iy],ix*2+1,2));
    end;
  end;
end;
  • 全て比率で計算していますので、たとえば、複合機に接続されていて、A3で印刷するとか、建築設計事務所にあるようなA2に印刷するとかの時には、多分、選ばれている紙のサイズに合わせて、自動的に調整されるはずです。ずれが生じないように、1文字ずつ位置を確定して書き込みます。そして、出来上がったら、とりあえず、むやみな印刷は、反エコなので、Image Objectに記述してみましょう。
  • そしたら、適当にボタンでも貼り付けて、
var num:integer;
begin
  Image3.Stretch:=False; Im3_By:=0;
  if Memo1.Lines.Count>0 then
  with Image3 do
  begin
    Width:=640*2; Height:=480*2;
    if Memo1.Lines.Count>length(Memo1.Lines[0]) div 2 then
      begin num:=Width; Width:=Height; Height:=num; end;
    kijutu(Canvas, Width, Height, False);
  end;
end;
  • 2つめのif以下で縦横を入れ替えているのは、もとの絵にあわせて縦長か横長を決めています。
  • 適当に保存のボタンでもつけて画像として保存しました。半分に縮めたものです。(2009/12/16)
  • さて、印刷機にデータを送るには、念のため、確認もつけまして、
begin
  if MessageDlg('印刷しますか?', mtConfirmation, mbOKCancel, 0) = id_OK then
  begin
    if Memo1.Lines.Count<length(Memo1.Lines[0]) div 2
      then Printer.Orientation := poLandscape
      else Printer.Orientation := poPortrait;
    try
      with Printer do
      begin
        Title:=Edit2.Text;
        BeginDoc;
          kijutu(canvas,PageWidth,PageHeight, True);
        EndDoc;
      end;
    finally
    end;
  end;
end;
  • 2つ目のif文が、横長、縦長の判断です。とりあえず、このへんまでが主なルーチンの記述で、後は、
    • ファイルの読み込みをどうするか。
    • 書き出しのときの、ファイル名をどうするか。
    • Text Fileを保存するのかしないのか。Memo objectに書き出しているので1行があまり大きいとエラーを起こす。
    • iniファイルをつくるか。
    • RGBの正確な比率計算。
    • 漢字の濃淡偏りの検証(kkは意外にType0だった)。
  • こんな問題点をプログラムしていくことにになります。
  • ここまでの部分で、自動的に中央配置縁取り調整終わって....この手の面白さは、文字を見る距離30cmでは分からないが、1m以上離れると見えてくる肖像画がたのしいですね。(2009/12/17)
  • Jpegを読み書きできるようにしたら、妙に重くなりました。Memory:512Mbyte CPU:Penthouse1GHzだと、使っているうちに動かなくなってしまいます。最近Mem:1Gbyte標準装備なので...ま....私のは、Mem:2Gbyte CPU:2.8GHzなので軽々動いています。
  • RGBについては、もう少し正確に出すなら、それぞれの成分に次の比をかけて計算(左)すると単純に足して÷3よりは、ましでしょう。Delphi2005プログラミングテクニックVol.9(2500円)の単純に足しての「モノクロ変換」(右)よりはましです。結構高い本なので、正確に算出する方法を例示してほしいものです。
    • B: 35%
    • R: 85%
    • G:180%  
    • 下位からRGBだったかな.....
    • (2009/12/18)
    • 効果ありますね。元の画像の色合いにもよるんでしょうが、東野英治郎さん眉が見やすくなり、目の周りのカクカクが減少しました。(2009/12/20)
    • 先発のというか、英語圏のソフトと出来栄えを比較しました。プログラムの出来栄えというよりは、漢字1文字の画数による濃淡の深さと字の豊富さから、同じ濃淡度合いの漢字を複数の候補からいくつも使えることが圧倒的に優位です。
  • テキストに編集をつけましたが、とてもドンくさいプログラムなので、やはり、先にGraphic Toolで作りたい範囲を調整したほうが楽です。ドン亀機能です。配置を変えたりしました。(2009/12/27)
  • あんまり概観できない画像ページをやめて、テキスト状態で概観できるようにしました(2010/01/10)

根性で変換しました。

  • 2010/2/21 2010/2/28

プログラム本体と付属のファイル

  • 前回掲載 2009/12/21, 2009/12/23, 2009/12/27
  • 最新進行状況 ProgramDown Loadです。(2010/01/10)
  • 注意:一度作ったTextデータから印刷できるようにしたので、わざわざサイズのでかいPDFで保存する必要はなくってます。
  • 使い方は以下の通り、システムは私が良く使う方法で、1つのフォルダー内であれこれ済ませる構造になっています。
    • 解凍したら、デスクトップにでも適当な名前のフォルダーを作って、そこにプログラム本体と付属の「MSGなんたら.dll」3個を入れます。
  • ファイルの説明
    • MSG.dll 実験用に作った、かなり少ない第2水準を含む文字セットです。MSG.dllをコピペして、好きなファイル名にして起動し、設定からそのファイルを選んでください。その後一緒にはいているエクセルファイルの文字セットを使って編集して好きな漢字セットを作ってください。
    • MSG-J.dll 難しい漢字ばかりでは、学校で遊べませんので、小学校6年間で習う漢字+英数+ギリシャ文字セット。
    • MSG-M.dll 小学校版+中学3年までに習う文字セット。設定のページから選択してください。
  • 起動前準備
    • 作りたい絵の元ファイル、JpegかBmpを同じフォルダーに入れます。元絵はそのまま変更しませんので、あとで元のフォルダーに戻してください。
    • プログラム本体を起動します。
  • 初回起動
    • 設定のページに移動、文字セットを選択、その他設定を選択し更新する。

使い方と各機能

  • 設定
    • 使用する漢字セット:前項ファイルの説明を参照。読めない漢字がいっぱいでは、面白くないので、それなりに選択ください。
    • 文字データの保存:このソフトは、画像として保存することを目的としていません。印刷を目的としたものなので、文字データから、プリンターにあった画質で印刷する機能をつけてあります。そのため、一度変換した文字データを残すか残さないかを選択できます。画像で残しておくよりは、文字データで残すほうがコンパクトです。
    • 文字数の自動調整:横文字数x縦文字数をプログラムがエラーを起こさない範囲に調整します。自動調整しない時には、横500文字以下にしてください。
  • Ascii変換
    • リストから直したい絵を選んで、横約300以下、長辺約500以下になる様に数字を調整します。
    • 字と分かるのは、短辺約70文字以下です。だまし絵的なものは、60*45あたりです。1のときは、元絵の1画素が1漢字になります。4なら4x4=16画素で1漢字になります。
    • 文字数の調整をしない設定の場合、横文字数は500以下にしないと、エラーを発生します。データは作れますが印刷ができません。全角512あたりで改行されます。この回避にはHDDに直接ファイルを書込むようにしますが、文字が読めないのでは意味がないし、このページの先頭にあるサンプルのようにほとんど白黒画像になってしまいますので、またの機会にしましょう。
    • 自動調整をしている場合は、およその文字数の上限と下限が自動的に設定されます。
    • 変換をクリックで文字化します。自動的に文字データのページにに切り替わります。この時、設定で文字データを保存にしていると、テキストデータとして自動的に保存します。
  • 文字データ
    • Fontの選択肢は、1,3,6,12ポイントの4種類だけです。概観できます。
    • [更新]ボタンは、上のリストの更新です。
    • [戻る]ボタンは画像選択のページに戻ります。
    • [保存]ボタンで、印刷がもったいない方は保存してください。印刷を目的に作っているので低機能です。
    • 印刷をクリックでデフォルトのプリンターに印刷します。
    • 右の文字データの枠内で右クリックメニューがあります。ドン亀な編集機能です。
    • 上または下の何行かがいらない時には、普通に編集してください。
    • 左5文字要らないときには、どこか1行だけ(絶対に1行だけ)5-6文字めの間で改行して「後残す」を選択します。
    • 同様に「前残す」で右の要らない部分を削除できます。
    • 保存は、できた文字データをファイル名+2桁数字で自動的に保存します。
    • また、文字データからもファイル選択で印刷できます。画像化したものを印刷するわけではなく、文字情報から、印刷用のものをその都度作成するので、保存した画像を印刷するよりは、プログラムの印刷機能を使うほうが、きれいに印刷できます。
  • 画像-文字情報のまま概観できるので、この機能ページは削除しました。画像の保存機能は残してあります。
  • 漢字分類
    • 文字セットの編集は、半角文字は必ず2個セットで使ってください。細かいエラーチェックはしていません。
    • 例えば、おめでたくない漢字を消したい時は、「漢字を入力する場所」を空にしておいて、上の、既にセットになっている漢字の欄から消したい字を削除して、「追加」ボタンをクリックしてください。
    • プログラムの判定した偏り型が今一の時があります。偏り型の絵をクリックすると、その偏り型に変更できます。その後に「追加」ボタンをクリックしてください。
    • 時々、また最後に「保存」ボタンをクリックするすることで、編集中の漢字セットが更新されます。





工事中