Jak vytvořit radiální alpha efekt v GDI?   zodpovězená otázka

VB.NET, Grafika

Zdravím,

pokud máte zkušenosti s tvorbou webu, možná jste se někdy setkali s alpha filtry, které podporuje MSIE. Rád bych se pokusil napodobit yto filtry v GDI. Doposud se mi to dařilo, zarazil jsem se však na radiálním filtru. Obrázek má být ve středu 100% neprůhledný a postupně do všech stran zprůhledňovat tak, aby v vertikální polovině obrázku na nultém pixelu zleva (i zprava) byl 100% průhledný. Tuším, že budu muset zapojit výpočty s PI apod., ale vůůbec nevím, kde začít.

http://www.jakpsatweb.cz/css/css-filtry-... (samozřejmě jen pro IE)

Děkuji za pomoc

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Stačí vám pythagorova věta, trocha násobení a přemýšlet. Pokud budete tápat, můžu zkusit nakopnout trošku konkrétněji.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Jestli Vám to pomůže, tak zkuste popřemýšlet nad tímto vztahem (jinak to je jenom upravená rovnice elipsy, protože je třeba počítat s tím, že většina obrázků nebude přesně čtvercová, takže to radiální dělení je třeba "zploštit"). Samozřejmě, pokud budete uvažovat pouze čtvercové vstupy, nebo chcete i u obdélníkovývh vstupů zachovat čistě kruhovou transformaci (s tím, že ale průhlednost ve středu delší a kratší strany bude rozdílná), pak se to ještě zjednoduší a buď můžete vyjít z rovnice kružnice, nebo v níže uvedeném vztahu zrušit ve vzorci (B/H)^2.:

Dim obrazek As Bitmap
Dim gama0 As Double                     ' průhlednost gama ve středu obrázku od 0 do 1
Dim gama1 As Double                     ' průhlednost gama v krajním bodě poloos (od 0 do 1)

Dim B As Integer = obrazek.Width / 2    ' hlavni poloosa elipsy (polovina sirky obrazku)
Dim H As Integer = obrazek.Height / 2   ' vedlejsi poloosa elipsy (polovina vysky obrazku)

Dim vzdalenost As Double                ' vzdálenost od středu obrázku (promítnutá po elipse na osu hlavní poloosy)
Dim X, Y As Integer                     ' souřadnice bodu, u kterého počítáme gama
Dim gama As Double                      ' hodnota hledané průhlednosti

' určit vzdálenost vyšetřovaného bodu od středu obrázku
' výsledkem je délka hlavní poloosy elipsy, která prochází
' hledaným bodem a je podobná maximální elipse vepsané do obrázku
vzdalenost = Math.Sqrt((X - B) ^ 2 + (B / H) ^ 2 * (Y - H) ^ 2)

' určíme hodnotu gama pro vypočtenou vzdálenost od počátku
' jedná se o zcela obyčejnou trojčlenku, kdy při vzdalenosti 0
' (bod ve středu obrazku) je hodnota gamy rovna gama0 a při vzdalenosti
' B (na konci hlavní poloosy, tj. na kraji obrázku) je hodnota gamy gama1
gama = gama0 + vzdalenost * (gama1 - gama0) / B

' protože hodnoty gama1 mohou být všelijaké
' a současně i při nastavení této hodnoty na 0 a nebo 1
' je třeba si uvědomit, že se jedná o hodnoty na okraji obrázku
' na středové ose, je jasné, že vzhledem k použitému rozpočítávání
' body vzdálenější, než je délka poloosy, budou mít vypočtenou hodnotu mimo
' reálný rozsah, tudíž je zapotřebí tyto hodnoty mimo rozsah "oříznout": (a takové body se budou nalézat
' v rozích obrázku)
If gama < 0 Then gama = 0
If gama > 1 Then gama = 1

bohužel jsem to neměl jak vyzkoušet tak doufám, že jsem v tom nenasekal v té rychlosti moc chyb

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Omluvte mou nechápavost, ale nějak nemůžu vymyslet, jak ty výpočty zakomponovat do samotného vykreslování.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Promiňte, nezdůraznil jsem, že se jedná pouze o matematický aparát pro určení "průhlednosti na daném místě". Nevím, jak řešíte ostatní filtry - zkuste napsat, jak máte třeba řešen lineární filtr a můžeme to zkusit dát nějak dohromady (netuším, jakým způsobem řešíte filtrování ve své aplikaci, jestli využíváte transformačních matic, zamykáte bitmapu v paměti nebo jak s tím dál pracujete (těch způsobů je opravdu hodně).

Jinak já svým příspěvkem toliko rozvíjel odpověď kolegy, který Vás odkázal na Pythagora a nějaké umocňování, tak jsem Vám ten postup zkonkretizoval.

Napsal jsem to "do programu" jenom z toho důvodu, že mi to připadalo čitelnější, než popisovat matematický vzorec v textu.

Ale opět pro příklad (prakticky to nemá význam, protože by to bylo šíleně pomalé, ale mělo by to fungovat), tak, jak je to napsáno, by se to mělo dát použít tím způsobem, že:

- obrázek si načtete ve formátu, který má pro každý pixel i gama hodnotu

- vše, co jsem napsal (vyjma X, Y,a gama) dáte (pro jednoduchost) jako globální proměnné, nebo to celé uděláte jako třídu a pak z toho uděláte veřejnou vlastnost, kterou na začátku nastavíte

- zbytek kódu dáte do funkce, kde vstupem budou hodnoty X a Y, funkce bude vracet hodnotu gama

- pak projdete (cyklem X a vnořeným cyklem Y) celý obrázek pixel po pixelu a pro každý nastavíte hodnotu gama (vrátí Vám ji na základě konkrétních X a Y ta Vaše funkce)

- no a vykreslíte s akceptováním hodnot gama

Ještě jednou podotýkám, jedná se pouze o matematický aparát a zřejmě touto cestou by to nebylo prakticky použitelné, ale pokud byste třeba použil metodu uzamknutí bitmapy v paměti, pak by rychlost mohla být dostačující a přitom byste postupoval vlastně dost podobně). Ale to už se točím v kruhu, nejlépe bude, napíšete-li, jak řešíte ostatní filtry a zjistíme, dá-li se na to naroubovat i tento postup.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Lineární filtr jsem řešil tak, že jsem si vzal šířku obrázku, dělil ji stem a každý odpovídající sloupce pixelů pak nastavoval pojednom jeho alpha (jak vy říkáte [a určitě lépe než já] gama) hodnotu.

Nevím, jak se "bitmapa uzamyká v paměti" a máte pravdu, bylo to strašně (!!!) pomalé, ale já jsem se již smířil s tím, že to nejde urychlit, otevírá se snad nová možnost?

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

S tím alfa kanálem máte pravdu - soustředil jsem se na něco jiného a tak to dopadlo. Zkusím se na to podívat jako celek a napíšu to, jen to nebude hned. A jenom tak pro zajímavost, jak nastavujete tu alfu pro jednotlivé pixely (jakým příkazem?)

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Beru si pomocí getpixel údaje o barvě a pak pomocí setpixel nastaví novou barvu z color.fromargb(a,r,g,b). Moc Vám děkuji za Vaši snahu pomoci, oceňuji to,

Dim PrevColor As Color = Bitmap.GetPixel(x,y)
Dim NextColor As Color = Color.FromARGB(Alpha, PrevColor.R, PrevColor.G, PrevColor.B)
Bitmap.SetPixel(x,y,NextColor)

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Tak už jsem zase tady. Něco jsem splácal - je to sice trošičku delší, ale snažil jsem se Vám to okomentovat (bez těch komentářů by to zas až tak dlouhé nebylo).

Ta rychlost sice není ani v tomto případě extrémní, ale myslím, že v porovnání s getpixel a setpixel bude nárůst výkonu raketový (zkoušel jsem to na obrázku 1600x1064, a trvalo to něco přes 1,7 s - ale možná by se to dalo ještě nějak zoptimalizovat a taky možná nějakou tu desetinku získáte ještě po kompilaci a spuštění mimo debuger.

Takže by to mohlo vypadat asi takhle:

Public Class Form1



    Private Sub Form1_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown


        ' tak tady jenom načtu obrázek do promenne obrazek
        ' vsechno kolem je zbytecne, jenom jsem v rychlosti nezjistil, jak nacist jpg-cko
        ' do promenne bitmap s tim, aby tato obsahovala i alpha kanal (urcite to jde jednodusejc
        ' ale neresil jsem se s tim, protoze podstatou reseni bylo neco jineho)
        Dim obrazek1 As New Bitmap("d:\obr2.jpg")
        Dim obrazek As New Bitmap(obrazek1.Width, obrazek1.Height, Imaging.PixelFormat.Format32bppArgb)
        Dim g1 As Graphics = Graphics.FromImage(obrazek)
        g1.DrawImageUnscaled(obrazek1, 0, 0)


        ' vytvorim si objekt graphics kam budu kreslit (pro jednoduchost na form)
        Dim g As Graphics = Me.CreateGraphics

        ' nastavim si alphu ve stredu obrazku (gama0) a na kraji (gama1)
        ' muzete si s tim pohrat a dostanete ruzne vysledky
        ' doporucuji vyzkouset i hodnoty mimo rozsah 0-255
        ' v kodu je to dale osetreno a vysledky jsou celkem zajimave
        ' zkouset muzete v obou smerech, i nahoru i do zapornych cisel
        Dim gama0 As Integer = 255
        Dim gama1 As Integer = 0

        'Jenom pomocne mezivypocty, abych toho nemusel pocitat tolik v cyklu
        Dim sirka As Integer = obrazek.Width
        Dim vyska As Integer = obrazek.Height
        Dim pomer As Integer = (sirka / vyska) ^ 2
        Dim B As Integer = sirka / 2
        Dim H As Integer = vyska / 2
        Dim soucin As Double = (gama1 - gama0) / B

        ' obdélník ohraničující bitmapu
        Dim Rect As New Rectangle(0, 0, sirka, vyska)
        ' uzamkneme bitmapu v paměti
        Dim DataObrazku As Imaging.BitmapData = obrazek.LockBits(Rect, Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
        ' ukazatel na uzamčená data (kde začínají
        Dim zacatekBitmapy As IntPtr = DataObrazku.Scan0
        ' celková velikost těchto dat (počet pixelů x 4 byty - pro R,G,B a alphu
        Dim velikostObr As Integer = obrazek.Width * obrazek.Height * 4
        ' deklarujeme pole bytů potřebné velikosti
        Dim ARGB(velikostObr - 1) As Byte
        ' zkopírujeme zamčenou bitmapu do našeho pole
        System.Runtime.InteropServices.Marshal.Copy(zacatekBitmapy, ARGB, 0, velikostObr)

        ' deklarace pomocných proměnných
        Dim gama As Integer
        Dim X, Y As Integer
        Dim vzdalenost As Integer

        ' to jsem tam měl pouze pro optimalizaci - ukáže čas transformace
        Dim cas As DateTime = Now

        ' vlastní cyklus, ve kterém projdeme celé pole bytů
        ' a nastavíme hodnotu gama pro každý bod [X,Y]
        ' dle vzorce, který jsem uváděl včera (toť odpověď na Vaši otázku, kam s tou funkcí
        ' tady si můžete dát libovolnou jinou transformaci (ať barev, nebo průhlednosti)
        ' normálně se to pole bytů prochází postupně (for i=0 to ARGB.getupperbound(0)),
        ' ale protože v této transformaci potřebuji znát konkrétní hodnotu X,Y daného bodu
        ' (kvůli výpočtu), zjistil jsem (zkusmo), že je rychlejší procházet vnořenými cykly
        ' X a Y a z těchto hodnot počítat pozici daného bodu v poli "i", než procházet 
        ' pozice v poli "i" a z nich zpětně počítat X a Y
        For Y = 0 To vyska - 1
            For X = 0 To sirka - 1

                vzdalenost = Math.Sqrt((X - B) ^ 2 + pomer * (Y - H) ^ 2)
                gama = Math.Max(0, Math.Min(255, gama0 + vzdalenost * soucin))
                If gama < 0 Then gama = 0
                If gama > 255 Then gama = 255
                ARGB(4 * (Y * sirka + X) + 3) = gama
            Next
        Next
        ' upravené pole bytů překopírujeme zpět do uzamknuté ho bufferu bitmapy
        System.Runtime.InteropServices.Marshal.Copy(ARGB, 0, zacatekBitmapy, velikostObr)
        ' odemknutím se obsah bufferu překopíruje zpět do objektu bitmapa
        obrazek.UnlockBits(DataObrazku)
        ' a vykreslíme
        g.DrawImageUnscaled(obrazek, 0, 0)

        ' toto je už jen konec testu na čas kvůli optimalizaci
        MsgBox(Now.Subtract(cas).ToString)
    End Sub
End Class

Snad to zvládnete přeluštit. Přeji příjemné laborování - dejte pak vědět, jestli je to to, co jste měl na mysli.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Páni! Je to přesně ono. Ani nevím jak Vám poděkovat.. Moc vám děkuji, že jste si na mně udělal čas. Děkuji

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback