Skip to content

Instantly share code, notes, and snippets.

@miou-gh
Last active March 9, 2017 07:34
Show Gist options
  • Save miou-gh/d2c090ad48b6bacbf0b45bab4b646df3 to your computer and use it in GitHub Desktop.
Save miou-gh/d2c090ad48b6bacbf0b45bab4b646df3 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AForge.Imaging;
using Image = System.Drawing.Image;
using Bitmap = System.Drawing.Bitmap;
using Rectangle = System.Drawing.Rectangle;
using Graphics = System.Drawing.Graphics;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
using Font = System.Drawing.Font;
using System.Diagnostics;
namespace EEBlockMatch
{
class Program
{
static void Main(string[] args)
{
var source = new Bitmap(@"input.png").ConvertToFormat(PixelFormat.Format24bppRgb);
var templates = new List<BlockTemplate>();
var tempmatch = new ExhaustiveTemplateMatching(0);
// load the templates
foreach (var filename in Directory.GetFiles(@"templates\foreground", "*.png")) {
templates.Add(new BlockTemplate() {
Filename = filename,
Bitmap = new Bitmap(filename).ConvertToFormat(PixelFormat.Format24bppRgb)
});
}
var stopWatch = new Stopwatch();
stopWatch.Start();
// obtain the initial matches to form a likely grid
var initialMatches = tempmatch.FindMatches(source, new Rectangle(0, 0, source.Width, source.Height), 0.999, 3, true, templates.ToArray());
var topLeftMatch = initialMatches.OrderByDescending(x => x.Region.X).OrderByDescending(x => x.Region.Y).Reverse().First();
var gridRegions = new List<Rectangle>();
var blockMatches = new List<BlockMatch>();
// add every region possible in the likely grid
for (var x = topLeftMatch.Region.X % 16; x + 16 < source.Width; x += 16) {
for (var y = topLeftMatch.Region.Y % 16; y + 16 < source.Height; y += 16) {
gridRegions.Add(new Rectangle(x, y, 16, 16));
}
}
foreach (var region in gridRegions) {
var matches = tempmatch.FindMatches(source, region, 0.9, -1, false, templates.ToArray());
var match = matches.Where(x => x.Region == region).OrderByDescending(x => x.Similarity).FirstOrDefault();
if (match != null) {
blockMatches.Add(match);
}
}
stopWatch.Stop();
Console.WriteLine($"Took { stopWatch.Elapsed.ToString() }.");
// display the blocks found in the matches
var graphics = Graphics.FromImage(source);
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
foreach (var block in blockMatches) {
// draw grid outline around the template match
graphics.DrawRectangle(System.Drawing.Pens.Red, block.Region.X, block.Region.Y, 16, 16);
var name = new string(new FileInfo(block.Template.Filename).Name.Where(x => char.IsDigit(x)).ToArray());
var font = graphics.GetAdjustedFont(name, new Font("Small Fonts", 7f), 16, 9, 5, true);
graphics.DrawString(name, font, System.Drawing.Brushes.Magenta, new System.Drawing.RectangleF(block.Region.X, block.Region.Y, 16, 16));
Console.WriteLine($"Discovered foreground block { name } at x: { block.Region.X } y: { block.Region.Y } with similarity { block.Similarity }.");
}
// draw grid outline around the initial template matches
foreach (var match in initialMatches) {
graphics.DrawRectangle(System.Drawing.Pens.Yellow, match.Region.X, match.Region.Y, 16, 16);
}
source.Save(@"output.png");
Console.ReadLine();
}
}
public static class Helpers
{
public static BlockMatch[] FindMatches(this ExhaustiveTemplateMatching templateMatcher, Bitmap sourceImage, Rectangle searchZone, double similarityThreshold = 0.8, int matchLimit = -1, bool seekGridConformity = false, params BlockTemplate[] templates)
{
var matches = new List<BlockMatch>();
foreach (var template in templates) {
foreach (var match in templateMatcher.ProcessImage(sourceImage, template.Bitmap, searchZone)) {
if (match.Similarity >= similarityThreshold) {
matches.Add(new BlockMatch() {
Region = match.Rectangle,
Similarity = match.Similarity,
Template = template
});
if (matchLimit != -1) {
if (matches.Count > matchLimit) {
goto limitReached;
}
}
}
}
}
limitReached:
// if a grid was not determined during processing
// find the most probable grid in the matches
if (seekGridConformity) {
var candidates = new List<(BlockMatch source, BlockMatch[] matches)>();
for (var i = 1; i < matches.Count; i++) {
var _matches = new List<BlockMatch>();
var match = matches[i];
var others = matches.ToList();
others.RemoveAt(i);
foreach (var other in others) {
if ((match.Region.X - other.Region.X) % 16 == 0 ||
(match.Region.Y - other.Region.Y) % 16 == 0) {
if (!match.Region.IntersectsWith(other.Region)) {
_matches.Add(other);
}
}
}
candidates.Add((match, _matches.ToArray()));
}
return candidates.OrderByDescending(x => x.matches.Count())
.OrderByDescending(x => x.matches.Select(n => n.Region).CheckIntersections()).Reverse()
.First().matches;
}
return matches.ToArray();
}
public static Font GetAdjustedFont(this Graphics GraphicRef, string GraphicString, Font OriginalFont, int ContainerWidth, int MaxFontSize, int MinFontSize, bool SmallestOnFail)
{
// We utilize MeasureString which we get via a control instance
for (var AdjustedSize = MaxFontSize; AdjustedSize >= MinFontSize; AdjustedSize--) {
var TestFont = new Font(OriginalFont.Name, AdjustedSize, OriginalFont.Style);
// Test the string with the new size
var AdjustedSizeNew = GraphicRef.MeasureString(GraphicString, TestFont);
if (ContainerWidth > Convert.ToInt32(AdjustedSizeNew.Width)) {
// Good font, return it
return TestFont;
}
}
// If you get here there was no fontsize that worked
// return MinimumSize or Original?
if (SmallestOnFail) {
return new Font(OriginalFont.Name, MinFontSize, OriginalFont.Style);
} else {
return OriginalFont;
}
}
public static Bitmap ConvertToFormat(this Image image, PixelFormat format)
{
var copy = new Bitmap(image.Width, image.Height, format);
using (var gr = Graphics.FromImage(copy)) {
gr.DrawImage(image, new Rectangle(0, 0, copy.Width, copy.Height));
}
return copy;
}
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
public static int CheckIntersections(this IEnumerable<Rectangle> rectangles)
{
return rectangles.Count(rect => rectangles.Where(r => !r.Equals(rect)).Any(r => r.IntersectsWith(rect)));
}
}
public class BlockTemplate
{
public Bitmap Bitmap { get; set; }
public string Filename { get; set; }
}
public class BlockMatch
{
public BlockTemplate Template { get; set; }
public Rectangle Region { get; set; }
public double Similarity { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment