الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

ارسم .. لون .. حرّك ... واستمتع مع الكونسول

بدأه مصطفى 36a2 في 26 يونيو 2012 · 3 رد · 1,227 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

بسم الله الرحمن الرحيم

الحلقة الأولى من سلسلة الشاشة السوداء المملة ..

هذا المقال موجّه لمن ؟؟

لمن يفضل البدء من الصفر ليعيد اختراع العجلة بشكل مثير للإعجاب ...

ما هو المستوى المطلوب معرفته لفهم هذا الدرس ؟

يكفي أن تكون ملمّا بالأساسيات كالدوال والبنى 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);

والآن لنبدأ بمثال عملي .. بسيط جدا ولكنه سيغير مفهومكم عن الشاشة السوداء المملة ... happy.gif

  #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 ورسمها على الشاشة بألوان قريبة من ألوانها كما رأيتم في الموضوع السابق ....ننتظر آراءكم ...

والله ولي التوفيق ...

والحمد لله رب العالمين...

والسلام عليكم ورحمة الله وبركاته...

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 26 يونيو 2012 في 20:42

1
#2

مشكوووووووور اخي مصطفى

موضوعك في غاية الجمال والروعة لكن كان من الافضل اضافة صورة او اثنين كنتيجة هاد الموضوع من اجل اشعال الحماسة اكثر

شكرا الك مرة اخرى :happy:

556891961.gif

I am learning from you

it is the programmer eye

#3

:blink:مستحييييييل ....لا أصدق هذا...

أحدهم قرأ هذا الموضوع .... :)

أنا في غاية السعادة ... أخي العزيز شيكامارو:lol: المشاكس ...

أشكرك من أعماق قلبي ... فعلاً نصيحتك هامة جداً .... :)

أما الآن وقد وُجد من يقرأ الموضوع ... فاستعد لدرس آخر من العيار الثقيل ... عن أدوات الدخل في الكونسول ( الكيبورد والفأرة )...خلال الأسبوع القادم بإذن الله تعالى ...

وهذه بناء على طلبك بعض الصور ...:)اعتبرها هدية خاصة مني لك ... أنا الذي يجب أن يشكرك .. :)

هذه الصورة يمكن تنفيذ مثلها من معطيات هذا الدرس ...

post-256536-025422500 1342283493_thumb.j

أما الصور التالية فهي كما قلت ( لإشعال الحماسة ):lol:

post-256536-071246600 1342283553_thumb.jpost-256536-011709400 1342283580_thumb.j

وأقدم إليك تشكيلة من الملفات التنفيذية .. (بدون المصدر لأنني أفضل أن أرفقه مشروحاً أو ألا أرفقه)

اختر ما تريد منها لأقوم بشرح الكود المصدري له .. طالما أنك الزبون الوحيد فأنت زبون شرفي اطلب وتمنى ...:lol:

Thanks.zip

أنتظر رأيك ... وأسكرك مرة أخرى ...

والسلام عليكم ورحمة الله وبركاته ..

بالتوفيق ..تقبل تحياتي ..

المرفقات
4.jpg2.jpg3.jpg
1 −1
#4

هناك برنامج في المرفقات .. اسمه DrowFromBM24

وهذا البرنامج أعتبره قمة ما وصلت إليه في رسوميات الكونسول ...والحمد لله...

ليس معقداً .. ولكنه طويل ...وطريقة تصميمه تطلبت مني قراءة الصور BMP بمحرر النصوص NotePad++:wacko:

طريقة عمله كما يلي : أحضر الصورة التي تريد عرضها واحفظها في نفس مجلد البرنامج ...

قم بتحرير الصورة واختر ( حفظ باسم ---> صورة BMP 24 BIT ) وسم الصورة 1

أي أن البرنامج يقوم بقراءة صورة اسمها 1.bmp ذات 24 بت (أي ما يتجاوز 16مليون لون )

وتقوم بتقريب الألوان إلى 16 لون ... وعرضها كما هي ...

أتمنى أن يعجبك ..

تحياتي ..

1 −1

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…