رمز C # العالمي لـ NET و JavaScript

في عام 2013 ، أثناء عملي في خدمة GFRANQ للصور ، شاركت في تطوير خدمة ويب مسمى لنشر ومعالجة الصور. تم تعريف المرشحات والتحويلات في الملف مع المعلمات ، وتم تنفيذ جميع العمليات على الخادم. أثناء تطوير الخدمة ، كانت هناك حاجة لدعم هذه التحويلات من جانب العميل للمعاينة. وفقا لاري وول ، واحدة من فضائل مبرمج هو الكسل. لذلك ، كمبرمجين كسالى حقًا ، فكرنا في إمكانية استخدام نفس الكود على جانبي الخادم والعميل. وقد تم تطوير كامل في C #. بعد البحث في المكتبات ومحاولتين ، استنتجنا بفخر أن ذلك كان ممكنًا وبدأنا في كتابة الكود العالمي.



لماذا هذه المادة مطلوبة؟ في الواقع ، لقد مرت 6 سنوات منذ عام 2013 ، وفقدت العديد من التقنيات أهميتها ، على سبيل المثال ، Script # . من ناحية أخرى ، ظهرت جديدة. على سبيل المثال ، Bridge.NET أو Blazor بناءً على WebAssembly الهائل .


ومع ذلك ، لا يزال من الممكن استخدام بعض الأفكار. في هذه المقالة حاولت أن أصفها بالتفصيل قدر الإمكان. آمل أن يتسبب ذكر Silverlight و Flash في ابتسامة مع تلميح من الحنين إلى الماضي ، وليس رغبة في انتقاد الحلول القديمة. على أي حال ، فقد ساهموا في تطوير صناعة الويب.


المحتويات





الهدف


ويتمثل التحدي في تنفيذ وظيفة تحرير الصور المجمعة وتحرير الصور المستندة إلى عامل التصفية على جانب العميل ، وإذا كان ذلك ممكنًا ، من جانب الخادم أيضًا. لبداية ، سأغطي كيفية تنفيذ المرشحات والفن التصويري.


وصف المرشحات


في سياق مشروعنا ، يعد المرشح سلسلة من الإجراءات التي يتم إجراؤها في Photoshop ويتم تطبيقها على صورة معينة. فيما يلي أمثلة على هذه الإجراءات:


  • تعديل السطوع
  • تعديل التباين
  • تعديل التشبع
  • منحنيات اللون التكيف
  • اخفاء في أوضاع مختلفة
  • تأطير
  • ...

نحن بحاجة إلى تنسيق معين لوصف هذه الإجراءات. بالتأكيد ، هناك تنسيقات شائعة مثل JSON و XML ولكن تقرر إنشاء التنسيق الخاص بنا للأسباب التالية:


  • الحاجة إلى بنية مستقلة عن النظام الأساسي للشفرة (.NET ، JavaScript ، WinPhone ، إلخ.)
  • تحتاج إلى تنسيق بسيط غير هرمي للمرشحات ، مما يجعل من السهل كتابة المحلل اللغوي
  • بيانات XML و JSON تستهلك ذاكرة أكبر (في هذه الحالة بالذات)

في ما يلي تسلسل الإجراءات لفلتر XPro Film :



بصرف النظر عن تحرير صورة باستخدام مرشح ، نحتاج إلى اقتصاص الصورة وتدويرها. نعم ، لقد عرفت أن هناك مكونات إضافية مسج لقص الصور وتدويرها ، لكن يبدو أنها كانت محمّلة بأكثر من اللازم وانحرافًا عن البنية العالمية للمشروع.


وصف الفن التصويري


الكولاج هو ترتيب لعدة صور مصغرة في صورة واحدة كاملة (مع أو بدون استخدام قناع). كان من الضروري أيضًا السماح للمستخدمين بسحب وإسقاط الصور المتاحة إلى الكلية ، وتغيير موقعهم وحجمهم. قد تبدو الصورة المجمعة كما يلي:



تتضمن ميزة Collage استخدام تنسيق بسيط لتخزين المستطيلات مع الإحداثيات النسبية من 0 إلى 1 ، وعناوين الصور ، وبيانات تعديل الصورة. يتم استخدام الإحداثيات النسبية لأنه يتم تطبيق نفس التحويلات من جانب العميل على الصور كبيرة الحجم من جانب الخادم.


التنفيذ


كان علينا أن نختار منصة تسمح للمستخدمين بالعمل مع المرشحات والفن التصويري


اختيار منصة لمعالجة الصور


هناك العديد من تقنيات تطبيقات الإنترنت الغنية ( RIA ) مثل:


  • أدوبي فلاش
  • مايكروسوفت سيلفرلايت
  • HTML 5 + JavaScript
  • العميل الأصلي

لأسباب واضحة ، يعد كل من Flash و HTML هما التقنيتان الوحيدتان اللتان تستحقان الاهتمام نظرًا لأن البقية منهم غير متوافقة مع الأنظمة الأساسية. علاوة على ذلك ، بدأ عميل Silverlight بالموت. على الرغم من أنني حقا أحب مفهوم ملح NaCl ، لسوء الحظ ، هذه التكنولوجيا مدعومة فقط من متصفح كروم ولم يعرف بعد متى سيتم دعمها (وستكون مدعومة من قبل) من قبل المتصفحات الشعبية الأخرى. ملاحظة من 2019: سيكون واسم WebAssembly .


تم الاختيار لصالح منصة HTML5 العصرية والتقدمية ، والتي تدعم وظيفة iOS حاليًا ، بدلاً من Flash. يعتمد هذا الاختيار أيضًا على حقيقة أن هناك الكثير من المكتبات ، والتي تتيح لك ترجمة رمز C # إلى Javascript. يمكنك أيضًا استخدام Visual Studio لهذا الغرض. وترد التفاصيل أدناه.


ترجمة C # إلى Javascript


تم اختيار HTML 5 + JavaScript كمنصة في القسم السابق. لذلك يترك لنا سؤالًا ، ما إذا كان من الممكن كتابة رمز C # عالمي يمكن تجميعه على كل من .NET و JavaScript.


وبالتالي ، تم العثور على عدد من المكتبات لإنجاز المهمة:


  • Jsil
  • شاربكيت
  • البرنامج النصي #
  • وبعض الآخرين المتاحة على جيثب .

نتيجة لذلك ، تقرر استخدام Script # نظرًا لحقيقة أن JSIL تعمل مباشرة مع التجميعات وتولد كودًا أقل نقاءًا (على الرغم من أنها تدعم نطاقًا أوسع من ميزات لغة C #) وأن SharpKit منتج تجاري. للحصول على مقارنة مفصلة بين هذه الأدوات ، راجع السؤال حول التدفق stackoverflow .


خلاصة القول ، ScriptSharp مقارنة بـ JavaScript المكتوبة يدويًا تحتوي على إيجابيات وسلبيات التالية:


المزايا


  • إمكانية كتابة رمز C # عالمي يمكن تجميعه على .NET ومنصات أخرى (WinPhone ، Mono)
  • التطوير بلغة C # مكتوبة بقوة تدعم OOP
  • دعم ميزات IDE (الإكمال التلقائي وإعادة التجهيز)
  • القدرة على اكتشاف غالبية الأخطاء في مرحلة الترجمة

العيوب


  • التكرار وعدم انتظام شفرة JavaScript المنشأة (بسبب mscorlib).
  • دعم ISO-2 فقط (لا يوجد حمل زائد للوظائف أو الاستدلال على النوع والامتداد والاستدلالات الجنيسة)

هيكل


يمكن توضيح عملية تجميع نفس رمز C # في .NET و Javascript بواسطة المخطط التالي:


ترجمة C # إلى مخطط .NET و JavaScript

على الرغم من أن .NET و HTML5 هما تقنيتان مختلفتان تمامًا ، إلا أنهما يمتازان بميزات مشابهة. وهذا ينطبق أيضا على العمل مع الرسومات. على سبيل المثال ، يدعم .NET الصورة النقطية ، ويدعم JavaScript جافا سكريبت التناظرية. الشيء نفسه ينطبق على الرسومات والسياق ومصفوفات البكسل. من أجل الجمع بين كل ذلك في رمز واحد ، تقرر تطوير البنية التالية:


.NET و JavaScript GraphicsContext الشائعة

بالطبع ، لا يقتصر على منصتين. كمتابعة ، من المخطط إضافة دعم لـ WinPhone ، ثم ، ربما ، Android و iOS.


تجدر الإشارة إلى أن هناك نوعان من العمليات الرسومية:


  • استخدام وظائف API ( DrawImage ، Arc ، MoveTo ، LineTo ). الأداء العالي والدعم لتسريع الأجهزة هي مزايا تنافسية مهمة. العيب هو أنه يمكن تنفيذها بشكل مختلف على منصات مختلفة.
  • بكسل بواسطة بكسل. يعد دعم تنفيذ أي تأثيرات وتغطية الأنظمة الأساسية من بين الفوائد. العيب هو الأداء المنخفض. ومع ذلك ، يمكنك تخفيف العيوب عن طريق الموازاة ، التظليل ، والجداول التي تم حسابها مسبقًا (سنناقشها أكثر في القسم التالي حول التحسين).

كما ترون ، تصف فئة الرسومات التجريدية جميع أساليب التعامل مع الرسومات ؛ يتم تنفيذ هذه الطرق لمختلف المنصات في الفئة المشتقة. تمت كتابة الأسماء المستعارة التالية إلى مجردة من فئتي صورة نقطية وقماش أيضاً. يستخدم إصدار WinPhone أيضًا نمط محول .


باستخدام الاسم المستعار


 #if SCRIPTSHARP using System.Html; using System.Html.Media.Graphics; using System.Runtime.CompilerServices; using Bitmap = System.Html.CanvasElement; using Graphics = System.Html.Media.Graphics.CanvasContext2D; using ImageData = System.Html.Media.Graphics.ImageData; using Image = System.Html.ImageElement; #elif DOTNET using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using Bitmap = System.Drawing.Bitmap; using Graphics = System.Drawing.Graphics; using ImageData = System.Drawing.Imaging.BitmapData; using Image = System.Drawing.Bitmap; #endif 

لسوء الحظ ، من المستحيل إنشاء أسماء مستعارة للأنواع والصفائف غير الآمنة ، بمعنى آخر ، الاسم المستعار للإشارة (البايت *) في C # :


 using PixelArray = byte*, using PixelArray = byte[] 

لإجراء معالجة سريعة للبكسل باستخدام شفرة C # غير مُدارة ، وفي نفس الوقت تم تجميعها على Script # ، قدمنا ​​المخطط التالي بمساعدة التوجيهات:


 #if SCRIPTSHARP PixelArray data = context.GetPixelArray(); #elif DOTNET byte* data = context.GetPixelArray(); #endif 

يتم استخدام صفيف data لاحقًا لتنفيذ عمليات بكسل متعددة بحسب وحدات البكسل (مثل التقنيع ، فيش ، ضبط التشبع ، وما إلى ذلك) ، سواء بشكل متوازي أم لا.



تتم إضافة مشروع منفصل إلى الحل لكل نظام أساسي ، ولكن بالطبع ، Mono و Script # وحتى Silverlight لا يمكن الرجوع إلى التجميعات .NET المعتادة. لحسن الحظ ، لدى Visual Studio آلية لإضافة روابط إلى الملفات ، والتي تتيح لك إعادة استخدام نفس الكود في مشاريع مختلفة.


يتم تعريف توجيهات برنامج التحويل البرمجي ( SCRIPTSHARP ، SCRIPTSHARP ) في خصائص المشروع في رموز التجميع الشرطي.


ملاحظات على تطبيق .NET


ساعدنا التجريد والأسماء المستعارة المذكورة أعلاه في كتابة شفرة C # بتكرار منخفض. علاوة على ذلك ، أود أن أشير إلى المشكلات التي واجهناها عند تطوير كود الحل في .NET و JavaScript.


باستخدام التخلص منها


يرجى ملاحظة أن تضمين أي مثيل لفئة C # ، والتي تنفذ واجهة IDisposable ، يتطلب استدعاء الأسلوب Dispose أو تطبيق Use statement . في هذا المشروع ، هذه الفئات هي صورة نقطية والسياق. ما قلته أعلاه ليس مجرد نظرية ، بل لديه بالفعل تطبيق عملي: معالجة عدد كبير من الصور كبيرة الحجم (تصل إلى 2400 × 2400 نقطة في البوصة) على ASP.NET Developer Server x86 نتجت عن استثناء من الذاكرة. تم حل المشكلة بعد إضافة Dispose في الأماكن الصحيحة. ترد بعض النصائح المفيدة الأخرى حول معالجة الصور في المادة 20 التالية: " تغيير حجم صورة مطبات" و "تسرب ذاكرة .NET": للتخلص أو عدم التخلص ، هذا هو السؤال بسعة 1 جيجابايت .


باستخدام قفل


في JavaScript ، هناك فرق بين الصورة التي تم تحميلها بالفعل مع tag img ، والتي يمكنك من خلالها تحديد الحدث المصدر والتحميل ، وقماش canvas tagged ، حيث يمكنك رسم شيء ما. في .NET ، يتم تمثيل هذه العناصر بواسطة نفس فئة Bitmap . وبالتالي ، فإن الأسماء المستعارة للصور النقطية والصورة في .NET تشير إلى نفس فئة System.Drawing.Bitmap . الصورة النقطية كما هو موضح أعلاه.


ومع ذلك ، كان هذا الانقسام إلى img و canvas في JavaScript مفيدًا للغاية في إصدار .NET أيضًا. النقطة المهمة هي أن المرشحات تستخدم الأقنعة التي تم تحميلها مسبقًا من مؤشرات ترابط مختلفة؛ وبالتالي ، فإن نمط القفل مطلوب لتجنب الاستثناء أثناء المزامنة (يتم نسخ الصورة مع القفل ويتم استخدام النتيجة دون قفل):


 internal static Bitmap CloneImage(Image image) { #if SCRIPTSHARP Bitmap result = (Bitmap)Document.CreateElement("canvas"); result.Width = image.Width; result.Height = image.Height; Graphics context = (Graphics)result.GetContext(Rendering.Render2D); context.DrawImage(image, 0, 0); return result; #else Bitmap result; lock (image) result = new Bitmap(image); return result; #endif } 

بعد كل شيء ، يجب أيضًا استخدام القفل عند الوصول إلى خصائص كائن متزامن (في الواقع ، أي خصائص هي طرق).


تخزين الأقنعة في الذاكرة


لتسريع المعالجة ، يتم تحميل جميع الأقنعة المحتمل استخدامها للمرشحات في الذاكرة عند بدء تشغيل الخادم. بغض النظر عن تنسيق القناع ، تستخدم الصورة النقطية التي تم تحميلها على الخادم 4 * 2400 * 2400 أو ≈24 MB من الذاكرة (الحد الأقصى لحجم الصورة 2400 * 2400 ؛ عدد البايت لكل بكسل هو 4). جميع أقنعة المرشحات (≈30) والفن التصويري (40) سوف تستهلك 1.5 جيجابايت - وهذا ليس كثيرًا للخادم ؛ ومع ذلك ، كما ينمو عدد الأقنعة قد يزيد هذا المبلغ بشكل ملحوظ. في المستقبل ، ربما نستخدم تقنيات الضغط للأقنعة المخزنة في الذاكرة (بتنسيقي jpg و .png) متبوعة بإلغاء الضغط عند الضرورة. في الواقع ، يمكن تقليل الحجم إلى 300 مرة. من المزايا الإضافية لهذا النهج أن نسخ الصور المضغوطة يتم بشكل أسرع مقارنة بالصور الكبيرة ؛ وبالتالي ، سوف تستغرق عملية القفل وقتًا أقل وسيتم حظر المواضيع في كثير من الأحيان.


ملاحظات حول تنفيذ JavaScript


التقليل


لقد رفضت استخدام مصطلح "التعتيم" للسبب التالي: هذا المصطلح بالكاد ينطبق على لغة مفتوحة المصدر بالكامل ، والتي في حالتنا هي JavaScript. ومع ذلك ، يمكن إخفاء الهوية من المعرفات الفوضى قراءة رمز والمنطق. والأهم من ذلك ، ستقلل هذه التقنية من حجم البرنامج النصي بشكل كبير (الإصدار المضغوط هو ≈80 كيلوبايت).


هناك طريقتان لتصغير JavaScript:


  • التصغير اليدوي ، والذي يتم تنفيذه في مرحلة التوليد باستخدام ScriptSharp.
  • التصغير الآلي ، الذي يتم تنفيذه بعد مرحلة التوليد باستخدام أدوات خارجية مثل Google Closure Compiler و Yui وأدوات أخرى.

التقليل اليدوي

لتقصير أسماء الطرق والفئات والسمات ، استخدمنا بناء الجملة هذا قبل إعلان الكيانات المذكورة أعلاه. بالطبع ، ليست هناك حاجة للقيام بذلك إذا كنت تعمل مع الأساليب التي يتم استدعاؤها من البرامج النصية والفئات الخارجية (العامة).


 #if SCRIPTSHARP && !DEBUG [ScriptName("a0")] #endif 

على أي حال ، لا يمكن تصغير المتغيرات المحلية. هذه البنى تلوث الكود وتضعف قراءة الكود ، وهو أيضا عيب خطير. ومع ذلك ، يمكن لهذه التقنية أن تقلل إلى حد كبير من كمية شفرة جافا سكريبت التي تم إنشاؤها وأن تفسدها أيضًا.


عيب آخر هو أنك تحتاج إلى مراقبة مثل هذه الأسماء القصيرة إذا قاموا بإعادة تسمية الطريقة وأسماء الحقول (خاصة ، الأسماء التي تم تجاوزها في الفصول الفرعية) لأنه في هذه الحالة لن يهتم البرنامج النصي # بالأسماء المتكررة. ومع ذلك ، فإنه لن يسمح فصول مكررة.


بالمناسبة ، تم بالفعل إضافة وظيفة التصغير للأساليب والحقول الخاصة والداخلية إلى الإصدار المطوّر من البرنامج النصي #.


التصغير الآلي

على الرغم من وجود الكثير من الأدوات اللازمة لتصغير جافا سكريبت ، فقد استخدمت برنامج Google Closure Compiler للعلامة التجارية ونوعية الضغط الجيدة. عيب أداة التقليل من جوجل هو أنه لا يمكن ضغط ملفات CSS ؛ على النقيض من ذلك ، YUI يواجه هذا التحدي بنجاح. في الواقع ، يمكن لـ Script # أيضًا تقليل البرامج النصية ولكن يتعامل مع هذا التحدي أسوأ بكثير من Google Closure.


تحتوي أداة تصغير Google على عدة مستويات من الضغط: المسافة البيضاء ، البسيطة والمتقدمة. لقد اخترنا مستوى بسيط للمشروع. على الرغم من أن المستوى المتقدم يسمح لنا بتحقيق أقصى قدر من الجودة للضغط ، إلا أنه يتطلب تعليمات برمجية مكتوبة بطريقة بحيث يمكن الوصول إلى الطرق من خارج الفصل. تم تنفيذ هذا التصغير جزئيًا يدويًا باستخدام البرنامج النصي #.


أوضاع التصحيح والإصدار


تمت إضافة مكتبات Debug و Release إلى صفحات ASP.NET كما يلي:


 <% if (Gfranq.JavaScriptFilters.HtmlHelper.IsDebug) { %> <script src="Scripts/mscorlib.debug.js" ></script> <script src="Scripts/imgProcLib.debug.js" ></script> <% } else { %> <script src="Scripts/mscorlib.js" ></script> <script src="Scripts/imgProcLib.js" ></script> <% } %> 

في هذا المشروع ، قمنا بتصغير كل من البرامج النصية وملفات وصف المرشح.


crossOrigin الملكية


للوصول إلى بكسلات بعض الصور المعينة ، نحتاج إلى تحويلها إلى قماش أولاً. ولكن قد يؤدي ذلك إلى حدوث خطأ في أمان طلب الشراء المتقاطع (CORS). في حالتنا ، تم حل المشكلة على النحو التالي:


  • تعيين crossOrigin = '' على جانب الخادم.
  • إضافة رأس محدد إلى حزمة HTTP على جانب الخادم.

نظرًا لأن ScriptSharp لا يدعم هذه الخاصية لعناصر img ، تمت كتابة التعليمات البرمجية التالية:


 [Imported] internal class AdvImage { [IntrinsicProperty] internal string CrossOrigin { get { return string.Empty; } set { } } } 

بعد ذلك ، سوف نستخدمها كما يلي:


 ((AdvImage)(object)result).CrossOrigin = ""; 

تسمح لك هذه التقنية بإضافة أي ميزة إلى الكائن دون أخطاء الترجمة. بشكل خاص ، لم يتم تطبيق خاصية wheelDelta بعد في ScriptSharp (على الأقل في الإصدار 0.7.5). تشير هذه الخاصية إلى مقدار عجلة التمرير ، والتي يتم استخدامها لإنشاء صور مجمعة. لهذا السبب تم تنفيذه بهذه الطريقة. مثل هذا الاختراق القذر مع الخصائص ليست جيدة. عادة ، تحتاج إلى إجراء تغييرات على المشروع. ولكن فقط للسجل ، لم أحسب بعد طريقة لتجميع ScriptSharp من المصدر.


تتطلب هذه الصور من الخادم إرجاع الرؤوس التالية في رؤوس الاستجابة الخاصة به (في Global.asax):


 Response.AppendHeader("Access-Control-Allow-Origin", "\*"); 

لمزيد من المعلومات حول الأمان عبر طلب الأصل ، تفضل بزيارة http://enable-cors.org/ .


التحسينات


باستخدام القيم قبل حسابها


استخدمنا التحسين في بعض العمليات مثل ضبط السطوع والتباين ومنحنيات اللون من خلال الحساب الأولي لمكونات اللون الناتجة (r ، g ، b) لجميع القيم الممكنة واستخدامًا إضافيًا للصفائف التي تم الحصول عليها لتغيير ألوان البكسل مباشرةً . تجدر الإشارة إلى أن هذا النوع من التحسين مناسب فقط للعمليات التي لا يتأثر فيها لون البكسل الناتج بالبكسل المجاور.


حساب مكونات اللون الناتجة لجميع القيم الممكنة:


 for (int i = 0; i < 256; i++) { r[i] = ActionFuncR(i); g[i] = ActionFuncG(i); b[i] = ActionFuncB(i); } 

استخدام مكونات اللون precalculated:


 for (int i = 0; i < data.Length; i += 4) { data[i] = r[data[i]]; data[i + 1] = g[data[i + 1]]; data[i + 2] = b[data[i + 2]]; } 

إذا كانت عمليات الجدول هذه واحدة تلو الأخرى ، فليست هناك حاجة لحساب الصور الوسيطة - يمكنك تمرير صفائف مكون اللون فقط. نظرًا لأن الشفرة عملت بسرعة إلى حد ما على جانب العميل والخادم ، فقد تقرر وضع هذا التحسين جانباً. علاوة على ذلك ، تسبب التحسين في بعض السلوك غير المرغوب فيه. ومع ذلك ، سأعطيك قائمة بالتحسين:


الرمز الأصليرمز الأمثل
`` `خدمات العملاء
// حساب القيم للجدول الأول.
لـ (int i = 0 ؛ i <256 ؛ i ++)
{
r [i] = ActionFunc1R (i)؛
g [i] = ActionFunc1G (i)؛
b [i] = ActionFunc1B (i)؛
}
// ...

// حساب الصورة الوسيطة الناتجة.
لـ (int i = 0 ؛ أنا <data.Length ؛ i + = 4)
{
البيانات [i] = r [data [i]]؛
data [i + 1] = g [data [i + 1]] ؛
data [i + 2] = b [data [i + 2]] ؛
}
// ...

// حساب القيم للجدول الثاني.
لـ (int i = 0 ؛ i <256 ؛ i ++)
{
r [i] = ActionFunc2R (i)؛
g [i] = ActionFunc2G (i)؛
b [i] = ActionFunc2B (i)؛
}
// ...

// حساب الصورة الناتجة.
لـ (int i = 0 ؛ أنا <data.Length ؛ i + = 4)
{
البيانات [i] = r [data [i]]؛
data [i + 1] = g [data [i + 1]] ؛
data [i + 2] = b [data [i + 2]] ؛
}
`` ``

`` `خدمات العملاء
// حساب القيم للجدول الأول.
لـ (int i = 0 ؛ i <256 ؛ i ++)
{
r [i] = ActionFunc1R (i)؛
g [i] = ActionFunc1G (i)؛
b [i] = ActionFunc1B (i)؛
}
// ...

// حساب القيم للجدول الثاني.
tr = r.Clone () ؛
tg = g.Clone ()؛
tb = b.Clone ()؛
لـ (int i = 0 ؛ i <256 ؛ i ++)
{
r [i] = tr [ActionFunc2R (i)]؛
g [i] = tg [ActionFunc2G (i)]؛
b [i] = tb [ActionFunc2B (i)]؛
}
// ...

// حساب الصورة الناتجة.
لـ (int i = 0 ؛ أنا <data.Length ؛ i + = 4)
{
البيانات [i] = r [data [i]]؛
data [i + 1] = g [data [i + 1]] ؛
data [i + 2] = b [data [i + 2]] ؛
}
`` ``


ولكن حتى هذا ليس كل شيء. إذا ألقيت نظرة على الجدول على اليمين ، فستلاحظ أنه يتم إنشاء صفائف جديدة باستخدام طريقة Clone . في الواقع ، يمكنك فقط تغيير المؤشرات إلى الصفيفين القديم والجديد بدلاً من نسخ الصفيف نفسه (يشير ذلك إلى تشبيه التخزين المؤقت المزدوج ).


تحويل صورة إلى مجموعة من البكسل


كشف منشئ ملفات JavaScript في Google Chrome أن وظيفة GetImageData (التي تُستخدم لتحويل اللوحة القماشية إلى صفيف وحدات البكسل) تعمل لفترة كافية. هذه المعلومات ، بالمناسبة ، يمكن العثور عليها في مقالات مختلفة عن تحسين اللوحة القماشية في JavaScript.


ومع ذلك ، يمكن تقليل عدد استدعاءات هذه الوظيفة. وهي ، يمكننا استخدام نفس مجموعة البكسل لعمليات البكسل بالبكسل ، عن طريق القياس مع التحسين السابق.


أمثلة التعليمات البرمجية


في الأمثلة أدناه ، سأقدم أجزاء من التعليمات البرمجية التي وجدتها ممتعة ومفيدة. للحفاظ على المقالة أطول من اللازم ، قمت بإخفاء الأمثلة الموجودة أسفل المفسد.


عام


اكتشاف ما إذا كانت السلسلة رقم


 internal static bool IsNumeric(string n) { #if !SCRIPTSHARP return ((Number)int.Parse(n)).ToString() != "NaN"; #else double number; return double.TryParse(n, out number); #endif } 

تقسيم صحيح


 internal static int Div(int n, int k) { int result = n / k; #if SCRIPTSHARP result = Math.Floor(n / k); #endif return result; } 

تدوير وتقليب صورة باستخدام Canvas والصور النقطية


يرجى ملاحظة أنه في html5 يمكن تدوير الصور القماشية 90 و 180 درجة فقط باستخدام المصفوفات ، بينما يوفر .NET وظائف محسّنة. وبالتالي ، تم كتابة وظيفة دقيقة مناسبة للعمل مع بكسل.


تجدر الإشارة أيضًا إلى أن أي دورة جانبية بزاوية 90 درجة في إصدار .NET قد تؤدي إلى نتائج غير صحيحة. لذلك ، تحتاج إلى إنشاء Bitmap جديدة بعد استخدام وظيفة RotateFlip .


شفرة المصدر
 public static Bitmap RotateFlip(Bitmap bitmap, RotFlipType rotFlipType) { #if SCRIPTSHARP int t, i4, j4, w, h, c; if (rotFlipType == RotFlipType.RotateNoneFlipNone) return bitmap; GraphicsContext context; PixelArray data; if (rotFlipType == RotFlipType.RotateNoneFlipX) { context = GraphicsContext.GetContext(bitmap); data = context.GetPixelArray(); w = bitmap.Width; h = bitmap.Height; for (int i = 0; i < h; i++) { c = (i + 1) * w * 4 - 4; for (int j = 0; j < w / 2; j++) { i4 = (i * w + j) * 4; j4 = j * 4; t = (int)data[i4]; data[i4] = data[c - j4]; data[c - j4] = t; t = (int)data[i4 + 1]; data[i4 + 1] = data[c - j4 + 1]; data[c - j4 + 1] = t; t = (int)data[i4 + 2]; data[i4 + 2] = data[c - j4 + 2]; data[c - j4 + 2] = t; t = (int)data[i4 + 3]; data[i4 + 3] = data[c - j4 + 3]; data[c - j4 + 3] = t; } } context.PutImageData(); } else if (rotFlipType == RotFlipType.Rotate180FlipNone || rotFlipType == RotFlipType.Rotate180FlipX) { context = GraphicsContext.GetContext(bitmap); data = context.GetPixelArray(); w = bitmap.Width; h = bitmap.Height; c = w * 4 - 4; int dlength4 = data.Length - 4; for (int i = 0; i < data.Length / 4 / 2; i++) { i4 = i * 4; if (rotFlipType == RotFlipType.Rotate180FlipNone) j4 = i4; else j4 = (Math.Truncate((double)i / w) * w + (w - i % w)) * 4; t = (int)data[j4]; data[j4] = data[dlength4 - i4]; data[dlength4 - i4] = t; t = (int)data[j4 + 1]; data[j4 + 1] = data[dlength4 - i4 + 1]; data[dlength4 - i4 + 1] = t; t = (int)data[j4 + 2]; data[j4 + 2] = data[dlength4 - i4 + 2]; data[dlength4 - i4 + 2] = t; t = (int)data[j4 + 3]; data[j4 + 3] = data[dlength4 - i4 + 3]; data[dlength4 - i4 + 3] = t; } context.PutImageData(); } else { Bitmap tempBitmap = PrivateUtils.CreateCloneBitmap(bitmap); GraphicsContext tempContext = GraphicsContext.GetContext(tempBitmap); PixelArray temp = tempContext.GetPixelArray(); t = bitmap.Width; bitmap.Width = bitmap.Height; bitmap.Height = t; context = GraphicsContext.GetContext(bitmap); data = context.GetPixelArray(); w = tempBitmap.Width; h = tempBitmap.Height; if (rotFlipType == RotFlipType.Rotate90FlipNone || rotFlipType == RotFlipType.Rotate90FlipX) { c = w * h - w; for (int i = 0; i < temp.Length / 4; i++) { t = Math.Truncate((double)i / h); if (rotFlipType == RotFlipType.Rotate90FlipNone) i4 = i * 4; else i4 = (t * h + (h - i % h)) * 4; j4 = (c - w * (i % h) + t) * 4; //j4 = (w * (h - 1 - i4 % h) + i4 / h) * 4; data[i4] = temp[j4]; data[i4 + 1] = temp[j4 + 1]; data[i4 + 2] = temp[j4 + 2]; data[i4 + 3] = temp[j4 + 3]; } } else if (rotFlipType == RotFlipType.Rotate270FlipNone || rotFlipType == RotFlipType.Rotate270FlipX) { c = w - 1; for (int i = 0; i < temp.Length / 4; i++) { t = Math.Truncate((double)i / h); if (rotFlipType == RotFlipType.Rotate270FlipNone) i4 = i * 4; else i4 = (t * h + (h - i % h)) * 4; j4 = (c + w * (i % h) - t) * 4; // j4 = w * (1 + i4 % h) - i4 / h - 1; data[i4] = temp[j4]; data[i4 + 1] = temp[j4 + 1]; data[i4 + 2] = temp[j4 + 2]; data[i4 + 3] = temp[j4 + 3]; } } context.PutImageData(); } return bitmap; #elif DOTNET Bitmap result = null; switch (rotFlipType) { case RotFlipType.RotateNoneFlipNone: result = bitmap; break; case RotFlipType.Rotate90FlipNone: bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone); result = new Image(bitmap); bitmap.Dispose(); break; case RotFlipType.Rotate270FlipNone: bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone); result = new Image(bitmap); bitmap.Dispose(); break; case RotFlipType.Rotate180FlipNone: bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); result = bitmap; break; case RotFlipType.RotateNoneFlipX: bitmap.RotateFlip(RotateFlipType.RotateNoneFlipX); result = bitmap; break; case RotFlipType.Rotate90FlipX: bitmap.RotateFlip(RotateFlipType.Rotate90FlipX); result = new Image(bitmap); bitmap.Dispose(); break; case RotFlipType.Rotate180FlipX: bitmap.RotateFlip(RotateFlipType.Rotate180FlipX); result = bitmap; break; case RotFlipType.Rotate270FlipX: bitmap.RotateFlip(RotateFlipType.Rotate270FlipX); result = new Image(bitmap); bitmap.Dispose(); break; } return result; #endif } 

تحميل الصور المتزامن وغير المتزامن


لاحظ أنه في الإصدار Script # ، نحدد وظيفة مختلفة CollageImageLoad ، والتي تسمى بعد تحميل صورة ، بينما تحدث هذه العمليات في إصدار .NET في وقت واحد (من نظام ملفات أو الإنترنت).


شفرة المصدر
 public CollageData(string smallMaskPath, string bigMaskPath, List<CollageDataPart> dataParts) { SmallMaskImagePath = smallMaskPath; BigMaskImagePath = bigMaskPath; #if SCRIPTSHARP CurrentMask = PrivateUtils.CreateEmptyImage(); CurrentMask.AddEventListener("load", CollageImageLoad, false); CurrentMask.Src = CurrentMaskImagePath; #else CurrentMask = PrivateUtils.LoadBitmap(CurrentMaskImagePath); if (!CurrentMaskImagePath.Contains("http://") && !CurrentMaskImagePath.Contains("https://")) CurrentMask = Bitmap(CurrentMaskImagePath); else { var request = WebRequest.Create(CurrentMaskImagePath); using (var response = request.GetResponse()) using (var stream = response.GetResponseStream()) CurrentMask = (Bitmap)Bitmap.FromStream(stream); } #endif DataParts = dataParts; } 

البرنامج النصي # فقط


اكتشاف نوع وإصدار المستعرض


تستخدم هذه الوظيفة لتحديد قدرات السحب والإفلات في المتصفحات المختلفة. لقد حاولت استخدام modernizr ، لكنه عاد إلى Safari و (في حالتي ، كان إصدار Win) يقوم IE9 بتنفيذه. في الممارسة العملية ، تفشل هذه المتصفحات في تنفيذ قدرات السحب والإفلات بشكل صحيح.


شفرة المصدر
 internal static string BrowserVersion { get { DetectBrowserTypeAndVersion(); return _browserVersion; } } private static void DetectBrowserTypeAndVersion() { if (!_browserDetected) { string userAgent = Window.Navigator.UserAgent.ToLowerCase(); if (userAgent.IndexOf("opera") != -1) _browser = BrowserType.Opera; else if (userAgent.IndexOf("chrome") != -1) _browser = BrowserType.Chrome; else if (userAgent.IndexOf("safari") != -1) _browser = BrowserType.Safari; else if (userAgent.IndexOf("firefox") != -1) _browser = BrowserType.Firefox; else if (userAgent.IndexOf("msie") != -1) { int numberIndex = userAgent.IndexOf("msie") + 5; _browser = BrowserType.IE; _browserVersion = userAgent.Substring(numberIndex, userAgent.IndexOf(';', numberIndex)); } else _browser = BrowserType.Unknown; _browserDetected = true; } } 

تقديم خط Dash-dot


يستخدم هذا الرمز لمستطيل لصور المحاصيل. شكرا للأفكار لكل من أجاب على هذا السؤال على stackoverflow .


شفرة المصدر
 internal static void DrawDahsedLine(GraphicsContext context, double x1, double y1, double x2, double y2, int[] dashArray) { if (dashArray == null) dashArray = new int[2] { 10, 5 }; int dashCount = dashArray.Length; double dx = x2 - x1; double dy = y2 - y1; bool xSlope = Math.Abs(dx) > Math.Abs(dy); double slope = xSlope ? dy / dx : dx / dy; context.MoveTo(x1, y1); double distRemaining = Math.Sqrt(dx * dx + dy * dy); int dashIndex = 0; while (distRemaining >= 0.1) { int dashLength = (int)Math.Min(distRemaining, dashArray[dashIndex % dashCount]); double step = Math.Sqrt(dashLength * dashLength / (1 + slope * slope)); if (xSlope) { if (dx < 0) step = -step; x1 += step; y1 += slope * step; } else { if (dy < 0) step = -step; x1 += slope * step; y1 += step; } if (dashIndex % 2 == 0) context.LineTo(x1, y1); else context.MoveTo(x1, y1); distRemaining -= dashLength; dashIndex++; } } 

دوران الرسوم المتحركة


setInterval وظيفة setInterval لتنفيذ الرسوم المتحركة تدوير الصورة. لاحظ أن الصورة الناتجة تُحسب أثناء الرسوم المتحركة بحيث لا توجد أي تأخيرات في نهاية الرسوم المتحركة.


شفرة المصدر
 public void Rotate(bool cw) { if (!_rotating && !_flipping) { _rotating = true; _cw = cw; RotFlipType oldRotFlipType = _curRotFlipType; _curRotFlipType = RotateRotFlipValue(_curRotFlipType, _cw); int currentStep = 0; int stepCount = (int)(RotateFlipTimeSeconds * 1000 / StepTimeTicks); Bitmap result = null; _interval = Window.SetInterval(delegate() { if (currentStep < stepCount) { double absAngle = GetAngle(oldRotFlipType) + currentStep / stepCount * Math.PI / 2 * (_cw ? -1 : 1); DrawRotated(absAngle); currentStep++; } else { Window.ClearInterval(_interval); if (result != null) Draw(result); _rotating = false; } }, StepTimeTicks); result = GetCurrentTransformResult(); if (!_rotating) Draw(result); } } private void DrawRotated(double rotAngle) { _resultContext.FillColor = FillColor; _resultContext.FillRect(0, 0, _result.Width, _result.Height); _resultContext.Save(); _resultContext._graphics.Translate(_result.Width / 2, _result.Height / 2); _resultContext._graphics.Rotate(-rotAngle); _resultContext._graphics.Translate(-_origin.Width / 2, -_origin.Height / 2); _resultContext._graphics.DrawImage(_origin, 0, 0); _resultContext.Restore(); } private void Draw(Bitmap bitmap) { _resultContext.FillColor = FillColor; _resultContext.FillRect(0, 0, _result.Width, _result.Height); _resultContext.Draw2(bitmap, (int)((_result.Width - bitmap.Width) / 2), (int)((_result.Height - bitmap.Height) / 2)); } 

الخاتمة


توضح هذه المقالة كيف يمكن استخدام لغة C # (الجمع بين التعليمات البرمجية غير المدارة وتجميعها لجافا سكريبت) لإنشاء حل عبر الأنظمة الأساسية حقًا. على الرغم من التركيز على .NET و JavaScript ، فإن الترجمة إلى Android و iOS (باستخدام Mono) و Windows Phone ممكنة أيضًا استنادًا إلى هذا النهج ، والذي ، بالطبع ، لديه عيوبه. الشفرة زائدة بعض الشيء بسبب شموليتها ، لكنها لا تؤثر على الأداء لأن العمليات الرسومية عادة ما تستغرق وقتًا أكبر بكثير.

Source: https://habr.com/ru/post/ar440342/


All Articles