بسم الله الرحمن الرحيم
الحلقة الأولى من سلسلة الشاشة السوداء المملة ..
هذا المقال موجّه لمن ؟؟
لمن يفضل البدء من الصفر ليعيد اختراع العجلة بشكل مثير للإعجاب ...
ما هو المستوى المطلوب معرفته لفهم هذا الدرس ؟
يكفي أن تكون ملمّا بالأساسيات كالدوال والبنى structures والمصفوفات والقليل عن المؤشرات ..
ملاحظة هامة لن نستعمل أي شيء خارجي سوى مكتبة windows.h لاستخدام دوال ال API
بداية علينا معرفة بعض الأمور البسيطة عن الشاشة بشكل عام :
آلية عمل الشاشة أيا كان نوعها تعتمد على مصفوفة نقاط مضيئة (Pixels) كل نقطة من هذه النقاط يمكن إضاءتها بثلاثة ألوان مختلفة الأزرق والأحمر والأخضر ... وتنتج جميع الألوان المعروفة عن دمج هذه الألوان الثلاثة بنسب مختلفة ...
عند تساوي نسبة الألوان الثلاثة يعطينا تدرجات الأبيض ( أسود ثم رمادي ثم أبيض ويزداد سطوع اللون بزيادة قيمة الألوان مع المحافظة على النسبة بينها )
وهناك الكثيير من الكلام عن الألوان وتدريجاتها .. عن السطوع والإشباع والتدرج .. إلخ ..لن نحتاجه في درسنا لليوم.
عندما نريد عرض شيء ما على الشاشة سنتحتاج إلى تزويد العرض بشدة إضاءة كل لون في كل بكسل للشاشة وحسب ... وهذا يكفي لإظهار أي شيء ..
فإذا أردنا تقليد هذه الآلية على نافذتنا السوداء المملة فسنحتاج إلى :
1-مصفوفة حجمها بحجم الشاشة ..
2-نجعل حجم النافذة بحجم الشاشة ..
3-نجعل حجم الحرف الواحد هو 1 بكسل *1 بكسل
وعندما نريد إظهار أي شيء نتبع الخطوات التالية :
1-ننسخ الشكل الذي نريد إظهاره إلى مصفوفة الإظهار
2-نطبع المصفوفة على الشاشة (وذلك دون محي العرض السابق )
هذه هي الخطوات الأساسية للإظهار (خوارزمية الإظهار العامة التي سنتبعها )
والآن لنضع أمام كل خطوة الخوارزمية المفصلة الخاصة بها :
أولا:مصفوفة حجمها بحجم الشاشة :
1-نأخذ حجم الشاشة الأعظمي
2-ننشئ مصفوفة ألوان (سنرى كيف نقوم بذلك)... وحجمها هو حجم الشاشة الذي أخذناه للتو
ثانيا: نجعل حجم النافذة بحجم الشاشة :
1-نأخذ حجم الشاشة الأعظمي
2-نجعل حجم النافذة بنفس حجم الشاشة .
ثالثا : نجعل حجم الحرف الواحد 1*1 بكسل مربع
في الواقع (على حد علمي ) لا يمكننا الوصول إلى هذا الحجم الصغير جداً ..(وذلك لأن الكونسول مخصصة للحروف المقروءة ويستحيل كتابة حرف مقروء في 1 بكسل )
ولكن يمكننا الوصول إلى 4*6 بكسل للحرف
والآن لنبدأ بالجزء العملي : دوال الAPI السحرية والتي ستنفذ كل ما سبق ..
بداية علينا معرفة بعض بني المعطيات الهامة , وسنتعرف على :
بنية محرف من ضمن معطياتها لون المحرف ... (هذا ما عنيته بمصفوفة الألوان ) واسمها : : CHAR_INFO
بنيتها كما يلي:
struct CHAR_INFO {
union {
WCHAR UnicodeChar;
CHAR AsciiChar;
} Char;
WORD Attributes;
};أول قيمة لها هي حرف بترميز آسكي.. وقيمة ال Attributes خاصة بلون المحرف ... مع لون خلفيته
ولمعرفة أرقام الألوان إليكم هذا التعداد :
enum Colours {Black=0,Blue=1,Green=2,Aqua=3,Red=4,Purple=5,Yellow=6,White=7,
Gray=8,LightBlue=9,LightGreen=10,LightAqua=11,LightRed=12,
LightPurple=13,LightYellow=14,BrightWhite=15};بنية الإحداثيات لتخزين x,y إحداثيات نقطة على الشاشة ..واسمها : COORD
وبنيتها بسيطة جدا :
struct COORD {
SHORT X;
SHORT Y;
};بنية المستطيل .. تخزين أبعاد مستطيل سنستخدمه لتحديد أبعاد النافذة ..اسمها SMALL_RECT
بنيتها:
struct SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
};والآن لنبدأ بالدوال :
أهم دالة للإخراج هي الدالة : WriteConsoleOutputA
وتقوم بطبع مصفوفة كاملة على الشاشة في وقت واحد ..تقريبا(وليس حرفا حرفا ) ...
دالة أخذ أكبر حجم للنافذة GetLargestConsoleWindowSize
وتقوم بإعادة أكبر طول وعرض للنافذة على شكل COORD( وذلك تابع لحجم الخط كلما صغر الخط زاد أكبر حجم ممكن)
سنتحدث عن كل ذلك عندما نمر عليه ... بقي أمر واحد فقط للتنبيه إليه وهو فكرة المقبض :
المقبض هو مجرد مؤشر يحتفظ بعنوان لشيء ما في الذاكرة .. عناون النافذة .. عنوان الدالة .. عنوان الخرج .. عنوان الدخل ..إلخ ..
ومعظم الدوال التي سنستعملها هدفها هو الخرج وبالتالي يجب أن نحتفظ بعنوان الدخل في الذاكرة عن طريق الدالة :
GetStdHandle(STD_OUTPUT_HANDLE);
الآن يمكننا الحديث عن زسطاء الدوال حتى تكون جاهزة للاستخدام :
أولا :
WriteConsoleOutputA( HANDLE hConsoleOutput,مقبض للخرج CONST CHAR_INFO *lpBuffer,مصفوفة الألوان خاصتنا COORD dwBufferSize,مساحة النافذة الكلية COORD dwBufferCoord,احداثيات نهاية الشاشة من الزاوية السفلى اليمنى (لن نستعملها سنضعها أصفارا ) PSMALL_RECT lpWriteRegion مساحة المنطقة التي نريد الرسم فيها );
بعض التفصيل عن آخر وسيط : عناصر هذه البنية LTRB أولا اليسار ثم فوق ثم اليمين ثم االأسفل : وتعني امتداد المنطقة بهذه التجاهات ...
ونحن سنمتد باتجاه اليمين والأسفل فقط ... حاليا.. لذلك سنضع الباقي أصفارا
ثانيا :
GetLargestConsoleWindowSize( HANDLE hConsoleOutputمقبض فقط ونتذكر أن القيمة المعادة من نوع الإحداثيات );
وأخيرا لأخذ مقبض العرض سنستعمل في أول برنامجنا :
HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE);
والآن لنبدأ بمثال عملي .. بسيط جدا ولكنه سيغير مفهومكم عن الشاشة السوداء المملة ... ![]()
#include<windows.h>
void main()
{
HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE);
COORD ConsoleSize=GetLargestConsoleWindowSize(H);
SetConsoleScreenBufferSize(H,ConsoleSize);
SMALL_RECT Window={0,0,--ConsoleSize.X/3,--ConsoleSize.Y/3};
SetConsoleWindowInfo(H,1,&Window);
CHAR_INFO *Screen=new CHAR_INFO[ConsoleSize.Y * ConsoleSize.X];
COORD BufferCoord={0,0};
for(int i=0;i<0x36a2;i++)
{
for(int y=0;y<ConsoleSize.Y;y++)
for(int x=0;x<ConsoleSize.X;x++)
Screen[y*ConsoleSize.X+x].Attributes=x*y/10+i;
WriteConsoleOutputA(H,Screen,ConsoleSize,BufferCoord,&Window);
}
}في الحلقة التي استخدمناها قمنا بإسناد قيم الألوان للمصفوفة وفق معادلة بسيطة بحيث أن لون النقطة يكون متعلقا بإحداثياتها
وبتغيير في هذا السطر يمكننا رسم أي شكل تلقائي (لا يحتاج إلا إلى معادلة (أي أنه لا يحتاج لإسناده بشكل منفصل ))
وفي كل دور للحلقة يتغير لون كل عنصر ...ثم نعرض المصفوفة ... ومن خلال العرض السريع يظهر وكأنها متحركة ..
وهذا مثال آخر مرسوم فيه عدة أشكال معاً
#include<windows.h>
void main()
{
HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE);
COORD ConsoleSize=GetLargestConsoleWindowSize(H);
SetConsoleScreenBufferSize(H,ConsoleSize);
SMALL_RECT Window={0,0,--ConsoleSize.X/3,--ConsoleSize.Y/3};
SetConsoleWindowInfo(H,1,&Window);
CHAR_INFO *Screen=new CHAR_INFO[ConsoleSize.Y * ConsoleSize.X];
COORD BufferCoord={0,0};
for(int i=0;i<0x36a2;i++)
{
for(int y=0;y<ConsoleSize.Y;y++)
for(int x=0;x<ConsoleSize.X;x++)
{
if(x==y)
Screen[y*ConsoleSize.X+x].Attributes=1+x*i;
else if(x*2==y)
Screen[y*ConsoleSize.X+x].Attributes=2*i;
else if(y+x<20)
Screen[y*ConsoleSize.X+x].Attributes=3*(i+1);
else if((x-20)*(x-20)+(y-20)*(y-20)<100)
Screen[y*ConsoleSize.X+x].Attributes=5+x+y+i;
else
Screen[y*ConsoleSize.X+x].Attributes=x*y+i;
}
WriteConsoleOutputA(H,Screen,ConsoleSize,BufferCoord,&Window);
}
}استخدمنا حجم نافذة صغيرا حتى لا يكون العرض بطيئا .. لأننا نغير جميع النقاط معاً ..
ولكن يمكننا أن نرسم على شاشة كاملة دون أن يكون العرض بطيئا بحيث لا نغير جميع النقاط معا
هذا مثال على شاشة كاملة
#include<windows.h>
void main()
{
HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE);
COORD ConsoleSize=GetLargestConsoleWindowSize(H);
SetConsoleScreenBufferSize(H,ConsoleSize);
SMALL_RECT Window={0,0,--ConsoleSize.X,--ConsoleSize.Y};
SetConsoleWindowInfo(H,1,&Window);
CHAR_INFO *Screen=new CHAR_INFO[ConsoleSize.Y * ConsoleSize.X];
COORD BufferCoord={0,0};
for(int i=0x36a2;i>0;i--)
{
for(int y=0;y<ConsoleSize.Y;y++)
for(int x=0;x<ConsoleSize.X;x++)
{
if(x==y)
Screen[y*ConsoleSize.X+x].Attributes=1+x*i;
else if(x*2==y)
Screen[y*ConsoleSize.X+x].Attributes=2*i;
else if(y+x<20)
Screen[y*ConsoleSize.X+x].Attributes=3*(i+1);
else if((x-(i%100))*(x-(i%100))+(y-(i%100))*(y-(i%100))<(i*i)%400)
Screen[y*ConsoleSize.X+x].Attributes=5+x+y+i;
else if(10*y<x)
Screen[y*ConsoleSize.X+x].Attributes=x*y+i;
else if((x-77)*(x-77)+(y-50)*(y-50)<i%200)
Screen[y*ConsoleSize.X+x].Attributes=x+y*i;
else if(10*x<y)
Screen[y*ConsoleSize.X+x].Attributes=x*y+i;
else
Screen[y*ConsoleSize.X+x].Attributes=0;
}
WriteConsoleOutputA(H,Screen,ConsoleSize,BufferCoord,&Window);
}
}اليوم قمنا برسم أشكال عشوائية ... ولكن ..
لنعتبر هذا الدرس مقدمة عن تحويل الكونسول إلى بيئة نابضة بالجمال والبساطة ...والحمد لله على توفيقه وكرمه..
في الدرس القادم بإذن الله سنتحدث عن قراءة صورة BMP ورسمها على الشاشة بألوان قريبة من ألوانها كما رأيتم في الموضوع السابق ....ننتظر آراءكم ...
والله ولي التوفيق ...
والحمد لله رب العالمين...
والسلام عليكم ورحمة الله وبركاته...
