Kode C # Universal untuk NET dan JavaScript

Pada 2013, saat bekerja di layanan foto GFRANQ, saya berpartisipasi dalam pengembangan layanan web eponymous untuk penerbitan dan pemrosesan foto. Filter dan transformasi didefinisikan dalam file dengan parameter, dan semua pemrosesan dilakukan di server. Selama pengembangan layanan, ada kebutuhan untuk mendukung transformasi ini di sisi klien untuk pratinjau. Menurut Larry Wall, salah satu keutamaan seorang programmer adalah kemalasan. Oleh karena itu, sebagai pemrogram yang benar-benar malas, kami memikirkan kemungkinan menggunakan kode yang sama di sisi server dan klien. Seluruh pengembangan dilakukan dalam C #. Setelah meneliti perpustakaan dan beberapa upaya, kami dengan bangga menyimpulkan bahwa ini mungkin dan mulai menulis kode universal.



Mengapa artikel ini dibutuhkan? Memang, 6 tahun telah berlalu sejak 2013, dan banyak teknologi telah kehilangan relevansinya, misalnya, Script # . Di sisi lain, yang baru telah muncul. Misalnya, Bridge.NET atau Blazor berdasarkan pada WebAssembly yang mewah.


Meskipun demikian, beberapa ide masih bisa digunakan. Pada artikel ini saya mencoba menggambarkannya sedetail mungkin. Saya berharap bahwa penyebutan Silverlight dan Flash akan menyebabkan senyum dengan sedikit nostalgia, dan bukan keinginan untuk mengkritik solusi lama. Lagi pula, mereka telah berkontribusi pada pengembangan industri web.


Isi





Tujuan


Tantangannya adalah untuk mengimplementasikan kolase foto dan fungsionalitas pengeditan foto berbasis filter pada sisi klien dan, jika mungkin, pada sisi server juga. Sebagai permulaan, saya akan membahas bagaimana filter dan kolase diimplementasikan.


Deskripsi filter


Dalam konteks proyek kami, filter adalah serangkaian tindakan yang dilakukan dalam Photoshop dan diterapkan pada foto tertentu. Berikut adalah contoh tindakan tersebut:


  • Penyesuaian kecerahan
  • Penyesuaian kontras
  • Penyesuaian saturasi
  • Penyesuaian kurva warna
  • Masking dalam mode yang berbeda
  • Membingkai
  • ...

Kami membutuhkan format tertentu untuk menggambarkan tindakan ini. Tentu, ada format umum seperti JSON dan XML, tetapi diputuskan untuk membuat format sendiri karena alasan berikut:


  • Perlu arsitektur kode platform-independen (.NET, JavaScript, WinPhone, dll.)
  • Perlu format filter non-hierarkis yang sederhana, yang membuatnya mudah untuk menulis parser
  • Data XML dan JSON lebih banyak memakan memori (dalam kasus khusus ini)

Berikut ini urutan tindakan untuk filter Film XPro :



Selain mengedit foto dengan filter, kami perlu memotong dan memutar gambar. Ya, saya tahu bahwa ada plugin jQuery untuk memotong dan memutar gambar, tetapi tampaknya kelebihan beban dan menyimpang dari arsitektur universal proyek.


Deskripsi kolase


Kolase adalah susunan beberapa foto mini ke dalam satu foto utuh (dengan atau tanpa menggunakan topeng). Itu juga diperlukan untuk memungkinkan pengguna untuk menarik dan melepaskan gambar yang tersedia ke kolase, mengubah posisi dan skala mereka. Kolase Anda mungkin terlihat seperti ini:



Fitur kolase menyiratkan menggunakan format sederhana untuk menyimpan persegi panjang dengan koordinat relatif dari 0 hingga 1 , alamat foto, dan data modifikasi gambar. Koordinat relatif digunakan karena transformasi sisi klien yang sama diterapkan ke gambar ukuran besar di sisi server.


Implementasi


Kami harus memilih platform yang memungkinkan pengguna bekerja dengan filter dan kolase


Memilih Platform untuk Pemrosesan Foto


Ada beberapa teknologi Rich Internet Application ( RIA ) seperti:


  • Adobe flash
  • Microsoft silverlight
  • HTML 5 + JavaScript
  • Klien asli

Untuk alasan yang jelas, Flash dan HTML adalah satu-satunya teknologi yang pantas mendapat perhatian karena sisanya tidak kompatibel lintas platform. Selanjutnya, klien Silverlight mulai mati. Meskipun saya sangat suka konsep garam NaCl, sayangnya, teknologi ini hanya didukung oleh browser Chrome dan belum diketahui kapan akan didukung (dan apakah akan pernah didukung) oleh browser populer lainnya. Catatan dari 2019: itu akan dan namanya adalah WebAssembly .


Pilihan ini dibuat untuk mendukung platform HTML5 yang trendi dan progresif, yang fungsinya saat ini didukung oleh iOS, berbeda dengan Flash. Pilihan ini juga didasarkan pada kenyataan bahwa ada banyak perpustakaan, yang memungkinkan Anda untuk mengkompilasi kode C # ke dalam Javascript. Anda juga dapat menggunakan Visual Studio untuk tujuan ini. Detail diberikan di bawah ini.


Menerjemahkan C # ke dalam Javascript


HTML 5 + JavaScript telah dipilih sebagai platform di bagian sebelumnya. Jadi itu meninggalkan kita pertanyaan, apakah mungkin untuk menulis kode C # universal yang dapat dikompilasi untuk .NET dan JavaScript.


Dengan demikian, sejumlah perpustakaan untuk menyelesaikan tugas ditemukan:


  • Jsil
  • Sharpkit
  • Skrip #
  • Dan beberapa lainnya tersedia di GitHub .

Akibatnya, diputuskan untuk menggunakan Script # karena fakta bahwa JSIL bekerja langsung dengan majelis dan menghasilkan kode yang kurang murni (meskipun mendukung berbagai fitur bahasa C # yang lebih luas) dan SharpKit adalah produk komersial. Untuk perbandingan terinci dari alat-alat ini, lihat pertanyaan tentang stackoverflow .


Singkatnya, ScriptSharp dibandingkan dengan JavaScript yang ditulis secara manual memiliki pro dan kontra berikut:


Keuntungan


  • Kemungkinan untuk menulis kode C # universal yang dapat dikompilasi ke .NET dan platform lainnya (WinPhone, Mono)
  • Pengembangan bahasa C # yang sangat diketik mendukung OOP
  • Dukungan untuk fitur IDE (pelengkapan otomatis dan refactoring)
  • Kemampuan untuk mendeteksi sebagian besar kesalahan pada tahap kompilasi

Kekurangan


  • Redundansi dan penyimpangan kode JavaScript yang dihasilkan (karena mscorlib).
  • Dukungan hanya untuk ISO-2 (tidak ada kelebihan fungsi atau tipe, ekstensi, dan inferensi generik)

Struktur


Proses kompilasi kode C # yang sama ke dalam .NET dan Javascript dapat diilustrasikan dengan skema berikut:


Terjemahan C # ke Skema .NET & JavaScript

Meskipun .NET dan HTML5 adalah teknologi yang sama sekali berbeda, mereka juga memiliki fitur serupa. Ini juga berlaku untuk bekerja dengan grafik. Misalnya, .NET mendukung Bitmap , JavaScript mendukung analognya - Kanvas . Hal yang sama berlaku dengan Grafik , Konteks , dan array piksel. Untuk menggabungkan semuanya dalam satu kode, diputuskan untuk mengembangkan arsitektur berikut:


Konteks Umum .NET & JavaScript Graphics

Tentu saja, ini tidak terbatas pada dua platform. Sebagai tindak lanjut direncanakan untuk menambahkan dukungan untuk WinPhone, dan kemudian, mungkin, Android dan iOS.


Perlu dicatat bahwa ada dua jenis operasi grafis:


  • Menggunakan fungsi API ( DrawImage , Arc , MoveTo , LineTo ). Kinerja tinggi dan dukungan untuk akselerasi perangkat keras adalah keunggulan kompetitif yang penting. Kekurangannya adalah mereka dapat diimplementasikan secara berbeda pada platform yang berbeda.
  • Pixel demi pixel. Dukungan untuk implementasi segala efek dan cakupan lintas platform adalah di antara manfaatnya. Kerugiannya adalah kinerja yang rendah. Namun, Anda dapat mengurangi kerugian dengan paralelisasi, shader, dan tabel yang dihitung sebelumnya (kami akan membahasnya lebih lanjut di bagian selanjutnya tentang pengoptimalan).

Seperti yang dapat Anda lihat, Grafik kelas abstrak menjelaskan semua metode untuk bekerja dengan grafik; metode ini diimplementasikan untuk berbagai platform di kelas turunan. Alias ​​berikut ditulis untuk abstrak dari Bitmap dan kelas Canvas juga. Versi WinPhone juga menggunakan pola adaptor .


Menggunakan alias


 #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 

Sayangnya, tidak mungkin untuk membuat alias untuk tipe dan array yang tidak aman, dengan kata lain, Alias ​​to pointer (byte *) di C # :


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

Untuk melakukan pemrosesan piksel yang cepat menggunakan kode C # yang tidak dikelola, sementara pada saat yang sama mengkompilasinya ke Script #, kami memperkenalkan skema berikut dengan bantuan arahan:


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

Array data selanjutnya digunakan untuk mengimplementasikan berbagai operasi pixel demi pixel (seperti masking, fisheye, penyesuaian saturasi, dan sebagainya), baik secara paralel maupun tidak.



Proyek terpisah ditambahkan ke solusi untuk setiap platform, tetapi, tentu saja, Mono, Script #, dan bahkan Silverlight tidak dapat merujuk ke rakitan .NET yang biasa. Untungnya, Visual Studio memiliki mekanisme untuk menambahkan tautan ke file, yang memungkinkan Anda untuk menggunakan kembali kode yang sama di berbagai proyek.


Arahan DOTNET ( DOTNET , SCRIPTSHARP ) didefinisikan dalam properti proyek dalam Simbol Kompilasi Bersyarat.


Catatan tentang Implementasi .NET


Abstraksi dan alias di atas membantu kami untuk menulis kode C # dengan redundansi yang rendah. Selanjutnya, saya ingin menunjukkan masalah dengan platform .NET dan JavaScript yang kami hadapi ketika mengembangkan kode solusi.


Gunakan buang


Harap dicatat bahwa penyertaan instance dari kelas C #, yang mengimplementasikan antarmuka IDisposable , memerlukan pemanggilan metode Dispose atau terapkan pernyataan Menggunakan . Dalam proyek ini, kelas-kelas ini adalah Bitmap dan Konteks. Apa yang saya katakan di atas bukan hanya teori, itu sebenarnya memiliki aplikasi praktis: Memproses sejumlah besar foto ukuran besar (hingga 2400 x 2400 dpi) di ASP.NET Developer Server x86 menghasilkan pengecualian memori. Masalah ini diselesaikan setelah menambahkan Dispose di tempat yang tepat. Beberapa saran bermanfaat lainnya tentang manipulasi gambar diberikan dalam artikel berikut 20 Kesalahan Pengubahan Ukuran Gambar dan .NET Memory Leak: Untuk membuang atau tidak membuang, itulah pertanyaan 1 GB .


Menggunakan kunci


Dalam JavaScript, ada perbedaan antara gambar yang sudah diunggah dengan tag img , yang img Anda dapat menentukan sumber dan acara pemuatan, dan kanvas yang ditandai canvas , di mana Anda dapat menggambar sesuatu. Dalam .NET elemen-elemen ini diwakili oleh kelas Bitmap sama. Dengan demikian, alias Bitmap dan Gambar di .NET menunjuk ke kelas yang sama System.Drawing.Bitmap . Bitmap seperti yang ditunjukkan di atas.


Namun demikian, pemisahan ini menjadi img dan canvas dalam JavaScript juga sangat membantu dalam versi .NET. Intinya adalah bahwa filter menggunakan topeng preloaded dari utas yang berbeda; dengan demikian, pola kunci diperlukan untuk menghindari pengecualian selama sinkronisasi (gambar disalin dengan kunci dan hasilnya digunakan tanpa mengunci):


 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 } 

Bagaimanapun, kunci juga harus digunakan ketika mengakses properti dari objek yang disinkronkan (Faktanya, semua properti adalah metode).


Menyimpan topeng di memori


Untuk mempercepat pemrosesan, semua masker yang berpotensi digunakan untuk filter dimuat ke dalam memori ketika server dimulai. Apa pun format topengnya, Bitmap yang diunggah ke server menggunakan 4 * 2400 * 2400 atau β‰ˆ24 MB memori (ukuran gambar maksimum adalah 2400 * 2400 ; jumlah byte per piksel adalah 4). Semua masker untuk filter (β‰ˆ30) dan kolase (40) akan mengkonsumsi 1,5 GB - itu tidak banyak untuk server; Namun, seiring dengan meningkatnya jumlah topeng, jumlah ini dapat meningkat secara signifikan. Di masa mendatang, kami mungkin akan menggunakan teknik kompresi untuk masker yang disimpan dalam memori (dalam format .jpg dan .png) diikuti dengan dekompresi bila perlu. Sebenarnya ukurannya bisa dikurangi hingga 300 kali lipat. Keuntungan tambahan dari pendekatan ini adalah bahwa penyalinan gambar terkompresi berjalan lebih cepat dibandingkan dengan yang besar; dengan demikian, operasi kunci akan memakan waktu lebih sedikit dan utas akan diblokir lebih jarang.


Catatan tentang Implementasi JavaScript


Minifikasi


Saya menolak untuk menggunakan istilah "kebingungan" karena alasan berikut: istilah ini hampir tidak berlaku untuk bahasa open-source sepenuhnya, yang dalam kasus kami adalah JavaScript. Namun, penganoniman pengidentifikasi dapat mengacaukan keterbacaan kode dan logika. Dan yang paling penting, teknik ini akan secara signifikan mengurangi ukuran skrip (versi terkompresi adalah β‰ˆ80 KB).


Ada dua pendekatan untuk minifikasi JavaScript:


  • Minifikasi manual, yang dilakukan pada tahap generasi menggunakan ScriptSharp.
  • Pengotomatisasi otomatis, yang dilakukan setelah tahap pembuatan menggunakan alat eksternal seperti Google Closure Compiler, Yui dan alat lainnya.

Minifikasi manual

Untuk mempersingkat nama metode, kelas, dan atribut kami menggunakan sintaks ini sebelum deklarasi entitas yang disebutkan di atas. Tentu saja, tidak perlu melakukan itu jika Anda bekerja dengan metode yang dipanggil dari skrip dan kelas eksternal (publik).


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

Bagaimanapun, variabel lokal tidak dapat dikecilkan. Ini membangun mencemari kode dan merusak keterbacaan kode, yang juga merupakan kerugian serius. Namun, teknik ini dapat secara signifikan mengurangi jumlah kode JavaScript yang dihasilkan dan mengacaukannya juga.


Kerugian lain adalah bahwa Anda perlu mengawasi nama-nama pendek seperti itu jika mereka mengganti nama metode dan nama bidang (terutama, nama-nama yang diganti dalam kelas anak) karena dalam kasus ini Skrip # tidak akan peduli dengan nama berulang. Namun, itu tidak akan mengizinkan kelas digandakan.


Omong-omong, fungsi minifikasi untuk metode dan bidang pribadi dan internal sudah ditambahkan ke versi Script # yang dikembangkan.


Minifikasi Otomatis

Meskipun ada banyak alat untuk minifikasi JavaScript, saya menggunakan Google Closure Compiler untuk mereknya dan kualitas kompresi yang baik. Kerugian dari alat minifikasi Google adalah tidak dapat memampatkan file CSS; sebaliknya, YUI berhasil mengatasi tantangan ini. Bahkan, Skrip # juga dapat memperkecil skrip tetapi menangani tantangan ini jauh lebih buruk daripada Google Penutupan.


Alat minifikasi Google memiliki beberapa tingkat kompresi: spasi putih, sederhana, dan canggih. Kami telah memilih level Sederhana untuk proyek; meskipun, tingkat Mahir memungkinkan kita untuk mencapai kualitas kompresi maksimum, itu memerlukan kode yang ditulis sedemikian rupa sehingga metode dapat diakses dari luar kelas. Pengecilan ini sebagian dilakukan secara manual menggunakan Script #.


Mode Debug dan Rilis


Perpustakaan Debug dan Rilis ditambahkan ke halaman ASP.NET sebagai berikut:


 <% 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> <% } %> 

Dalam proyek ini, kami meminimalkan skrip dan memfilter file deskripsi.


Properti crossOrigin


Untuk mengakses piksel beberapa gambar tertentu, kita perlu mengubahnya menjadi kanvas terlebih dahulu. Tetapi ini dapat menyebabkan kesalahan Cross Origin Request Security (CORS). Dalam kasus kami, masalahnya diselesaikan sebagai berikut:


  • Mengatur crossOrigin = '' di sisi server.
  • Menambahkan header spesifik ke paket HTTP di sisi server.

Karena ScriptSharp tidak mendukung properti ini untuk elemen img, kode berikut ditulis:


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

Lalu, kita akan menggunakannya seperti ini:


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

Teknik ini memungkinkan Anda untuk menambahkan fitur apa pun ke objek tanpa kesalahan kompilasi. Khususnya, properti wheelDelta belum diimplementasikan di ScriptSharp (setidaknya dalam versi 0.7.5). Properti ini menunjukkan jumlah roda gulir, yang digunakan untuk membuat kolase. Itu sebabnya diimplementasikan dengan cara ini. Retas kotor dengan properti seperti itu tidak baik; biasanya, Anda perlu melakukan perubahan pada proyek. Tetapi hanya sebagai catatan, saya belum menemukan cara untuk mengkompilasi ScriptSharp dari sumber.


Gambar tersebut mengharuskan server untuk mengembalikan header berikut dalam header responsnya (di Global.asax):


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

Untuk informasi lebih lanjut tentang Cross Origin Request Security, kunjungi http://enable-cors.org/ .


Optimalisasi


Menggunakan Nilai yang Dihitung


Kami menggunakan optimasi untuk beberapa operasi seperti penyesuaian kecerahan, kontras, dan kurva warna melalui perhitungan awal komponen warna yang dihasilkan (r, g, b) untuk semua nilai yang mungkin dan penggunaan lebih lanjut dari array yang diperoleh untuk mengubah warna piksel secara langsung . Perlu dicatat bahwa optimasi semacam ini hanya cocok untuk operasi di mana warna piksel yang dihasilkan tidak terpengaruh oleh piksel yang berdekatan.


Perhitungan komponen warna yang dihasilkan untuk semua nilai yang mungkin:


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

Penggunaan komponen warna yang dihitung sebelumnya:


 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]]; } 

Jika operasi tabel seperti itu berjalan satu per satu, maka tidak perlu menghitung gambar antara - Anda hanya dapat melewati array komponen warna. Karena kode bekerja cukup cepat pada sisi klien dan sisi server, diputuskan untuk mengesampingkan penerapan optimasi ini. Selain itu, optimasi menyebabkan beberapa perilaku yang tidak diinginkan. Namun, saya akan memberi Anda daftar optimasi:


Kode asliKode yang dioptimalkan
`` `cs
// Perhitungan nilai untuk tabel pertama.
untuk (int i = 0; i <256; i ++)
{
r [i] = ActionFunc1R (i);
g [i] = ActionFunc1G (i);
b [i] = ActionFunc1B (i);
}
// ...

// Perhitungan gambar perantara yang dihasilkan.
untuk (int i = 0; i <data.Panjang; i + = 4)
{
data [i] = r [data [i]];
data [i + 1] = g [data [i + 1]];
data [i + 2] = b [data [i + 2]];
}
// ...

// Perhitungan nilai untuk tabel kedua.
untuk (int i = 0; i <256; i ++)
{
r [i] = ActionFunc2R (i);
g [i] = ActionFunc2G (i);
b [i] = ActionFunc2B (i);
}
// ...

// Perhitungan gambar yang dihasilkan.
untuk (int i = 0; i <data.Panjang; i + = 4)
{
data [i] = r [data [i]];
data [i + 1] = g [data [i + 1]];
data [i + 2] = b [data [i + 2]];
}
`` ``

`` `cs
// Perhitungan nilai untuk tabel pertama.
untuk (int i = 0; i <256; i ++)
{
r [i] = ActionFunc1R (i);
g [i] = ActionFunc1G (i);
b [i] = ActionFunc1B (i);
}
// ...

// Perhitungan nilai untuk tabel kedua.
tr = r.Clone ();
tg = g.Clone ();
tb = b.Clone ();
untuk (int i = 0; i <256; i ++)
{
r [i] = tr [ActionFunc2R (i)];
g [i] = tg [ActionFunc2G (i)];
b [i] = tb [ActionFunc2B (i)];
}
// ...

// Perhitungan gambar yang dihasilkan.
untuk (int i = 0; i <data.Panjang; i + = 4)
{
data [i] = r [data [i]];
data [i + 1] = g [data [i + 1]];
data [i + 2] = b [data [i + 2]];
}
`` ``


Tetapi inipun belum semuanya. Jika Anda melihat tabel di sebelah kanan, Anda akan melihat bahwa array baru dibuat menggunakan metode Clone . Sebenarnya, Anda bisa mengubah pointer ke array lama dan baru alih-alih menyalin array itu sendiri (ini mengingat analogi buffering ganda ).


Mengubah Gambar menjadi Array Piksel


Profiler JavaScript di Google Chrome mengungkapkan bahwa fungsi GetImageData (yang digunakan untuk mengubah kanvas ke array piksel) berjalan cukup lama. Informasi ini, omong-omong, dapat ditemukan di berbagai artikel tentang pengoptimalan kanvas dalam JavaScript.


Namun, jumlah panggilan fungsi ini dapat diminimalkan. Yaitu, kita dapat menggunakan array piksel yang sama untuk operasi piksel demi piksel, dengan analogi dengan pengoptimalan sebelumnya.


Contoh Kode


Dalam contoh di bawah ini, saya akan memberikan fragmen kode yang menurut saya menarik dan bermanfaat. Agar artikelnya tidak terlalu panjang, saya telah menyembunyikan contoh di bawah spoiler.


Jenderal


Mendeteksi Apakah Suatu String Adalah Angka


 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 } 

Divisi integer


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

Memutar dan Memutar Gambar Menggunakan Canvas dan Bitmap


Harap dicatat bahwa dalam kanvas html5 gambar dapat diputar 90 dan 180 derajat hanya menggunakan matriks, sementara .NET menyediakan fungsionalitas yang ditingkatkan. Dengan demikian, fungsi presisi yang tepat untuk bekerja dengan piksel ditulis.


Perlu dicatat juga bahwa rotasi sisi 90 derajat dalam versi .NET dapat memberikan hasil yang salah. Karena itu, Anda perlu membuat Bitmap baru setelah menggunakan fungsi RotateFlip .


Kode sumber
 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 } 

Memuat Gambar Sinkron dan Asinkron


Perhatikan bahwa dalam versi Script # kami menentukan fungsi CollageImageLoad yang berbeda, yang dipanggil setelah memuat gambar, sedangkan dalam versi .NET proses ini berlangsung secara bersamaan (dari sistem file atau Internet).


Kode sumber
 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; } 

Hanya Skrip #


Mendeteksi Jenis dan Versi Peramban


Fungsi ini digunakan untuk menentukan kemampuan drag & drop di berbagai browser. Saya sudah mencoba menggunakan modernizr , tetapi mengembalikan Safari itu dan (dalam kasus saya, itu adalah versi Win) IE9 mengimplementasikannya. Dalam praktiknya, peramban ini gagal menerapkan kemampuan seret & lepas dengan benar.


Kode sumber
 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; } } 

Rendering Garis Dash-dot


Kode ini digunakan untuk persegi panjang untuk memotong gambar. Terima kasih atas ide untuk semua orang yang menjawab pertanyaan ini di stackoverflow .


Kode sumber
 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++; } } 

Animasi rotasi


fungsi setInterval digunakan untuk mengimplementasikan animasi rotasi gambar. Perhatikan bahwa gambar hasil dihitung selama animasi sehingga tidak ada jeda pada akhir animasi.


Kode sumber
 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)); } 

Kesimpulan


Artikel ini menjelaskan bagaimana bahasa C # (menggabungkan kode yang tidak dikelola dan kompilasi untuk JavaScript) dapat digunakan untuk membuat solusi lintas platform yang sesungguhnya. Terlepas dari fokus pada .NET dan JavaScript, kompilasi ke Android, iOS (dengan menggunakan Mono), dan Windows Phone juga dimungkinkan berdasarkan pendekatan ini, yang tentu saja memiliki jebakan. Kode ini agak berlebihan karena sifatnya yang universal, tetapi tidak mempengaruhi kinerja karena operasi grafis biasanya memakan waktu lebih lama.

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


All Articles