Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc
Titulní Blogy Fotky Soubory

Tomášův blog ..

((.)$(.)) The owl has type (a->b ->c)->a->(a1->b)->a1->c

Vytváření černobílé (stupně šedi) bitmapy z barevné

Dnes jsem na Builder.cz narazil na problém jak vytvořit z bitmapy vytvořit černobílou (tedy s odstíny šedi). Samotné GDI+ na to funkci nemá, takže je potřeba napsat si funkci vlastní. Úplně nejjednodušší řešení je pomocí funkcí GetPixel a SetPixel projít celou bitmapu a spočítat barvu pixelů. Toto řešení je sice velmi snadné napsat, ale jeho výkonnost je velmi špatná (tento přístup k datům bitmapy je prostě strašně pomalý). Pokud vám ale nevadí použití unsafe kódu, tak můžete použít metodu LockBits a pomocí ukazatelů si pěkně zašedivět bitmapu v unsafe kódu, což je přibližně desetkrát rychlejší.

Ještě jedna menší poznámka k počítání odstínu šedi k dané barvě. Nejjednodušší je spočítat aritmetický průměr všech tří složek, ale protože lidské oko je citlivější na zelenou barvu, tak mnohem lépe vypadají černobílé obrázky, kde se počítá vážený průměr. Barva se tedy počítá podle vzorce: V = 0.11*R + 0.59*G + 0.30*B.

Takže zde je menší ukázka jak používat unsafe kód k vytváření černobílého obrázku z barevného:

/// <summary>Vytvori cernobilou kopii bitmapy</summary>
/// <param name="bmp">Vstupni bitmapa</param>
/// <returns>Cernobila bitmapa</returns>
private unsafe Bitmap CreateGrayscaleBitmap(Bitmap bmp)
{
  // Vytvori novou bitmapu, do ktere se bude ukladat cernobila
  Bitmap ret=new Bitmap(bmp.Width,bmp.Height);
 
  // zamkne data bitmapy, takze k nim lze primo pristupovat
  BitmapData data=bmp.LockBits(
     new Rectangle(0,0,bmp.Width,bmp.Height),
    ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb);
   BitmapData retData=ret.LockBits(
     new Rectangle(0,0,bmp.Width,bmp.Height),
    ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);
 
  // prochazi postupne po radcich
  for(int y=0; y<data.Height; y++)
  {
    // vypocita pozice, kde zacinaji data daneho radku
    int* pos=(int*)((int)data.Scan0+(y*data.Stride));
    int* retPos=(int*)((int)retData.Scan0+(y*retData.Stride));
 
    int x=data.Width;
    while(x-->0)
    {
      // vypocitat cernobilou barvu
      Color c=Color.FromArgb(*pos);
      int nc=(11*c.R+59*c.G+30*c.B)/100;
      *retPos=Color.FromArgb(c.A,nc,nc,nc).ToArgb();
      pos++; retPos++;
    }
  }
 
  // uvolni data bitmapy
  bmp.UnlockBits(data);
  ret.UnlockBits(retData);
  return ret;
}

Na závěr ještě k výkonnosti: Funkci jsem zkoušel (včetně alokace nové bitmapy) na fotce o velikosti 1600x1200 a pomocí GetPixel&SetPixel metody trvalo převedení přibližně 4600ms. Pomocí výše uvedené funkce s přímým přístupem do paměti pomocí unsafe kódu to trvalo pouze 470ms (používání váženého, nebo normálního průměru nemá na rychlost výrazný vliv - přibližně +/-50ms).

PS: Tak teď už opravdu hezké vánoce :-).

Zveřejněno 22. prosince 2004 23:31 by tomas
Vedeno pod:

Upozornění na nové komentáře

Pokud chčeš dostávat upozornění emailem na změny u toho příspěvku,tak se zaregistruj zde.zde

Odebírat komentáře k tomuto příspěvku pomocí RSS

Komentář

 

Petr napsal:

Velmi užitečná funkce, díky
prosince 23, 2004 15:03
 

Michal napsal:

Pokud tato problematika nekoho zaujala, doporucuji serii clanku "Image Processing for Dummies" na CodeProjectu od Christiana Grauseho:
http://www.codeproject.com/script/Articles/list_articles.asp?userid=6556

- per pixel filtery
- Convolution Filtery
- detekce hran
- displacement filtery, ohybani obrazu
- zmeny velikosti

velmi dobre napsane, velmi dobre funkcni
prosince 24, 2004 0:39
 

splite napsal:

Máš tu drobnou chybu...

Ve vzorci pro přepočet je prohozené RED a BLUE...

Shodou okolností GDI+ vrací BGR a ne RGB (vyzkoušej vynulovat druhej a třetí byte a uvidíš co to udělá :) ), takže se tím zruší tvé prohození Red a Blue, ale tím vzorcem co píšeš nahoře náš stejně jen mateš :)

Jinak JEŠTĚ přesnější výpočet by byl takhle:

.299 * red + .587 * green + .114 * blue

což v c# vypadá nějak takhle:

public static bool GrayScale(Bitmap b) {

 BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

 int stride = bmData.Stride;

 System.IntPtr Scan0 = bmData.Scan0;

 unsafe {

   byte * p = (byte *)(void *)Scan0;

   int nOffset = stride - b.Width*3;

   byte red, green, blue;

   for(int y=0;y<b.Height;++y){

     for(int x=0; x < b.Width; ++x ){

       // GDI+ vrací BGR a ne RGB, lžou nám!

       blue = p[0];

       green = p[1];

       red = p[2];

       p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue);

       p += 3;

       }

     p += nOffset;

     }

   }

 b.UnlockBits(bmData);

 return true;

}

(snad to komentář moc nerozhasí... Pracujeme přímo na Bitmapě, pokud chceš nějaké Undo, tak Bitmapa obsahuje metodu .Clone() :) )

března 14, 2009 13:10
 

splite napsal:

Ou, až teď jsem si všiml že to je 5 let starý spot :D

No nic, tak až sem někoho zavede google při hledání GrayScale tak bude vědět...

března 14, 2009 13:12

Vytvoření nového komentáře

(povinný) 
(nepovinný)
(povinný) 
Opiš čísla, která vidíš na obrázku:
Odeslat
Powered by Community Server (Personal Edition), by Telligent Systems