OSDN Git Service

Capture the current playing game in specified games
[buragesnap/BurageSnap.git] / BurageSnap / Capture.cs
1 // Copyright (C) 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
2 //
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
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 using System;
16 using System.Drawing;
17 using System.Drawing.Imaging;
18 using System.IO;
19 using System.Runtime.InteropServices;
20 using System.Text;
21
22 namespace BurageSnap
23 {
24     public class Capture
25     {
26         private const int WidthMin = 600, HeightMin = 400;
27         private IntPtr _hWnd;
28         private Rect _rect;
29         private Rectangle _rectangle;
30         private string _title;
31
32         public Bitmap CaptureGameScreen()
33         {
34             if (_hWnd == IntPtr.Zero || _rectangle.IsEmpty)
35                 return null;
36             using (var bmp = CaptureWindow(_hWnd, _rect))
37                 return bmp.Clone(_rectangle, bmp.PixelFormat);
38         }
39
40         public Bitmap CaptureGameScreen(string[] titles)
41         {
42             int index;
43             _hWnd = FindWindow(titles, out index);
44             if (_hWnd == IntPtr.Zero)
45                 return null;
46             var rect = new Rect();
47             GetWindowRect(_hWnd, ref rect);
48             using (var bmp = CaptureWindow(_hWnd, rect))
49             {
50                 var rectangle = DetectGameScreen(bmp);
51                 if (!rectangle.IsEmpty)
52                 {
53                     _rect = rect;
54                     _rectangle = rectangle;
55                     _title = titles[index];
56                 }
57                 else
58                 {
59                     using (var file = File.Create("debug.png"))
60                         bmp.Save(file, ImageFormat.Png);
61                     if (_rectangle.IsEmpty || !_rect.Equals(rect) || _title != titles[index])
62                         return null;
63                 }
64                 return bmp.Clone(_rectangle, bmp.PixelFormat);
65             }
66         }
67
68         private IntPtr FindWindow(string[] titles, out int index)
69         {
70             var found = IntPtr.Zero;
71             var idx = 0;
72             EnumWindows((hWnd, lParam) =>
73             {
74                 var rect = new Rect();
75                 if (GetWindowRect(hWnd, ref rect) == 0 || rect.Right - rect.Left < WidthMin ||
76                     rect.Bottom - rect.Top < HeightMin)
77                     return true;
78                 var text = GetWindowText(hWnd);
79                 for (var i = 0; i < titles.Length; i++)
80                     if (text.Contains(titles[i]))
81                     {
82                         found = hWnd;
83                         idx = i;
84                         return false;
85                     }
86                 return true;
87             }, IntPtr.Zero);
88             index = idx;
89             return found;
90         }
91
92         public static string GetWindowText(IntPtr hWnd)
93         {
94             var size = GetWindowTextLength(hWnd);
95             if (size == 0)
96                 return "";
97             var sb = new StringBuilder(size + 1);
98             GetWindowText(hWnd, sb, sb.Capacity);
99             return sb.ToString();
100         }
101
102         [DllImport("user32.dll", CharSet = CharSet.Unicode)]
103         private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
104
105         [DllImport("user32.dll", CharSet = CharSet.Unicode)]
106         private static extern int GetWindowTextLength(IntPtr hWnd);
107
108         private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
109
110         [DllImport("user32.dll", CharSet = CharSet.Unicode)]
111         private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
112
113         public static Bitmap CaptureWindow(IntPtr hWnd, Rect rect)
114         {
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);
120             return bmp;
121         }
122
123         [DllImport("user32.dll")]
124         private static extern int GetWindowRect(IntPtr hWnd, ref Rect lpRec);
125
126         [StructLayout(LayoutKind.Sequential)]
127         public struct Rect : IEquatable<Rect>
128         {
129             public int Left;
130             public int Top;
131             public int Right;
132             public int Bottom;
133
134             public bool Equals(Rect other)
135             {
136                 return Left == other.Left && Top == other.Top && Right == other.Right && Bottom == other.Bottom;
137             }
138         }
139
140         private Rectangle DetectGameScreen(Bitmap bmp)
141         {
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);
147             unsafe
148             {
149                 var ptr = (byte*)data.Scan0;
150                 for (var y = 0; y < data.Height; y++)
151                 {
152                     for (var x = 0; x < data.Width; x++)
153                     {
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);
156                     }
157                 }
158             }
159             bmp.UnlockBits(data);
160             for (var y = 1; y < height - 1; y++)
161             {
162                 if (!CheckEdge(map, 0, width - 1, y, y, Edge.HorizontalTop))
163                     continue;
164                 for (var x = 1; x < width - 1; x++)
165                 {
166                     var rect = Rectangle.Empty;
167                     rect.Y = y;
168                     if (!CheckEdge(map, x, x, rect.Y, height - 1, Edge.VerticalLeft))
169                         continue;
170                     rect.X = x;
171                     rect = FindBottomAndRight(map, rect);
172                     if (rect == Rectangle.Empty)
173                         continue;
174                     if (!CheckEdgeStrict(map, rect.X, rect.Right, y, y, Edge.HorizontalTop))
175                         break;
176                     if (!CheckEdgeStrict(map, x, x, rect.Y, rect.Bottom, Edge.VerticalLeft))
177                         continue;
178                     RoundUpRectangle(map, ref rect);
179                     return rect;
180                 }
181             }
182             return Rectangle.Empty;
183         }
184
185         private Rectangle FindBottomAndRight(byte[,] map, Rectangle rect)
186         {
187             var height = map.GetLength(0);
188             var width = map.GetLength(1);
189             for (var y = rect.Y; y < height - 1; y++)
190             {
191                 if (!CheckEdge(map, rect.X, width - 1, y, y, Edge.HorizontalBottom))
192                     continue;
193                 rect.Height = y - rect.Y + 1;
194                 rect.Width = 0;
195                 for (var x = rect.X; x < width - 1; x++)
196                 {
197                     if (!CheckEdgeStrict(map, x, x, rect.Y, rect.Bottom, Edge.VerticalRight))
198                         continue;
199                     rect.Width = x - rect.X + 1;
200                     break;
201                 }
202                 if (rect.Width == 0)
203                     continue;
204                 if (CheckEdgeStrict(map, rect.X, rect.Right, rect.Bottom, rect.Bottom, Edge.HorizontalBottom))
205                     break;
206             }
207             if (rect.Width == 0)
208                 return Rectangle.Empty;
209             // check a smaller rectangle
210             for (var y = rect.Y; y <= rect.Bottom; y++)
211             {
212                 if (CheckEdgeStrict(map, rect.X, rect.Right, y, y, Edge.HorizontalBottom))
213                 {
214                     rect.Height = y - rect.Y + 1;
215                     break;
216                 }
217             }
218             return rect.Width >= WidthMin && rect.Height >= HeightMin ? rect : Rectangle.Empty;
219         }
220
221         private bool CheckEdge(byte[,] map, int left, int right, int top, int bottom, Edge edge)
222         {
223             var n = 0;
224             switch (edge)
225             {
226                 case Edge.HorizontalTop:
227                     for (var x = left; x <= right; x++)
228                     {
229                         if (!(map[top - 1, x] == 1 && map[top, x] == 0))
230                             continue;
231                         if (++n < WidthMin / 3)
232                             continue;
233                         return true;
234                     }
235                     return false;
236                 case Edge.VerticalLeft:
237                     for (var y = top; y <= bottom; y++)
238                     {
239                         if (!(map[y, left - 1] == 1 && map[y, left] == 0))
240                             continue;
241                         if (++n < HeightMin / 3)
242                             continue;
243                         return true;
244                     }
245                     return false;
246                 case Edge.HorizontalBottom:
247                     for (var x = left; x <= right; x++)
248                     {
249                         if (!(map[bottom, x] == 0 && map[bottom + 1, x] == 1))
250                             continue;
251                         if (++n < WidthMin / 3)
252                             continue;
253                         return true;
254                     }
255                     return false;
256                 case Edge.VerticalRight:
257                     for (var y = top; y <= bottom; y++)
258                     {
259                         if (!(map[y, right] == 0 && map[y, right + 1] == 1))
260                             continue;
261                         if (++n < HeightMin / 3)
262                             continue;
263                         return true;
264                     }
265                     return false;
266             }
267             return false;
268         }
269
270         private bool CheckEdgeStrict(byte[,] map, int left, int right, int top, int bottom, Edge edge)
271         {
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);
275         }
276
277         private bool CheckEitherEndClean(byte[,] map, int left, int right, int top, int bottom, Edge edge)
278         {
279             switch (edge)
280             {
281                 case Edge.HorizontalTop:
282                     for (var x = left; x <= left + WidthMin / 10; x++)
283                     {
284                         if (map[top - 1, x] == 0)
285                             goto tright;
286                     }
287                     return true;
288                     tright:
289                     for (var x = right; x >= right - WidthMin / 10; x--)
290                     {
291                         if (map[top - 1, x] == 0)
292                             return false;
293                     }
294                     return true;
295                 case Edge.VerticalLeft:
296                     for (var y = top; y <= top + HeightMin / 10; y++)
297                     {
298                         if (map[y, left - 1] == 0)
299                             goto lbottom;
300                     }
301                     return true;
302                     lbottom:
303                     for (var y = bottom; y >= bottom - HeightMin / 10; y--)
304                     {
305                         if (map[y, left - 1] == 0)
306                             return false;
307                     }
308                     return true;
309                 case Edge.HorizontalBottom:
310                     for (var x = left; x <= left + WidthMin / 10; x++)
311                     {
312                         if (map[bottom + 1, x] == 0)
313                             goto bright;
314                     }
315                     return true;
316                     bright:
317                     for (var x = right; x >= right - WidthMin / 10; x--)
318                     {
319                         if (map[bottom + 1, x] == 0)
320                             return false;
321                     }
322                     return true;
323                 case Edge.VerticalRight:
324                     for (var y = top; y <= top + HeightMin / 10; y++)
325                     {
326                         if (map[y, right + 1] == 0)
327                             goto rbottom;
328                     }
329                     return true;
330                     rbottom:
331                     for (var y = bottom; y >= bottom - HeightMin / 10; y--)
332                     {
333                         if (map[y, right + 1] == 0)
334                             return false;
335                     }
336                     return true;
337             }
338             return false;
339         }
340
341         private bool CheckEnoughLength(byte[,] map, int left, int right, int top, int bottom, Edge edge)
342         {
343             var n = 0;
344             var hlen = (right - left + 1) * 0.7;
345             var vlen = (bottom - top + 1) * 0.7;
346             switch (edge)
347             {
348                 case Edge.HorizontalTop:
349                     for (var x = left; x <= right; x++)
350                     {
351                         if (map[top - 1, x] == 1)
352                             n++;
353                     }
354                     return n >= hlen;
355                 case Edge.VerticalLeft:
356                     for (var y = top; y <= bottom; y++)
357                     {
358                         if (map[y, left - 1] == 1)
359                             n++;
360                     }
361                     return n >= vlen;
362                 case Edge.HorizontalBottom:
363                     for (var x = left; x <= right; x++)
364                     {
365                         if (map[bottom + 1, x] == 1)
366                             n++;
367                     }
368                     return n >= hlen;
369                 case Edge.VerticalRight:
370                     for (var y = top; y <= bottom; y++)
371                     {
372                         if (map[y, right + 1] == 1)
373                             n++;
374                     }
375                     return n >= vlen;
376             }
377             return false;
378         }
379
380         private enum Edge
381         {
382             HorizontalTop,
383             VerticalLeft,
384             HorizontalBottom,
385             VerticalRight
386         }
387
388         public void RoundUpRectangle(byte[,] map, ref Rectangle rect)
389         {
390             var r = rect.Height % 10;
391             if (r != 0)
392             {
393                 var top = 0;
394                 var bottom = 0;
395                 for (var x = rect.X; x < rect.Right; x++)
396                 {
397                     if (map[rect.Top - 1, x] == 1)
398                         top++;
399                     if (map[rect.Bottom + 1, x] == 1)
400                         bottom++;
401                 }
402                 rect.Height += 10 - r;
403                 if (top <= bottom) // expand unbiguous edge
404                     rect.Y -= 10 - r;
405             }
406             r = rect.Width % 10;
407             if (r != 0)
408             {
409                 var left = 0;
410                 var right = 0;
411                 for (var y = rect.Y; y < rect.Bottom; y++)
412                 {
413                     if (map[y, rect.Left - 1] == 1)
414                         left++;
415                     if (map[y, rect.Right + 1] == 1)
416                         right++;
417                 }
418                 rect.Width += 10 - r;
419                 if (right <= left) // expand unbiguous edge
420                     rect.X -= 10 - r;
421             }
422         }
423     }
424 }