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 :-).