#719 – ASCII Art Generator

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());
        }
    }

719-001

 

Full solution available here.

Advertisement

About Sean
Software developer in the Twin Cities area, passionate about software development and sailing.

One Response to #719 – ASCII Art Generator

  1. Pingback: Dew Drop – December 26, 2012 (#1,470) | Alvin Ashcraft's Morning Dew

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: