Mam aplikację konsoli, która zarządza obrazami. Teraz potrzebuję czegoś w rodzaju podglądu obrazów w aplikacji konsoli. Czy jest sposób, aby wyświetlić je w konsoli?
Oto porównanie aktualnych odpowiedzi opartych na znakach:
W konsoli, jak w oknie konsoli? Nie. Możesz jednak uruchomić osobne okno / okno.
Christian.K
Aplikacje konsolowe są używane głównie w aplikacjach tekstowych. Nie ma możliwości wyświetlenia obrazu. Możesz uruchomić inną aplikację, która wyświetla obraz. Ta inna aplikacja najprawdopodobniej musiałaby obsługiwać opcję wiersza poleceń, aby przekazać do niej obraz.
Lindos Pechos
Dlaczego używasz aplikacji konsolowej? Gdzie to biegnie? zawsze możesz rozpocząć proces, aby wywołać domyślną przeglądarkę zdjęć lub po prostu własną aplikację winforms itp.
TaW
1
Potrzebuję ulepszenia kodu Antonína Lejska (co jest świetne). Jest kilka niedopasowań kolorów i przy lepszej wydajności mogę również wyświetlać animowane gify
Byyo
Odpowiedzi:
55
Dalej bawiłem się kodem z @DieterMeemken. Zmniejszyłem o połowę rozdzielczość pionową i dodałem dithering przez ░▒▓. Po lewej wynik Dietera Meemkena, po prawej mój. U dołu znajduje się oryginalny obraz, którego rozmiar został zmieniony w celu zgrubnego dopasowania do obrazu wyjściowego.
O ile funkcja konwersji Malwynsa robi wrażenie, to nie wykorzystuje wszystkich szarych kolorów, szkoda.
Bitmap bmpSrc = new Bitmap(@"HuwnC.gif", true);
ConsoleWriteImage(bmpSrc);
EDYTOWAĆ
Odległość kolorów to złożony temat ( tutaj , tutaj i linki na tych stronach ...). Próbowałem obliczyć odległość w YUV i wyniki były raczej gorsze niż w RGB. Mogłyby być lepsze z Lab i DeltaE, ale nie próbowałem tego. Odległość w RGB wydaje się wystarczająca. W rzeczywistości wyniki są bardzo podobne dla odległości euklidesowej i manhattanu w przestrzeni kolorów RGB, więc podejrzewam, że jest zbyt mało kolorów do wyboru.
Reszta to po prostu brutalne porównanie koloru ze wszystkimi kombinacjami kolorów i wzorów (= symboli). Podałem, że współczynnik wypełnienia ░▒▓█ wynosi 1/4, 2/4, 3/4 i 4/4. W takim przypadku trzeci symbol jest w rzeczywistości zbędny w stosunku do pierwszego. Ale gdyby proporcje nie były tak jednolite (w zależności od czcionki), wyniki mogłyby się zmienić, więc zostawiłem to na przyszłe ulepszenia. Średni kolor symbolu jest obliczany jako średnia ważona z foregroudColor i backgroundColor zgodnie ze współczynnikiem wypełnienia. Przyjmuje liniowe kolory, co jest również dużym uproszczeniem. Jest więc jeszcze miejsce na ulepszenia.
Dziękuję @fubo. Przy okazji eksperymentowałem z RGB i Lab z korekcją gamma i oba były ulepszone. Ale współczynnik wypełnienia musiał być ustawiony tak, aby pasował do używanej czcionki, a to w ogóle nie działało w przypadku czcionek TrueType. Więc nie byłby to już jeden rozmiar dla wszystkich.
Antonín Lejsek
88
Chociaż wyświetlanie obrazu w konsoli nie jest zamierzonym użyciem konsoli, z pewnością możesz zhakować rzeczy, ponieważ okno konsoli jest tylko oknem, jak każde inne okno.
Właściwie to kiedyś zacząłem tworzyć bibliotekę kontrolek tekstu dla aplikacji konsolowych z obsługą grafiki. Nigdy tego nie skończyłem, chociaż mam działające demo typu proof-of-concept:
A jeśli uzyskasz rozmiar czcionki konsoli, możesz bardzo precyzyjnie umieścić obraz.
Oto jak możesz to zrobić:
staticvoidMain(string[] args)
{
Console.WriteLine("Graphics in console window!");
Point location = new Point(10, 10);
Size imageSize = new Size(20, 10); // desired image size in characters// draw some placeholders
Console.SetCursorPosition(location.X - 1, location.Y);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y);
Console.Write("<");
Console.SetCursorPosition(location.X - 1, location.Y + imageSize.Height - 1);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y + imageSize.Height - 1);
Console.WriteLine("<");
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), @"Sample Pictures\tulips.jpg");
using (Graphics g = Graphics.FromHwnd(GetConsoleWindow()))
{
using (Image image = Image.FromFile(path))
{
Size fontSize = GetConsoleFontSize();
// translating the character positions to pixels
Rectangle imageRect = new Rectangle(
location.X * fontSize.Width,
location.Y * fontSize.Height,
imageSize.Width * fontSize.Width,
imageSize.Height * fontSize.Height);
g.DrawImage(image, imageRect);
}
}
}
Oto jak możesz uzyskać aktualny rozmiar czcionki konsoli:
privatestatic Size GetConsoleFontSize()
{
// getting the console out buffer handle
IntPtr outHandle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
0,
IntPtr.Zero);
int errorCode = Marshal.GetLastWin32Error();
if (outHandle.ToInt32() == INVALID_HANDLE_VALUE)
{
thrownew IOException("Unable to open CONOUT$", errorCode);
}
ConsoleFontInfo cfi = new ConsoleFontInfo();
if (!GetCurrentConsoleFont(outHandle, false, cfi))
{
thrownew InvalidOperationException("Unable to get font information.");
}
returnnew Size(cfi.dwFontSize.X, cfi.dwFontSize.Y);
}
Oraz wymagane dodatkowe wywołania, stałe i typy WinApi:
Wow, to naprawdę interesujące! Czy mógłbyś wyjaśnić trochę, co masz na myśli, mówiąc, że nigdy tego nie skończyłem, chociaż mam działające demo potwierdzające koncepcję ? Jakieś wady czy po prostu brakujący lakier ..?
Dzień
Oznacza to, że projekt jest bardzo niekompletny. Zrobiłem podstawową architekturę, podstawa zdarzeniami środowiska OO, obsługę myszy, pompę wiadomość, itp ale nawet najbardziej podstawowe elementy sterujące, takie jak Button, TextBoxetc nadal brakuje. Moim marzeniem jest stworzenie w miarę pełnej obsługi języka XAML z wiązaniem danych i podobną do WPF filozofią „osadzania wszystkiego w czymkolwiek”. Ale jestem od tego bardzo daleki ... cóż, w tej chwili :)
György Kőszeg
3
OK, rozumiem, ale wygląda na to, że kod może być użyty do mniej kompletnego, mniej ambitnego projektu, tak?
Dzień
1
Cóż, w obecnej formie ... nie do końca. Ale planuję opublikować go na GitHubie, gdy tylko sprawię, że będzie stabilny i spójny.
György Kőszeg
Wygląda to naprawdę fajnie, z wyjątkiem konieczności używania niezarządzanych bibliotek DLL. Ponadto, jak możemy śledzić rozwój bibliotek?
skórka
56
Jeśli użyjesz ASCII 219 (█) dwa razy, otrzymasz coś w rodzaju piksela (██). Teraz ogranicza Cię liczba pikseli i liczba kolorów w aplikacji konsolowej.
jeśli zachowasz ustawienia domyślne, masz około 39x39 pikseli, jeśli chcesz więcej, możesz zmienić rozmiar konsoli za pomocą Console.WindowHeight = resSize.Height + 1;iConsole.WindowWidth = resultSize.Width * 2;
musisz zachować jak największy współczynnik proporcji obrazu, więc w większości przypadków nie będziesz mieć 39 x 39
Malwyn opublikował całkowicie niedocenianą metodę konwersji System.Drawing.ColornaSystem.ConsoleColor
więc moje podejście byłoby
using System.Drawing;
publicstaticintToConsoleColor(System.Drawing.Color c)
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
}
publicstaticvoidConsoleWriteImage(Bitmap src)
{
int min = 39;
decimal pct = Math.Min(decimal.Divide(min, src.Width), decimal.Divide(min, src.Height));
Size res = new Size((int)(src.Width * pct), (int)(src.Height * pct));
Bitmap bmpMin = new Bitmap(src, res);
for (int i = 0; i < res.Height; i++)
{
for (int j = 0; j < res.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
System.Console.WriteLine();
}
}
@willywonka_dailyblah - To fioletowa macka z Day of the Tentacle. Not Doom
Blaatz0r
@ Blaatz0r mam na myśli grafikę w stylu Dooma ... imma nowy dzieciak na tym bloku Znam tylko Dooma
3
Wszystko jest wybaczone :-p. Jeśli kiedykolwiek będziesz miał okazję spróbować Day, jeśli macka to świetna gra, stara, ale świetna.
Blaatz0r
37
to było zabawne. Dzięki fubo , wypróbowałem Twoje rozwiązanie i udało mi się zwiększyć rozdzielczość podglądu o 4 (2x2).
Odkryłem, że możesz ustawić kolor tła dla każdego pojedynczego znaku. Tak więc, zamiast używać dwóch znaków ASCII 219 (█), użyłem dwa razy ASCII 223 (▀) z różnymi kolorami pierwszego planu i tła. To dzieli duży piksel (██) na 4 takie subpiksele (▀▄).
W tym przykładzie umieściłem oba obrazy obok siebie, aby łatwo było zobaczyć różnicę:
Oto kod:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespaceConsoleWithImage
{
classProgram
{
publicstaticvoidConsoleWriteImage(Bitmap bmpSrc)
{
int sMax = 39;
decimal percent = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height));
Size resSize = new Size((int)(bmpSrc.Width * percent), (int)(bmpSrc.Height * percent));
Func<System.Drawing.Color, int> ToConsoleColor = c =>
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
};
Bitmap bmpMin = new Bitmap(bmpSrc, resSize.Width, resSize.Height);
Bitmap bmpMax = new Bitmap(bmpSrc, resSize.Width * 2, resSize.Height * 2);
for (int i = 0; i < resSize.Height; i++)
{
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
Console.BackgroundColor = ConsoleColor.Black;
Console.Write(" ");
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2 + 1));
Console.Write("▀");
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2 + 1));
Console.Write("▀");
}
System.Console.WriteLine();
}
}
staticvoidMain(string[] args)
{
System.Console.WindowWidth = 170;
System.Console.WindowHeight = 40;
Bitmap bmpSrc = new Bitmap(@"image.bmp", true);
ConsoleWriteImage(bmpSrc);
System.Console.ReadLine();
}
}
}
Aby uruchomić przykład, mapa bitowa „image.bmp” musi znajdować się w tym samym katalogu, co plik wykonywalny. Zwiększyłem rozmiar konsoli, rozmiar podglądu nadal wynosi 39 i można go zmienić na int sMax = 39;.
Rozwiązanie od taffer jest również bardzo fajne. Wy dwoje macie moje głosy za ...
Cytując stronę Wikipedii CIELAB , zalety tej przestrzeni kolorów to:
W przeciwieństwie do modeli kolorów RGB i CMYK, kolory Lab są zaprojektowane tak, aby przybliżać ludzkie widzenie. Dąży do jednorodności percepcji, a jej składnik L ściśle pasuje do ludzkiego postrzegania lekkości. W związku z tym może być używany do dokładnych korekt balansu kolorów poprzez modyfikację krzywych wyjściowych w składowych a i b.
Aby zmierzyć odległość między kolorami, możesz użyć odległości Delta E.
Dzięki temu możesz lepiej przybliżać od Colordo ConsoleColor:
Po pierwsze, możesz zdefiniować CieLabklasę reprezentującą kolory w tej przestrzeni:
publicclassCieLab
{
publicdouble L { get; set; }
publicdouble A { get; set; }
publicdouble B { get; set; }
publicstaticdoubleDeltaE(CieLab l1, CieLab l2)
{
return Math.Pow(l1.L - l2.L, 2) + Math.Pow(l1.A - l2.A, 2) + Math.Pow(l1.B - l2.B, 2);
}
publicstatic CieLab Combine(CieLab l1, CieLab l2, double amount)
{
var l = l1.L * amount + l2.L * (1 - amount);
var a = l1.A * amount + l2.A * (1 - amount);
var b = l1.B * amount + l2.B * (1 - amount);
returnnew CieLab { L = l, A = a, B = b };
}
}
Istnieją dwie statyczne metody, jedna do pomiaru odległości za pomocą Delta E ( DeltaE), a druga do łączenia dwóch kolorów, określając ilość każdego koloru ( Combine).
A do przekształcenia z RGBdo LABmożesz użyć następującej metody ( stąd ):
publicstatic CieLab RGBtoLab(int red, int green, int blue)
{
var rLinear = red / 255.0;
var gLinear = green / 255.0;
var bLinear = blue / 255.0;
double r = rLinear > 0.04045 ? Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear / 12.92);
double g = gLinear > 0.04045 ? Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear / 12.92);
double b = bLinear > 0.04045 ? Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear / 12.92);
var x = r * 0.4124 + g * 0.3576 + b * 0.1805;
var y = r * 0.2126 + g * 0.7152 + b * 0.0722;
var z = r * 0.0193 + g * 0.1192 + b * 0.9505;
Func<double, double> Fxyz = t => ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
returnnew CieLab
{
L = 116.0 * Fxyz(y / 1.0) - 16,
A = 500.0 * (Fxyz(x / 0.9505) - Fxyz(y / 1.0)),
B = 200.0 * (Fxyz(y / 1.0) - Fxyz(z / 1.0890))
};
}
Pomysł polega na użyciu znaków cieni, takich jak @AntoninLejsek do ('█', '▓', '▒', '░'), co pozwala uzyskać więcej niż 16 kolorów łączących kolory konsoli ( Combinemetodą).
Tutaj możemy wprowadzić pewne ulepszenia, wstępnie obliczając kolory do użycia:
Innym ulepszeniem może być bezpośredni dostęp do danych obrazu przy użyciu LockBitszamiast używania GetPixel.
AKTUALIZACJA : Jeśli obraz ma części o tym samym kolorze, możesz znacznie przyspieszyć proces rysowania fragmentów znaków o tych samych kolorach zamiast pojedynczych znaków:
publicstaticvoidDrawImage(Bitmap source)
{
int width = Console.WindowWidth - 1;
int height = (int)(width * source.Height / 2.0 / source.Width);
using (var bmp = new Bitmap(source, width, height))
{
var unit = GraphicsUnit.Pixel;
using (var src = bmp.Clone(bmp.GetBounds(ref unit), PixelFormat.Format24bppRgb))
{
var bits = src.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, src.PixelFormat);
byte[] data = newbyte[bits.Stride * bits.Height];
Marshal.Copy(bits.Scan0, data, 0, data.Length);
for (int j = 0; j < height; j++)
{
StringBuilder builder = new StringBuilder();
var fore = ConsoleColor.White;
var back = ConsoleColor.Black;
for (int i = 0; i < width; i++)
{
int idx = j * bits.Stride + i * 3;
var pixel = DrawPixel(data[idx + 2], data[idx + 1], data[idx + 0]);
if (pixel.Forecolor != fore || pixel.Backcolor != back)
{
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.Write(builder);
builder.Clear();
}
fore = pixel.Forecolor;
back = pixel.Backcolor;
builder.Append(pixel.Char);
}
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.WriteLine(builder);
}
Console.ResetColor();
}
}
}
privatestatic ConsolePixel DrawPixel(int r, int g, int b)
{
var l = RGBtoLab(r, g, b);
double diff = double.MaxValue;
var pixel = pixels[0];
foreach (var item in pixels)
{
var delta = CieLab.DeltaE(l, item.Lab);
if (delta < diff)
{
diff = delta;
pixel = item;
}
}
return pixel;
}
Poniższe rozwiązania nie są oparte na znakach, ale zapewniają pełne szczegółowe obrazy
Możesz rysować po dowolnym oknie, używając jego obsługi, aby utworzyć Graphicsobiekt. Aby uzyskać obsługę aplikacji konsolowej, możesz to zrobić importując GetConsoleWindow:
Następnie utwórz grafikę za pomocą handlera (używając Graphics.FromHwnd) i narysuj obraz używając metod w Graphicsobiekcie, na przykład:
staticvoidMain(string[] args)
{
var handler = GetConsoleHandle();
using (var graphics = Graphics.FromHwnd(handler))
using (var image = Image.FromFile("img101.png"))
graphics.DrawImage(image, 50, 50, 250, 200);
}
Wygląda dobrze, ale jeśli rozmiar konsoli zostanie zmieniony lub przewinięty, obraz znika, ponieważ okna są odświeżane (być może w twoim przypadku możliwe jest zaimplementowanie jakiegoś mechanizmu do przerysowania obrazu).
Innym rozwiązaniem jest osadzenie Formmetody window ( ) w aplikacji konsoli. Aby to zrobić, musisz zaimportować SetParent(i MoveWindowprzenieść okno wewnątrz konsoli):
[DllImport("user32.dll")]
publicstaticextern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
publicstaticexternboolMoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Następnie wystarczy utworzyć Formi ustawić BackgroundImagewłaściwość na żądany obraz (zrób to na Threadlub, Taskaby uniknąć blokowania konsoli):
staticvoidMain(string[] args)
{
Task.Factory.StartNew(ShowImage);
Console.ReadLine();
}
staticvoidShowImage()
{
var form = new Form
{
BackgroundImage = Image.FromFile("img101.png"),
BackgroundImageLayout = ImageLayout.Stretch
};
var parent = GetConsoleHandle();
var child = form.Handle;
SetParent(child, parent);
MoveWindow(child, 50, 50, 250, 200, true);
Application.Run(form);
}
Oczywiście możesz ustawić FormBorderStyle = FormBorderStyle.Noneukrywanie granic okien (prawy obraz)
W takim przypadku możesz zmienić rozmiar konsoli, a obraz / okno nadal tam będzie.
Jedną z korzyści tego podejścia jest to, że możesz zlokalizować okno w wybranym miejscu i zmienić obraz w dowolnym momencie, po prostu zmieniając BackgroundImagewłaściwość.
Teraz wszystko, czego potrzebujesz, aby dodać coś takiego:
Form form1 = new Form();
form1.BackgroundImage = bmp;
form1.ShowDialog();
Oczywiście możesz także użyć PictureBox...
Możesz też użyć, form1.Show();aby utrzymać konsolę przy życiu podczas wyświetlania podglądu.
Oryginalny post: Oczywiście nie możesz poprawnie wyświetlić obrazu w oknie 25x80; nawet jeśli używasz większego okna i blokowej grafiki, nie byłby to podgląd, ale bałagan!
Aktualizacja: Wygląda na to, że możesz mimo wszystko GDI narysować obraz na formularzu konsoli; zobacz odpowiedź Taffera!
Odpowiedzi:
Dalej bawiłem się kodem z @DieterMeemken. Zmniejszyłem o połowę rozdzielczość pionową i dodałem dithering przez ░▒▓. Po lewej wynik Dietera Meemkena, po prawej mój. U dołu znajduje się oryginalny obraz, którego rozmiar został zmieniony w celu zgrubnego dopasowania do obrazu wyjściowego. O ile funkcja konwersji Malwynsa robi wrażenie, to nie wykorzystuje wszystkich szarych kolorów, szkoda.
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(); }
stosowanie:
Bitmap bmpSrc = new Bitmap(@"HuwnC.gif", true); ConsoleWriteImage(bmpSrc);
EDYTOWAĆ
Odległość kolorów to złożony temat ( tutaj , tutaj i linki na tych stronach ...). Próbowałem obliczyć odległość w YUV i wyniki były raczej gorsze niż w RGB. Mogłyby być lepsze z Lab i DeltaE, ale nie próbowałem tego. Odległość w RGB wydaje się wystarczająca. W rzeczywistości wyniki są bardzo podobne dla odległości euklidesowej i manhattanu w przestrzeni kolorów RGB, więc podejrzewam, że jest zbyt mało kolorów do wyboru.
Reszta to po prostu brutalne porównanie koloru ze wszystkimi kombinacjami kolorów i wzorów (= symboli). Podałem, że współczynnik wypełnienia ░▒▓█ wynosi 1/4, 2/4, 3/4 i 4/4. W takim przypadku trzeci symbol jest w rzeczywistości zbędny w stosunku do pierwszego. Ale gdyby proporcje nie były tak jednolite (w zależności od czcionki), wyniki mogłyby się zmienić, więc zostawiłem to na przyszłe ulepszenia. Średni kolor symbolu jest obliczany jako średnia ważona z foregroudColor i backgroundColor zgodnie ze współczynnikiem wypełnienia. Przyjmuje liniowe kolory, co jest również dużym uproszczeniem. Jest więc jeszcze miejsce na ulepszenia.
źródło
Chociaż wyświetlanie obrazu w konsoli nie jest zamierzonym użyciem konsoli, z pewnością możesz zhakować rzeczy, ponieważ okno konsoli jest tylko oknem, jak każde inne okno.
Właściwie to kiedyś zacząłem tworzyć bibliotekę kontrolek tekstu dla aplikacji konsolowych z obsługą grafiki. Nigdy tego nie skończyłem, chociaż mam działające demo typu proof-of-concept:
A jeśli uzyskasz rozmiar czcionki konsoli, możesz bardzo precyzyjnie umieścić obraz.
Oto jak możesz to zrobić:
static void Main(string[] args) { Console.WriteLine("Graphics in console window!"); Point location = new Point(10, 10); Size imageSize = new Size(20, 10); // desired image size in characters // draw some placeholders Console.SetCursorPosition(location.X - 1, location.Y); Console.Write(">"); Console.SetCursorPosition(location.X + imageSize.Width, location.Y); Console.Write("<"); Console.SetCursorPosition(location.X - 1, location.Y + imageSize.Height - 1); Console.Write(">"); Console.SetCursorPosition(location.X + imageSize.Width, location.Y + imageSize.Height - 1); Console.WriteLine("<"); string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), @"Sample Pictures\tulips.jpg"); using (Graphics g = Graphics.FromHwnd(GetConsoleWindow())) { using (Image image = Image.FromFile(path)) { Size fontSize = GetConsoleFontSize(); // translating the character positions to pixels Rectangle imageRect = new Rectangle( location.X * fontSize.Width, location.Y * fontSize.Height, imageSize.Width * fontSize.Width, imageSize.Height * fontSize.Height); g.DrawImage(image, imageRect); } } }
Oto jak możesz uzyskać aktualny rozmiar czcionki konsoli:
private static Size GetConsoleFontSize() { // getting the console out buffer handle IntPtr outHandle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); int errorCode = Marshal.GetLastWin32Error(); if (outHandle.ToInt32() == INVALID_HANDLE_VALUE) { throw new IOException("Unable to open CONOUT$", errorCode); } ConsoleFontInfo cfi = new ConsoleFontInfo(); if (!GetCurrentConsoleFont(outHandle, false, cfi)) { throw new InvalidOperationException("Unable to get font information."); } return new Size(cfi.dwFontSize.X, cfi.dwFontSize.Y); }
Oraz wymagane dodatkowe wywołania, stałe i typy WinApi:
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr CreateFile( string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetCurrentConsoleFont( IntPtr hConsoleOutput, bool bMaximumWindow, [Out][MarshalAs(UnmanagedType.LPStruct)]ConsoleFontInfo lpConsoleCurrentFont); [StructLayout(LayoutKind.Sequential)] internal class ConsoleFontInfo { internal int nFont; internal Coord dwFontSize; } [StructLayout(LayoutKind.Explicit)] internal struct Coord { [FieldOffset(0)] internal short X; [FieldOffset(2)] internal short Y; } private const int GENERIC_READ = unchecked((int)0x80000000); private const int GENERIC_WRITE = 0x40000000; private const int FILE_SHARE_READ = 1; private const int FILE_SHARE_WRITE = 2; private const int INVALID_HANDLE_VALUE = -1; private const int OPEN_EXISTING = 3;
A wynik:
[
źródło
Button
,TextBox
etc nadal brakuje. Moim marzeniem jest stworzenie w miarę pełnej obsługi języka XAML z wiązaniem danych i podobną do WPF filozofią „osadzania wszystkiego w czymkolwiek”. Ale jestem od tego bardzo daleki ... cóż, w tej chwili :)Jeśli użyjesz ASCII 219 (█) dwa razy, otrzymasz coś w rodzaju piksela (██). Teraz ogranicza Cię liczba pikseli i liczba kolorów w aplikacji konsolowej.
jeśli zachowasz ustawienia domyślne, masz około 39x39 pikseli, jeśli chcesz więcej, możesz zmienić rozmiar konsoli za pomocą
Console.WindowHeight = resSize.Height + 1;
iConsole.WindowWidth = resultSize.Width * 2;
musisz zachować jak największy współczynnik proporcji obrazu, więc w większości przypadków nie będziesz mieć 39 x 39
Malwyn opublikował całkowicie niedocenianą metodę konwersji
System.Drawing.Color
naSystem.ConsoleColor
więc moje podejście byłoby
using System.Drawing; public static int ToConsoleColor(System.Drawing.Color c) { int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0; index |= (c.R > 64) ? 4 : 0; index |= (c.G > 64) ? 2 : 0; index |= (c.B > 64) ? 1 : 0; return index; } public static void ConsoleWriteImage(Bitmap src) { int min = 39; decimal pct = Math.Min(decimal.Divide(min, src.Width), decimal.Divide(min, src.Height)); Size res = new Size((int)(src.Width * pct), (int)(src.Height * pct)); Bitmap bmpMin = new Bitmap(src, res); for (int i = 0; i < res.Height; i++) { for (int j = 0; j < res.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i)); Console.Write("██"); } System.Console.WriteLine(); } }
więc możesz
ConsoleWriteImage(new Bitmap(@"C:\image.gif"));
przykładowe dane wejściowe:
przykładowe wyjście:
źródło
to było zabawne. Dzięki fubo , wypróbowałem Twoje rozwiązanie i udało mi się zwiększyć rozdzielczość podglądu o 4 (2x2).
Odkryłem, że możesz ustawić kolor tła dla każdego pojedynczego znaku. Tak więc, zamiast używać dwóch znaków ASCII 219 (█), użyłem dwa razy ASCII 223 (▀) z różnymi kolorami pierwszego planu i tła. To dzieli duży piksel (██) na 4 takie subpiksele (▀▄).
W tym przykładzie umieściłem oba obrazy obok siebie, aby łatwo było zobaczyć różnicę:
Oto kod:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace ConsoleWithImage { class Program { public static void ConsoleWriteImage(Bitmap bmpSrc) { int sMax = 39; decimal percent = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height)); Size resSize = new Size((int)(bmpSrc.Width * percent), (int)(bmpSrc.Height * percent)); Func<System.Drawing.Color, int> ToConsoleColor = c => { int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0; index |= (c.R > 64) ? 4 : 0; index |= (c.G > 64) ? 2 : 0; index |= (c.B > 64) ? 1 : 0; return index; }; Bitmap bmpMin = new Bitmap(bmpSrc, resSize.Width, resSize.Height); Bitmap bmpMax = new Bitmap(bmpSrc, resSize.Width * 2, resSize.Height * 2); for (int i = 0; i < resSize.Height; i++) { for (int j = 0; j < resSize.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i)); Console.Write("██"); } Console.BackgroundColor = ConsoleColor.Black; Console.Write(" "); for (int j = 0; j < resSize.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2)); Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2 + 1)); Console.Write("▀"); Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2)); Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2 + 1)); Console.Write("▀"); } System.Console.WriteLine(); } } static void Main(string[] args) { System.Console.WindowWidth = 170; System.Console.WindowHeight = 40; Bitmap bmpSrc = new Bitmap(@"image.bmp", true); ConsoleWriteImage(bmpSrc); System.Console.ReadLine(); } } }
Aby uruchomić przykład, mapa bitowa „image.bmp” musi znajdować się w tym samym katalogu, co plik wykonywalny. Zwiększyłem rozmiar konsoli, rozmiar podglądu nadal wynosi 39 i można go zmienić na
int sMax = 39;
.Rozwiązanie od taffer jest również bardzo fajne. Wy dwoje macie moje głosy za ...
źródło
Czytałem o przestrzeniach kolorów, a przestrzeń LAB wydaje się być dla Ciebie dobrym rozwiązaniem (zobacz to pytanie: Znajdowanie dokładnej „odległości” między kolorami i Algorytm, aby sprawdzić podobieństwo kolorów )
Cytując stronę Wikipedii CIELAB , zalety tej przestrzeni kolorów to:
Aby zmierzyć odległość między kolorami, możesz użyć odległości Delta E.
Dzięki temu możesz lepiej przybliżać od
Color
doConsoleColor
:Po pierwsze, możesz zdefiniować
CieLab
klasę reprezentującą kolory w tej przestrzeni:public class CieLab { public double L { get; set; } public double A { get; set; } public double B { get; set; } public static double DeltaE(CieLab l1, CieLab l2) { return Math.Pow(l1.L - l2.L, 2) + Math.Pow(l1.A - l2.A, 2) + Math.Pow(l1.B - l2.B, 2); } public static CieLab Combine(CieLab l1, CieLab l2, double amount) { var l = l1.L * amount + l2.L * (1 - amount); var a = l1.A * amount + l2.A * (1 - amount); var b = l1.B * amount + l2.B * (1 - amount); return new CieLab { L = l, A = a, B = b }; } }
Istnieją dwie statyczne metody, jedna do pomiaru odległości za pomocą Delta E (
DeltaE
), a druga do łączenia dwóch kolorów, określając ilość każdego koloru (Combine
).A do przekształcenia z
RGB
doLAB
możesz użyć następującej metody ( stąd ):public static CieLab RGBtoLab(int red, int green, int blue) { var rLinear = red / 255.0; var gLinear = green / 255.0; var bLinear = blue / 255.0; double r = rLinear > 0.04045 ? Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear / 12.92); double g = gLinear > 0.04045 ? Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear / 12.92); double b = bLinear > 0.04045 ? Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear / 12.92); var x = r * 0.4124 + g * 0.3576 + b * 0.1805; var y = r * 0.2126 + g * 0.7152 + b * 0.0722; var z = r * 0.0193 + g * 0.1192 + b * 0.9505; Func<double, double> Fxyz = t => ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0)); return new CieLab { L = 116.0 * Fxyz(y / 1.0) - 16, A = 500.0 * (Fxyz(x / 0.9505) - Fxyz(y / 1.0)), B = 200.0 * (Fxyz(y / 1.0) - Fxyz(z / 1.0890)) }; }
Pomysł polega na użyciu znaków cieni, takich jak @AntoninLejsek do ('█', '▓', '▒', '░'), co pozwala uzyskać więcej niż 16 kolorów łączących kolory konsoli (
Combine
metodą).Tutaj możemy wprowadzić pewne ulepszenia, wstępnie obliczając kolory do użycia:
class ConsolePixel { public char Char { get; set; } public ConsoleColor Forecolor { get; set; } public ConsoleColor Backcolor { get; set; } public CieLab Lab { get; set; } } static List<ConsolePixel> pixels; private static void ComputeColors() { pixels = new List<ConsolePixel>(); char[] chars = { '█', '▓', '▒', '░' }; int[] rs = { 0, 0, 0, 0, 128, 128, 128, 192, 128, 0, 0, 0, 255, 255, 255, 255 }; int[] gs = { 0, 0, 128, 128, 0, 0, 128, 192, 128, 0, 255, 255, 0, 0, 255, 255 }; int[] bs = { 0, 128, 0, 128, 0, 128, 0, 192, 128, 255, 0, 255, 0, 255, 0, 255 }; for (int i = 0; i < 16; i++) for (int j = i + 1; j < 16; j++) { var l1 = RGBtoLab(rs[i], gs[i], bs[i]); var l2 = RGBtoLab(rs[j], gs[j], bs[j]); for (int k = 0; k < 4; k++) { var l = CieLab.Combine(l1, l2, (4 - k) / 4.0); pixels.Add(new ConsolePixel { Char = chars[k], Forecolor = (ConsoleColor)i, Backcolor = (ConsoleColor)j, Lab = l }); } } }
Innym ulepszeniem może być bezpośredni dostęp do danych obrazu przy użyciu
LockBits
zamiast używaniaGetPixel
.AKTUALIZACJA : Jeśli obraz ma części o tym samym kolorze, możesz znacznie przyspieszyć proces rysowania fragmentów znaków o tych samych kolorach zamiast pojedynczych znaków:
public static void DrawImage(Bitmap source) { int width = Console.WindowWidth - 1; int height = (int)(width * source.Height / 2.0 / source.Width); using (var bmp = new Bitmap(source, width, height)) { var unit = GraphicsUnit.Pixel; using (var src = bmp.Clone(bmp.GetBounds(ref unit), PixelFormat.Format24bppRgb)) { var bits = src.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, src.PixelFormat); byte[] data = new byte[bits.Stride * bits.Height]; Marshal.Copy(bits.Scan0, data, 0, data.Length); for (int j = 0; j < height; j++) { StringBuilder builder = new StringBuilder(); var fore = ConsoleColor.White; var back = ConsoleColor.Black; for (int i = 0; i < width; i++) { int idx = j * bits.Stride + i * 3; var pixel = DrawPixel(data[idx + 2], data[idx + 1], data[idx + 0]); if (pixel.Forecolor != fore || pixel.Backcolor != back) { Console.ForegroundColor = fore; Console.BackgroundColor = back; Console.Write(builder); builder.Clear(); } fore = pixel.Forecolor; back = pixel.Backcolor; builder.Append(pixel.Char); } Console.ForegroundColor = fore; Console.BackgroundColor = back; Console.WriteLine(builder); } Console.ResetColor(); } } } private static ConsolePixel DrawPixel(int r, int g, int b) { var l = RGBtoLab(r, g, b); double diff = double.MaxValue; var pixel = pixels[0]; foreach (var item in pixels) { var delta = CieLab.DeltaE(l, item.Lab); if (delta < diff) { diff = delta; pixel = item; } } return pixel; }
Na koniec zadzwoń w ten
DrawImage
sposób:static void Main(string[] args) { ComputeColors(); Bitmap image = new Bitmap("image.jpg", true); DrawImage(image); }
Obrazy wyników:
Poniższe rozwiązania nie są oparte na znakach, ale zapewniają pełne szczegółowe obrazy
Możesz rysować po dowolnym oknie, używając jego obsługi, aby utworzyć
Graphics
obiekt. Aby uzyskać obsługę aplikacji konsolowej, możesz to zrobić importującGetConsoleWindow
:[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)] private static extern IntPtr GetConsoleHandle();
Następnie utwórz grafikę za pomocą handlera (używając
Graphics.FromHwnd
) i narysuj obraz używając metod wGraphics
obiekcie, na przykład:static void Main(string[] args) { var handler = GetConsoleHandle(); using (var graphics = Graphics.FromHwnd(handler)) using (var image = Image.FromFile("img101.png")) graphics.DrawImage(image, 50, 50, 250, 200); }
Wygląda dobrze, ale jeśli rozmiar konsoli zostanie zmieniony lub przewinięty, obraz znika, ponieważ okna są odświeżane (być może w twoim przypadku możliwe jest zaimplementowanie jakiegoś mechanizmu do przerysowania obrazu).
Innym rozwiązaniem jest osadzenie
Form
metody window ( ) w aplikacji konsoli. Aby to zrobić, musisz zaimportowaćSetParent
(iMoveWindow
przenieść okno wewnątrz konsoli):[DllImport("user32.dll")] public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll", SetLastError = true)] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Następnie wystarczy utworzyć
Form
i ustawićBackgroundImage
właściwość na żądany obraz (zrób to naThread
lub,Task
aby uniknąć blokowania konsoli):static void Main(string[] args) { Task.Factory.StartNew(ShowImage); Console.ReadLine(); } static void ShowImage() { var form = new Form { BackgroundImage = Image.FromFile("img101.png"), BackgroundImageLayout = ImageLayout.Stretch }; var parent = GetConsoleHandle(); var child = form.Handle; SetParent(child, parent); MoveWindow(child, 50, 50, 250, 200, true); Application.Run(form); }
Oczywiście możesz ustawić
FormBorderStyle = FormBorderStyle.None
ukrywanie granic okien (prawy obraz)W takim przypadku możesz zmienić rozmiar konsoli, a obraz / okno nadal tam będzie.
Jedną z korzyści tego podejścia jest to, że możesz zlokalizować okno w wybranym miejscu i zmienić obraz w dowolnym momencie, po prostu zmieniając
BackgroundImage
właściwość.źródło
Nie ma bezpośredniego sposobu. Ale możesz spróbować użyć konwertera obrazu na ascii-art, takiego jak ten
źródło
Tak, możesz to zrobić, jeśli trochę rozciągniesz pytanie, otwierając
Form
z poziomu aplikacji Console.Oto, jak możesz otworzyć formularz w aplikacji konsolowej i wyświetlić obraz:
System.Drawing
iSystem.Windows.Forms
using System.Windows.Forms; using System.Drawing;
Zobacz ten post, jak to zrobić !
Teraz wszystko, czego potrzebujesz, aby dodać coś takiego:
Form form1 = new Form(); form1.BackgroundImage = bmp; form1.ShowDialog();
Oczywiście możesz także użyć
PictureBox
...Możesz też użyć,
form1.Show();
aby utrzymać konsolę przy życiu podczas wyświetlania podglądu.Oryginalny post: Oczywiście nie możesz poprawnie wyświetlić obrazu w oknie 25x80; nawet jeśli używasz większego okna i blokowej grafiki, nie byłby to podgląd, ale bałagan!
Aktualizacja: Wygląda na to, że możesz mimo wszystko GDI narysować obraz na formularzu konsoli; zobacz odpowiedź Taffera!
źródło