Renk uzayları hakkında okuyordum ve LAB alanı sizin için iyi bir seçenek gibi görünüyor (şu sorulara bakın: Renklerin benzerliğini kontrol etmek için renkler ve Algoritma arasında doğru bir "mesafe" bulma )
Wikipedia CIELAB sayfasından alıntı yapıldığında, bu renk uzayının avantajları şunlardır:
RGB ve CMYK renk modellerinden farklı olarak, Lab rengi insan görüşüne yakın olacak şekilde tasarlanmıştır. Algısal tekdüzelik arzular ve L bileşeni, insanın hafiflik algısıyla yakından eşleşir. Böylece, a ve b bileşenlerinde çıktı eğrilerini değiştirerek doğru renk dengesi düzeltmeleri yapmak için kullanılabilir.
Renkler arasındaki mesafeyi ölçmek için Delta E mesafesini kullanabilirsiniz .
Bununla, aşağıdakilerden daha iyi bir sonuç elde Color
edebilirsiniz ConsoleColor
:
İlk olarak, CieLab
bu alandaki renkleri temsil edecek bir sınıf tanımlayabilirsiniz :
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 };
}
}
Biri Delta E ( DeltaE
) kullanarak mesafeyi ölçmek ve diğeri her bir rengin ne kadarını ( Combine
) belirten iki rengi birleştirmek için iki statik yöntem vardır .
Ve gelen dönüşümü için RGB
için LAB
aşağıdaki yöntemi (den kullanabilirsiniz burada ):
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))
};
}
Fikir, @AntoninLejsek do ('█', '▓', '▒', '░') gibi gölge karakterleri kullanmaktır, bu, konsol renklerini birleştirerek ( Combine
yöntemi kullanarak ) 16'dan fazla renk elde etmenizi sağlar .
Burada, kullanılacak renkleri önceden hesaplayarak bazı iyileştirmeler yapabiliriz:
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
});
}
}
}
Diğer bir iyileştirme, kullanmak LockBits
yerine kullanarak doğrudan görüntü verilerine erişim olabilir.GetPixel
.
GÜNCELLEME : Görüntünün aynı renkte bölümleri varsa, tek tek karakterler yerine aynı renklere sahip karakter yığınlarını çizme sürecini önemli ölçüde hızlandırabilirsiniz:
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;
}
Son olarak, şöyle arayın DrawImage
:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Sonuç Resimleri:
Aşağıdaki çözümler karakterlere dayalı olmayıp tam ayrıntılı görüntüler sağlar
Bir nesne oluşturmak için işleyicisini kullanarak herhangi bir pencerenin üzerine çizim yapabilirsiniz Graphics
. Bir konsol uygulamasının işleyicisini almak için şunu içe aktarabilirsiniz GetConsoleWindow
:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Ardından, işleyiciyle (kullanarak Graphics.FromHwnd
) bir grafik oluşturun ve Graphics
nesnedeki yöntemleri kullanarak görüntüyü çizin, örneğin:
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);
}
Bu iyi görünüyor, ancak konsol yeniden boyutlandırılırsa veya kaydırılırsa, görüntü kaybolur çünkü pencereler yenilenir (belki de sizin durumunuzda görüntüyü yeniden çizmek için bir tür mekanizma uygulamak mümkündür).
Diğer bir çözüm, Form
konsol uygulamasına bir pencere ( ) yerleştirmektir . Bunu yapmak için içeri aktarmanız SetParent
(ve MoveWindow
konsolun içindeki pencereyi yeniden konumlandırmanız) gerekir:
[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);
Sonra sadece bir oluşturmanız gerekir Form
ve set BackgroundImage
istenen görüntüye özelliği (a bunu yapmak Thread
veya Task
konsol engelleme önlemek için):
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);
}
Tabii ki FormBorderStyle = FormBorderStyle.None
pencere kenarlıklarını gizlemeyi ayarlayabilirsiniz (sağdaki resim)
Bu durumda konsolu yeniden boyutlandırabilirsiniz ve görüntü / pencere hala orada olabilir.
Bu yaklaşımın bir yararı, pencereyi istediğiniz yere yerleştirebilmeniz ve sadece BackgroundImage
özelliği değiştirerek istediğiniz zaman görüntüyü değiştirebilmenizdir .