[C#]画像に対していろいろ行う(System.Drawing.Bitmap版)

**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

**画像に対していろいろやるシリーズ**

- 画像に対していろいろ行う(System.Drawing.Bitmap版)  
https://qiita.com/tera1707/items/385c9baca83f7c08e5e1
- 画像に対していろいろ行う(System.Windows.Imaging版)  
https://qiita.com/tera1707/items/1dfef61ccab7f7e3a381


# やりたいこと

WPFの画面表示や画像回りでいろいろやることがあるが、なんだかややこしくて、まとめようと思ってもまとめきれないまま時間が過ぎている。
いきなりまとめるのをちょっとあきらめて、やりたいこと、やったことのレシピ集みたいなのを作って経験値を貯めてから、見えてきたものを後でまとめようと思う。
→主に、`System.Drawing.Bitmap`を使って画像をファイルに保存したり、編集したときのことをまずは書く。(あとは、出てきたやつを都度追記していく。)


## 画像ファイルを開いて、好きな文字やら図形を書き込んで、別のファイルに保存する（その１）

1. 元画像ファイル(.bmp等)を`System.Drawing.Bitmap`に格納
- `System.Drawing.Graphics`で格納した画像を編集
- 出力先ファイルへの`FileStream`を作成
- `System.Drawing.Bitmap`の`Save`メソッドで、**出力先ファイルへの`FileStream`**を指定し保存
- **→これで、出力先ファイルに保存される**

```サンプル１.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var fs = new FileStream(@"output1.bmp", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 保存方法① streamで保存 → "output1.bmp"に保存される
    fs.SetLength(0);
    bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Bmp);
}
```

※fs(ファイルストリーム)がClose＝Disposeされた時点（つまり、usingを抜けた時点）で、実際にbmpファイルに書き出される。

## 画像ファイルを開いて、好きな文字やら図形を書き込んで、別のファイルに保存する（その２）

1. 元画像ファイル(.bmp等)を`System.Drawing.Bitmap`に格納
- `System.Drawing.Graphics`で格納した画像を編集
- `System.Drawing.Bitmap`の`Save`メソッドで、**出力先ファイルのパス**を指定し保存
- **→これで、出力先ファイルに保存される**


```サンプル２.cs
using (var bmp = new Bitmap(@"input.bmp"))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 保存方法② ファイルパス指定で保存 → "output2.bmp"に保存される
    bmp.Save(@"output2.bmp");
}
```

## 画像ファイルを開いて、好きな文字やら図形を書き込んで、画面に表示する

`FileStream`を使うやり方。

```サンプル３－１.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var fs = new FileStream(@"output1.bmp", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 画面に表示 ※MyImageは、xamlに配置された<Image Name="MyImage"/>
    var a = BitmapFrame.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    MyImage.Source = a;
}
```

下記でも、同じことができる。(ファイルをかまさずに、メモリ(`MemoryStream`)で済ます)

```３－２.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var ms = new MemoryStream())
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    //// MemoryStreamに一旦保存
    ms.SetLength(0);
    bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);

    // 画面に表示
    var a = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    MyImage.Source = a;
}
```


## 画面表示を取り込んで、ファイルに保存する
以前の記事参照。
→[BitmapSourceやその派生クラスから、System.Drawing.Bitmapに変換する](https://qiita.com/tera1707/items/f9808e9ebec60005d6c5#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB)

```.cs
// BitmapSourceの派生クラス「RenderTargetBitmap」で、画像を取ってくる
var canvas = new RenderTargetBitmap((int)MyImage.ActualWidth, (int)MyImage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
canvas.Render(MyImage); // canvasに画像を描画

// BmpBitmapEncoderに画像を入れる
using (var stream = new MemoryStream())
{
    // BmpBitmapEncoderに画像を書きこむ(使うBitmapEncoderの派生クラスによって、いろんなフォーマットのファイルを作れる)
    //BitmapEncoder encoder = new BmpBitmapEncoder();
    BitmapEncoder encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(canvas));// canvasからBitmapEncoder に書き込み
    encoder.Save(stream);

    // BmpBitmapEncoderからSystem.Drawing.Bitmapをつくる
    var bitmap = new System.Drawing.Bitmap(stream);
    bitmap.Save(@".\out.jpeg");
}
```

## 画面表示を取り込んで、画像に四角を書き込んで、それをもう一回画面に表示する

ややこしい。下記のようなクラスを経由していく流れ。

- RenderTargetBitmap（画面から画像の取り込み）
- BmpBitmapEncoder（RenderTargetBitmapをMemoryStreamに流し込む）
- MemoryStream（編集前の画像が流れてる）
- System.Drawing.Bitmap（MemoryStreamからBitmap作成）
- Graphics（BitmapをGraphicsで編集）
- MemoryStream（編集**後**の画像が流れてる）
- BitmapFrame(MemoryStreamからBitmapFrameを作成)
- Image.Source（BitmapFrameを画面にセット）
- →おわり

※画面上の画像に四角を書き込む、ということがこれでもできそう。
　できそうだが、画面上に四角を出したいなら、**xaml上に\<Canvas\>とその中に\<Rectangle/\>を置いて、そのWidthやheight、Canvas.TopやCanvas.Bottomをプログラムで制御する**ほうが絶対いいなと思った。(テキストでも同様(\<TextBlock\>で。))

```.cs
// 画面からRenderTargetBitmapに画像を描画
var canvas = new RenderTargetBitmap((int)MyImage.ActualWidth, (int)MyImage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
canvas.Render(MyImage);

using (var stream = new MemoryStream())
{
    // MemoryStreamに、RenderTargetBitmapから画像を流し込む
    BitmapEncoder encoder = new BmpBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(canvas));
    encoder.Save(stream);

    // MemoryStreamからBitmapを作成
    var editted = new System.Drawing.Bitmap(stream);

    // GraphicsでBitmapを編集(四角を書き込む)
    using (var g = Graphics.FromImage(editted))
    {
        g.DrawRectangle(Pens.Green, new System.Drawing.Rectangle(3, 3, 200, 200));
    }
    // →この時点で、editted(Bitmap)に緑の四角が書き込まれてる
    // Streamはまだ。

    // 四角を書き込んだ画像をstreamに流し込む
    editted.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
    editted.Dispose();

    // MemoryStreamからBitmapを作成から、BitmapFrame(BitmapSourceの子クラス)を作成
    stream.Seek(0, SeekOrigin.Begin);// seekでBeginに戻さないと例外
    var bitmapSource = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    // それをImageのSourceにセット → 表示！
    MyImage.Source = bitmapSource;
}
```

## 画像に別の画像を貼り付けて保存する(pngで透過も可)

ある画像に、別の画像を貼り付けて保存する。
貼り付ける側としては`.jpg`や`.bmp`、`.png`などが使えるが、
pngで透過部分がある画像だと、貼り付けるとその部分が透過されるの。
(ロゴマークとかを貼り付けるときに使えそう)

```cs
using (var bmp = new Bitmap(@"src.jpg"))            // 元の画像
{
    using (var g = Graphics.FromImage(bmp))         // 元の画像からGraphicsをつくる
    using (var pngbmp = new Bitmap("touka.png"))    // 貼り付けたい画像を開く(この場合はpngで、透明部分を透過できる)
    {
        // 貼り付ける画像を(5,5)の位置に描画する
        g.DrawImage(pngbmp, 5, 5, pngbmp.Width, pngbmp.Height);
    }
    // 保存する
    bmp.Save(@"output.bmp");
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/a2423bd4-9fc4-cb1b-f61d-50db2f20801d.png)

### 透過部分を含む.pngの作り方
余談だが、実験用の`.png`ファイルはエクセルを使って簡単に作れる。
→https://amaotolog.com/excel/42
(エクセルのシートをhtmlとして保存したときの`副産物`的な保存のされ方)


# 参照

いろんな画像関連の型の変換
https://qiita.com/YSRKEN/items/a24bf2173f0129a5825c

これで画像に四角とか書き足す、できんか？(System.Drawingではないやつで)
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/painting-with-images-drawings-and-visuals

# しらべたいこと

PixelFormats.Pbgra32 ってなに？
https://water2litter.net/gin/?p=987 でやってるCreateで指定してるやつ。
よく出てくるがじつはわかってない。