Delphi で Wio Terminal 用画像コンバータを作ってみる

# はじめに
**Wio Terminal** を購入しまして。画像を表示させるチュートリアルをやってみようと思ったのですが...

 - [Wio Terminal (seeed: en)](https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/)
 - [Wio Terminal (seeed: ja)](https://wiki.seeedstudio.com/jp/Wio-Terminal-Getting-Started/)
 - [LCD \| Wio \| Terminal \| Platform \| Loading Image (seeed)](https://wiki.seeedstudio.com/Wio-Terminal-LCD-Loading-Image/)


# 画像表示

画像を表示させるためにはチュートリアルの通りやればいいのですが、画像は 24bit Bitmap から、独自の 8bit / 16bit ビットマップへ変換しなくてはなりません。

| 色深度 | フォーマット |
|:---:|:---:|
| 8bit  | rgb332 |
| 16bit  | rgb565 |

変換ための Python スクリプト (bmp_converter.py) が用意されているのですが、

```
ImportError: No module named PIL
```

だの、

```
ModuleNotFoundError: No module named 'PIL'
```

だの言われて難儀しました。結局、チュートリアルページに書いてある `pip` では解決せず、`Anaconda` をインストールする必要がありました。その `Anaconda` を動作させるためには環境変数に...

## 面倒くさい...
チュートリアルを試したいだけなのに...。

`Linux  を常用している人` or `Python を常用している人` にはなんてことはないのでしょうけど、僕にとっては面倒くさかったので、変換ツールを **Delphi 10.3 Rio** で書く事にしました。

### ソースコード

書きました。

```pascal:bmp_converter.dpr
program bmp_converter;

{$APPTYPE CONSOLE}

uses
  System.Classes, System.SysUtils, System.IOUtils, System.UITypes, Vcl.Graphics;

type
  TRGBType = (rgb8bit, rgb16bit);

const
  SUBDIR: array [TRGBType] of string = ('rgb332', 'rgb565');

  function ConvertToRGB332(aColor: TColorRec): Byte;
  begin
    with aColor do
      begin
        R := R shr 5;
        G := G shr 5;
        B := B shr 6;
        Result := (R shl 5) or (G shl 2) or B;
      end;
  end; { ConvertToRGB332 }

  function ConvertToRGB565(aColor: TColorRec): Word;
  begin
    with aColor do
      begin
        R := R shr 3;
        G := G shr 2;
        B := B shr 3;
        Result := (R shl 11) or (G shl 5) or B;
      end;
  end; { ConvertToRGB565 }

begin
  // 色深度の選択
  Writeln('Enter (1) for 8-bit colour convert, Enter (2) for 16-bit colour convert');
  var c: Char;
  while True do
    begin
      Readln(c);
      if CharInSet(c, ['1'..'2']) then
        Break;
      Writeln('Invalid input!');
    end;
  var RGBType := TRGBType(Ord(c) - Ord('1'));

  // 入出力フォルダ
  var SrcDir := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), 'bmp');
  var DstDir := TPath.Combine(SrcDir, SUBDIR[RGBType]);
  if not TDirectory.Exists(DstDir) then
    TDirectory.CreateDirectory(DstDir);

  // bmp フォルダにある *.bmp を処理
  var im: TBitmap := TBitmap.Create;
  try
    for var FileName in TDirectory.GetFiles(SrcDir, '*.bmp', TSearchOption.soTopDirectoryOnly) do
      begin
        // 画像をロードして強制的に 24bit ビットマップに変換
        im.LoadFromFile(FileName);
        im.PixelFormat := TPixelFormat.pf24bit;
        // データの生成
        var v: TBytes := [im.Width  and $00FF, im.Width  shr 8,
                          im.Height and $00FF, im.Height shr 8];
        for var y := 0 to Pred(im.Height) do
          for var x := 0 to Pred(im.Width) do
            begin
              var Color: TColorRec;
              Color.Color := im.Canvas.Pixels[x, y];
              case RGBType of
                rgb8bit:
                  v := v + [ConvertToRGB332(Color)];
                rgb16bit:
                  begin
                    var value := ConvertToRGB565(Color);
                    v := v + [value shr 8, value and $00FF];
                  end;
              end;
            end;
        // バイト列をファイルに保存
        var bs := TBytesStream.Create(v);
        try
          bs.SaveToFile(TPath.Combine(DstDir, TPath.GetFileName(FileName)));
        finally
          bs.Free
        end;
      end;
  finally
    im.Free;
  end;
end.
```

Delphi 10.3 Rio さえインストールされていれば、上記ファイルを `bmp_converter.dpr` という名前で保存し、[ファイル | プロジェクトを開く] で開いてコンパイルするだけのお手軽さです。

 - 外部ライブラリや特別な設定は必要ありません。
 - 仕様は bmp_converter.py 準拠です (bmp サブフォルダにある *.bmp を変換します)。

**See also:**

 - [Vcl.Graphics.TCustomCanvas.Pixels (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/Vcl.Graphics.TCustomCanvas.Pixels)


### ソースコード (高速化版)
ピクセルの取得に **ScanLine** を使い、動的配列の領域確保回数を最小限に抑えた高速化版です。

```pascal:bmp_converter.dpr
program bmp_converter;

{$APPTYPE CONSOLE}

uses
  System.Classes, System.SysUtils, System.IOUtils, System.UITypes,
  Winapi.Windows, Vcl.Graphics;

type
  TRGBType = (rgb8bit, rgb16bit);

const
  SUBDIR: array [TRGBType] of string = ('rgb332', 'rgb565');

  function ConvertToRGB332(aColor: TRGBTriple): Byte;
  begin
    with aColor do
      begin
        rgbtRed   := rgbtRed   shr 5;
        rgbtGreen := rgbtGreen shr 5;
        rgbtBlue  := rgbtBlue  shr 6;
        Result := (rgbtRed shl 5) or (rgbtGreen shl 2) or rgbtBlue;
      end;
  end; { ConvertToRGB332 }

  function ConvertToRGB565(aColor: TRGBTriple): Word;
  begin
    with aColor do
      begin
        rgbtRed   := rgbtRed   shr 3;
        rgbtGreen := rgbtGreen shr 2;
        rgbtBlue  := rgbtBlue  shr 3;
        Result := (rgbtRed shl 11) or (rgbtGreen shl 5) or rgbtBlue;
      end;
  end; { ConvertToRGB565 }
begin
  // 色深度の選択
  Writeln('Enter (1) for 8-bit colour convert, Enter (2) for 16-bit colour convert');
  var c: Char;
  while True do
    begin
      Readln(c);
      if CharInSet(c, ['1'..'2']) then
        Break;
      Writeln('Invalid input!');
    end;
  var RGBType := TRGBType(Ord(c) - Ord('1'));

  // 入出力フォルダ
  var SrcDir := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), 'bmp');
  var DstDir := TPath.Combine(SrcDir, SUBDIR[RGBType]);
  if not TDirectory.Exists(DstDir) then
    TDirectory.CreateDirectory(DstDir);

  // bmp フォルダにある *.bmp を処理
  var im: TBitmap := TBitmap.Create;
  try
    for var FileName in TDirectory.GetFiles(SrcDir, '*.bmp', TSearchOption.soTopDirectoryOnly) do
      begin
        // 画像をロードして強制的に 24bit ビットマップに変換
        im.LoadFromFile(FileName);
        im.PixelFormat := TPixelFormat.pf24bit;
        // データの生成
        var v: TBytes := [im.Width  and $00FF, im.Width  shr 8,
                          im.Height and $00FF, im.Height shr 8];
        var i := Length(v);
        SetLength(v, im.Width * im.Height * Succ(Ord(RGBType)) + Length(v));
        for var y := 0 to Pred(im.Height) do
          begin
            var Lines: PRGBTriple := im.ScanLine[y];
            for var x := 0 to Pred(im.Width) do
              begin
                case RGBType of
                  rgb8bit:
                    v[i] := ConvertToRGB332(Lines^);
                  rgb16bit:
                    begin
                      var value := ConvertToRGB565(Lines^);
                      v[i] := value shr 8;
                      Inc(i);
                      v[i] := value and $00FF;
                    end;
                end;
                Inc(i);
                Inc(Lines);
              end;
          end;
        // バイト列をファイルに保存
        var bs := TBytesStream.Create(v);
        try
          bs.SaveToFile(TPath.Combine(DstDir, TPath.GetFileName(FileName)));
        finally
          bs.Free
        end;
      end;
  finally
    im.Free;
  end;
end.
```

多くのファイルを一括置換するならこちらの方がオススメです。

**See also:**

 - [Vcl.Graphics.TBitmap.ScanLine (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/Vcl.Graphics.TBitmap.ScanLine)
 - [動的配列 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E5.8B.95.E7.9A.84.E9.85.8D.E5.88.97)
 - [bmp_converter - Win32 コンパイル済バイナリ (ht-deko.com)](https://ht-deko.com/delphiforum/?vasthtmlaction=viewtopic&t=2268.0)


# おわりに
FireMonkey に移植すると macOS や Linux で使えて便利かもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/e3714a70-e33a-574d-6081-441708070e16.png)

 - [Delphi (Embarcadero)](https://www.embarcadero.com/jp/products/delphi)
 - [Delphi Community Edition (Embarcadero)](https://www.embarcadero.com/jp/products/delphi/starter)