Я читал о цветовых пространствах и LAB пространства, кажется, хороший вариант для вас (см этих вопросов: Finding an accurate “distance” between colors и Algorithm to check similarity of colors)
Цитирования Википедии CIELAB страницы, преимущество этого цветового пространства :
В отличие от цветовых моделей RGB и CMYK, цвет Lab предназначен для приближения человеческого зрения. Он стремится к единообразию восприятия, а его компонент L близко соответствует восприятию человеком легкости. Таким образом, его можно использовать для точной коррекции цветового баланса путем изменения выходных кривых в компонентах a и b.
Чтобы измерить расстояние между цветами, вы можете использовать расстояние Delta E.
С этим можно аппроксимировать лучше от Color
к ConsoleColor
:
Во-первых, вы можете определить CieLab
класс для представления цвета в этом пространстве:
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 };
}
}
Есть два статических метода, один для измерения расстояние, используя Delta E (DeltaE
) и другие, чтобы объединить два цвета, указав, сколько из каждого цвета (Combine
).
И для преобразования из RGB
в LAB
вы можете использовать следующий метод (от here):
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))
};
}
Идея является использование тени, как символы @AntoninLejsek сделать ('█', '▓', «▒ ',' ░ '), это позволяет получить более 16 цветов, сочетающих цвета консоли (с использованием метода Combine
).
Здесь мы можем сделать некоторые улучшения путем предварительного вычисления цвета использовать:
Другое усовершенствование может быть доступ непосредственно данные изображения, используя LockBits
вместо использования GetPixel
.
UPDATE: Если изображение имеет детали с тем же цветом вы можете значительно ускорить процесс рисования кусок символов, имеющих одни и те же цвета, а не отдельных людей символов:
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;
}
Наконец, вызов DrawImage
как так:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Результат изображения:
Следующие решения не основаны на гольцов, но предоставляет полные детальные изображения
Вы можете рисовать поверх любого окна, используя его обработчик для создания a Graphics
объект.Для того, чтобы получить обработчик консольного приложения вы можете сделать это импортировать GetConsoleWindow
:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Затем создать графику с обработчиком (с использованием Graphics.FromHwnd
) и нарисовать изображение с помощью методов в Graphics
объекта, например:
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);
}
Это выглядит хорошо, но если консоль изменяются или прокручивается, изображение исчезает, потому что окна обновляются (возможно осуществление некоторых к ind механизма для перерисовки изображения возможно в вашем случае).
Другим решением является встраивание окно (Form
) в консольном приложении. Для этого нужно импортировать SetParent
(и MoveWindow
переместить окно внутри консоли):
[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);
Тогда вам просто необходимо создать свойство Form
и установить BackgroundImage
на нужное изображение (сделать это на Thread
или Task
, чтобы избежать блокировки консоли):
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);
}
конечно, вы можете установить FormBorderStyle = FormBorderStyle.None
, чтобы скрыть границы окон (правое изображение)
В этом случае вы можете изменить размер консоли, а изображение/окно все еще должно быть там.
Одним из преимуществ такого подхода является то, что вы можете найти нужное окно и изменить изображение в любое время, просто изменив свойство BackgroundImage
.
В консоли, как в окне консоли? Нет. Однако вы можете запустить отдельный диалог/окно. –
Консольные приложения используются в основном для текстовых приложений. Невозможно отобразить изображение. Вы можете запустить другое приложение, которое отображает изображение. Для этого другого приложения, скорее всего, потребуется поддержка опции командной строки для передачи изображения на нее. –
Почему вы используете консольное приложение? Где это работает? вы всегда можете запустить процесс, чтобы открыть средство просмотра изображений по умолчанию или просто приложение winforms и т. д. – TaW