Staňte se programátorem: Napište si malý Photoshop

Bitmapový editor Paint.NET je napsaný v jazyku C#. Dnes vám ukážeme, jak snadno lze v tomto jazyku napsat základní fotografické efekty. Napište si s námi takový malý Photoshop.

Jedním z nejznámějších desktopových programů napsaných pro Microsoft .NET Framework je bitmapový editor Paint.NET. Ačkoliv se zatím svými možnostmi a schopnostmi nemůže rovnat s vyspělými grafickými editory, je jednou z vlajkových lodí této platformy, většina .NET vývojářů se totiž soustředí především na webové aplikace, ASP.NET aj.

Bitmapový editor s fotografickými efekty I.

Bitmapový editor bez množství nejrůznějších fotografických a kreativních filtrů by v těžké konkurenci nemohl obstát. Dnes vám tedy ukážeme, jak si v jazyku C# napsat filtr, který původní obrázek převede do odstínu šedi, přidá mu historický nádech pomocí sépiového efektu a převede jej do negativu. Kód si pak budete moci vyzkoušet v jednoduchém editoru, který obrázek s efektem bude umět i uložit.

efekty_gui.png  efekty_gui_2.png
Náš jednoduchý editor s třemi bitmapovými efekty

V dnešním projektu se tedy dozvíte, jakým způsobem lze manipulovat s jednotlivými pixely obrázku, jejichž změnou budete moci na bitmapě vytvářet grafické efekty. Nejprve vám vysvětlíme teorii a ukážeme několik jednoduchých grafických efektů. Příští týden pak přijde řada i na složitější algoritmy.

Úprava pixelů bitmapy standartním způsobem

K pixelům bitmapy, která je realizována třídou System.Drawing.Bitmap, můžete přistupovat pomocí metody GetPixel a SetPixel. Metoda GetPixel vrací instanci struktury System.Drawing.Color představující barvu pixelu ležící na určitých souřadnicích X,Y v bitmapě. K nastavení pixelu na určitou souřadnici lze pak použít metodu SetPixel.

Následující metoda nastaví pouze červený filtr bitmapy:

void NastavCervenou(Bitmap b)
{
  for (int x = 0; x< b.Width; x++)
  {
    for (int y = 0; y<b.Height; y++)
    {
      Color vybBarva = b.GetPixel(x,y);
      b.SetPixel(x,y, Color.FromArgb(vybBarva.R,0,0));
    }
  }
}

Kód tedy vlastně u každého pixelu obrázku v barevném modelu RGB ponechá pouze červený kanál a zelený a modrý nastaví na nulu. Jednoduše řečeno, výsledek bude vypadat jako zobrazení pouze červeného kanálu v kdejakém bitmapovém editoru.

Rychlejší úprava pixelů bitmapy pomocí unsafe kódu

Nevýhodou tohoto postupu je jeho pomalý výkon – metody SetPixel a GetPixel jsou časově poměrně náročné. Pro zrychlení manipulace s pixely budete muset pracovat přímo s pamětí pomocí unsafe (nezabezpečeného) kódu.

Například takto by vypadala předchozí metoda NastavCervenou za použití efektivnějšího unsafe kódu:

using System.Drawing.Imaging;
using System.Drawing;

. . .

unsafe Bitmap NastavCervenou(Bitmap b)
{
 
// Uzamkne v paměti data bitmapy tak, aby s nimi bylo možno pracovat.
 
BitmapData bd = b.LockBits(new Rectangle(0,0,b.Width,b.Height),
                             ImageLockMode.ReadWrite,
                             PixelFormat.Format32bppArgb);

  for (int y = 0; y< b.Height; y++)
  {
    // Vytvoří ukazatel na začátek y-té řádky bitmapy
    int* ukzt = (int*)((int)bd.Scan0 + y * bd.Stride);
    for (int x = 0; x <b.Width; x++)
    {
      // Vybere barvu pixelu
      Color c = Color.FromArgb(*ukzt);
      Color cervenyOdstin = Color.FromArgb(c.R,0,0);
      // Nastaví barvu pixelu
      *ukzt = cervenyOdstin.ToArgb();
      // Posune ukazatel na x-tý pixel v dané řádce
      ukzt++;
   
}
  }
 
b.UnlockBits(bd);
  return b;
}

Schválně si všimněte klíčového slova unsafe v deklaraci metody, které říká, že kód v této metodě budou používány ukazatele. Více o ukazatelích se dozvíte třeba na tomto webu.

K tomu, aby bylo možné ve Visual Studiu zkompilovat program obsahující unsafe kód, je nutné tuto vlastnost povolit v menu Project -> Project Options -> Debug.

Pro přístup k pixelum bitmapy je musíte uzamknout v paměti pomocí metody LockBits, která jako argument očekává instanci třídy Rectangle představující část bitmapy, jejíž data mají být uzamknuta, druhým argumentem je položka výčtového typu System.Drawing.Imaging.ImageLockMode, která nám definuje způsob, kterým můžete přistupovat k pixelům bitmapy.

Posledním argumentem je položka výčtového typu System.Drawing.Imaging.PixelFormat určující barevnou hloubku bitmapy. Následně cyklem projdete jednotlivé řádky bitmapy. Pozici prvního pixelu v y-tém řádku v bitmapy vypočítáte za použití vlastnosti Scan0 a Stride třídy BitmapData. Vlastnost Scan0 představuje počáteční pixel bitmapy a vlastnost Stride délku jedné řádky. Pro přesun na další pozici v řádce je nutné zvýšit ukazatel o hodnotu 1. Po dokončení úprav bitmapy, lze její data z paměti uvolnit pomocí metody UnLockBits.

A teď už slíbené fotografické filtry

Převod barev do odstínů šedi

sedy_efekt1.png sedy_efekt_obrazek.png
Převod do odstínů šedi a výsledný obrázek

Algoritmus s unsafe kódem může v C# vypadat třeba takto:

unsafe Bitmap SedaSkala(Bitmap b)
{
  BitmapData bd = b.LockBits(new Rectangle(0,0,b.Width,b.Height),
                            
ImageLockMode.ReadWrite,
                             PixelFormat.Format32bppArgb);

  for (int y = 0; y< b.Height; y++)
  {
    int* ukzt = (int*)((int)bd.Scan0 + y * bd.Stride);
    for (int x = 0; x <b.Width; x++)
    {
      Color c = Color.FromArgb(*ukzt);
      int seda = (int)(0.299 * c.R + 0.587 * c.G + 0.114 * c.B);
      Color sedaBarva = Color.FromArgb(seda,seda,seda);
      *ukzt = sedaBarva.ToArgb();
      ukzt++;
    }
  }
  b.UnlockBits(bd);
  return b;
}

Invertování barev

efekt_negativ_1.png  efekt_negativ2.png  efekt_negativ3.png  efekt_negativ4.png
Negativ v několika podobách, efekty totiž můžete kombinovat, a výsledný obrázek

Algoritmus s unsafe kódem může v C# vypadat třeba takto:

unsafe Bitmap Invert(Bitmap b)
{
  BitmapData bd = b.LockBits(new Rectangle(0,0,b.Width,b.Height),
                                           ImageLockMode.ReadWrite,
                                           PixelFormat.Format32bppArgb);

  for (int y = 0; y< b.Height; y++)
  {
    int* ukzt = (int*)((int)bd.Scan0 + y * bd.Stride);
    for (int x = 0; x <b.Width; x++)
    {
      Color vybrBarva = Color.FromArgb(*ukzt);
      Color invBarva = Color.FromArgb(255-vybrBarva.R,
                                      255-vybrBarva.G,
                                      
255-vybrBarva.B);

      *ukzt = invBarva.ToArgb();
      ukzt++;
    }
  }
  b.UnlockBits(bd);
  return b;
}

Vytvoření sépiového nádechu

efekt_sepia.png  efekt_sepia2.png
Nakonec nesmí chybět ještě sépiový efekt

Algoritmus s unsafe kódem může v C# vypadat třeba takto:

unsafe Bitmap Sepia(Bitmap b)
{
  BitmapData bd = b.LockBits(new Rectangle(0,0,b.Width,b.Height),
                                           ImageLockMode.ReadWrite,
                                           PixelFormat.Format32bppArgb);

  for (int y = 0; y< b.Height; y++)
  {
    int* ukzt = (int*)((int)bd.Scan0 + y * bd.Stride);
    for (int x = 0; x <b.Width; x++)
    {
      Color c = Color.FromArgb(*ukzt);
      int sepia = (int)(0.299 * c.R + 0.587 * c.G + 0.114 * c.B);
      int r = (int)((sepia > 206) ? 255 : sepia + 49);
      int g = (int)((sepia< 14) ? 0 : sepia - 14);
      int bl = (int)((sepia < 56) ? 0 : sepia - 56);
      Color sepiaColor = Color.FromArgb(r,g,bl);
      *ukzt = sepiaColor.ToArgb();
      ukzt++;
    }
  }
  b.UnlockBits(bd);
  return b;
}

Jak na složitější efekty? Přečtěte si příští článek

V tomto článku jsme vám ukázali, jakým způsobem lze manipulovat s pixely bitmapy za použití unsafe kódu. Příště vás seznámíme s pokročilejšími grafickými efekty.

Nakonec nesmí chybět ani slíbený spustitelný program a projekt.  K prvnímu budete potřebovat nainstalovaný novější Microsoft .NET Framework, pakliže si budete chtít projekt upravit a sestavit, budete potřebovat bezplatné vývojové prostředí Microsoft Visual C# 2008 Express Edition.

Máte zajímavý tip na drobnou aplikaci, kterou byste zde chtěli vidět? Neváhejte a napište nám váš nápad do diskuze pod článkem. 

Diskuze (25) Další článek: Rychlost internetu v USA stále zaostává za zbytkem světa

Témata článku: , , , , , , , , , , , , , , , ,