मैं रंग स्थानों के बारे में पढ़ रहा था और LAB स्थान आपके लिए एक अच्छा विकल्प प्रतीत होता है (इस प्रश्न को देखें: रंगों की समानता की जाँच करने के लिए रंगों और एल्गोरिथम के बीच एक सटीक "दूरी" ढूँढना )
विकिपीडिया CIELAB पृष्ठ का हवाला देते हुए , इस रंग स्थान के फायदे हैं:
आरजीबी और सीएमवाईके रंग मॉडल के विपरीत, लैब रंग मानव दृष्टि को अनुमानित करने के लिए डिज़ाइन किया गया है। यह अवधारणात्मक एकरूपता की आकांक्षा करता है, और इसका एल घटक प्रकाश की मानवीय धारणा से निकटता से मेल खाता है। इस प्रकार, इसका उपयोग ए और बी घटकों में आउटपुट घटता को संशोधित करके सटीक रंग संतुलन सुधार करने के लिए किया जा सकता है।
रंगों के बीच की दूरी को मापने के लिए आप डेल्टा ई दूरी का उपयोग कर सकते हैं ।
इस के साथ आप से बेहतर अनुमान लगा सकता है 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 };
}
}
दो स्थिर तरीके हैं, एक डेल्टा ई ( DeltaE
) का उपयोग करके दूरी को मापने के लिए और अन्य दो रंगों को संयोजित करने के लिए कि प्रत्येक रंग का कितना निर्दिष्ट है ( Combine
)।
और से बदलने के लिए RGB
करने के लिए LAB
आप निम्न विधि (से उपयोग कर सकते हैं यहाँ ):
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 do ('use', '▒', '▓', '░') जैसे शेड वर्णों का उपयोग करता है, यह आपको सांत्वना रंगों ( Combine
विधि का उपयोग करके ) को मिलाकर 16 से अधिक रंग प्राप्त करने की अनुमति देता है ।
यहां, हम रंगों का उपयोग करने के लिए पूर्व-गणना करके कुछ सुधार कर सकते हैं:
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
});
}
}
}
एक और सुधार का उपयोग LockBits
करने के बजाय सीधे छवि डेटा तक पहुंच प्राप्त की जा सकती है GetPixel
।
अद्यतन : यदि छवि में एक ही रंग के हिस्से हैं, तो आप वर्णों के बजाय समान रंगों वाले वर्णों को आकर्षित करने की प्रक्रिया को काफी तेज़ कर सकते हैं:
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);
}
परिणाम छवियाँ:
निम्नलिखित समाधान वर्णों पर आधारित नहीं हैं, लेकिन पूर्ण विस्तृत चित्र प्रदान करते हैं
आप किसी भी विंडो को ऑब्जेक्ट बनाने के लिए उसके हैंडलर का उपयोग करके खींच सकते हैं 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);
}
यह ठीक लग रहा है, लेकिन यदि कंसोल को आकार या स्क्रॉल किया जाता है, तो छवि गायब हो जाती है क्योंकि खिड़कियां ताज़ा होती हैं (शायद आपके मामले में छवि को फिर से बनाने के लिए किसी तरह के तंत्र को लागू करना संभव है)।
एक अन्य समाधान एक विंडो ( 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
संपत्ति बदल सकते हैं।