|
|
# Cvičenie 11
|
|
|
|
|
|
Cieľom dnešného cvičenia bude ukázať si niekoľko funkcií, ktoré umožňuje C# - extension methods, práca s bitmapou a unsafe code.
|
|
|
|
|
|
## 1. extension methods
|
|
|
|
|
|
Čo ak potrebujeme modifikovať triedu, ale nedokážeme ku nej priamo pristupovať? C# umožňuje použiť takzvané extension methods, viac o nich sa môžete dočítať na https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
|
|
|
|
|
|
"Extension methods are defined as static methods but are called by using instance method syntax. Their first parameter specifies which type the method operates on, and the parameter is preceded by the this modifier. Extension methods are only in scope when you explicitly import the namespace into your source code with a using directive."
|
|
|
|
|
|
V rámci daného odkazu si všimnite použitie __this__ v definícií metódy - musí byť ako prvý parameter. Klasicky by tento parameter bol skrytý (definovaný implicitne), ale v tomto prípade ho musíme explicitne uviesť.
|
|
|
|
|
|
Vytvorte si jednoduchú konzolovú aplikáciu a podľa príkladu implementujte extension method pre string, ktorá spočíta výskyt zvoleného písmena v reťazci
|
|
|
|
|
|
`public static int LetterCount(this string str, char c);`
|
|
|
|
|
|
## 2. bitmaps
|
|
|
|
|
|
Vytvorte si nový windows forms projekt. V rámci neho si pridajte triedu `ImageProcessor` a v rámci nej si dajte premennú typu Bitmap.
|
|
|
|
|
|
Existuje niekoľko preťažených konštruktorov pre bitmapu, teraz použijeme ten, čo akceptuje string, teda aj v rámci konštruktora pre ImageProcessor predajte string - cestu k obrázku. Na nájdenie obrázka použite OpenFileDialog (Cv8) vo formulári a cestu odovzdajte do ImageProcessor.
|
|
|
|
|
|
Pridajte si do triedy `ImageProcessor` metódu `public Bitmap GetImage()` nech tento vráti aktuálnu bitmapu.
|
|
|
|
|
|
Niekoľko užitočných metód a vlastností v rámci triedy Bitmap:
|
|
|
|
|
|
`Width, Height, GetPixel(int, int), SetPixel(int, int, Color)`
|
|
|
|
|
|
`Color` je enumeračný typ, ktorý vopred definuje rôzne farby alebo si možeme definovať vlastné pomocou RGB kódu (ARGB v prípade použitia priehľadnosti - dnes to nebudeme potrebovať)
|
|
|
|
|
|
Prístup k jednotlivým farebným kanálom je pomocou vlastností `Color.R, Color.G, Color.B, Color.A`
|
|
|
|
|
|
Vytvorenie novej farby (ktorá nie je v zozname) je pomocou metódy `public static Color FromArgb(int red, int green, int blue);`
|
|
|
|
|
|
## 2.1 Color channels
|
|
|
|
|
|
Zabezpečte, aby ImageProcessor vedel zobrazovať zvolené farebné kanály:
|
|
|
|
|
|
- pridajte si na formulár 3x checkbox pre červenú, zelenú a modrú
|
|
|
- keď sa niektorý checkbox zmení, prekreslite obrázok (používajte 2 bitmapy, neprekresľujte originál, inak by ste ho museli stále načítavať)
|
|
|
- na vytvorenie prázdnej bitmapy viete použiť konštruktor, ktorý akceptuje jej rozmery
|
|
|
- vhodne zvoľte v prípade CheckBox-u event, aby sa automaticky prekreslil obrázok v prípade jeho zmeny, bez nutnosti používať ďalšie tlačidlo
|
|
|
- pridajte si `enum ColorChannel`, ktorý bude definovať, ktorý farebný kanál sa má zmeniť a nech všetky CheckBox-y používajú len jednu metódu (rovnako, ako ste to mali spraviť pri kalkulačke)
|
|
|
- V rámci ImageProcessor si pridajte metódy zodpovedné za úpravu jednotlivých farebných kanálov - použite na to metódy uvedené v predchádzajúcej časti
|
|
|
- potrebujete dva vnorené cykly aby ste prešli všetky pixely, nastavte hodnotu zodpovedajúceho kanálu na 0 ak ho chcete skryť
|
|
|
- výsledok zobrazte pomocou komponentu PictureBox na formulári.
|
|
|
|
|
|
```csharp
|
|
|
pictureBox.Image = bitmap;
|
|
|
```
|
|
|
|
|
|
## 2.2 It takes an eternity
|
|
|
|
|
|
Ak máte väčší obrázok, jeho spracovanie chvíľku trvá (nie je to optimálny spôsob, aj keď ľahko čitateľný)
|
|
|
Upravte metódu, ktorá modifikuje obrázok tak, aby pracovala s vláknami (nech nám to nezablokuje UI vlákno) - vyskúšajte vytvoriť vlákno priamo v `ImageProcessor` a vo formulári. Je nejaký rozdiel?
|
|
|
|
|
|
Pridajte si na formulár ProgressBar, hodnoty `Minimum` a `Maximum` nechajte nezmenené, upravte `Step`, nech sa aktualizuje častejšie. Pridajte si event handler, ktorý po upravení každého riadku obrázku aktualizuje progress bar (`Value`), aby sme videli aktuálny priebeh práce.
|
|
|
|
|
|
## 3. unsafe to the rescue
|
|
|
|
|
|
Existuje ale aj rýchlejší spôsob, ako upravovať bitmapu - stačí si odkryť smerníky.
|
|
|
|
|
|
__Pozor pre použitie unsafe musíte mať povolený prepínač: Project -> Properties -> Build -> Allow unsafe code.__ (tu si môžete definovať aj iné veci, ako napríklad architektúru atď.)
|
|
|
|
|
|
Kľúčové slovo unsafe môžete použiť v rámci deklarácie metódy alebo ako blok kódu: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/unsafe
|
|
|
|
|
|
Príklad práce s bitmapou pomocou smerníkov
|
|
|
|
|
|
```csharp
|
|
|
unsafe
|
|
|
{
|
|
|
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb); //get bitmap data from the corresponding bitmap with appropriate pixel format - 24 bits per pixel in this case, so we can't use transparency (alpha) channel
|
|
|
int stride = bmpData.Stride; //stride defines how many pixels are in a single row
|
|
|
byte* ptr = (byte*)bmpData.Scan0; //pointer to the actual bitmap, watch out for segmentation fault here!
|
|
|
|
|
|
//insert your loops here
|
|
|
|
|
|
//modification of a single pixel
|
|
|
ptr[getR(x, y, stride)] = color.R;
|
|
|
ptr[getG(x, y, stride)] = color.G;
|
|
|
ptr[getB(x, y, stride)] = color.B;
|
|
|
|
|
|
bmp.UnlockBits(bmpData); //allow bitmap to accessible once again
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A samozrejme treba spočítať kde presne v pamäti sa hľadané bytes nachádzajú
|
|
|
|
|
|
```csharp
|
|
|
//calculates pointer offsets for the given colour channel and returns the corresponding colour value (0-255)
|
|
|
private int GetB(int x, int y, int stride)
|
|
|
{
|
|
|
return 3 * x + y * stride;
|
|
|
}
|
|
|
|
|
|
private int GetG(int x, int y, int stride)
|
|
|
{
|
|
|
return 3 * x + y * stride + 1;
|
|
|
}
|
|
|
|
|
|
private int GetR(int x, int y, int stride)
|
|
|
{
|
|
|
return 3 * x + y * stride + 2;
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
(nezabudnite importovať chýbajúce veci!)
|
|
|
|
|
|
V tomto prípade to bude fungovať rýchlo - nie je nutné použiť vlákna.
|
|
|
|
|
|
Odporúčaný spôsob implementácie:
|
|
|
|
|
|
Vytvorte si novú triedu, napríklad `FastImageProcessor` v ktorej budete mať obdobne, ako v predchádzajúcich úlohách kód na manipuláciu obrázku. V tomto prípade ale nebudeme upravovať farebné kanály ako predtým - pridajte si miesto metód na ich úpravu metódy ktoré budú nastavovať bitové masky pre jednotlivé kanály - následne vieme pomocou logickej konjunkcie (and) na bitovej úrovni meniť farby bez toho, aby sme si program komplikovali zbytočnými podmienkami.
|
|
|
|
|
|
Následne v GetBitmap iba spočítate novú bitmapu a tú vrátite.
|
|
|
|
|
|
Pozor, narozdiel od príkladu, budete potrebovať otvoriť súčasne obidve bitmapy (originál a modifikovanú) pomocou unsafe
|
|
|
|
|
|
Príklad bitovej operácie s maskami:
|
|
|
```csharp
|
|
|
byte mask = 0xFF; //255 - 0x is used for hexadecimal notation
|
|
|
byte mask2 = 0x0; //0
|
|
|
byte x = 126;
|
|
|
byte y, z;
|
|
|
|
|
|
y = (byte)(x & mask); // y = 126
|
|
|
z = (byte)(x & mask2); // z = 0
|
|
|
``` |
|
|
\ No newline at end of file |