1 // Copyright (C) 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
17 using System.Drawing.Imaging;
19 using System.Runtime.InteropServices;
26 private const int WidthMin = 600, HeightMin = 400;
29 private Rectangle _rectangle;
30 private string _title;
32 public Bitmap CaptureGameScreen()
34 if (_hWnd == IntPtr.Zero || _rectangle.IsEmpty)
36 using (var bmp = CaptureWindow(_hWnd, _rect))
37 return bmp.Clone(_rectangle, bmp.PixelFormat);
40 public Bitmap CaptureGameScreen(string[] titles)
43 _hWnd = FindWindow(titles, out index);
44 if (_hWnd == IntPtr.Zero)
46 var rect = new Rect();
47 GetWindowRect(_hWnd, ref rect);
48 using (var bmp = CaptureWindow(_hWnd, rect))
50 var rectangle = DetectGameScreen(bmp);
51 if (!rectangle.IsEmpty)
54 _rectangle = rectangle;
55 _title = titles[index];
59 using (var file = File.Create("debug.png"))
60 bmp.Save(file, ImageFormat.Png);
61 if (_rectangle.IsEmpty || !_rect.Equals(rect) || _title != titles[index])
64 return bmp.Clone(_rectangle, bmp.PixelFormat);
68 private IntPtr FindWindow(string[] titles, out int index)
70 var found = IntPtr.Zero;
72 EnumWindows((hWnd, lParam) =>
74 var rect = new Rect();
75 if (GetWindowRect(hWnd, ref rect) == 0 || rect.Right - rect.Left < WidthMin ||
76 rect.Bottom - rect.Top < HeightMin)
78 var text = GetWindowText(hWnd);
79 for (var i = 0; i < titles.Length; i++)
80 if (text.Contains(titles[i]))
92 public static string GetWindowText(IntPtr hWnd)
94 var size = GetWindowTextLength(hWnd);
97 var sb = new StringBuilder(size + 1);
98 GetWindowText(hWnd, sb, sb.Capacity);
102 [DllImport("user32.dll", CharSet = CharSet.Unicode)]
103 private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
105 [DllImport("user32.dll", CharSet = CharSet.Unicode)]
106 private static extern int GetWindowTextLength(IntPtr hWnd);
108 private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
110 [DllImport("user32.dll", CharSet = CharSet.Unicode)]
111 private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
113 public static Bitmap CaptureWindow(IntPtr hWnd, Rect rect)
115 var width = rect.Right - rect.Left;
116 var height = rect.Bottom - rect.Top;
117 var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
118 using (var g = Graphics.FromImage(bmp))
119 g.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
123 [DllImport("user32.dll")]
124 private static extern int GetWindowRect(IntPtr hWnd, ref Rect lpRec);
126 [StructLayout(LayoutKind.Sequential)]
127 public struct Rect : IEquatable<Rect>
134 public bool Equals(Rect other)
136 return Left == other.Left && Top == other.Top && Right == other.Right && Bottom == other.Bottom;
140 private Rectangle DetectGameScreen(Bitmap bmp)
142 var height = bmp.Height;
143 var width = bmp.Width;
144 var map = new byte[height, width];
145 var data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
146 PixelFormat.Format24bppRgb);
149 var ptr = (byte*)data.Scan0;
150 for (var y = 0; y < data.Height; y++)
152 for (var x = 0; x < data.Width; x++)
154 var p = ptr + y * data.Stride + x * 3;
155 map[y, x] = (byte)(p[0] == 255 && p[1] == 255 && p[2] == 255 ? 1 : 0);
159 bmp.UnlockBits(data);
160 for (var y = 1; y < height - 1; y++)
162 if (!CheckEdge(map, 0, width - 1, y, y, Edge.HorizontalTop))
164 for (var x = 1; x < width - 1; x++)
166 var rect = Rectangle.Empty;
168 if (!CheckEdge(map, x, x, rect.Y, height - 1, Edge.VerticalLeft))
171 rect = FindBottomAndRight(map, rect);
172 if (rect == Rectangle.Empty)
174 if (!CheckEdgeStrict(map, rect.X, rect.Right, y, y, Edge.HorizontalTop))
176 if (!CheckEdgeStrict(map, x, x, rect.Y, rect.Bottom, Edge.VerticalLeft))
178 RoundUpRectangle(map, ref rect);
182 return Rectangle.Empty;
185 private Rectangle FindBottomAndRight(byte[,] map, Rectangle rect)
187 var height = map.GetLength(0);
188 var width = map.GetLength(1);
189 for (var y = rect.Y; y < height - 1; y++)
191 if (!CheckEdge(map, rect.X, width - 1, y, y, Edge.HorizontalBottom))
193 rect.Height = y - rect.Y + 1;
195 for (var x = rect.X; x < width - 1; x++)
197 if (!CheckEdgeStrict(map, x, x, rect.Y, rect.Bottom, Edge.VerticalRight))
199 rect.Width = x - rect.X + 1;
204 if (CheckEdgeStrict(map, rect.X, rect.Right, rect.Bottom, rect.Bottom, Edge.HorizontalBottom))
208 return Rectangle.Empty;
209 // check a smaller rectangle
210 for (var y = rect.Y; y <= rect.Bottom; y++)
212 if (CheckEdgeStrict(map, rect.X, rect.Right, y, y, Edge.HorizontalBottom))
214 rect.Height = y - rect.Y + 1;
218 return rect.Width >= WidthMin && rect.Height >= HeightMin ? rect : Rectangle.Empty;
221 private bool CheckEdge(byte[,] map, int left, int right, int top, int bottom, Edge edge)
226 case Edge.HorizontalTop:
227 for (var x = left; x <= right; x++)
229 if (!(map[top - 1, x] == 1 && map[top, x] == 0))
231 if (++n < WidthMin / 3)
236 case Edge.VerticalLeft:
237 for (var y = top; y <= bottom; y++)
239 if (!(map[y, left - 1] == 1 && map[y, left] == 0))
241 if (++n < HeightMin / 3)
246 case Edge.HorizontalBottom:
247 for (var x = left; x <= right; x++)
249 if (!(map[bottom, x] == 0 && map[bottom + 1, x] == 1))
251 if (++n < WidthMin / 3)
256 case Edge.VerticalRight:
257 for (var y = top; y <= bottom; y++)
259 if (!(map[y, right] == 0 && map[y, right + 1] == 1))
261 if (++n < HeightMin / 3)
270 private bool CheckEdgeStrict(byte[,] map, int left, int right, int top, int bottom, Edge edge)
272 return CheckEdge(map, left, right, top, bottom, edge) &&
273 CheckEitherEndClean(map, left, right, top, bottom, edge) &&
274 CheckEnoughLength(map, left, right, top, bottom, edge);
277 private bool CheckEitherEndClean(byte[,] map, int left, int right, int top, int bottom, Edge edge)
281 case Edge.HorizontalTop:
282 for (var x = left; x <= left + WidthMin / 10; x++)
284 if (map[top - 1, x] == 0)
289 for (var x = right; x >= right - WidthMin / 10; x--)
291 if (map[top - 1, x] == 0)
295 case Edge.VerticalLeft:
296 for (var y = top; y <= top + HeightMin / 10; y++)
298 if (map[y, left - 1] == 0)
303 for (var y = bottom; y >= bottom - HeightMin / 10; y--)
305 if (map[y, left - 1] == 0)
309 case Edge.HorizontalBottom:
310 for (var x = left; x <= left + WidthMin / 10; x++)
312 if (map[bottom + 1, x] == 0)
317 for (var x = right; x >= right - WidthMin / 10; x--)
319 if (map[bottom + 1, x] == 0)
323 case Edge.VerticalRight:
324 for (var y = top; y <= top + HeightMin / 10; y++)
326 if (map[y, right + 1] == 0)
331 for (var y = bottom; y >= bottom - HeightMin / 10; y--)
333 if (map[y, right + 1] == 0)
341 private bool CheckEnoughLength(byte[,] map, int left, int right, int top, int bottom, Edge edge)
344 var hlen = (right - left + 1) * 0.7;
345 var vlen = (bottom - top + 1) * 0.7;
348 case Edge.HorizontalTop:
349 for (var x = left; x <= right; x++)
351 if (map[top - 1, x] == 1)
355 case Edge.VerticalLeft:
356 for (var y = top; y <= bottom; y++)
358 if (map[y, left - 1] == 1)
362 case Edge.HorizontalBottom:
363 for (var x = left; x <= right; x++)
365 if (map[bottom + 1, x] == 1)
369 case Edge.VerticalRight:
370 for (var y = top; y <= bottom; y++)
372 if (map[y, right + 1] == 1)
388 public void RoundUpRectangle(byte[,] map, ref Rectangle rect)
390 var r = rect.Height % 10;
395 for (var x = rect.X; x < rect.Right; x++)
397 if (map[rect.Top - 1, x] == 1)
399 if (map[rect.Bottom + 1, x] == 1)
402 rect.Height += 10 - r;
403 if (top <= bottom) // expand unbiguous edge
411 for (var y = rect.Y; y < rect.Bottom; y++)
413 if (map[y, rect.Left - 1] == 1)
415 if (map[y, rect.Right + 1] == 1)
418 rect.Width += 10 - r;
419 if (right <= left) // expand unbiguous edge