// مثال عن كود برمجي يقوم برسم النصوص على سطوح دايركت ثري دي
// لغة البرمجة هي دوت نيت مع
// C++/CLI
// ونستخدم مكتبة
// GDI+
// لكتابة النصوص
// تقديم: وسام البهنسي
// الشبكة العربية لمطوري الألعاب
// http:/ / www.agdn-online.com
// رداً على الموضوع:
// http:/ / www.agdn-online.com/ communities.aspx?view= posts& threadid= 544
// يقوم هذا الكلاس بإدارة الموارد اللازمة لرسم النصوص العربية
private ref class ArabicTextRenderer
{
public:
ArabicTextRenderer() :
m_NativeData(0)
{
// أنشئ صورة بأبعاد تتسع لأكبر جملة نصية تتوقع كتابتها بضربة واحدة
// لاحظ أن هيئة البكسلات مهمة جداً، ويجب أن توافق هيئة السطح في محرك الرسم
// وذلك لتفادي أي عمليات تحويل بطيئة
m_Bitmap = gcnew Bitmap(256, 128, PixelFormat::Format32bppRgb); // كل بكسل 32 بت. لا يهمنا قناة ألفا
m_BitmapData = gcnew BitmapData();
m_Graphics = Graphics::FromImage(m_Bitmap);
// حدد خواص الكتابة. تنسيق يمين-يسار
m_Format = gcnew StringFormat();
m_Format->FormatFlags = StringFormatFlags::NoClip | StringFormatFlags::DirectionRightToLeft;
m_Format->Alignment = StringAlignment::Near;
m_Format->HotkeyPrefix = HotkeyPrefix::None;
m_Format->LineAlignment = StringAlignment::Near;
m_Format->Trimming = StringTrimming::None;
// معلومات عن كيفية حفظ الصورة في الذاكرة. كي نستطيع قراءتها دون تحويلات مكلفة
m_Stride = (m_Bitmap->Width * Bitmap::GetPixelFormatSize(m_Bitmap->PixelFormat)) / 8; // عرض الصورة بالبايت
int iBytesCount = m_Bitmap->Height * m_Stride; // حجم الصورة ككل بالبايت
m_NativeData = new char[iBytesCount]; // المصفوفة الوسيطة التي ستستلم القيم اللونية للصورة
// تجهيز معلومات الصورة لإجراء القفل
m_BitmapData->Width = m_Bitmap->Width;
m_BitmapData->Height = m_Bitmap->Height;
m_BitmapData->PixelFormat = m_Bitmap->PixelFormat;
m_BitmapData->Stride = m_Stride;
m_BitmapData->Scan0 = (IntPtr)m_NativeData;
}
~ArabicTextRenderer()
{
// حرر الذاكرة التي حجزناها بأنفسنا
delete [] m_NativeData;
}
// إجراءات لتسهيل الوصول إلى أعضاء الكلاس
property Bitmap^ TextBitmap { Bitmap^ get(void) { return m_Bitmap; } }
property BitmapData^ TextBitmapData { BitmapData^ get(void) { return m_BitmapData; } }
property Graphics^ TextGraphics { Graphics^ get(void) { return m_Graphics; } }
property StringFormat^ TextFormat { StringFormat^ get(void) { return m_Format; } }
property int Stride { int get(void) { return m_Stride; } }
property void* NativeData { void* get(void) { return m_NativeData; } }
private:
Graphics ^m_Graphics; // جهاز الرسم بجي دي آي بلس
Bitmap ^m_Bitmap; // الصورة التي سيتم الرسم عليها
BitmapData ^m_BitmapData; // معلومات القفل والتعبئة
StringFormat ^m_Format; // خواص الكتابة
int m_Stride; // عرض الصورة بالبايت. انتبه، قد يكون أكبر من عدد البكسلات مضروباً بحجم كل منها
void *m_NativeData; // مؤشر إلى القيم اللونية للصورة
};
// إجراء الكتابة على صورة وتحويلها إلى بايتات يمكن إرسالها إلى أي محرك رسم
// البارامتر الأول هو النص المرغوب كتابته
// البارامتر الثاني هو الخط المرغوب للكتابة
// البارامتر الثالث هو كلاس الكتابة بالعربية والذي يجب أن يكون قد تم إنشاؤه من قبل
// البارامتر الأخير هو سطح دايركت ثري الذي ترغب بالرسم عليه
void SetText(String ^text, Font ^font, ArabicTextRenderer^ renderer, IDirect3DSurface9* D3DSurface)
{
Bitmap ^bmp = renderer->TextBitmap;
Graphics ^gfx = renderer->TextGraphics;
// أولاً نقوم بمسح محتويات الصورة كي نتخلص من أي بقايا من المرة الماضية
gfx->Clear(System::Drawing::Color::Transparent);
// يجب علينا أن نقيس النص كي نستطيع توضيعه بشكل صحيح في الصورة
SizeF sizef = gfx->MeasureString(text, font, PointF(0,0), renderer->TextFormat);
short width = (short)sizef.Width;
short height = (short)sizef.Height+2; // ضع هامشاً بمقدار بكسل واحد كارتفاع
float yo = 1; // الإزاحة من أعلى الصورة
// ارسم النص بدءاً من النقطة المحددة
// لاحظ أن نقطة الكتابة هي الزاوية العليا اليمنى للنص لأننا نكتب بالعربية،
// وبالتالي نحن بحاجة إلى وضع هذه النقطة على أقصى يمين الصورة
gfx->DrawString(text, font, Brushes::White, width , yo, renderer->TextFormat);
// يمكنك رسم إطار حول النص لو أردت التحقق من صحة الحسابات
//gfx->DrawRectangle(Pens::White, 0, 0, width-1, height-1);
// المستطيل الذي يحدد المنطقة التي نود قفلها من الصورة. عملياً كل الصورة
Rectangle rc(0,0,bmp->Width,bmp->Height);
// نقل القيم اللونية من الصورة إلى المؤشر الخاص بنا
// هذه العملية تتم بمجرد قفل بتات الصورة، وذلك وفقاً للبارامترات التي نمررها
// لإجراء القفل. هذه العملية سريعة نسبياً لأننا نسحب المعلومات بدون أي تحويل.
bmp->LockBits(rc,ImageLockMode::ReadOnly|ImageLockMode::UserInputBuffer,
bmp->PixelFormat,renderer->TextBitmapData);
bmp->UnlockBits(renderer->TextBitmapData);
// الآن لدينا كل ما يلزم لوضع البكسلات في محرك الرسم
// الإجراء التالي يضع البكسلات في سطح دايركت ثري دي كمثال
CopyImageToD3DSurface(D3DSurface, renderer->NativeData, renderer->Stride, width, height);
//bmp->Save(L"C:\\صورة.png"); // احفظ الصورة للتحقق
}
// إجراء تعبئة سطح دايركت ثري دي بقيم لونية من مصفوفة ما. أي محرك رسم يجب أن يقدم إجراءً مماثلاً
// البارامتر الأول هو السطح المرغوب تعبئته
// البارامتر الثاني هو مصفوفة القيم اللونية التي تريد نسخها على السطح
// البارامتر الثالث هو عرض مصفوفة القيم اللونية بالبايت، وليس بالبكسل
// البارامتر الرابع والخامس هما أبعاد مصفوفة القيم اللونية بالبكسل. العرض والارتفاع على الترتيب
void CopyImageToD3DSurface(IDirect3DSurface9* D3DSurface, void* sourcePixels,
int stride, int width, int height)
{
// هناك عدة افتراضات هنا لتبسيط الكود للمتعلم:
// حجم السطح أكبر من أو يساوي أبعاد مصفوفة القيم اللونية
// هيئة السطح هي ذاتها هيئة مصفوفة القيم اللونية. بمعنى آخر، لو كانت القيمة
// اللونية مؤلفة من 4 بايتات، كل منها قناة لونية أحمر، أخضر، أزرق، ألفا
// فإن سطح دايركت ثري دي يجب أن يكون من الهيئة:
// D3DFMT_A8R8G8B8 أو D3DFMT_X8R8G8B8
// انسخ القيم اللونية من كامل المصفوفة
RECT srcRect = {0, 0, height, width};
// انسخ القيم اللونية إلى الزاوية العليا اليسارية من السطح
RECT destRect = {0, 0, height, width};
// إجراء تعبئة السطح من مصفوفة قيم لونية مقدم من مكتبة دايركت ثري دي الإضافية
D3DXLoadSurfaceFromMemory(
D3DSurface, // السطح الذي يتم تعبئته
NULL, // مصفوفة بجدول الألوان. لا نستخدم هذه الميزة
&destRect, // المستطيل الذي سيتم تعبئته بالقيم اللونية في الصورة
sourcePixels, // مصفوفة القيم اللونية التي سيتم النسخ منها
D3DFMT_X8R8G8B8, // هيئة القيم اللونية. 32 بت وقناة ألفا غير مهمة
stride, // عرض مصفوفة القيم اللونية بالبايت
NULL, // مصفوفة بجدول الألوان في مصفوفة القيم اللونية. لا نستخدم هذه الميزة
&srcRect, // المستطيل الذي يعبر عن مكان النسخ من مصفوفة القيم اللونية
D3DX_FILTER_NONE, // طريقة الترشيح في حال عدم تطابق الأبعاد. لا نريد أي ترشيح
0); // اللون المفتاحي. سيتم استبدال جميع القيم اللونية المطابقة لهذا اللون باللون الشفاف
}
وفي 21/ربيع الأول/1430 04:21 م، ظهر شبح ابتسامة على وجه وسام البهنسي وهو يقول:
طبعاً لو كان البرنامج بحاجة إلى إعادة كتابة كاملة فقط لأجل إظهار النصوص على نظام تشغيل مختلف فإننا نتهم المبرمج بسوء تصميم الكود.وفي 28/ربيع الأول/1430 12:13 ص، أعرب basha ali عن رأيه بالموقف كالآتي:
أذكرك أخي في النهاية بـGLAL ، حاول جديا بدراسة هذا البحث و التواصل مع كاتبه, لا سيما أنه من نفس البلد☺وفي 25 آذار 2009 03:24 ص، قال basha ali متحمساً:
بالمناسبة المقالة يتم شراؤها. أنا عندي رابط للمكتبة, و لكن لن أضع وصلة لها هنا إحتراما لسياسة الموقع.في 29/ربيع الأول/1430 01:24 ص، عقد basha ali حاجبيه بتفكير وقال:
أقصد أن الأستاذ / أبو عبد الملك من من نفس بلد الأخ الشمري (أي من المملكة العربية السعودية). عموما هذا هو رابط السيرة الذاتية له: