文字列を追加します。文字列を画像にして追加してもいいのですが、拡大・縮小を繰り返すと画像が劣化してしまうので文字列は文字列として管理することにします。
文字列を描画するときに必要なものは、描画したい文字列のほかにフォント、色、描画位置、回転する角度です。そこで以下のようなクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class StringLayer : Layer { [System.Xml.Serialization.XmlIgnore] public override Bitmap Bitmap { get { return BitmapEx.GetBitmapRotateString(String, new Font(FontName, FontSize), Color.FromArgb(SelectColorArgb), Angle); } } public string String = ""; public string FontName = ""; public int FontSize = 0; public int SelectColorArgb = 0; public int Angle = 0; } |
BitmapEx.GetBitmapRotateString(string str, Font font, Color color, int angle)メソッドは文字列、フォント、カラー、角度(度数法)から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 |
static public class BitmapEx { static public Bitmap GetBitmapRotateString(string str, Font font, Color color, int angle) { if (str == "") return null; //大きさを調べるためのダミーのBitmapオブジェクトの作成 Bitmap img0 = new Bitmap(1, 1); //文字列を描画したときの大きさを計算する Graphics bg0 = Graphics.FromImage(img0); int w = (int)bg0.MeasureString(str, font).Width; int h = (int)bg0.MeasureString(str, font).Height; bg0.Dispose(); //Bitmapオブジェクトを作り直す Bitmap img = new Bitmap(w, h); //imgに文字列を描画する Graphics bg = Graphics.FromImage(img); bg.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; bg.DrawString(str, font, new SolidBrush(color), 0, 0); bg.Dispose(); int widthHalf = img.Width / 2; int heightHalf = img.Height / 2; List<PointF> pts = new List<PointF>(); pts.Add(new PointF(0, 0)); pts.Add(new PointF(img.Width, 0)); pts.Add(new PointF(0, img.Height)); pts = pts.Select(pt => new PointF(pt.X - widthHalf, pt.Y - heightHalf)).ToList(); //ラジアン単位に変換 double rad = angle / (180 / Math.PI); pts = pts.Select(pt => new PointF( pt.X * (float)Math.Cos(rad) + pt.Y * (float)Math.Sin(rad), -pt.X * (float)Math.Sin(rad) + pt.Y * (float)Math.Cos(rad)) ).ToList(); float newWidthHalf = pts.Max(pt => Math.Abs(pt.X)); float newHeightHalf = pts.Max(pt => Math.Abs(pt.Y)); Bitmap canvas = new Bitmap((int)(newWidthHalf * 2), (int)(newHeightHalf * 2)); pts = pts.Select(pt => new PointF(pt.X + newWidthHalf, pt.Y + newHeightHalf)).ToList(); Graphics g = Graphics.FromImage(canvas); //画像を描画 g.DrawImage(img, pts.ToArray()); g.Dispose(); // 後始末 img0.Dispose(); img.Dispose(); return canvas; } } |
メニューで[文字列を挿入する]を選択するとダイアログが表示され、描画したい文字列に関する設定ができるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void MenuItemAddLayerString_Click(object sender, EventArgs e) { FormInsertString form2 = new FormInsertString(); form2.FixedLayer += Form2_FixedLayer; form2.MovedLayer += Form2_TemporaryFixingLayer; form2.TemporaryFixingLayer += Form2_TemporaryFixingLayer; form2.EditCanceled += Form2_EditCanceled; form2.Show(); } } |
ではFormInsertStringクラスはどうなっているのでしょうか?
ShowImageString()メソッドはBitmapEx.GetBitmapRotateStringメソッドを呼び出すことで描画したい文字列のBitmapをフィールド変数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 FormInsertString : Form { public StringLayer Layer = null; Bitmap Bitmap = null; string String = ""; Font SelectedFont = null; Color SelectedColor = Color.Black; bool ignoreNumericUpDownValueChanged = false; public FormInsertString() { InitializeComponent(); SelectedFont = Font; } void ShowImageString() { Bitmap bitmap = BitmapEx.GetBitmapRotateString(String, SelectedFont, SelectedColor, (int)numericUpDownAngle.Value); ignoreNumericUpDownValueChanged = true; if (bitmap != null) { numericUpDownWidth.Value = bitmap.Width; numericUpDownHeight.Value = bitmap.Height; } else { numericUpDownWidth.Value = numericUpDownWidth.Minimum; numericUpDownHeight.Value = numericUpDownHeight.Minimum; } ignoreNumericUpDownValueChanged = false; scroolPictureBox1.Bitmap = bitmap; Bitmap = bitmap; } } |
以下は生成されたBitmapをForm1に仮表示するためのメソッドです。Form1クラスのForm2_TemporaryFixingLayerメソッドが呼び出されて全体がどのようになるのかを確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class FormInsertString : Form { public delegate void SetLayeredHandler(object sender, SetLayeredArgs args); public event SetLayeredHandler TemporaryFixingLayer; private void ButtonTemporaryFix_Click(object sender, EventArgs e) { IsSetLayer = true; SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, BitmapEx.GetBitmapRotateString(String, SelectedFont, SelectedColor, (int)numericUpDownAngle.Value), (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); TemporaryFixingLayer?.Invoke(this, args); } } |
MoveLayer()メソッドは、すでに仮表示がおこなわれているときにダイアログで変更がおこなわれると仮表示されているレイヤーを移動させるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class FormInsertString : Form { public event SetLayeredHandler MovedLayer; void MoveLayer() { if (!IsSetLayer) return; SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, BitmapEx.GetBitmapRotateString(String, SelectedFont, SelectedColor, (int)numericUpDownAngle.Value), (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); MovedLayer?.Invoke(this, args); } } |
[レイヤーの位置を確定する]ボタンをクリックするとレイヤーの位置が確定し、ダイアログが閉じます。このときにレイヤーがまだ生成されていないのであれば生成し、すでに存在するのであればその内容を変更します。そのあとForm1クラスのForm2_FixedLayerメソッドが呼び出され、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 |
public partial class FormInsertString : Form { public event SetLayeredHandler FixedLayer; private void ButtonFix_Click(object sender, EventArgs e) { CloseWithLayerFix(); } void CloseWithLayerFix() { // レイヤーが生成されていないのであれば生成する。 // すでに存在するならその内容を変更する if (Layer == null) { Layer = new StringLayer() { Angle = (int)numericUpDownAngle.Value, FontSize = (int)SelectedFont.Size, FontName = SelectedFont.Name, String = String, SelectColorArgb = SelectedColor.ToArgb(), X = (int)numericUpDownX.Value, Y = (int)numericUpDownY.Value, Width = (int)numericUpDownWidth.Value, Height = (int)numericUpDownHeight.Value, Name = textBoxLayerName.Text, }; } else { Layer.Angle = (int)numericUpDownAngle.Value; Layer.FontSize = (int)SelectedFont.Size; Layer.FontName = SelectedFont.Name; Layer.String = String; Layer.SelectColorArgb = SelectedColor.ToArgb(); Layer.X = (int)numericUpDownX.Value; Layer.Y = (int)numericUpDownY.Value; Layer.Width = (int)numericUpDownWidth.Value; Layer.Height = (int)numericUpDownHeight.Value; Layer.Name = textBoxLayerName.Text; } SetLayeredArgs args = new SetLayeredArgs( Layer, textBoxLayerName.Text, Bitmap, (int)numericUpDownX.Value, (int)numericUpDownY.Value, (int)numericUpDownWidth.Value, (int)numericUpDownHeight.Value); FixedLayer?.Invoke(this, args); IsSetLayer = true; IsReflect = true; this.Close(); } } |
もし確定しない状態でダイアログを閉じようとすると、ダイアログで設定した値を反映させるかどうかを確認するメッセージボックスが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class FormInsertString : Form { public event EventHandler EditCanceled; 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); } } |
[オリジナルサイズに戻す]をクリックすると元のサイズに戻ります。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class FormInsertString : Form { private void buttonOrgSize_Click(object sender, EventArgs e) { Bitmap bitmap = BitmapEx.GetBitmapRotateString(String, SelectedFont, SelectedColor, (int)numericUpDownAngle.Value); numericUpDownWidth.Value = bitmap.Width; numericUpDownHeight.Value = bitmap.Height; scroolPictureBox1.Bitmap = bitmap; Bitmap = bitmap; } } |
テキストボックスに文字列を入力すると描画されるBitmapが表示されます。またボタンをクリックすることで使用するフォントや色の指定ができます。そしてレイヤーがすでに追加されている状態であれば変更がForm1側にも反映されます。
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 |
public partial class FormInsertString : Form { private void textBox1_TextChanged(object sender, EventArgs e) { String = textBox1.Text; ShowImageString(); MoveLayer(); } private void buttonFont_Click(object sender, EventArgs e) { FontDialog dialog = new FontDialog(); dialog.Font = SelectedFont; if (dialog.ShowDialog() == DialogResult.OK) { SelectedFont = dialog.Font; ShowImageString(); MoveLayer(); } dialog.Dispose(); } private void buttonColor_Click(object sender, EventArgs e) { ColorDialog dialog = new ColorDialog(); dialog.Color = SelectedColor; if (dialog.ShowDialog() == DialogResult.OK) { SelectedColor = dialog.Color; ShowImageString(); MoveLayer(); } dialog.Dispose(); } } |
アップダウンコントロールの値が変化した場合も同様にShowImageString()メソッドとMoveLayer()メソッドが呼び出されて処理がおこなわれます。
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 |
public partial class FormInsertString : Form { private void numericUpDownAngle_ValueChanged(object sender, EventArgs e) { if (!ignoreNumericUpDownValueChanged) { ShowImageString(); MoveLayer(); } } private void numericUpDownWidth_ValueChanged(object sender, EventArgs e) { if (!ignoreNumericUpDownValueChanged) { ShowImageString(); MoveLayer(); } } private void numericUpDownHeight_ValueChanged(object sender, EventArgs e) { if (!ignoreNumericUpDownValueChanged) { ShowImageString(); MoveLayer(); } } private void numericUpDownX_ValueChanged(object sender, EventArgs e) { if(!ignoreNumericUpDownValueChanged) MoveLayer(); } private void numericUpDownY_ValueChanged(object sender, EventArgs e) { if (!ignoreNumericUpDownValueChanged) MoveLayer(); } } |
Form1のメニューで[レイヤーの編集]のドロップダウンメニューが選択されると、そのレイヤーがStringLayerの場合、FormInsertStringダイアログが表示されます。このときはすでに組み込まれているレイヤーの情報を表示させます。
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 |
public partial class Form1 : Form { private void Item_Click(object sender, EventArgs e) { ToolStripItem item = (ToolStripItem)sender; Layer layer =(Layer)item.Tag; if (layer is StringLayer) ShowStringLayerForm((StringLayer)layer); else if (layer is ImageLayer) ShowImageLayerForm((ImageLayer)layer); } void ShowStringLayerForm(StringLayer layer) { FormInsertString form2 = new FormInsertString(); form2.Layer = layer; // 編集するレイヤーをセットする form2.IsSetLayer = true; // すでに仮表示された状態にする form2.FixedLayer += Form2_FixedLayer; form2.MovedLayer += Form2_TemporaryFixingLayer; form2.TemporaryFixingLayer += Form2_TemporaryFixingLayer; form2.EditCanceled += Form2_EditCanceled; form2.Show(); } } |
この場合はすでに存在するレイヤーの情報を表示させるためにFormInsertStringクラス側でも以下の処理が必要です。
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 |
public partial class FormInsertString : Form { private void FormInsertString_Load(object sender, EventArgs e) { if (Layer != null) { ignoreNumericUpDownValueChanged = true; { textBoxLayerName.Text = Layer.Name; numericUpDownAngle.Value = Layer.Angle; String = Layer.String; SelectedColor = Color.FromArgb(Layer.SelectColorArgb); SelectedFont = new Font(Layer.FontName, Layer.FontSize); numericUpDownHeight.Value = Layer.Height; numericUpDownWidth.Value = Layer.Width; numericUpDownX.Value = Layer.X; numericUpDownY.Value = Layer.Y; } ignoreNumericUpDownValueChanged = false; textBox1.Text = String; } } } |
さて、このプログラムには問題があります。FormInsertStringダイアログの表示サイズを変更しようとしても変更することができません。アップダウンコントロールの値を変更するとShowImageString()メソッドが実行されるため、変更しようとした値が強制的に元の値に戻されてしまいます。
さてどうしたものか?