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)
- pridajte si
- 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.
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
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ú
//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:
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