Sprite készítő algoritmusok
Elég sűrűn előfordul egy weblapnál, hogy a sok képfájl miatt nyitott sok HTTP kapcsolat lelassítja az oldal betöltődését. Ezen sprite-okkal szoktak segíteni. Ezt úgy kell elképzelni, hogy fogunk egy nagy (jobb szó híján) vásznat, amire feltesszük az összes kis képet, amit az adott oldal be akar tölteni. Ezek után lementjük ezt a vásznat egy képfájlba, az oldal pedig egyetlen HTTP kapcsolattal ezt a fájlt szedi le, és darabolja fel ismét a megadott koordináták szerint css segítségével. A sprite készítést természetesen érdemes automatizálni. Én is ebbe kezdtem bele.
A sprite-ok készítésénél a lényegi kérdés, hogy hogyan helyezzük el egymás mellé a kis képeinket. A matematika nyelvére lefordítva ez annyit tesz, hogy vannak különböző méretű téglalapjaink, és ezeket szeretnénk elrendezni egymás mellett úgy, hogy az eredmény a lehető legközelebb legyen egy téglalaphoz. Tegyük fel, hogy mondjuk nem téglalap alakban rendezzük el a képeinket, hanem mondjuk kör alakban. Az eredmény kicsit kisarkítva, vagy inkább kerekítve valami ilyesmi lesz:
Ez egy 100x100-as kép, szóval az átmérő az 100px. Tehát a kör által a vásznon foglalt terület: d^2*pi/4, ami 7854 px^2. A teljes vászon területe viszont 10 000 px^2. Szóval rengeteg kihasználatlan terület található a sprite-ban, amiben átküldenénk a kör alakban összerendezett képeinket. Emiatt kell, hogy a képek összerendezésével kapott alakzat téglalapra hasonlítson, mert ugye minél jobban hasonlít egy téglalapra, annál kisebb rajta a kihasználatlan terület, és annál kevesebb felesleges adatot küldünk át a sprite-unkkal.
Nyilván nem rakhatunk össze minden egyes sprite-ot kézzel, mert az rengeteg időt elvinne, ezért valamilyen csomagoló algoritmust kell használnunk, ami elvégzi helyettünk a munkát.
Milyen adatokat használhat egy ilyen algoritmus?
A képek, a képcsoportok és a vászon tulajdonságai:
Vannak egyszer a téglalapok tulajdonságai, amelyeket ugye a képekre és a vászonra is meghatározhatunk. A szélesség, a magasság és az átmérő elég egyértelmű, ahogy a terület is.
A képarány, ami egy kicsi többletinformációval bír, hiszen abból meg lehet mondani a téglalap alakját. Ha r<1 (w<h), akkor álló téglalapról, ha r>1, akkor fekvő téglalapról, ha r=1, akkor pedig négyzetről van szó. További érdekesség ezzel kapcsolatban, hogyha olyan csoportokat alkotunk a képekből, amelyek szintén téglalap alakúak, akkor ezekre ugyanúgy használhatóak a fenti tulajdonságok. Így mondjuk négy egyforma nagyságú négyzetet lehet úgy kombinálni, hogy a csoport fekvő vagy álló téglalapot, de akár négyzetet is alkothat.
A csoportok és a vászon tulajdonságai
A hézag a csoport területe és a benne található képek területe közötti különbség, amit százalékban is megadhatjuk a képek összterületéhez viszonyítva. A vászon pedig az a csoport, aminek a minimális hézag méretét keressük. Ezt nem biztos, hogy úgy érjük el, hogy az egyes csoportok hézagmérete a lehető legkisebb. Nagyon sokat számít az, hogy ezeket a csoportokat hogyan tudjuk összeilleszteni. Ez körülbelül ugyanaz, mint amit a tetrisznél is csináltunk.
A legegyszerűbb algoritmus
Csoportokban gondolkodó algoritmust elég nehéz leprogramozni, vannak azonban ennél jóval egyszerűbb megoldások is.
Szinte mindig onnan indulunk, hogy csoportosítjuk az építőelemeinket. Ezt megtehetjük a fenti tulajdonságok szerint.
A legegyszerűbb algoritmus az, ha egymás mellé vagy alá rakjuk a képeinket magasság vagy szélesség szerint sorba rendezve.
Ennél már kicsit bonyolultabb az, ha fogjuk a fenti ábrán látható képeket, és elkezdjük egymásra pakolni úgy, hogy fokozatosan csökkentjük a szélességet, ahogy az az alábbi ábrákon látható.
Ilyen módon fokozatosan csökken a hézag területe egészen az összes kép területének 36%-áig. Ennél jelen esetben kézzel sem tudnánk jobb eredményt kihozni.
Vannak azonban olyan helyzetek, amikor ez az algoritmus nem a legjobb eredményt adja:
Mint látható, itt a normál algoritmussal nem tudunk elérni 17% hézagnál jobb eredményt, azonban kézzel könnyedén ki lehet rakni a hézagmentes változatot.
Az olyan algoritmusok, amelyek erre képesek nyilván már bonyolultabbak. Ha érdekel titeket a téma szívesen várom az ötleteket, hogy mi alapján lehetne ilyen algoritmust fejleszteni, illetve ha találtok neten ilyen algoritmusokat, akkor azok szintén gazdagítanák ennek a leírásnak az értékét.
Amit még lehet csinálni az ötletelésen kívül, hogy kézzel kirakunk egy csomó sprite-ot, lementjük a koordinátákat, illetve a csoportok jellemzőit. Ezek után meg lehet próbálni data mininggal kinyerni az adatokból, hogy az egyes esetekben milyen tulajdonságú csoportok a leghatásosabbak, majd az ebből készült adatbázis felhasználható a sprite-ok készítésére, esetleg új algoritmusok gyártására.
■ A sprite-ok készítésénél a lényegi kérdés, hogy hogyan helyezzük el egymás mellé a kis képeinket. A matematika nyelvére lefordítva ez annyit tesz, hogy vannak különböző méretű téglalapjaink, és ezeket szeretnénk elrendezni egymás mellett úgy, hogy az eredmény a lehető legközelebb legyen egy téglalaphoz. Tegyük fel, hogy mondjuk nem téglalap alakban rendezzük el a képeinket, hanem mondjuk kör alakban. Az eredmény kicsit kisarkítva, vagy inkább kerekítve valami ilyesmi lesz:
Ez egy 100x100-as kép, szóval az átmérő az 100px. Tehát a kör által a vásznon foglalt terület: d^2*pi/4, ami 7854 px^2. A teljes vászon területe viszont 10 000 px^2. Szóval rengeteg kihasználatlan terület található a sprite-ban, amiben átküldenénk a kör alakban összerendezett képeinket. Emiatt kell, hogy a képek összerendezésével kapott alakzat téglalapra hasonlítson, mert ugye minél jobban hasonlít egy téglalapra, annál kisebb rajta a kihasználatlan terület, és annál kevesebb felesleges adatot küldünk át a sprite-unkkal.
Nyilván nem rakhatunk össze minden egyes sprite-ot kézzel, mert az rengeteg időt elvinne, ezért valamilyen csomagoló algoritmust kell használnunk, ami elvégzi helyettünk a munkát.
Milyen adatokat használhat egy ilyen algoritmus?
A képek, a képcsoportok és a vászon tulajdonságai:
- szélesség: w {px}
- magasság: h {px}
- átmérő: d=sqrt(w^2+h^2) {px}
- képarány: r=w/h {}
- terület: A=w*h {px^2}
Vannak egyszer a téglalapok tulajdonságai, amelyeket ugye a képekre és a vászonra is meghatározhatunk. A szélesség, a magasság és az átmérő elég egyértelmű, ahogy a terület is.
A képarány, ami egy kicsi többletinformációval bír, hiszen abból meg lehet mondani a téglalap alakját. Ha r<1 (w<h), akkor álló téglalapról, ha r>1, akkor fekvő téglalapról, ha r=1, akkor pedig négyzetről van szó. További érdekesség ezzel kapcsolatban, hogyha olyan csoportokat alkotunk a képekből, amelyek szintén téglalap alakúak, akkor ezekre ugyanúgy használhatóak a fenti tulajdonságok. Így mondjuk négy egyforma nagyságú négyzetet lehet úgy kombinálni, hogy a csoport fekvő vagy álló téglalapot, de akár négyzetet is alkothat.
A csoportok és a vászon tulajdonságai
- hézag: g=A(csoport)-sum(A(képek)) {px^2}
A hézag a csoport területe és a benne található képek területe közötti különbség, amit százalékban is megadhatjuk a képek összterületéhez viszonyítva. A vászon pedig az a csoport, aminek a minimális hézag méretét keressük. Ezt nem biztos, hogy úgy érjük el, hogy az egyes csoportok hézagmérete a lehető legkisebb. Nagyon sokat számít az, hogy ezeket a csoportokat hogyan tudjuk összeilleszteni. Ez körülbelül ugyanaz, mint amit a tetrisznél is csináltunk.
A legegyszerűbb algoritmus
Csoportokban gondolkodó algoritmust elég nehéz leprogramozni, vannak azonban ennél jóval egyszerűbb megoldások is.
Szinte mindig onnan indulunk, hogy csoportosítjuk az építőelemeinket. Ezt megtehetjük a fenti tulajdonságok szerint.
A legegyszerűbb algoritmus az, ha egymás mellé vagy alá rakjuk a képeinket magasság vagy szélesség szerint sorba rendezve.
Ennél már kicsit bonyolultabb az, ha fogjuk a fenti ábrán látható képeket, és elkezdjük egymásra pakolni úgy, hogy fokozatosan csökkentjük a szélességet, ahogy az az alábbi ábrákon látható.
Ilyen módon fokozatosan csökken a hézag területe egészen az összes kép területének 36%-áig. Ennél jelen esetben kézzel sem tudnánk jobb eredményt kihozni.
Vannak azonban olyan helyzetek, amikor ez az algoritmus nem a legjobb eredményt adja:
Mint látható, itt a normál algoritmussal nem tudunk elérni 17% hézagnál jobb eredményt, azonban kézzel könnyedén ki lehet rakni a hézagmentes változatot.
Az olyan algoritmusok, amelyek erre képesek nyilván már bonyolultabbak. Ha érdekel titeket a téma szívesen várom az ötleteket, hogy mi alapján lehetne ilyen algoritmust fejleszteni, illetve ha találtok neten ilyen algoritmusokat, akkor azok szintén gazdagítanák ennek a leírásnak az értékét.
Amit még lehet csinálni az ötletelésen kívül, hogy kézzel kirakunk egy csomó sprite-ot, lementjük a koordinátákat, illetve a csoportok jellemzőit. Ezek után meg lehet próbálni data mininggal kinyerni az adatokból, hogy az egyes esetekben milyen tulajdonságú csoportok a leghatásosabbak, majd az ebből készült adatbázis felhasználható a sprite-ok készítésére, esetleg új algoritmusok gyártására.
Ismétlődő grafika
Nem teljesen értem a kérdést.
background-repeat
A tapasztalatom szerint a hagyományos háttérnek használt (background-repeat: repeat;) képeket egyszerűen nem lehet sprite-ra tenni. Különösen nem, ha az a doboz, aminek beállítod nem rendelkezik fix dimenziókkal.
Hmm ezzel még nem
tar, vagy background-crop...
Szerintem se. Az igazi megoldás az valami olyan lenne, hogy pl tar fájlba összepakolni a képeket, ekkor csak a képekre hivatkozás lesz szokatlan. A másik ami eszembejutott, hogy oké, vágjunk ki egy kis darabot a színátmenetből, pont ami kell, aztán találjuk ki a background-crop (mivel a crop már foglalt) tulajdonságot(?), majd az ezzel kivágott képpel már azt csinálunk, amit nem szégyellünk. A probléma ezzel csak az, hogy szerintem igen ronda lenne a css, szóval részemről maradnék az előző változatnál, de ha jól emlékszem a resource packages pont ezt csinálja.
Lehet
Optimális algoritmus
Optimal rectangle packing: Initial results
Ez pedig egy C# implementáció.
Mivel azonban a legtöbb esetben a sprite fennmaradó részeit egyszerűen kitölthetjük feketével, és ez simán tömöríthető PNG vagy JPG formátumban a fájlméret szignifikáns növekedése nélkül, ezért valószínűleg nem éri meg a bonyolultabb algoritmusok implementálása, hacsak nem könnyű téli esti ujjgyakorlat gyanánt ;)
Memória
Ez olyan vita, amit talán
Egyébként az algoritmusok kapcsán még eszembe jutott, hogy az ideális elrendezéseket megvizsgálom statisztikai módszerekkel, de a data mining még nem megy annyira, mint szeretném... :D Nem tudom más hogyan csinálja, de nekem egy nap fele annyi dologra sem elég, mint szeretném... :-)
De.
Szerintem az elsődleges cél
Mobil eszközökön nagyon is
Ohh, hát erre nem gondoltam
Sokáig én sem, de az idei web
Hát én is elgondolkodtam,
Ezt a problémát egyébként nevezik bin packing problem-nek és pallet loading problem-nek is, és "combinatorial NP-hard"-nak írják wikipedián. Egyébként elég sok algoritmus van a témában, találtam én is egy párat, de még nem jutottam el odáig, hogy elolvassam a cikkeket, amiket róluk írtak.
Ikonok...
A problémával én is találkoztam, egy webes alkalmazás kapcsán. Mivel egy napi 8 órában használt rendszerről van szó az elvárások a kinézettel kapcsolatban kb. 3 szóval leírhatók: 'handy', 'dizájnos', de ugyanakkor 'átlátható'. Namármost a gyakorlatban ez azt jelenti, hogy egyértelmű ikonok mindenhova... Sokáig kevés volt és IMG-ben, aztán persze CSS-be kerültek, de egyre több lett belőlük, ezért automatizálni kellett.
Én így csináltam:
- Az ikonok vízszintesen egymásmellé kerülnek. (egyszerűség a generálásban)
- Külön fájl készül a különböző ikonoknak: 16-22-32-48-64 (16-22 a leginkább használt, a 32-48 a még nem lett kigyomlálva teljesen, 64px-es a fejlécbe a modul ikonja). Azért jó, mert jelentős eltérés van az ikonok számában, illetve az egymásmellé pakolt méretben. (Vagyis a szélesség x darabszám eltér nagyon)
- Az ikonok megtalálása automatikusan történik a smink-könyvtárakból. Ahány smink, annyi összepakolt CSS-fájl készül, az 'alaprendszer' mindegyikben benne van, plussz még a modulok.
- Az számít ikonnak, amelyik PNG-fájl ÉS négyzet alakú. (Így az esetleges új méretet is automatikusan feldolgozza - bár ha lennének nem négyzet alakú képek, akkor lehetne méretlistából dolgoznia.)
- Az CSS-class elnevezése automatikus: [MODUL]_[FÁJLNÉV]_[MÉRET].
Igazából egy dolgot hiányolok még: sok helyen ugyanazt az ikont használjuk, ezzel a generáláskor most nem foglalkozom, viszont ha ezt is nézné a scriptecske, akkor még -30% lenne kicsi ikonok száma, mérete és ezzel együtt a CSS is :D
Azzal sosem foglalkoztam, hogy esetleg egyetlen fájlba bepakolni az összes ikont, vagy ezeket elrendezni, hogy nem széles, hanem lehetőleg minél inkább négyzet alakú téglalap legyen (darabszámból és az ismert, számítható képméretből ez könyen megoldható). Lenne ez utóbbinak valami előnye? Max. könnyebben átlátható, de méretben nem hiszem, hogy nyernék rajta annyit, amennyit a bele ölt idővel elszúrok...
Várom az észrevételeket :)
Másik probléma...
Egyszer elrendezi optimálisan és ezt használod az alkalmazásban.
Eddig minden ok.
Aztán jön egy új elem, ami mondjuk közepes méretű és berendezi a sprite közepébe.
Esetleg kompletten újrarendezi az algoritmus az egészet.
A régi CSS-sel az oldal szinte garantáltan széthullik vizuálisan.
Szvsz célszerű a sprite generáláshoz kapásból hozzávenni a CSS generálást is, ha az ember ilyenben gondolkozik.
timestamp
src="generatedStyle?20111110140012"
Eddig nem volt vele problémám.
Ugyanez működik az aggregált JS-fájlokra.
(Legalábbis eddig ezt használtam és nem volt vele gond :D )
Ha 'normális' rendszered van, ezt két sor beleírni. Szerintem amúgy a CSS-generálást alapból belevettük, hiszen nincs értelme a képeket átpakolászni, aztán kézzel próbálkozni, mi hova került :)
Mindenképpen
Persze, ez egyértelmű, én is
Kedvenc spritegenerátorom
http://spritegenerator.codeplex.com/
Kár hogy nem fejlesztik tovább, mert még így is nagyságrendekkel jobb, mint bármelyik sprite készítő alkalmazéás.