Ú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
