Skip to content

Instantly share code, notes, and snippets.

@RobThree
Created September 18, 2017 11:25
Show Gist options
  • Save RobThree/8c9ec1b8ba78338dd313ba5c0012c5ba to your computer and use it in GitHub Desktop.
Save RobThree/8c9ec1b8ba78338dd313ba5c0012c5ba to your computer and use it in GitHub Desktop.
A simple bitmap-to-ascii-art converter featuring configurable fonts, configurable chars, configurable dimension (width) etc.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
public class Bitmap2Ascii
{
private static ConcurrentDictionary<Font, string> _maps = new ConcurrentDictionary<Font, string>();
private static ColorMatrix grayscale = new ColorMatrix(
new float[][]
{
new float[] {.3f, .3f, .3f, 0, 0},
new float[] {.59f, .59f, .59f, 0, 0},
new float[] {.11f, .11f, .11f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
});
private static ColorMatrix blackandwhite = new ColorMatrix(
new float[][]
{
new float[] {1.5f, 1.5f, 1.5f, 0, 0},
new float[] {1.5f, 1.5f, 1.5f, 0, 0},
new float[] {1.5f, 1.5f, 1.5f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {-1, -1, -1, 0, 1}
});
//private static ColorMatrix invert = new ColorMatrix(
// new float[][]
// {
// new float[] {-1, 0, 0, 0, 0},
// new float[] {0, -1, 0, 0, 0},
// new float[] {0, 0, -1, 0, 0},
// new float[] {0, 0, 0, 1, 0},
// new float[] {1, 1, 1, 0, 1}
// });
//private static ColorMatrix sepia = new ColorMatrix(
// new float[][]
// {
// new float[] {.393f, .349f, .272f, 0, 0},
// new float[] {.769f, .686f, .534f, 0, 0},
// new float[] {.189f, .168f, .131f, 0, 0},
// new float[] {0, 0, 0, 1, 0},
// new float[] {0, 0, 0, 0, 1}
// });
public string Convert(string path, int width, Font font, IEnumerable<char> chars)
{
using (var bm = (Bitmap)Bitmap.FromFile(path, true))
using (var gray = ApplyColorMatrix(bm, grayscale))
using (var resized = Resize(gray, width))
{
return ToAscii(resized, font, chars);
}
}
private static string GetBrightnessMap(Font font, IEnumerable<char> chars)
{
return _maps.GetOrAdd(font, (f) =>
{
var l = new List<KeyValuePair<char, double>>();
using (var b = new Bitmap(100, 100))
{
using (var g = Graphics.FromImage(b))
{
// For all chars...
foreach (var c in chars)
{
// Draw char
g.Clear(Color.White);
var s = new string(c, 1);
g.DrawString(s, font, Brushes.Black, 0, 0);
var r = g.MeasureString(s, font).ToSize();
var tb = 0d;
// Determine total brightness value
for (var y = 0; y < r.Height; y++)
for (var x = 0; x < r.Width; x++)
tb += b.GetPixel(x, y).GetBrightness();
// Add char with average brightness (total brightness / number of pixels for char) to list
l.Add(new KeyValuePair<char, double>(c, tb / (r.Width * r.Height)));
}
}
}
// Return a string sorted by brightness (dark->light)
return string.Concat(l.GroupBy(b => b.Value).Select(g => g.OrderBy(v => v.Key).First()).OrderBy(v => v.Value).Select(v => v.Key));
});
}
private static Bitmap ApplyColorMatrix(Bitmap original, ColorMatrix colorMatrix)
{
// Create a blank bitmap the same size as original
var newBitmap = new Bitmap(original.Width, original.Height);
// Get a graphics object from the new image
using (var g = Graphics.FromImage(newBitmap))
{
// Create some image attributes
var attributes = new ImageAttributes();
// Set the color matrix attribute
attributes.SetColorMatrix(colorMatrix);
// Draw the original image on the new image using the grayscale color matrix
g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height), 0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
}
return newBitmap;
}
private static Bitmap Resize(Bitmap original, int width)
{
// Calculate scale factors
var ratio = ((float)width / original.Width);
var destWidth = (int)(original.Width * ratio);
var destHeight = (int)(original.Height * ratio);
// Create a blank bitmap the desired size
var newBitmap = new Bitmap(destWidth, destHeight);
using (var g = Graphics.FromImage(newBitmap))
{
// Set resizing options
g.InterpolationMode = InterpolationMode.High;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Draw the resized image
g.Clear(Color.White);
g.DrawImage(original, new Rectangle(0, 0, destWidth, destHeight));
return newBitmap;
}
}
private unsafe static string ToAscii(Bitmap original, Font font, IEnumerable<char> chars)
{
var sb = new StringBuilder(original.Width * original.Height);
var bmpdata = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat);
var charmap = GetBrightnessMap(font, chars);
var l = charmap.Length - 1;
byte* currentPixel = (byte*)bmpdata.Scan0;
for (var y = 0; y < original.Height; y++)
{
for (var x = 0; x < original.Width; x++)
{
var r = currentPixel[x * 4];
var g = currentPixel[x * 4 + 1];
var b = currentPixel[x * 4 + 2];
var brightness = Color.FromArgb(r, g, b).GetBrightness();
sb.Append(charmap[(int)(brightness * l)]);
}
currentPixel += bmpdata.Stride;
sb.AppendLine();
}
original.UnlockBits(bmpdata);
return sb.ToString();
}
}
static void Main(string[] args)
{
var chars = CharRange(32, 126)
.Concat(CharRange(160, 255))
.Select(i => (char)i)
.ToArray();
var bm = new Bitmap2Ascii();
foreach (var f in Directory.GetFiles(@"D:\test\ba\org"))
{
Console.Write($"Processing {f}: ");
var s = Stopwatch.StartNew();
var txt = bm.Convert(f, 80, new Font("Consolas", 12), chars);
Console.WriteLine(s.ElapsedMilliseconds + "ms");
File.WriteAllText(Path.Combine(@"D:\test\ba\proc", Path.GetFileNameWithoutExtension(f) + ".txt"), txt);
}
}
private static IEnumerable<int> CharRange(int start, int end)
{
for (int i = start; i <= end; i++)
yield return i;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment