Created
September 18, 2017 11:25
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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