fortran66のブログ

fortran について書きます。

Fortran で ASCII ART

FortranBMP -> ASCII 変換のプログラムを作ります。

基本的な原理は、フォントのビットマップを(非直行、非完全、非独立)基底ベクトルとして、画像をフォントのサイズごとに細切れにして、その小さなビットマップをベクトルとみなして、各フォント基底とユークリッド距離をとり、距離最小のフォントを選択するものです。

ユークリッド距離を他の距離やエントロピー風に変えてもあんまり結果は変わらない感じです。

RGB三原色に分けて出力し、色を付けて再合成した例を以下に示します。
f:id:fortran66:20140626033922j:plain

モノクロのコンソール出力
f:id:fortran66:20140626034249j:plain

大きいフォントでの拡大図
f:id:fortran66:20140626035612j:plain


プログラムは、BMPファイル読み書き用のモジュールと、メインのプログラムからなります。Fortran95 で導入された elemental 属性のスカラー関数を定義すれば、ループを明示的に書かなくて済むので、30行程度で実現できました。

このほかに、フォントデータを生成する別のプログラムが必要です。これが一番面倒かもしれません。BMPファイルはy軸方向の座標がひっくり返っているので少し混乱しました。また、Fortran には符号なし整数がないため、RGB データの1バイト整数が見かけ上負数になったりして混乱しました。

なお、Fortran2003 の配列自動再割り付けを使っていますので、適切なコンパイラ・オプションが必要となります。

メイン・プログラム 

    module m_bmp
      implicit none
      type :: t_bmp_file_header
        sequence  
        integer(2) :: bfType = transfer('BM', 0_2, 1) ! BitMap
        integer(4) :: bfSize          ! file size in bytes
        integer(2) :: bfReserved1 = 0 ! always 0
        integer(2) :: bfReserved2 = 0 ! always 0
        integer(4) :: bfOffBits
      end type t_bmp_file_header
      !
      type :: t_bmp_info_header
        sequence
        integer(4) :: biSize     = Z'28' ! size of bmp_info_header ; 40bytes 
        integer(4) :: biWidth
        integer(4) :: biHeight
        integer(2) :: biPlanes   = 1 ! always 1
        integer(2) :: biBitCount
        integer(4) :: biCompression = 0 ! 0:nocompression, 1:8bitRLE, 2:4bitRLE, 3:bitfield
        integer(4) :: biSizeImage
        integer(4) :: biXPelsPerMeter = 3780 ! 96dpi
        integer(4) :: biYPelsPerMeter = 3780 ! 96dpi 
        integer(4) :: biClrUsed      = 0
        integer(4) :: biClrImportant = 0 
      end type t_bmp_info_header
      !
      type :: t_rgb
        sequence
        integer(1) :: ib, ig, ir
      end type t_rgb  
    contains   
      subroutine rd_bmp(fn, bmp)
        character(len = *), intent(in) :: fn
        type(t_rgb), allocatable, intent(out) :: bmp(:, :)
        type(t_bmp_file_header) :: bmp_file_header
        type(t_bmp_info_header) :: bmp_info_header
        open(9, file = fn//'.bmp', form = 'binary', status = 'unknown')
        read(9) bmp_file_header
        read(9) bmp_info_header
        allocate( bmp(bmp_info_header%biWidth, bmp_info_header%biHeight) )
        read(9) bmp
        close(9)
        return
      end subroutine rd_bmp  
    end module m_bmp

    program BMP_data
      use m_bmp 
      implicit none
      integer, parameter :: mx = 4, my = 8
      type :: t_cha_bmp
        type(t_rgb) :: m(mx, my)  
      end type t_cha_bmp
      integer, parameter :: ncol = 48, nrow = 2
      type (t_cha_bmp) :: aa(ncol * nrow), tmp ! 20H..AEH
      type(t_rgb), allocatable :: bmp(:, :)
      integer :: ix, iy, nx, ny, ipos
!
      open(13, file = 'font'//'.dat', status = 'unknown', form = 'unformatted')
      read(13) aa
      call rd_bmp('tktt', bmp) ! call rd_bmp('font4x8', bmp)
      nx = size(bmp, 1) 
      ny = size(bmp, 2) 
      do iy = 1, ny / my
        do ix = 1, nx / mx
          associate( kx => mx * (ix - 1) + 1, ky => ny - my * (iy - 1) )
            tmp%m = bmp(kx:kx + mx - 1, ky:ky - my + 1:-1)  
            ipos = minloc( distance(tmp, aa), 1 ) + z'1F'
            write(*, '(g0)', advance = 'no') achar(ipos)
          end associate    
        end do
        write(*, *)
      end do  
      stop
    contains  
      pure elemental integer function distance(cha1, cha2)
        type(t_cha_bmp), intent(in) :: cha1, cha2
        real, parameter :: f = 6.5 / 3.0
        distance = sum( ( (int1to4(cha1%m%ir) - f * int1to4(cha2%m%ir)) &
                        + (int1to4(cha1%m%ig) - f * int1to4(cha2%m%ig)) &
                        + (int1to4(cha1%m%ib) - f * int1to4(cha2%m%ib)) )**2 ) 
      end function distance
      
      pure elemental integer function int1to4(i)
        integer(1), intent(in) :: i
        int1to4 = transfer(i, 0)
      end function int1to4  
    end program BMP_data

ここで、距離を求める関数 distance 中の係数 f は経験的なパラメータで、画像の露光量にあたるものを変化させるために導入しました。微妙に変えてやると見栄えが良くなります。




フォントのビットマップデータ作成プログラム。フォントとしては日本語DOS窓のMSゴシック 4x8ドットを用いました。たまたま試してみただけで、深い理由はありません。フォントの使用範囲はASCIIの 20H から 7FH までの96個を用いました。 

    program BMP_data
      use m_bmp 
      implicit none
      integer, parameter :: mx = 4, my = 8
      type(t_rgb), allocatable :: bmp(:, :)
      type :: t_cha_bmp
        type(t_rgb) :: m(mx, my)  
      end type t_cha_bmp
      integer, parameter :: ncol = 48, nrow = 2
      integer :: ix, iy
      type (t_cha_bmp) :: aa(ncol, nrow) ! 20H..AEH
!      
      call rd_bmp('font4x8', bmp)  
      do ix = 1, ncol
        do iy = 1, nrow
          associate( kx => mx * (ix - 1) + 1, ky => my * iy )
            aa(ix, nrow - iy)%m = bmp(kx:kx + mx - 1, ky:ky - my + 1:-1)  
          end associate
        end do
      end do
      open(13, file = 'font.dat', status = 'unknown', form = 'unformatted')
      write(13) aa
      stop
    end program BMP_data

フォント表示プログラム

    program abc
      implicit none
      integer, parameter :: n = 16 * 6
      character :: cha(n)
      integer :: i
      print *, n
      do i = 1, n
        cha(i) = achar(32 - 1 + i)
      end do  
      print '(48g0)', cha
    end program abc

DOS窓を ALT + PrintScreen でキャプチャーし、ペイントで必要な二行分のフォント画像を切り取って 24bit BMP 形式で保存します。
f:id:fortran66:20140626035613j:plain

f:id:fortran66:20140626035614j:plain
これをフォント生成プログラムの入力とします。出力された font.dat は、 ASCII ART 生成プログラムの入力となります、