完全に間違えた消しゴムマジック！

# ネコチャンかわいい

我が家のネコチャンがかわいいのでキャットタワーにいるところを撮りました！

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/838d5c1e-a6e1-694d-f0ef-30f431840beb.jpeg">

しまった！
[「やわらぎ」](https://www.momoya.co.jp/shop/products/detail/21)が写りこんでしまった！

ヘビーやわらぎユーザーの方なら誰もが経験する良くあるミス！
やわらぎ写りこみガチ！

# 消しゴムマジック！アプリ！？

Google Pixel だと「消しゴムマジック」でこういうの消せるらしいじゃないですか！
でも僕は生粋の Xperia ユーザーなので消しゴムマジック使えない！
無いなら作るしかない！！

ということで、なぞったところを透過させる？アプリを作りました！
なお、Delphi 好きだから Delphi で作った！

<img width="480" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/dd385538-321b-de74-f744-ae15fe627442.gif">

## 保存された画像
保存された画像を見ると…わあ～！

<img width="480" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/49dae244-45e8-34bf-412c-f2e60c01ef42.png">

やわらぎが消えました！！
消しゴムマジック！ヤッター！

## え！消しゴムマジックってこういう事じゃないの！？

えっ！えっ！
[Photoshop の「コンテンツに応じた塗りつぶし」](https://helpx.adobe.com/jp/photoshop-elements/using/content-aware-fill.html)と同じ機能なのか～！(棒)

**しかも、このアプリ実際には透過してないし！**
えっ！えっ！

## 実体は「画像に市松模様を描きこむアプリ」

というわけで、このクソアプリの実体は「画像にαを表す市松模様を描き込むアプリ」でした。
透過 PNG とかではなく たたただ市松模様を描き込んで透過しているかのように欺く！だけ！
できあがった画像は何にも使えない！
完璧なクソアプリ！！

ちなみに Delphi で作ったので色んな OS で動きます！
Android はムービーで。iOS, macOS はスクリーンショット(Linux でも動くけど割愛)

Android
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/dc9e9488-0f9e-6b5b-b3a7-9b57be511550.gif">

<img width="480" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/13723088-a595-8335-5536-b5036978e451.png">

# 解説
ここから Delphi 使い向けへの解説です。

## UI 素材について
今回は UI 素材を全て SVG で作りました。
そのため、Delphi 12 から使えるようになった Skia の TSkSVG を使っています。
.svg ファイルやコピーした SVG をそのままペーストできます。
TPath は SVG の中の d タグだけしか扱えず色々面倒でした。
TSkSVG はそういった諸々の面倒から解放されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/31199917-4984-fcf3-df74-4270daf0a2c2.png)


## TakePhotoFromLibraryAction は使ってはいけない
チュートリアルやサンプルアプリで紹介される [TakePhotoFromLibraryAction](https://docwiki.embarcadero.com/Libraries/ja/FMX.MediaLibrary.Actions.TTakePhotoFromLibraryAction) ですが、これを使って取れる Bitmap は幅と高さを画面スケールで割った値になります。
たとえば画面スケールが 2.0 の端末で、800 x 600 の画像を取得すると、400 x 300 になってしまうということです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/c8e49eb3-cc94-786a-35c1-f20d307d4539.png)

TakePhotoFromLibraryAction を使う代わりに [IFMXTakenImageService](https://docwiki.embarcadero.com/Libraries/ja/FMX.MediaLibrary.IFMXTakenImageService) サービスを使います。

```delphi:IFMXTakeenImageService
// IFMXTakenImageService は FMX.MediaLibrary に定義
var Service: IFMXTakenImageService;
if
  TPlatformServices.Current.SupportsPlatformService(
    IFMXTakenImageService,
    Service
  )
then
begin
  var P: TParamsPhotoQuery;
  // 画像サイズを最大にすることで画像の劣化を防ぐ
  P.RequiredResolution := TSize.Create($ffff, $ffff);
  P.Editable := True;
  P.NeedSaveToAlbum := False;
  P.PickerPresentation := TPickerPresentation.Latest;
  // 画像取得イベントハンドラを指定
  P.OnDidFinishTaking := TakePhotoFinishHandler;
  P.OnDidCancelTaking := nil;
  P.OnDidFailTaking  := nil;

  Service.TakeImageFromLibrary(nil, P);
end
```
ここで重要なのは RequiredResolution に指定している値です。
最大の画像サイズを指定するのですが、TakePhotoFromLibraryAction はここに画面スケールで割った値をいれるので画像が劣化してしまいます。
ここに最大サイズである $ffff を指定してあげれば画像はそのままの大きさで取れます。
画像を受取るイベントハンドラは TakePhotoFromLibraryAction.OnDidFinishTaking と同じです。

```delphi:OnDidFinishTakingイベントハンドラ
procedure TfrmMain.TakePhotoFinishHandler(AImage: TBitmap);
begin
  imgImage.Bitmap.Assign(AImage);
end;
```

## 市松模様の描画方法

16x16 pixel の下のような画像を用意します。

<img width="360" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/a38f117a-6489-9fd1-7ad5-5f649bf18f2f.png">

この画像を Stroke.Bitmap に指定して線を引くと簡単に市松模様が描画できます！
※ちゃんと市松模様の整合性を保って描かれます
※Stroke で描く場合アンチエイリアスが効く＆スケーリングされてしまうので Photoshop と同じ市松模様では無くなります

```delphi:Strokeを使って市松模様画像を書き込む
with imgImage.Bitmap.Canvas do
begin
  BeginScene;
  try
    // Fill は使わない
    Fill.Kind := TBrushKind.None;

    // ペンの太さ
    Stroke.Thickness := FDiameter;

    // ペン先と接続方法
    Stroke.Cap := TStrokeCap.Round;
    Stroke.Join := TStrokeJoin.Round;

    // ペンにビットマップを使う
    Stroke.Kind := TBrushKind.Bitmap;

    // 市松模様画像を指定
    Stroke.Bitmap.Bitmap := FAlphaBitmap;

    // 線を引く
    DrawLine(FOldPos, Pos, 1);
  finally
    EndScene;
  end;
end;
```

**かんたん！**

### おまけ
このアプリではプログラムで上記の 16x16 の市松模様画像を作っています。
プログラムは下記の通りです。

```delphi:参考：市松模様画像を生成する
procedure CreateAlphaBitmap;
type
  TAlphaColorArray = array [0.. 0] of TAlphaColor;
  PAlphaColorArray = ^TAlphaColorArray;
const
  TRANS_COLORS: array [0.. 1] of TAlphaColor = ($ff_ff_ff_ff, $ff_cc_cc_cc);
begin
  // 16 x 16 のビットマップを作成
  FAlphaBitmap := TBitmap.Create(16, 16);

  // Bitmap のピクセルデータを取得
  var Data: TBitmapData;
  FAlphaBitmap.Map(TMapAccess.Write, Data);
  try
    for var Y := 0 to Data.Height - 1 do
    begin
      // Y 列の先頭のポインタを取得
      var Line := PAlphaColorArray(Data.GetScanline(Y));

      {$R-} // 範囲チェック例外を無効化
      // X, Y の値から $ffffffff か $ffcccccc を書き込む
      for var X := 0 to Data.Width - 1 do
        Line[X] := TRANS_COLORS[((X xor Y) and %0000_1000) shr 3];
    end;
  finally
    FAlphaBitmap.Unmap(Data);
  end;
end;
```

コード中に出てくる配列のインデックスに使っている式についてちょっとだけ解説

座標値 X か Y のどちらか片方の 4bit 目 (= 8) が立っている場合は ```1``` を、どちらも立っていないか両方立っている場合は ```0``` とすると、8pixel 毎に交互に変わる値 ```0, 1``` を取り出せます。
これを立式すると下記のようになります（%接頭辞は2進数を表します）。

```delphi
((X and %0000_1000) xor (Y and %0000_1000)) shr 3
```

式を整理すると下記の様になり

```delphi
((X xor Y) and %0000_1000) shr 3
```

プログラム中で使っている式が導出されます。

算出された 0, 1 を配列のインデックスとして使って ```#ffffff``` と ```#cccccc``` を切りかえることで 8 pixel 毎に色が変わる市松模様が描かれます。

## ソース
今回のクソアプリのソースコードはこちら！
https://github.com/freeonterminate/MagicEraser

ただし、[Delphi 12 Athens](https://blogs.embarcadero.com/ja/announcing-the-availability-of-rad-studio-12-athens-ja/) で作ったので Skia を使っています。

無料の [Community Edition](https://www.embarcadero.com/jp/products/delphi/starter) など Dephi 11 以前の場合は、Skia を自前でインストールするとビルド出来るかもしれません。

Skia 4 Delphi
https://github.com/skia4delphi/skia4delphi

## 実行ファイル
Windows 用の実行ファイルはこちら！
https://github.com/freeonterminate/MagicEraser/releases/tag/v1.0.0

実行するためには同梱の readme.txt をご覧ください。
Chrome がダウンロードを妨げないように拡張子を変更しています。

# 最後に
本当に何の使い道も無いクソアプリを作ってしまった…！

Delphi は無料で全ての機能が使える [Community Edition](https://www.embarcadero.com/jp/products/delphi/starter) があるので興味がわいた方は使ってみてください！

## 参考：今までのクソアプリ
■2021 年：KusoGame
https://qiita.com/pik/items/185b89e805239a979408

■2020 年：Delphi で KUSO アプリを作った件
https://qiita.com/pik/items/4e815b0bca8af52f970a

■2019 年：クソアプリを作った件
https://qiita.com/pik/items/4759d8597a434c9a691f