Úkol 07 - Seriózní desktopová aplikace - Mandala
Domácí úkol
Cílem domácího úkolu je naprogramovat seriózní desktopovou aplikaci pro vykreslování mandal.
Část 1 - Vyplňování místo kreslení
Udělejte si kopii programu z hodiny, který uměl kreslit.
Při kliknutí na labObrazek
ale NEkreslete bod
(voláním metody nakresliBod(x, y)
),
místo toho zavolejte metodu vyplnObrazek(x, y)
.
V hodině jsem vám dodal zdrojový text této metody.
Použijte ale prosím tento aktualizovaný zdrojový text:
private void priKliknutiNaLabObrazek(MouseEvent e) { int x = e.getX(); int y = e.getY(); vyplnObrazek(x, y); labObrazek.repaint(); } public void vyplnObrazek(int x, int y) { if (barvaStetce == null) { barvaStetce = new Color(255, 255, 0); } if (x < 0 || x >= obrazek.getWidth() || y < 0 || y >= obrazek.getHeight()) { return; } WritableRaster pixely = obrazek.getRaster(); int[] novyPixel = new int[] {barvaStetce.getRed(), barvaStetce.getGreen(), barvaStetce.getBlue(), barvaStetce.getAlpha()}; int[] staryPixel = new int[] {255, 255, 255, 255}; staryPixel = pixely.getPixel(x, y, staryPixel); // Pokud uz je pocatecni pixel obarven na cilovou barvu, nic nemen if (isEqualRgb(novyPixel, staryPixel)) { return; } // Zamez prebarveni cerne cary int[] cernyPixel = new int[] {0, 0, 0, staryPixel[3]}; if (isEqualRgb(cernyPixel, staryPixel)) { return; } floodLoop(pixely, x, y, novyPixel, staryPixel); } // Recursively fills surrounding pixels of the old color private void floodLoop(WritableRaster raster, int x, int y, int[] newColor, int[] originalColor) { Rectangle bounds = raster.getBounds(); int[] currentColor = new int[] {255, 255, 255, 255}; Deque<Point> stack = new ArrayDeque<>(); stack.push(new Point(x, y)); while (stack.size() > 0) { Point point = stack.pop(); x = point.x; y = point.y; // finds the left side, filling along the way int fillL = x; do { raster.setPixel(fillL, y, newColor); fillL--; } while (fillL >= 0 && isEqualRgb(raster.getPixel(fillL, y, currentColor), originalColor)); fillL++; // find the right right side, filling along the way int fillR = x; do { raster.setPixel(fillR, y, newColor); fillR++; } while (fillR < bounds.width - 1 && isEqualRgb(raster.getPixel(fillR, y, currentColor), originalColor)); fillR--; // checks if applicable up or down for (int i = fillL; i <= fillR; i++) { if (y > 0 && isEqualRgb(raster.getPixel(i, y - 1, currentColor), originalColor)) { stack.add(new Point(i, y - 1)); } if (y < bounds.height - 1 && isEqualRgb(raster.getPixel(i, y + 1, currentColor), originalColor)) { stack.add(new Point(i, y + 1)); } } } } // Returns true if RGB arrays are equivalent, false otherwise private boolean isEqualRgb(int[] color1, int[] color2) { // Could use Arrays.equals(int[], int[]), but this is probably a little faster... return color1[0] == color2[0] && color1[1] == color2[1] && color1[2] == color2[2] && color1[3] == color2[3]; }
Dejte pozor, aby labObrazek
měl nastavené zarovnání horizontalAlignment = LEFT
a verticalAlignment = TOP
.
Tedy aby obrázek mandaly byl vlevo nahoře.
Jinak nebudou souřadnice X
a Y
z událostního objektu
MouseEvent e
správně odpovídat souřadnicím bodů na
BufferedImage obrazek;
a po kliknutí na mandalu se vám vyplní "náhodná oblast".
V hodině jsem ukazoval, jak nahrát obrázek mandaly ze souboru.
Níže je vylepšená verze, která lépe ošetřuje chyby,
nastaví labObrazek
na rozměry obrázku PNG
a zvětší okno,
aby se do něj obrázek vešel.
Metodu můžete zavolat buď s parametrem null:
nahrajObrazek(null);
V tom případě se nahraje zabudovaná mandala.png,
kterou musíte vložit do projektu do složky src/com/example/kresleni
vedle HlavniOkno.java
.
Pokud budete chtít nahrát jiný soubor,
předáte ho jako parametr.
File soubor; soubor = new File("C:\\Users\\Kamil\\Documents\\mandala2.png"); nahrajObrazek(soubor);
Celé znění metody je zde:
private void nahrajObrazek(File soubor) { if (soubor == null) { try { obrazek = ImageIO.read(getClass().getResourceAsStream("mandala.png")); } catch (IOException ex) { throw new ApplicationPublicException(ex, "Nepodařilo se nahrát zabudovaný obrázek mandaly"); } } else { try { obrazek = ImageIO.read(soubor); } catch (IOException ex) { throw new ApplicationPublicException(ex, "Nepodařilo se nahrát obrázek mandaly ze souboru " + soubor.getAbsolutePath()); } } labObrazek.setIcon(new ImageIcon(obrazek)); labObrazek.setPreferredSize(null); setMinimumSize(null); pack(); setMinimumSize(getSize()); }
Ukázkový celý program si můžete stáhnout zde a spustit. Měl by vypadat takto:
Rady na cestu:
Připravte si vlastní sadu barev k vyplňování. Můžete použít Adobe KULER.
V první fázi neřešte načítání a ukládání jiných souborů než oné 1 zabudované mandaly.
V odevzdávárně ve složce Ukol07 je nahrané moje řešení i s zdrojovými texty. Můžete ho využít, když nebudete vědět kudy kam. Ale pokud to půjde bez něj, bude to výrazně lepší.
Část 2 - Distribuce programu
Opravte si soubor
váš_projekt/res/META-INF/MANIFEST.MF
tak, aby správně odkazoval knihovny MigLayout.
Potom začne fungovat výroba distribuční složky
váš_projekt/dist
během překladu (spouštění) aplikace.
Manifest-Version: 1.0 Class-Path: lib/sevecek-net-utils.jar lib/miglayout-core.jar lib/miglayout-swing.jar Main-Class: com.example.mandala.SpousteciTrida
Po opravě zkuste spustit program normálně z IntelliJ IDEA
a potom vykopírovat složku dist
na plochu a spustit buď
Spustit-Windows.bat
nebo
Spustit-Mac.command
přímo na ploše bez IntelliJ IDEA.
Část 3 - Příprava vlastní mandaly
Mandal je na internetu spousta. Najděte nebo si nakreslete jednu vlastní. Mandala akorát musí být striktně černo-bílá. Pouze dvě barvy! Pro příklad vyjděme třeba z této alternativní mandaly: ukol07-mandala2.png Vypadá sice, že je černobílá, ale ve skutečnosti má vyhlazené čáry pomocí stupňů šedi. Tyto stupně šedi je nutné oříznout na striktně černou a bílou. Například v IrfanView je menu Image -> Decrease Color Depth... a zvolte 2 barvy.
Černo-bílá mandala pak vypadá takto (srovnejte s tou výše): ukol07-mandala2-blackwhite.png
Dále je ale nutné opět obrázek mandaly povýšit na plnobarevný obrázek (16 milionů barev neboli 24 bitů na pixel). Proto znovu zvolte Image -> Increase Color Depth... a zvolte 16,7 M barev.
Rozumná velikost mandaly je 640x640 bodů, ale můžete použít i jinak velké obrázky. IrfanView umí obrázky i zvětšovat a zmenšovat. Vyzkoušejte a uvidíte samy.
Nepovinná část 4 - Nahrávání a ukládání obrázků
Bylo by fajn přidat klasické menu a do něj možnost uložit a nahrát libovolný obrázek. Inspirujte se mým vzorovým řešením: Mandala.zip
Zde najdete metodu na uložení obrázku:
private void ulozObrazek(File soubor) { try { ImageIO.write(obrazek, "png", soubor); } catch (IOException ex) { throw new ApplicationPublicException(ex, "Nepodařilo se uložit obrázek mandaly do souboru " + soubor.getAbsolutePath()); } }
Zde je kód, který se uživatele zeptá na jméno souboru klasických ukládacím dialogem:
private void ulozitJako() { JFileChooser dialog; dialog = new JFileChooser("."); int vysledek = dialog.showSaveDialog(this); if (vysledek != JFileChooser.APPROVE_OPTION) { return; } File soubor = dialog.getSelectedFile(); if (!soubor.getName().contains(".") && !soubor.exists()) { soubor = new File(soubor.getParentFile(), soubor.getName() + ".png"); } if (soubor.exists()) { int potvrzeni = JOptionPane.showConfirmDialog(this, "Soubor " + soubor.getName() + " už existuje.\nChcete jej přepsat?", "Přepsat soubor?", JOptionPane.YES_NO_OPTION); if (potvrzeni == JOptionPane.NO_OPTION) { return; } } ulozObrazek(soubor); }
Odevzdání domácího úkolu
Domácí úkol (složky s projekty)
zabalte pomocí 7-Zipu pod jménem Ukol07-Vase_Jmeno.7z
.
(Případně lze použít prostý zip, například na Macu).
Takto vytvořený archív nahrajte na Google Drive
do složky Ukol07
.
Vaši novou mandalu (soubor PNG)
nahrejte pod vaším jménem (Vase_Jmeno_mandala.png
).
do složky Ukol07/Mandaly
na Google Drive.
Vytvořte snímek obrazovky spuštěného programu a pochlubte se s ním v galerii na Facebooku.
Pokud byste chtěli odevzdat revizi úkolu (např. po opravě),
zabalte ji a nahrajte ji na stejný Google Drive znovu,
jen tentokrát se jménem Ukol07-Vase_Jmeno-verze2.7z