Adott egy mappában lévő sok-sok képfájl, többféle formátumban, kiterjesztéssel. A feladat az, hogy időzítve jelenítsük meg ezeket a képeket véletlen sorrendben saját fejlesztésű Java program segítségével. A tervezés során áttekintünk többféle lehetőséget. Bemutatjuk a megoldáshoz szükséges lépéseket és a program működését.
A program tervezése
A szükséges bemeneti adatok
- Egy mappa, abszolút vagy relatív útvonal, ahol a képfájlok megtalálhatók. A mappa átvehető a program paramétereként (ha parancssorban meghívva átadjuk) vagy lehet az aktuális mappa (ahonnan a programot
jar fájlként elindítjuk). A program a mappában közvetlenül megtalálható képeket olvassa be. Az ott található almappákba nem megy bele.
- A képfájlok különböző kiterjesztéseit tárolni kell. Többféle is lehet, így ehhez szükséges alkalmas adatszerkezet. A listában nem szereplő kiterjesztéssel rendelkező fájlok nem kerülnek feldolgozásra.
- Érdemes a képfájlokat egy lépésben betölteni a memóriába. Így a program takarékos erőforrásként bánik a tárhellyel (merevlemez, pen-drive, SSD, hálózati meghajtó). Csak egyszer dolgozza fel (olvassa végig) a mappát. Feltételezzük, hogy a képfájlok beférnek a memóriába.
- A program teljes képernyős, amiből elérhető a rendelkezésre álló terület mérete, ahol megjeleníthetők a képek. A program a betöltött képfájlok méreteihez is hozzáfér. Ez a méret kétféle lehet: bájtban kifejezhető a képfájl elfoglalt tárhelye, illetve pixelben kifejezhető a képfájl dimenziója (másképpen a megjelentéséhez szükséges terület mérete a képernyőn).
Hogyan működik a program?
- Egyszerre egy kép jelenik meg. Időzítő befolyásolja a képfájlok közötti váltást. Meghatározza, hogy a képfájlok meddig látszanak (másképpen: eltelt idő, késleltetés, várakoztatás). A swing GUI-hoz tartozó időzítőt kell hozzá használni.
- A program alkalmazkodik a képernyő, kijelző felbontásához, képarányához. A program végtelenítve működik, Alt + F4 billentyűkombinációval lehet kilépni belőle.
- A képfájlok megjelenítésük során optimálisan, dinamikusan kitöltik a rendelkezésre álló téglalap alakú területet. A túl kicsi képeket nagyítani kell. A túl nagy képeket kicsinyíteni kell. Mindezt úgy, hogy a képarányt (aspect ratio) meg kell tartani, hogy a képek ne torzuljanak el. Az alábbi három példa balról-jobbra mutatja az optimális kitöltést, illetve azt a két esetet, ami akkor történik, amikor a kép méretéhez képest a megjelenítésre használható terület túl magas vagy túl széles:
- A galériába tartozó képek közötti véletlen sorrendet meg kell oldani. A program a memóriába betöltött képek sorszámai alapján valósítja meg a véletlenszerű kiválasztást. A sorszámok összekeverednek. Egymás után nem jöhet ugyanaz a kép többször. Ha a képek „elfogynak”, akkor a program végtelenített működése szerint a képek sorszámai újra összekeverednek és „lejátszásra kerülnek”.
A program megvalósítása
A mappát a
java.io csomag
File osztályából létrehozott
folder objektum tárolja (a
"./" szövegliterál jelöli az aktuális mappát). A feldolgozandó képfájlok kiterjesztéseinek listáját egy dinamikus tömbből létrehozott generikus lista oldja meg:
ArrayList<String> imageFileExtensionList=new ArrayList<>(Arrays.asList("JPG", "JPEG", "PNG", "GIF")). Egy képfájl memóriabeli tárolását a
java.awt.image.BufferedImage típus valósítja meg, amelyekből szintén generikus lista épül:
ArrayList<BufferedImage> imageList. A grafikus felhasználói felülethez tartozó
javax.swing csomagbeli
Timer osztály szükséges, például 2 mp-es várakoztatás és eseménykezelés:
timer=new Timer(2000, (ActionEvent) -> { showRandomImage(); }). A GUI
JFrame leszármazott keretobjektum. A grafikus felhasználói felület a teljes képernyőt elfoglalja:
setExtendedState(MAXIMIZED_BOTH) és
setUndecorated(true). A keretre egyetlen
JLabel típusú, fekete hátterű
lbImage objektum kerül, az alapértelmezett határmenti elrendezésmenedzser közepére (vízszintesen és függőlegesen egyaránt). A képfájlok sorszámai (a későbbi véletlen kiválasztáshoz) az
imageIndexList generikus listába/kollekcióba kerülnek. Az
index változó jelöli az aktuális, memóriába betöltött képfájl sorszámát, ami kezdetben nulláról indul.
A képfájlok betöltése az alábbiak szerinti:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
private void createImageList() { File[] imageArray=folder.listFiles((File path) -> { String extension=path.getName().toUpperCase(). substring(path.getName().lastIndexOf(".")+1); return imageFileExtensionList.contains(extension); }); try { for(int i=0; i<imageArray.length; i++) { imageList.add(ImageIO.read(imageArray[i])); imageIndexList.add(i); } Collections.shuffle(imageIndexList); } catch(IOException e) { // } } |
A fájlok kiterjesztésének szűrése a
FileFilter interfész
accept() metódusának megvalósításával történik. A fenti forráskódban mindez tömör, lambda kifejezéssel (művelettel) valósul meg. A fájlszűrőn az képfájl megy át, aminek a nagybetűssé alakított kiterjesztését tartalmazza az
imageFileExtensionList kollekció. Az
i-edik képfájl memóriába való betöltését az
ImageIO osztály statikus
read() függvénye oldja meg. A képfájlok sorszámainak véletlen összekeverése kezdetben megtörténik:
Collections.shuffle(imageIndexList). A fájlkezelés miatt kötelező kivételkezelést most – itt a szakmai blogban – nem részletezzük.
Az időzítő eseménykezelése, a 2 másodpercenkénti képváltás így valósul meg:
|
private void showRandomImage() { BufferedImage img=imageList.get(imageIndexList.get(index)); Dimension d=getScaledDimension(new Dimension( img.getWidth(), img.getHeight()), lbImage.getSize()); ImageIcon imageIcon=new ImageIcon(img.getScaledInstance( (int)d.getWidth(), (int)d.getHeight(), Image.SCALE_SMOOTH)); lbImage.setIcon(imageIcon); index++; if(index==imageIndexList.size()) { Collections.shuffle(imageIndexList); index=0; } } |
A program alábbi metódusa felel a képarányhoz kötődő műveletekért:
|
private Dimension getScaledDimension( Dimension imageSize, Dimension area) { double widthRatio=area.getWidth()/imageSize.getWidth(); double heightRatio=area.getHeight()/imageSize.getHeight(); double ratio=Math.min(widthRatio, heightRatio); return new Dimension((int)(imageSize.width*ratio), (int)(imageSize.height*ratio)); } |
A program tesztelése
- Érdemes lehet tesztelni nem ajánlott (rossz) megoldásként azt, hogy a program az időzítőnek megfelelően, dinamikusan olvasná be a képfájlokat, amivel lényegesen kevesebb memóriát igényelne.
- Van-e reális korlát arra, hogy mennyi, mekkora képek „férnek el” a memóriában?
- Hogyan befolyásolja a képfájlok száma és az általuk elfoglalt tárhely a program indulását?
- Mi történik, ha nincs megfelelő kiterjesztésű képfájl a mappában? És ha több 1000 kép van benne?
- Hogyan jelennek meg (megjelennek-e) az animációt tartalmazó képfájlok? Például a GIF képformátum nem csak statikus egyetlen képet tartalmazhat, hanem lehet animált is.
- Teljesen megvalósul-e a reszponzivitás? Ha igen, mi indokolja? Ha nem, miért nem és hogyan lehetne megoldani?
Ha átmenetileg kikapcsoljuk a teljes képernyős megjelenítést, akkor könnyen tesztelhetővé válik a megvalósuló reszponzivitás. Másképpen a program dinamikusan alkalmazkodik a rendelkezésre álló (rajzolható) terület méreteihez (szélesség és magasság):
A program továbbfejlesztési lehetőségei
- A program rekurzívan bejárhatná a
folder által megjegyzett útvonalból kiindulva a teljes (al)mappaszerkezetet.
- A program paraméterezhető lehetne a képfájlok kiterjesztéseivel. Akár konfigurációs fájlból is beolvashatná az
imageFileExtensionList adatszerkezetet, például XML, JSON formátumban is.
- A program ellenőrizhetné, hogy a mappában lévő összes kép befér-e a memóriába. A program kezelhetne ehhez kötődően többféle limitet: például az első 100 db képet töltené be, és/vagy csak annyi képet tölt be, ami belefér például 64 MB-ba.
- A program mutathatná folyamatindikátorral induláskor a képfájlok betöltését. Vagy betölthetné például az első 5 db-ot és háttérszálon a többit, amíg az első 5-öt „lejátssza”.
- Ha például a program 10 képet tölt be mappában lévő képfájlokból, akkor ezek 0-tól 9-ig sorszámozódnak. A sorszámok összekeverve következnek. Ha az első menetben az utolsó kép sorszáma például a 7 volt, akkor a következő ismétlődő menet nem kezdődhetne 7-tel.
- A programból ki lehetne lépni az Esc billentyűvel is.
KeyListener interfésszel megoldható.
- A program kezelhetne egyéb képfájlformátumokat is: például animált GIF, statikus WebP, animált WebP.
- A program könnyen kiegészíthető prezentációk diáinak időzített/felülbírált megjelenítésére.
- A program által beolvasott képfájlokból generálható PDF fájl is (rácsos sablonnal, például 6 db kép laponként). A feldolgoztt mappában lévő képfájlok könnyen feltölthetők FTP szerver adott mappájába, átméretezhetőek csoportosan, elküldhetők nyomtatási sorba is.
- Érdemes megismerni a JDK-n kívüli egyéb, képfájlokat kezelő osztályok, csomagok funkcióit, például: OpenIMAJ, TwelveMonkeys ImageIO.
- A swing-es felület kiegészíthető mappatallózással, egyéni fájl(típus)szűrőkkel, paraméterezhető lehet a véletlenszerű kiválasztás algoritmusa, változtatható az időzítés késleltetése.
- Mivel a program teljes képernyős, így elrejthető az egérmutató.
- A képek „lejátszásából” lehetne generálni animált GIF-et.
A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.
A Java SE szoftverfejlesztő tanfolyamunkon, a szakmai modul Objektumorientált programozás témakörét követő 29-36. óra Grafikus felhasználói felület alkalmain már tudunk egyszerűbb animációs, szimulációs programot tervezni, kódolni, tesztelni.