Search code examples
c#imageconsole

Display an Image in a console application


I have a console application that manages images. Now I need something like a preview of the Images within the console application.

Is there a way to display them in the console?

Here is a comparison of the current character based answers:

Input:

enter image description here

Output:

enter image description here

enter image description here

enter image description here

enter image description here


Solution

  • I further played with code from @DieterMeemken. I halved vertical resolution and added dithering via ░▒▓. On the left is Dieter Meemken result, on the right my. On the bottom is original picture resized to rougly match the output. Output result While Malwyns conversion function is impressive, it does not use all gray colors, what is pity.

    static int[] cColors = { 0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0, 0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF };
    
    public static void ConsoleWritePixel(Color cValue)
    {
        Color[] cTable = cColors.Select(x => Color.FromArgb(x)).ToArray();
        char[] rList = new char[] { (char)9617, (char)9618, (char)9619, (char)9608 }; // 1/4, 2/4, 3/4, 4/4
        int[] bestHit = new int[] { 0, 0, 4, int.MaxValue }; //ForeColor, BackColor, Symbol, Score
    
        for (int rChar = rList.Length; rChar > 0; rChar--)
        {
            for (int cFore = 0; cFore < cTable.Length; cFore++)
            {
                for (int cBack = 0; cBack < cTable.Length; cBack++)
                {
                    int R = (cTable[cFore].R * rChar + cTable[cBack].R * (rList.Length - rChar)) / rList.Length;
                    int G = (cTable[cFore].G * rChar + cTable[cBack].G * (rList.Length - rChar)) / rList.Length;
                    int B = (cTable[cFore].B * rChar + cTable[cBack].B * (rList.Length - rChar)) / rList.Length;
                    int iScore = (cValue.R - R) * (cValue.R - R) + (cValue.G - G) * (cValue.G - G) + (cValue.B - B) * (cValue.B - B);
                    if (!(rChar > 1 && rChar < 4 && iScore > 50000)) // rule out too weird combinations
                    {
                        if (iScore < bestHit[3])
                        {
                            bestHit[3] = iScore; //Score
                            bestHit[0] = cFore;  //ForeColor
                            bestHit[1] = cBack;  //BackColor
                            bestHit[2] = rChar;  //Symbol
                        }
                    }
                }
            }
        }
        Console.ForegroundColor = (ConsoleColor)bestHit[0];
        Console.BackgroundColor = (ConsoleColor)bestHit[1];
        Console.Write(rList[bestHit[2] - 1]);
    }
    
    
    public static void ConsoleWriteImage(Bitmap source)
    {
        int sMax = 39;
        decimal percent = Math.Min(decimal.Divide(sMax, source.Width), decimal.Divide(sMax, source.Height));
        Size dSize = new Size((int)(source.Width * percent), (int)(source.Height * percent));   
        Bitmap bmpMax = new Bitmap(source, dSize.Width * 2, dSize.Height);
        for (int i = 0; i < dSize.Height; i++)
        {
            for (int j = 0; j < dSize.Width; j++)
            {
                ConsoleWritePixel(bmpMax.GetPixel(j * 2, i));
                ConsoleWritePixel(bmpMax.GetPixel(j * 2 + 1, i));
            }
            System.Console.WriteLine();
        }
        Console.ResetColor();
    }
    

    usage:

    Bitmap bmpSrc = new Bitmap(@"HuwnC.gif", true);    
    ConsoleWriteImage(bmpSrc);
    

    EDIT

    Color distance is complex topic (here, here and links on those pages...). I tried to calculate distance in YUV and results were rather worse than in RGB. They could be better with Lab and DeltaE, but I did not try that. Distance in RGB seems to be good enough. In fact results are very simmilar for both euclidean and manhattan distance in RGB color space, so I suspect there are just too few colors to choose from.

    The rest is just brute force compare of color against all combinations of colors and patterns (=symbols). I stated fill ratio for ░▒▓█ to be 1/4, 2/4, 3/4 and 4/4. In that case the third symbol is in fact redundant to the first. But if ratios were not such uniform (depends on font), results could change, so I left it there for future improvements. Average color of symbol is calculated as weighed average of foregroudColor and backgroundColor according to fill ratio. It assumes linear colors, what is also big simplification. So there is still room for improvement.