とある事情により今回は文字の輪郭をビットマップで取得するプログラムを作成します。
ビットマップを用意して白で塗りつぶします。そしてここに文字を描画します。各ピクセルを調べて白ではない部分が文字の部分です。そのなかで上下左右のどこかが白であればそれが輪郭部分であるとわかります。
文字はMS Pゴシック 72ポイントで描画します。そこでそれだけの描画が可能なビットマップのサイズを調べます。文字列が何行か、一行は最大何文字であるかを調べ、これに100を掛けて50を足しています(かなりいい加減)。
1 2 3 4 5 6 7 8 9 10 |
public class StringOutline { Size GetBitmapSize(string str) { string[] vs = str.Split(new string[] { "\n" }, StringSplitOptions.None); int width = vs.Max(x => x.Length) * 100 + 50; int height = vs.Count() * 100 + 50; return new Size(width, height); } } |
コンストラクタのなかで文字を書き、輪郭部分の位置を求めるところまでやってしまいます。OutlinePointsプロパティで座標のリストを取得できるようにします。またStringPointsプロパティで輪郭部分だけでなく文字全体の座標のリストを取得できるようにしました。
それから輪郭部分の座標のリストを取得しても点の順番が違うとこれを元に文字の輪郭の描画ができません。そこで輪郭部分の座標のリストを取得したあと、実際にビットマップ上にこれを描画します。そして白ではない点を一点求めてそこから連続して繋がっている点をリストのなかに格納していきます。このリストのリストが輪郭を構成する点の集まりとなります。
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
public class StringOutline { public StringOutline(string str) { Size size = GetBitmapSize(str); Bitmap bmp = new Bitmap(size.Width, size.Height); Graphics g = Graphics.FromImage(bmp); g.Clear(Color.White); g.DrawString(str, new Font("MS Pゴシック", 72), new SolidBrush(Color.Black), new Point(1, 1)); g.Dispose(); OutlinePoints = GetOutlinePoints(bmp); StringPoints = GetStringPoints(bmp); bmp.Dispose(); } public List<List<Point>> OutlinePoints { get; private set; } public List<List<Point>> StringPoints { get; private set; } List<List<Point>> GetOutlinePoints(Bitmap bmp) { List<Point> pts = new List<Point>(); for(int x = 1; x < bmp.Width - 1; x++) { for(int y = 1; y < bmp.Height - 1; y++) { // 各ピクセルを調べて白ではない部分が文字の部分 Color color = bmp.GetPixel(x, y); if(color.ToArgb() != -1) { // 上下左右のどこかが白であればそれが輪郭部分 bool b = false; if(bmp.GetPixel(x + 1, y).ToArgb() == -1) b = true; if(bmp.GetPixel(x - 1, y).ToArgb() == -1) b = true; if(bmp.GetPixel(x, y + 1).ToArgb() == -1) b = true; if(bmp.GetPixel(x, y - 1).ToArgb() == -1) b = true; if(b) pts.Add(new Point(x, y)); } } } // 輪郭部分の座標のリストを取得したあと、実際にビットマップ上に描画する Bitmap bitmap = new Bitmap(bmp); Graphics g = Graphics.FromImage(bitmap); g.Clear(Color.White); g.Dispose(); foreach(Point pt in pts) { bitmap.SetPixel(pt.X, pt.Y, Color.Black); } // 白ではない点を一点求めてそこから連続して繋がっている点を取得する List<List<Point>> ret = new List<List<Point>>(); while(true) { List<Point> a = GetContinuedPoints(bitmap); if(a.Count == 0) break; ret.Add(a); } bitmap.Dispose(); // 取得されたデータを修正する return CutOff(ret); } // 白ではない点を一点求めてそこから連続して繋がっている点を取得する // 取得ずみのピクセルは白にする List<Point> GetContinuedPoints(Bitmap bmp) { List<Point> ret = new List<Point>(); // 白ではない点を一点求める // ない場合は処理は終了 Point point = GetFirstPoint(bmp); if(point == Point.Empty) { return ret; } while(true) { bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(new Point(point.X, point.Y)); if(bmp.GetPixel(point.X + 1, point.Y).ToArgb() != -1) { point = new Point(point.X + 1, point.Y); continue; } if(bmp.GetPixel(point.X - 1, point.Y).ToArgb() != -1) { point = new Point(point.X - 1, point.Y); continue; } if(bmp.GetPixel(point.X, point.Y + 1).ToArgb() != -1) { point = new Point(point.X, point.Y + 1); continue; } if(bmp.GetPixel(point.X, point.Y - 1).ToArgb() != -1) { point = new Point(point.X, point.Y - 1); continue; } if(bmp.GetPixel(point.X - 1, point.Y - 1).ToArgb() != -1) { point = new Point(point.X - 1, point.Y - 1); continue; } if(bmp.GetPixel(point.X - 1, point.Y + 1).ToArgb() != -1) { point = new Point(point.X - 1, point.Y + 1); continue; } if(bmp.GetPixel(point.X + 1, point.Y - 1).ToArgb() != -1) { point = new Point(point.X + 1, point.Y - 1); continue; } if(bmp.GetPixel(point.X + 1, point.Y + 1).ToArgb() != -1) { point = new Point(point.X + 1, point.Y + 1); continue; } break; } return ret; } // 白ではない点を一点求める Point GetFirstPoint(Bitmap bmp) { for(int x = 0; x < bmp.Width; x++) { for(int y = 0; y < bmp.Height; y++) { Color color = bmp.GetPixel(x, y); if(color.ToArgb() != -1) { return new Point(x, y); } } } return Point.Empty; } } |
文字は中途半端な座標に描画されているので、上と左を詰めます。取得されたリストの各点の座標が左と上になっている部分(X座標とY座標それぞれの最小値)を取得すれば、左と上の空白部分が何ピクセルかがわかります。それだけ座標をずらしたものを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class StringOutline { List<List<Point>> CutOff(List<List<Point>> pointsList) { List<int> xMins = new List<int>(); List<int> yMins = new List<int>(); foreach(List<Point> points in pointsList) { xMins.Add(points.Min(x => x.X)); yMins.Add(points.Min(x => x.Y)); } int xMin = xMins.Min(); int yMin = yMins.Min(); List<List<Point>> ret = new List<List<Point>>(); foreach(List<Point> points in pointsList) { ret.Add(points.Select(x => new Point(x.X - xMin, x.Y - yMin)).ToList()); } return ret; } } |
輪郭部分だけでなく文字全体の座標のリストも取得します。
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 95 96 97 98 99 |
public class StringOutline { List<List<Point>> GetStringPoints(Bitmap bmp) { List<List<Point>> ret = new List<List<Point>>(); while(true) { List<Point> a = GetPoints(bmp); if(a.Count == 0) break; ret.Add(a); } return CutOff(ret); } List<Point> GetPoints(Bitmap bmp) { List<Point> ret = new List<Point>(); List<Point> next = new List<Point>(); Point start = GetFirstPoint(bmp); if(start == Point.Empty) { return ret; } next.Add(start); bmp.SetPixel(start.X, start.Y, Color.White); while(next.Count > 0) { List<Point> temp = new List<Point>(); foreach(Point pt in next) { if(bmp.GetPixel(pt.X + 1, pt.Y).ToArgb() != -1) { Point point = new Point(pt.X + 1, pt.Y); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X - 1, pt.Y).ToArgb() != -1) { Point point = new Point(pt.X - 1, pt.Y); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X, pt.Y + 1).ToArgb() != -1) { Point point = new Point(pt.X, pt.Y + 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X, pt.Y - 1).ToArgb() != -1) { Point point = new Point(pt.X, pt.Y - 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X - 1, pt.Y - 1).ToArgb() != -1) { Point point = new Point(pt.X - 1, pt.Y - 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X - 1, pt.Y + 1).ToArgb() != -1) { Point point = new Point(pt.X - 1, pt.Y + 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X + 1, pt.Y - 1).ToArgb() != -1) { Point point = new Point(pt.X + 1, pt.Y - 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } if(bmp.GetPixel(pt.X + 1, pt.Y + 1).ToArgb() != -1) { Point point = new Point(pt.X + 1, pt.Y + 1); bmp.SetPixel(point.X, point.Y, Color.White); ret.Add(point); temp.Add(point); } next = temp; } } return ret.OrderBy(x => x.Y).ThenBy(x => x.X).ToList(); } } |
また縦横のピクセル数もわかるようにしました。
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 |
public class StringOutline { int maxX = -1; public int MaxX { get { if(maxX != -1) return maxX; List<int> vs = new List<int>(); foreach(List<Point> pts in StringPoints) { vs.Add(pts.Max(x => x.X)); } maxX = vs.Max() + 1; return maxX; } } int maxY = -1; public int MaxY { get { if(maxY != -1) return maxY; List<int> vs = new List<int>(); foreach(List<Point> pts in StringPoints) { vs.Add(pts.Max(x => x.Y)); } maxY = vs.Max() + 1; return maxY; } } } |
実際に使ってみます。ボタンを押すと文字列の輪郭だけが描画されたビットマップデータがクリップボードに転送されます。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { StringOutline class1 = new StringOutline(textBox1.Text); var outlines = class1.OutlinePoints; var stringPoints = class1.StringPoints; Bitmap bmp = new Bitmap(class1.MaxX, class1.MaxY); Graphics g = Graphics.FromImage(bmp); g.Clear(Color.White); foreach(List<Point> pts in stringPoints) { foreach(Point pt in pts) { bmp.SetPixel(pt.X, pt.Y, Color.Black); } } Clipboard.SetImage(bmp); } } |