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

C# Projects

Malá řešení pro velké projekty.
GraphicButton: .NET Compact Framework Control - jak to nefunguje

V minulém příspěvku jsem popisoval tvorbu vlastního ovládacího prvku, 'GraphicButton'. Ve svém obchodním řešení jsem potřeboval mnoho takových ovládacích prvků a když si představíte, že pro každý typ musíte vytvářet tři obrázky (pro každý stav jeden), přestává takové psaní být zábavou a stává se otročinou, při které otevíráte obrázek ve Photoshopu, převádíte jej do černobílé (stav 'disabled'), pak opět otevíráte a zjasňujete barvy a zdůrazňujete světla (stav 'pressed'). Zkrátka zkusil jsem metodu, se kterou bych vás rád seznámil a varoval vás před ní. Tudy skutečně cesta nevede.

Myšlenka byla taková, že předhodím ovládacímu prvku jeden obrázek a on sám si vytvoří stavové ekvivalenty, tedy černobílý pro stav 'disabled' a barevně rozlišený pro stav 'pressed'. Vše se mělo odehrávat ve chvíli, kdy jste vlastnosti Image přiřadili vámi vybraný obrázek.

 

    public Image Image

    {

        get { return _Image; }

        set {               

            _Image = value;

            _DisabledImage = CreateDisabledImage(value, this._TransparentColor);

            _PressedImage = CreatePressedImage(value, this._TransparentColor);

        }

    }

 

Názvy funkcí jasně hovoří pro co která je použita a tak tedy zbývá jen ukázat jak vypadají. Popis hledejte přímo v kódu.

 

   

    private static Image CreateDisabledImage(Image image, Color transparentColor)

    {

        // vytvoření bitmapy z obrázku

        Bitmap disabledBitmap = new Bitmap(image);

 

        // pro každý bod v bitmapě

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

        {

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

            {

                // barva bodu

                Color oldColor = disabledBitmap.GetPixel(x, y);

 

                //pokud se jedná o transparentní barvu, ignoruj bod

                if (oldColor != transparentColor)

                {

                    // zprůměruj hodnoty složek barvy bodu

                    int averageColor = (oldColor.R + oldColor.G + oldColor.B) / 3;

 

                    // vytvoř šedou barvu a nastav barvu bodu v bitmapě

                    Color newColor = Color.FromArgb(averageColor, averageColor, averageColor);

                    disabledBitmap.SetPixel(x, y, newColor);

                }

            }

        }

        // vrať obrázek převedený do odstínů šedé

        return (Image)disabledBitmap;

    }

 

    private static Image CreatePressedImage(Image image, Color transparentColor)

    {

        // vytvoření bitmapy z obrázku

        Bitmap pressedBitmap = new Bitmap(image);

 

        // pro každý bod v bitmapě

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

        {

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

            {

                // barva bodu

                Color oldColor = pressedBitmap.GetPixel(x, y);

 

                //pokud se jedná o transparentní barvu, ignoruj bod

                if (oldColor != transparentColor)

                {

                    // ztmav hodnoty složek barvy bodu

                    int newR = (oldColor.R - 20) < 0 ? 0 : (oldColor.R - 20);

                    int newG = (oldColor.G - 20) < 0 ? 0 : (oldColor.G - 20);

                    int newB = (oldColor.B - 20) < 0 ? 0 : (oldColor.B - 20);

 

                    // vytvoř novou barvu a nastav barvu bodu v bitmapě

                    Color newColor = Color.FromArgb(newR, newG, newB);

                    pressedBitmap.SetPixel(x, y, newColor);

                }

            }

        }

        // vrať obrázek převedený do odstínů šedé

        return (Image)pressedBitmap;

    }

 

Myslel jsem si, že tohle není až tak špatná metoda. V podstatě funguje, jenže... má to jedno velmi podstatné jenže. Každá z těchto metod si na celkem výkonném zařízení DELL Axim X51v vezme okolo šesti vteřin. Určitě se budopu časy lišit dle toho, jak veliké obrázky použijete, ale s obrázky o standardní velikosti ikon 32x32 bodů si každá z nich vzala těch 6 vteřin. Tedy dohromady 12 vteřin!!!

Když si představím, že takových tlačítek použiji ve formuláři třeba pět, tak jen inicializací tlačítek při otevírání formuláře ztratím jednu minutu, což je neúnosné. Tedy závěr zní: 'Tudy cesta nevede!'

Nicméně to nevzdávám a pracuji na řešení, které využije ImageAttributes právě pro vytvoření bitmap pro jednotlivé stavy. Uvidíme, zda se zadaří :)

Mirek

P.S.: tak pomocí ImageAttributes to také nezkoušejte. V .NET Compact Framework není jiná možné apllikovat matrici, takže bohužel, ani tímto způsobem ne. Nezbývá než zůstat prozatím vytváření třech bitmap.

Posted: 17. srpna 2007 1:00 by MirekE
Vedeno pod:

Komentář

vlko napsal:

a neslo by ten obrazok vykreslit cez brush? nacitat ho ako texturu a vykreslit nejakou farbou, teoreticky by to malo byt omnoho rychlejsie ako pixel operacie

# srpna 17, 2007 9:35

MirekE napsal:

Nejsem si jist, jestli je to to správné řešení. Compact Framework podporuje jen jediný Brush a tím je SolidBrush. Nevím jak by se toto dělalo, ale pokud bych si řekl, že například vykreslím obrázek 'disabled' šedou barvou, pak bych u komplikovanějších obrázků s vícero odstíny mohl získat jen šedý flek. Kdyžtak napiš jak bys to chtěl přes Brush řešit. Nejsem si jist že chápu postup.

# srpna 17, 2007 10:29

jachymko napsal:

vlko: takovej brush neexistuej ani ve velkym GDI+, natoz v CF

pruser tady v tom pripade je strasnej overhead volani metod GetPixel a SetPixel. doporucuju se podivat na metodu LockBits, ktera umozni unsafe access primo k bitum bitmapy. Eric Gunnerson mel na blogu nedavno clanek kterej to popisuje. tez tam ma lepsi vyraz na pocitani grayscale barvy, nez je prosty aritmeticky prumer:

http://blogs.msdn.com/ericgu/archive/2007/06/20/lost-column-2-unsafe-image-processing.aspx

# srpna 17, 2007 10:34

vlko napsal:

no s tym brush som to myslel nejak takto:

http://www.java2s.com/Code/CSharp/2D-Graphics/TextureBrushes.htm

ale to bol len napad, ak cf nepodporuje iny ako solid brush, tak toto asi riesenie nie je, ale kazdopadne namiesto takehoto konvertovania by som pouzil nejaky typ brush a prekreslil existujucu bitmapu nim, co sa vlastne rovna nejakemu tomu aplikovaniu filtru

no ale co tak spravit si tool, ktory zoberie nejaky obrazok a vygeneruje dissabled a pressed image len raz? podla mna by to bolo rychlejsie ako to generovat runtime na cf, ktorej vykon je uz takto namahany:)

to jachymko: a predsa existuje: http://msdn2.microsoft.com/En-US/library/aa983677(VS.71).aspx

# srpna 17, 2007 10:43

MirekE napsal:

to vlko: tool v podobě generátoru obrázků mám :) Photoshop kde mám udělaná makra :). Přemýšlel jsem nad tím, že bych tuto funkcionalitu udělal do design modu toho kontrolu. Tedy že by se tyto bitmapy generovaly ve chvíli návrhu a uložili do resources. Je to trochu komplikované, ale mohlo by to jít.

Ale metodu co popisuje jachymkuv odkaz vyzkouším.

# srpna 17, 2007 11:15

golem2 napsal:

Ahoj, vím, že diskuze je .Net Compact Framework, ve kterém jsem ještě pořádně bohužel nedělal, ale co je společné určitě pro pro Compact verzi i obyčejnou verzi je pomalost funkcí GetPixel a SetPixel ..., obzvlášť, když se ty funkce volají 32x32 krát ... v normální verze .Net Frameworku lze toto vyřešit tak, že si bitmapu převedeme na 2D pole Coloru (využijeme zamčení bloku paměti s bitmapou),  provedeme patřičné operace nad tímto polem a pak to zase celé převedeme zpátky, výsledek je mnohem rychlejší ... (nevím teď, jestli jenom 10x nebo i 100x ..., ale je rychlejší ... :]). Metody mohou vypadat třeba takto:

public unsafe static Color[,] BitmapToColorArray(Bitmap bmp)

       {

           Color[,] img = new Color[bmp.Width, bmp.Height];

           BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

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

           {

               // vypocte ukazatel na zacatek y-teho radku

               int* retPos = (int*)((int)data.Scan0 + (y * data.Stride));

               int x = 0;

               while (x < data.Width)

               {

                   // vyplni pixel nahodnou barvou

                   img[x, y] = Color.FromArgb(*retPos);

                   // posun na dalsi pixel

                   retPos++; x++;

               }

           }

           bmp.UnlockBits(data);

           return img;

       }

public unsafe static Bitmap ColorArrayToBitmap(Color[,] pixels)

       {

           Bitmap bmp = new Bitmap(pixels.GetLength(0), pixels.GetLength(1));

           BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

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

           {

               // vypocte ukazatel na zacatek y-teho radku

               int* retPos = (int*)((int)data.Scan0 + (y * data.Stride));

               int x = 0;

               while (x < data.Width)

               {

                   // vyplni pixel nahodnou barvou

                   *retPos = pixels[x, y].ToArgb();

                   // posun na dalsi pixel

                   retPos++; x++;

               }

           }

           bmp.UnlockBits(data);

           return bmp;

       }

Jenom nápad ..., netuším, jestli v Compact verzi jsou všechny potřebné třídy, ale jestli jo, tak to snad někomu pomůže ... :]

# prosince 29, 2008 4:21
Neregistrovaní uživatele nemužou přidávat komentáře.
Vyvojar.cz na prodej!