#719 – ASCII Art Generator
December 25, 2012 1 Comment
Here’s a WPF-based ASCII Art generator that I wrote. Merry Christmas!
The GUI just consists of a button for selecting a file and a label indicating the file that was selected.
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Content="{Binding InputFile}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10"/> <Button Content="Select File" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="10,5" Margin="10" Click="btnSelectFile_Click"/> </Grid>
In the code-behind, we generate an output file name and load the input file into a Bitmap before calling the main ASCII Art generation method.
public partial class MainWindow : Window, INotifyPropertyChanged { public MainWindow() { InitializeComponent(); this.DataContext = this; inputFile = "Select an image file to use for ASCII Art"; } private string inputFile; public string InputFile { get { return inputFile; } set { if (value != inputFile) { inputFile = value; RaisePropertyChanged("InputFile"); } } } async private void btnSelectFile_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Image Files (*.jpg, *.png)|*.jpg;*.png;"; ofd.Title = "Select an image file to convert"; try { bool? bResult = ofd.ShowDialog(); if (bResult.HasValue && (bool)bResult) { InputFile = ofd.FileName; await Task.Run(() => DoGenerate(InputFile)); } } catch (Exception xx) { MessageBox.Show(string.Format("Fatal Error: {0}", xx.ToString()), "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private void DoGenerate(string inputFile) { int outputWidth = 100; FileInfo fi = new FileInfo(inputFile); if (!fi.Exists) throw new Exception(string.Format("File {0} not found", inputFile)); string outputFile = Path.Combine(fi.DirectoryName, Path.GetFileNameWithoutExtension(inputFile) + ".txt"); Bitmap bmInput = new Bitmap(inputFile); if (outputWidth > bmInput.Width) throw new Exception("Output width must be <= pixel width of image"); // Generate the ASCII art AscArt.GenerateAsciiArt(bmInput, outputFile, outputWidth); // This will fire up default app for .txt files Process.Start(outputFile); } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string prop) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(prop)); } }
Finally, here is the code for the class that generates the ASCII art.
public static class AscArt { // Typical width/height for ASCII characters private const double FontAspectRatio = 0.6; // Available character set, ordered by decreasing intensity (brightness) private const string OutputCharSet = "@%#*+=-:. "; // Alternate char set uses more chars, but looks less realistic private const string OutputCharSetAlternate = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "; public static void GenerateAsciiArt(Bitmap bmInput, string outputFile, int outputWidth) { // pixelChunkWidth/pixelChunkHeight - size of a chunk of pixels that will // map to 1 character. These are doubles to avoid progressive rounding // error. double pixelChunkWidth = (double)bmInput.Width / (double)outputWidth; double pixelChunkHeight = pixelChunkWidth / FontAspectRatio; // Calculate output height to capture entire image int outputHeight = (int)Math.Round((double)bmInput.Height / pixelChunkHeight); // Generate output image, row by row double pixelOffSetTop = 0.0; StringBuilder sbOutput = new StringBuilder(); for (int row = 1; row <= outputHeight; row++) { double pixelOffSetLeft = 0.0; for (int col = 1; col <= outputWidth; col++) { // Calculate brightness for this set of pixels by averaging // brightness across all pixels in this pixel chunk double brightSum = 0.0; int pixelCount = 0; for (int pixelLeftInd = 0; pixelLeftInd < (int)pixelChunkWidth; pixelLeftInd++) for (int pixelTopInd = 0; pixelTopInd < (int)pixelChunkHeight; pixelTopInd++) { // Each call to GetBrightness returns value between 0.0 and 1.0 int x = (int)pixelOffSetLeft + pixelLeftInd; int y = (int)pixelOffSetTop + pixelTopInd; if ((x < bmInput.Width) && (y < bmInput.Height)) { brightSum += bmInput.GetPixel(x, y).GetBrightness(); pixelCount++; } } // Average brightness for this entire pixel chunk, between 0.0 and 1.0 double pixelChunkBrightness = brightSum / pixelCount; // Target character is just relative position in ordered set of output characters int outputIndex = (int)Math.Floor(pixelChunkBrightness * OutputCharSet.Length); if (outputIndex == OutputCharSet.Length) outputIndex--; char targetChar = OutputCharSet[outputIndex]; sbOutput.Append(targetChar); pixelOffSetLeft += pixelChunkWidth; } sbOutput.AppendLine(); pixelOffSetTop += pixelChunkHeight; } // Dump output string to file File.WriteAllText(outputFile, sbOutput.ToString()); } }
Full solution available here.
Pingback: Dew Drop – December 26, 2012 (#1,470) | Alvin Ashcraft's Morning Dew