画像ファイルからレイヤーを追加する機能を追加します。
[レイヤーの追加] ⇒ [画像ファイルから]を選択するとファイルが読み込まれてダイアログが出現します。これをつかってレイヤーの表示位置やサイズを変更することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public partial class Form1 : Form { private void MenuItemAddLayerImageFile_Click(object sender, EventArgs e) { FormMovePosition form2 = new FormMovePosition(); form2.FixedLayer += Form2_FixedLayer; form2.MovedLayer += Form2_TemporaryFixingLayer; form2.TemporaryFixingLayer += Form2_TemporaryFixingLayer; form2.EditCanceled += Form2_EditCanceled; OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { try { Image image = Image.FromFile(dialog.FileName); Bitmap bitmap = new Bitmap(image), form2.Layer = new Layer() { SourceBitmap = bitmap, EditInfos = new List<EditInfo>(), X = 0, Y = 0, Width = image.Width, Height = image.Height, Name = "", }; image.Dispose(); } catch { MessageBox.Show("選択されたファイルは画像ファイルではありません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); dialog.Dispose(); form2.Dispose(); } } dialog.Dispose(); form2.Show(); } } |
ダイアログが生成されるときにImageLayerオブジェクトが生成されています。ImageLayerクラスは以下のようになっています。画像をBase64に変換してXmlとして保存できるようにしています。
EditInfosというものがありますが、これは画像をそのまま貼り付けるのではなくトリミングや回転処理をして貼り付けるときに必要になります。詳細は次回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public class ImageLayer : Layer { [System.Xml.Serialization.XmlIgnore] public override Bitmap Bitmap { get { // BitmapExクラスとGetLastImageメソッドに関しては後述 return BitmapEx.GetLastImage(SourceBitmap, EditInfos); } } [System.Xml.Serialization.XmlIgnore] public Bitmap SourceBitmap = null; public List<EditInfo> EditInfos = new List<EditInfo>(); public string BitmapBase64 { get { using (MemoryStream ms = new MemoryStream()) { if (SourceBitmap == null) return ""; SourceBitmap.Save(ms, ImageFormat.Png); byte[] vs = ms.ToArray(); return Convert.ToBase64String(vs); } } set { if (value == "") { SourceBitmap = null; return; } byte[] vs = Convert.FromBase64String(value); using (MemoryStream ms = new MemoryStream(vs)) { if (SourceBitmap != null) SourceBitmap.Dispose(); SourceBitmap = new Bitmap(ms); } } } } |
ではFormMovePositionクラスはどうなっているのでしょうか?
[レイヤーを仮止めする]をクリックするとレーヤーが指定された部分に仮止めされます。実際にどのように画像が配置されるのか値をみながら調整できます。そして[レイヤーの位置を確定する]を選択すると位置が確定されます。[レイヤーの位置を確定する]をクリックしないで「×」をクリックしてダイアログを閉じようとすると確認のメッセージボックスが表示されます。
ダイアログには加工前の画像と加工後の画像が表示されます。これは読み込んだ画像ファイルをそのままつかわずにトリミングや回転の処理をしたときのためにあります。ここでは特に処理はおこなわず、そのまま座標とサイズだけ指定して貼り付ける処理を考えます。
最初にFormMovePositionオブジェクトを生成するときにフィールド変数 Layerに作成したImageLayerオブジェクトが渡されます。そして左右のピクチャーボックスに読み込んだばかりの画像ファイルが表示されます。
フィールド変数 ignoreNumericUpDownValueChanged はアップダウンコントロールの値が変化したときに新しくBitmapが生成されてレイヤーの大きさが変更されるのですが、その処理がおこなわれてはいけないときのために存在します。レイヤーの幅や高さがゼロを示しているときにBitmapを生成しようとすると例外が発生してしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class FormMovePosition : Form { bool ignoreNumericUpDownValueChanged = false; public FormMovePosition() { InitializeComponent(); } // Layerオブジェクト public ImageLayer Layer = null; protected override void OnLoad(EventArgs e) { pictureBoxBeforeAfter1.BeforeLabelText = "加工前の画像"; pictureBoxBeforeAfter1.AfterLabelText = "加工後の画像"; if (Layer != null) { EditedBitmap = Layer.Bitmap; // 左右のピクチャーボックスに画像を表示 pictureBoxBeforeAfter1.AfterBitmap = Layer.Bitmap; pictureBoxBeforeAfter1.BeforeBitmap = Layer.SourceBitmap; this.textBoxLayerName.Text = Layer.Name; ignoreNumericUpDownValueChanged = true; { numericUpDownWidth.Value = Layer.Width; numericUpDownHeight.Value = Layer.Height; numericUpDownX.Value = Layer.X; numericUpDownY.Value = Layer.Y; } ignoreNumericUpDownValueChanged = false; } } } |
次にFormMovePositionクラスにおけるイベントです。
[レイヤーを仮止めする]がクリックされたあと、アップダウンコントロールの値が変更されたときはそのつどどのようにBitmapが表示されるのかを確認できるようにしています。レイヤーの位置を確定するが選択されたらダイアログを閉じると同時にその情報をForm1に伝える必要があります。
それから以下の処理、BitmapExクラスのメソッドについては後日解説します。現段階ではLayer.SourceBitmapと同じ内容を返します。
1 2 |
// BitmapExクラスとGetLastImageメソッドに関しては後述 Bitmap = BitmapEx.GetLastImage(Layer.SourceBitmap, Editinfos), |
1 2 3 4 5 6 7 8 |
public partial class FormMovePosition : Form { public delegate void SetLayeredHandler(object sender, SetLayeredArgs args); public event SetLayeredHandler MovedLayer; public event SetLayeredHandler FixedLayer; public event SetLayeredHandler TemporaryFixingLayer; public event EventHandler EditCanceled; } |
イベントハンドラの引数になるSetLayeredArgsは以下のようになっています。レイヤーの位置が確定する前のデータをLayerオブジェクトに格納したくはないので別のオブジェクトをつくって渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
public class SetLayeredArgs { public SetLayeredArgs(Layer layer, string name, Bitmap bitmap, int x, int y, int width, int height) { Layer = layer; Name = name; Bitmap = bitmap; X = x; Y = y; Width = width; Height = height; } // 確定した位置情報をLayerに反映させるためのもの public void Reflect() { Layer.Name = Name; Layer.X = X; Layer.Y = Y; Layer.Width = Width; Layer.Height = Height; } public Layer Layer { get; protected set; } = null; // 以下は確定前の仮の値がはいる public Bitmap Bitmap { get; protected set; } = null; public string Name { get; protected set; } = ""; public int X { get; protected set; } = 0; public int Y { get; protected set; } = 0; public int Width { get; protected set; } = 0; public int Height { get; protected set; } = 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
public partial class FormMovePosition : Form { public bool IsSetLayer = false; // 仮止めされているのであれば true public bool IsReflect = false; // 設定を反映させて終了するのであれば true // 仮止めしたときにおこなわれる処理 private void ButtonTemporaryFix_Click(object sender, EventArgs e) { SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, BitmapEx.GetLastImage(Layer.SourceBitmap, Layer.EditInfos), (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); IsSetLayer = true; TemporaryFixingLayer?.Invoke(this, args); } // 表示位置や表示サイズのアップダウンコントロールの値が変化したときの処理 // レイヤーを移動させる private void numericUpDownX_ValueChanged(object sender, EventArgs e) { if (IsSetLayer && !ignoreNumericUpDownValueChanged) MoveLayer(); } private void numericUpDownY_ValueChanged(object sender, EventArgs e) { if (IsSetLayer && !ignoreNumericUpDownValueChanged) MoveLayer(); } private void numericUpDownWidth_ValueChanged(object sender, EventArgs e) { if (IsSetLayer && !ignoreNumericUpDownValueChanged) MoveLayer(); } private void numericUpDownHeight_ValueChanged(object sender, EventArgs e) { if (IsSetLayer && !ignoreNumericUpDownValueChanged) MoveLayer(); } void MoveLayer() { SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, BitmapEx.GetLastImage(Layer.SourceBitmap, Layer.EditInfos), (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); MovedLayer?.Invoke(this, args); } // [レイヤーの位置を確定する] がクリックされたときの処理 private void ButtonFix_Click(object sender, EventArgs e) { CloseWithLayerFix(); } void CloseWithLayerFix() { SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, BitmapEx.GetLastImage(Layer.SourceBitmap, Editinfos), (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); IsSetLayer = true; IsReflect = true; FixedLayer?.Invoke(this, args); this.Close(); } // ダイアログを閉じるときの処理 // セットされた値を反映させるかどうかで処理を切り分ける protected override void OnFormClosing(FormClosingEventArgs e) { if (!IsReflect) { DialogResult dr = MessageBox.Show( "このままダイアログを閉じると変更は反映されません。変更を反映させますか?", "確認", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); if (dr == DialogResult.Yes) CloseWithLayerFix(); else if (dr == DialogResult.No) EditCanceled?.Invoke(this, new EventArgs()); else if (dr == DialogResult.Cancel) { e.Cancel = true; return; } } base.OnFormClosing(e); } } |
ではイベントが発生したときはForm1クラスではどのような処理がおこなわれるのでしょうか?
以下は仮止めされたときのイベントに伴っておこなわれる処理です。レイヤーのなかでもっとも右側の座標と下側の座標が大きなものを調べればBitmapの大きさがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public partial class Form1 : Form { List<Layer> Layers = new List<Layer>(); private void Form2_TemporaryFixingLayer(object sender, SetLayeredArgs args) { int newWidth = 0; int newHeight = 0; // 確定しているレイヤーから作成すべきBitmapの大きさを調べる if (Layers.Count > 0) { newWidth = Layers.Max(x => x.Right); newHeight = Layers.Max(x => x.Bottom); } // 新たに仮止めされたレイヤーによっては // Bitmapの大きさをさらに大きなものにしなければならないかもしれない。 if (args.X + args.Width > newWidth) newWidth = args.X + args.Width; if (args.Y + args.Height > newHeight) newHeight = args.Y + args.Height; // Bitmapの幅、高さのいずれかが0以下であればなにも表示しない。 if (newWidth <= 0 || newHeight <= 0) { scroolPictureBox1.Bitmap = null; return; } Bitmap bitmap = new Bitmap(newWidth, newHeight); Graphics g = Graphics.FromImage(bitmap); // 新たに仮止めされたレイヤーはまだ確定されていないので // フィールド変数Layersのなかには格納されていない。 // argsのなかには仮止め段階の座標が格納されている。 if (!Layers.Any(x => x == args.Layer)) { foreach (Layer layer in Layers) g.DrawImage(layer.Bitmap, new Rectangle(layer.X, layer.Y, layer.Width, layer.Height)); g.DrawImage(args.Bitmap, new Rectangle(args.X, args.Y, args.Width, args.Height)); } else { // すでに組み込まれているレイヤーを再編集する場合、 // 確定しているレイヤーとは区別して処理をしなければならない。 foreach (Layer layer in Layers) { if (layer != args.Layer) g.DrawImage(layer.Bitmap, new Rectangle(layer.X, layer.Y, layer.Width, layer.Height)); else g.DrawImage(args.Bitmap, new Rectangle(args.X, args.Y, args.Width, args.Height)); } } g.Dispose(); // Form1のピクチャーボックスに表示すべきBitmapが生成されたらこれを表示する scroolPictureBox1.Bitmap = bitmap; } } |
以下はレイヤーの位置が確定したときに呼び出されるイベントハンドラです。追加されたレイヤーは[レイヤーの編集]メニューのドロップダウンメニューに表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
public partial class Form1 : Form { private void Form2_FixedLayer(object sender, SetLayeredArgs args) { args.Reflect(); // Layersに新しいレイヤーが含まれていないなら追加する if (!Layers.Any(x => x == args.Layer)) { Layer newLayer = args.Layer; // 新しいレイヤーを一番最後に追加(したがって最後=最前面に描画される) LayerAdd(newLayer); } else { // 確定したレイヤーが既存のものであればデータを上書きする Layer layer = Layers.First(x => x == args.Layer); } ShowLayers(); } void LayerAdd(Layer newLayer) { // Zの値が大きいものが後から(したがって前面に)描画される int i = 0; foreach (Layer layer in Layers) { layer.Z = i; i += 10; } newLayer.Z = i; Layers.Add(newLayer); } // レイヤーのリストからBitmapを生成して表示する void ShowLayers() { int newWidth = 1; int newHeight = 1; // レイヤーがひとつも存在しないのであればなにも表示しない。 if (Layers.Count > 0) { newWidth = Layers.Max(x => x.Right); newHeight = Layers.Max(x => x.Bottom); } else { scroolPictureBox1.Bitmap = null; return; } // 幅、高さのいづれかが0以下であればなにも表示しない。 if (newWidth <= 0 || newHeight <= 0) { scroolPictureBox1.Bitmap = null; return; } Bitmap bitmap = new Bitmap(newWidth, newHeight); Graphics g = Graphics.FromImage(bitmap); foreach (Layer layer in Layers) { g.DrawImage(layer.Bitmap, new Rectangle(layer.X, layer.Y, layer.Width, layer.Height)); } g.Dispose(); scroolPictureBox1.Bitmap = bitmap; } } |
以下はレイヤーの編集または追加がキャンセルされた場合の処理です。
1 2 3 4 5 6 7 |
public partial class Form1 : Form { private void Form2_EditCanceled(object sender, EventArgs e) { ShowLayers() } } |
最後にレイヤーをファイルとして保存するための処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using System.Xml.Serialization; using System.IO; public class Doc { public List<Layer> Layers = new List<Layer>(); } public partial class Form1 : Form { private void MenuItemSaveFile_Click(object sender, EventArgs e) { SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "データファイル(*.txt)|*.txt"; if (dialog.ShowDialog() == DialogResult.OK) { Doc doc = new Doc(); doc.Layers = this.Layers; XmlSerializer xml = new XmlSerializer(typeof(Doc), new Type[] { typeof(TrimingInfo), typeof(RotateInfo), }); StreamWriter sw = new StreamWriter(dialog.FileName); xml.Serialize(sw, doc); sw.Close(); } dialog.Dispose(); } private void MenuItemOpenFile_Click(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "データファイル(*.txt)|*.txt"; if (dialog.ShowDialog() == DialogResult.OK) { XmlSerializer xml = new XmlSerializer(typeof(Doc), new Type[] { typeof(TrimingInfo), typeof(RotateInfo), }); StreamReader sr = new StreamReader(dialog.FileName); Doc doc = (Doc)xml.Deserialize(sr); sr.Close(); this.Layers = doc.Layers; MenuItemLayers.DropDownItems.Clear(); foreach (Layer layer in doc.Layers) { AddLayerToMenu(layer); } ShowLayers(); } dialog.Dispose(); } } |