WPF、画像から複数箇所を矩形(Rect)に切り抜いて、それぞれ位置を合わせて1枚の画像にしてファイルに保存する
表現が難しい、百聞は一見にしかず
こういうの
作っている環境
- Visual Studio Community 2019
- Windows 10 Home
- WPF
- .NET Core 5
- C#
2021WPF/20210124_画像の切り抜き、複数画像を1枚にする
github.com
MainWindow.xaml
<Window x:Class="_20210124_画像の切り抜き_複数画像を1枚にする.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20210124_画像の切り抜き_複数画像を1枚にする" mc:Ignorable="d" Title="MainWindow" Height="650" Width="800"> <Window.Resources> <Style TargetType="Image"> <Setter Property="Margin" Value="10"/> <Setter Property="Stretch" Value="None"/> </Style> </Window.Resources> <Grid UseLayoutRounding="True"> <StackPanel> <Image x:Name="MyOriginImage"/> <Image x:Name="MyImage"/> </StackPanel> </Grid> </Window>
MainWindow.xaml.cs
using System; using System.Collections.Generic; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; namespace _20210124_画像の切り抜き_複数画像を1枚にする { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); MyInitialize(); } private void MyInitialize() { //元の画像の読み込みと表示 var img = new BitmapImage(new Uri( @"D:\ブログ用\チェック用2\WP_20201222_10_21_40_Pro_2020_12_22_午後わてん_ラーメン.jpg")); MyOriginImage.Source = img; //切り抜き範囲のリスト作成 List<Rect> MyRectList = new() { new Rect(5, 110, 85, 60), new Rect(65, 135, 130, 130), new Rect(270, 50, 135, 130), }; //切り抜いて BitmapSource bmp = CroppedBitmapFromRects(img, MyRectList); MyImage.Source = bmp;//表示 SaveImage(bmp);//保存 } /// <summary> /// 複数Rect範囲を組み合わせた形にbitmapを切り抜く /// </summary> /// <param name="source">元の画像</param> /// <param name="rectList">Rectのコレクション</param> /// <returns></returns> private BitmapSource CroppedBitmapFromRects(BitmapSource source, List<Rect> rectList) { var dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { //それぞれのRect範囲で切り抜いた画像を描画していく foreach (var rect in rectList) { dc.DrawImage(new CroppedBitmap(source, RectToIntRectWith切り捨て(rect)), rect); } } //描画位置調整 dv.Offset = new Vector(-dv.ContentBounds.X, -dv.ContentBounds.Y); //bitmap作成、縦横サイズは切り抜き後の画像全体がピッタリ収まるサイズにする //PixelFormatsはPbgra32で決め打ち、これ以外だとエラーになるかも、 //画像を読み込んだbitmapImageのPixelFormats.Bgr32では、なぜかエラーになった var bmp = new RenderTargetBitmap( (int)Math.Ceiling(dv.ContentBounds.Width), (int)Math.Ceiling(dv.ContentBounds.Height), 96, 96, PixelFormats.Pbgra32); bmp.Render(dv); return bmp; } //RectからInt32Rect作成、小数点以下切り捨て編 private Int32Rect RectToIntRectWith切り捨て(Rect re) { return new Int32Rect((int)re.X, (int)re.Y, (int)re.Width, (int)re.Height); } //今回は未使用 //RectからInt32Rect作成、小数点以下四捨五入 private Int32Rect RectToIntRectWith簡易四捨五入(Rect re) { return new Int32Rect( My簡易四捨五入(re.X), My簡易四捨五入(re.Y), My簡易四捨五入(re.Width), My簡易四捨五入(re.Height)); //double型からint型の変換は切り捨てなので、 //0.5足してから変換すると簡易四捨五入になる int My簡易四捨五入(double value) { return (int)(value + 0.5); } } //bitmapをpng画像ファイルで保存 private void SaveImage(BitmapSource source) { PngBitmapEncoder encoder = new(); encoder.Frames.Add( BitmapFrame.Create(source)); string path = DateTime.Now.ToString("HH時mm分ss秒"); path = "cropped_" + path + ".png"; using (var pp = new System.IO.FileStream( path, System.IO.FileMode.Create, System.IO.FileAccess.Write)) { encoder.Save(pp); } } } }
実行後のアプリの状態は
上に元の画像、下に切り抜き画像が表示される、これは確認用で、本命の切り抜き画像ファイルはアプリの実行ファイルのあるフォルダにできる
元の画像
給付金によって9年ぶりにラーメン食べる機会を得られて、驚くほど美味しかったのはマルちゃん正麺醤油味、なお写真は日清ラ王味噌 2食/5食パックx6、これも美味しい
切り抜く場所とサイズになる矩形範囲のRectは
今回は決め打ちで、3箇所にした
これは図にしてみると
こんな感じで、さらに画像と合わせてみると
こう
CroppedBitmapクラス
画像の切り抜きはCroppedBitmapクラスを使うとラクにできる
CroppedBitmapに渡すのは元の画像とInt32Rect、画像のサイズに小数点はないからRectじゃなくてInt32Rectなので
RectをInt32Rectに変換
これは単純にdouble型をint型にキャストしているから小数点以下は切り捨てになる、今回の決め打ちRectは整数しか入っていのがわかっているからこれでいい
そうじゃないときは切り上げしたほうがいい、Mathクラスの切り上げのメソッドCeiling
切り抜いた画像群を位置合わせして1枚の画像にする
DrawingVisualとDrawingContxtとRenderTargetBitmapクラスを使っている
と言ってもよくわかっていない
要なのは54行目かな
CroppedBitmapで得た切り抜き画像を、DrawingContxtのDrawImageメソッドでDrawしている
ってことはDrawingVisualが画用紙とかキャンバスみたいな感じなのかなあ、それともDrawingContxtが画用紙とかキャンバス?この辺のイメージがねえ、わからん
あと重要なのが、59行目のOffsetでの位置調整で、これをしないと
赤の点線が画像の範囲になって、豆腐が欠ける、コップの右も地味に欠けている
Offsetする値は、DrawingVisualのContentBoundsプロパティのXとYをマイナスにした値でぴったりになった
最後にBitmapの作成はRenderTargeBitmapクラスを使う、RenderメソッドにDrawingVisualを渡して完成!
RenderTargeBitmapの作成時に渡しているのは、
横ピクセル数、縦ピクセル数、横dpi、縦dpi、ピクセルフォーマット
できあがりの画像の縦横サイズは切り抜き画像がピッタリ収まるサイズにしたい、そうしないでもとの画像サイズにすると余計な余白ができてしまう
赤枠がもとの画像サイズ、緑枠がぴったりサイズ
で、このピッタリサイズは位置調整にも使ったDrawingVisualのContentBoundsプロパティ、これのWidthとHeight、それを利用しているのが65、66行目
dpiはWindows標準の96で決め打ちしているけど、元の画像に合わせたほうがいいかもしれない?今回は元画像のdpiも96だった
ピクセルフォーマットは元画像に合わせようとしたらエラーになったのでPbgra32に指定した、元画像はBgr32だった
画像の保存
png形式画像、ファイル名はcropped_に今の時間をつけただけ
できた!
ラーメンには豆腐、このことは今後も繰り返し主張していきたい
それはともかく9年ぶりってのは大きかったようで、食べ終わったときは手を合わせるとかじゃなくて、ショーシャンクの空にみたいになってた
今回の記事中の画像で使ったフォントは
直線的でスッキリした感じがいい!
Fontworks|フォントワークス
https://fontworks.co.jp/
フォントワークス8書体が無料公開!商用利用や埋め込みも可能で「Google Fonts」にも対応 | Game*Spark - 国内・海外ゲーム情報サイト
https://www.gamespark.jp/article/2021/01/19/105389.html
関連記事
次回WPF記事は4日後
約2年前
gogowaten.hatenablog.com
このときはPathGeometryを使って切り抜いて表示してから、その要素を画像として取得して保存していた。PathGeometryを使うから矩形以外にも楕円、ベジェ曲線やフォントの形とかも使えるから形状の柔軟性が高い
今回の方法だと矩形しかできないけど、表示しなくても保存できるっていう違いがある
4年前
gogowaten.hatenablog.com
画像として保存はこっちだった