diff --git a/Image.java b/Image.java index 3be578c..71b33de 100644 --- a/Image.java +++ b/Image.java @@ -33,14 +33,29 @@ public class Image return points; } + /** + * Liest eine JPG-Bilddatei ein und gibt diese als ArrayList von RGB-Objekten zurück + * + * @param filename Dateiname die eingelesen werden soll + * @return BufferedImage + * @throws Exception + */ + public static BufferedImage loadFile(String filename) throws Exception { + ArrayList<RGB> points = new ArrayList<>(); + + File file= new File(filename); + return ImageIO.read(file); + } + /** * Schreibt die RGB-Daten in eine Datei * * @param input Dateiname der ursprünglichen Datei (wird für Dimensionen gebraucht) * @param output Dateiname der neuen Datei * @param data RGB-Daten der einzelnen Pixel + * @return BufferedImage */ - public static void writeFile(String input, String output, ArrayList<RGB> data) throws Exception { + public static BufferedImage writeFile(String input, String output, ArrayList<RGB> data) throws Exception { File file = new File(input); BufferedImage img = ImageIO.read(file); @@ -54,8 +69,12 @@ public class Image } } - File f = new File(output); - ImageIO.write(img, "jpg", f); + if (output != "") { + File f = new File(output); + ImageIO.write(img, "jpg", f); + } + + return img; } /** @@ -66,8 +85,9 @@ public class Image * @param output Dateiname der neuen Datei * @param data RGB-Daten der einzelnen Pixel * @param quants Quantifizierungs-Punkte + * @return BufferedImage */ - public static void writeFile(String input, String target, ArrayList<RGB> data, ArrayList<RGB> quants) throws Exception { + public static BufferedImage writeFile(String input, String target, ArrayList<RGB> data, ArrayList<RGB> quants) throws Exception { ArrayList<RGB> q = new ArrayList<>(); // Mappe punkte auf quantifizierte Punkte @@ -76,6 +96,6 @@ public class Image } // schreibe Datei - writeFile(input, target, q); + return writeFile(input, target, q); } } diff --git a/OGL_Window.java b/OGL_Window.java new file mode 100644 index 0000000..8ea719e --- /dev/null +++ b/OGL_Window.java @@ -0,0 +1,399 @@ +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.glu.GLU; +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.awt.GLCanvas; + +import javax.swing.JFrame; +import java.awt.Color; +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; + +import java.util.ArrayList; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.io.File; +import java.util.Collections; + +import java.awt.image.BufferedImage; + +public class OGL_Window implements GLEventListener, KeyListener, ActionListener { + private int imgHeight = 200; + + private JFrame frame; + private JPanel drawPanel; + private JPanel drawImg; + private JPanel drawQImg; + + private JButton vl; + private JButton vr; + private JButton vu; + private JButton vd; + + private JButton b_load; + private JButton b_gen; + private JButton b_epoc; + private JButton b_reset; + private JButton b_save; + + private JComboBox s_input; + private JTextField t_anz; + private JTextField t_output; + + private final GLCanvas glcanvas; + + private ArrayList<RGB> image; + private ArrayList<RGB> orig; + private ArrayList<RGB> quants; + + private BufferedImage img; + private BufferedImage qimg; + + private int azimut = 25; + private int rot = 45; + + @Override + public void display(GLAutoDrawable drawable) { + final GL2 gl = drawable.getGL().getGL2(); + + gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_STENCIL_BUFFER_BIT); + + final GLU glu = GLU.createGLU(gl); + gl.glMatrixMode(GL2.GL_PROJECTION); + gl.glLoadIdentity(); + double w = this.drawPanel.getWidth(); + double h = this.drawPanel.getHeight(); + glu.gluPerspective(45,w/h,0.1,100); + gl.glMatrixMode(GL2.GL_MODELVIEW); + gl.glLoadIdentity(); + glu.gluLookAt( + 0,0,3, + 0,0,0, + 0,1,0 + ); + gl.glRotatef(this.azimut,1,0,0); + gl.glRotatef(-this.rot,0,1,0); + + gl.glLineWidth(5); + gl.glBegin(GL2.GL_LINES); + + gl.glColor4f(0,0,0,1); + gl.glVertex3f(0,0,0); + gl.glColor4f(1,0,0,1); + gl.glVertex3f(1,0,0); + + gl.glColor4f(0,0,0,1); + gl.glVertex3f(0,0,0); + gl.glColor4f(0,1,0,1); + gl.glVertex3f(0,1,0); + + gl.glColor4f(0,0,0,1); + gl.glVertex3f(0,0,0); + gl.glColor4f(0,0,1,1); + gl.glVertex3f(0,0,1); + gl.glEnd(); + + gl.glEnable(GL2.GL_BLEND); + gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); + + gl.glPointSize(8); + gl.glBegin(GL2.GL_POINTS); + for (RGB p : this.image) { + gl.glColor4f(((float)(p.r))/256/4,((float)(p.g))/256/4,((float)(p.b))/256/4,0.15f); + gl.glVertex3f( + ((float)(p.r))/256, + ((float)(p.g))/256, + ((float)(p.b))/256 + ); + } + gl.glEnd(); + + gl.glPointSize(20); + gl.glBegin(GL2.GL_POINTS); + for (RGB p : this.quants) { + gl.glColor4f(((float)(p.r))/256,((float)(p.g))/256,((float)(p.b))/256,1f); + gl.glVertex3f( + ((float)(p.r))/256, + ((float)(p.g))/256, + ((float)(p.b))/256 + ); + } + gl.glEnd(); + + gl.glPointSize(20); + gl.glBegin(GL2.GL_POINTS); + gl.glColor4ub((byte)0,(byte)0,(byte)0,(byte)255); + gl.glVertex3f(0,0,0); + gl.glEnd(); + } + + @Override + public void dispose(GLAutoDrawable arg0) {} + + @Override + public void init(GLAutoDrawable drawable) {} + + @Override + public void reshape(GLAutoDrawable drawable, int arg1, int arg2, int arg3, int arg4) {} + + @Override + public void keyPressed(KeyEvent e) {} + + @Override + public void keyTyped(KeyEvent e) {} + + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_LEFT) { + this.rot += 15; + } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { + this.rot -= 15; + } else if (e.getKeyCode() == KeyEvent.VK_UP) { + this.azimut -= 15; + } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { + this.azimut += 15; + } else { + return; + } + + this.glcanvas.repaint(); + } + + private OGL_Window() { + this.image = new ArrayList<>(); + this.orig = new ArrayList<>(); + this.quants = new ArrayList<>(); + + //getting the capabilities object of GL2 profile + final GLProfile profile = GLProfile.get(GLProfile.GL2); + GLCapabilities capabilities = new GLCapabilities(profile); + capabilities.setBackgroundOpaque(false); + + // The canvas + glcanvas = new GLCanvas(capabilities); + glcanvas.addGLEventListener(this); + glcanvas.setSize(500, 500); + glcanvas.setBackground(new Color(220, 220, 220)); + glcanvas.addKeyListener(this); + + OGL_Window that = this; + + //creating frame + frame = new JFrame("RGB Quantisierer"); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + frame.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + int width = (int)that.frame.getSize().getWidth(); + int height = (int)that.frame.getSize().getHeight() - 160 - that.imgHeight; + that.glcanvas.setSize(width, height); + that.drawPanel.setSize(new Dimension(width, height)); + } + }); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + + drawPanel = new JPanel(); + JPanel view = new JPanel(); + JPanel steps = new JPanel(); + JPanel config = new JPanel(); + JPanel images = new JPanel(); + + mainPanel.add(drawPanel); + mainPanel.add(view); + mainPanel.add(steps); + mainPanel.add(config); + mainPanel.add(images); + + vl = new JButton("<"); + vl.addActionListener(this); + view.add(vl); + vr = new JButton(">"); + vr.addActionListener(this); + view.add(vr); + vu = new JButton("^"); + vu.addActionListener(this); + view.add(vu); + vd = new JButton("v"); + vd.addActionListener(this); + view.add(vd); + + b_load = new JButton("Datei laden"); + b_load.addActionListener(this); + steps.add(b_load); + + b_gen = new JButton("Zufällige Punkte erzeugen"); + b_gen.addActionListener(this); + steps.add(b_gen); + + b_epoc = new JButton("Epoche durchspielen"); + b_epoc.addActionListener(this); + steps.add(b_epoc); + + b_reset = new JButton("Punkte versetzen"); + b_reset.addActionListener(this); + steps.add(b_reset); + + b_save = new JButton("Datei speichern"); + b_save.addActionListener(this); + steps.add(b_save); + + config.add(new JLabel("Input:")); + s_input = new JComboBox(getJPGs()); + config.add(s_input); + + config.add(new JLabel("Anzahl Punkte:")); + t_anz = new JTextField("32"); + t_anz.setPreferredSize(new Dimension(100,20)); + config.add(t_anz); + + config.add(new JLabel("Output:")); + t_output = new JTextField("output.jpg"); + t_output.setPreferredSize(new Dimension(100,20)); + config.add(t_output); + + drawImg = new JPanel(); + drawImg.setPreferredSize(new Dimension(this.imgHeight + 50,this.imgHeight + 10)); + drawQImg = new JPanel(); + drawQImg.setPreferredSize(new Dimension(this.imgHeight + 50,this.imgHeight + 10)); + images.add(drawImg); + images.add(drawQImg); + + frame.add(mainPanel); + drawPanel.add(glcanvas); + + frame.pack(); + frame.setVisible(true); + } + + /** + * Button-Reaktivität + * + * @param e ActionEvent + */ + public void actionPerformed(ActionEvent e) { + /** + * Pfeilbuttons: Ändern der Ansicht + */ + if (e.getSource() == this.vr) this.rot = (this.rot + 345) % 360; + if (e.getSource() == this.vl) this.rot = (this.rot + 15) % 360; + if (e.getSource() == this.vu) this.azimut = (this.azimut + 345) % 360; + if (e.getSource() == this.vd) this.azimut = (this.azimut + 15) % 360; + + /** + * Datei laden und RGB-Daten in ArrayList speichern + */ + if (e.getSource() == this.b_load) { + try { + this.orig = Image.readFile("./inputs/" + (String)this.s_input.getSelectedItem()); + this.img = Image.loadFile("./inputs/" + (String)this.s_input.getSelectedItem()); + this.image = new ArrayList<>(); + for (RGB p : this.orig) { + // kopiere die originalen Daten, damit die Liste später gemischt werden kann + this.image.add(p); + } + + this.drawImage(this.img, this.drawImg); + } catch (Exception ex) {} + } + /** + * Zufällige Punkte erzeugen + */ + if (e.getSource() == this.b_gen) { + this.quants = new ArrayList<>(); + for (int i = 0; i < Integer.parseInt(this.t_anz.getText()); i++) { + this.quants.add(new RGB(true)); + } + + try { + BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants); + this.drawImage(i, this.drawQImg); + } catch (Exception ex) {} + } + /** + * Epoche durchlaufen + */ + if (e.getSource() == this.b_epoc) { + Quant.doEpoch(this.image, this.quants); + + try { + BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants); + this.drawImage(i, this.drawQImg); + } catch (Exception ex) {} + } + /** + * Nicht bewegte Punkte versetzen + */ + if (e.getSource() == this.b_reset) { + Quant.resetQuants(this.quants); + + try { + BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants); + this.drawImage(i, this.drawQImg); + } catch (Exception ex) {} + } + /** + * quantifiziertes Bild speichern + */ + if (e.getSource() == this.b_save) { + try { + Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "./outputs/" + this.t_output.getText(), this.orig, this.quants); + } catch (Exception ex) {} + } + + // neue Anzeige rendern + this.glcanvas.repaint(); + } + + private void drawImage(BufferedImage i, JPanel p) { + int w = i.getWidth(); + int h = i.getHeight(); + + if (w > h) { + h = h * this.imgHeight / w; + w = this.imgHeight; + } else { + w = w * this.imgHeight / h; + h = this.imgHeight; + } + + JLabel img = new JLabel(new ImageIcon(i.getScaledInstance(w,h,1))); + img.setPreferredSize(new Dimension(this.imgHeight, this.imgHeight)); + p.removeAll(); + p.add(img); + p.revalidate(); + } + + /** + * Lies die vorhandenen JPG-Dateien aus dem Ordner inputs aus + * + * @return String[] Dateinamen + */ + private String[] getJPGs() { + ArrayList<String> result = new ArrayList<String>(); + + File d = new File("./inputs/"); + File[] list = d.listFiles(); + for(int i=0; i<list.length; i++) { + String n = list[i].getName(); + // nur JPG-Bilder + if (n.matches(".*\\.[jJ][pP][eE]?[gG]$")) { + result.add(n); + } + } + + String[] ret = new String[result.size()]; + return result.toArray(ret); + } + + public static void main() throws Exception { + new OGL_Window(); + } +} \ No newline at end of file diff --git a/README.TXT b/README.TXT index 2a754b7..bb81d23 100644 --- a/README.TXT +++ b/README.TXT @@ -4,16 +4,19 @@ PROJEKTZWECK: Liest ein Bild aus einer JPG-Datei ein und stellt die Anschließend werden zufallsverteilte Punkte den Originaldaten angenähert um abschließend das Bild auf diese Farben reduziert wieder abzuspeichern. -VERSION oder DATUM: 1.0.1 (26.10.2023) -WIE IST DAS PROJEKT ZU STARTEN: Instanz der Klasse Window anlegen +VERSION oder DATUM: 1.1 (27.10.2023) +WIE IST DAS PROJEKT ZU STARTEN: OGL_Window -> main()-Methode starten AUTOR: Alexander Kimmig (a.kimmig@dhg-rw.de) BENUTZERHINWEISE: Die ChartDirector_s.jar muss in den BlueJ-Einstellungen als externe Benutzerbibliothek eingebunden werden. (Quelle: https://www.advsofteng.com/doc/cdjava.htm) CHANGELOG: +* Version 1.1 (27.10.2023) + * umgestellt auf OpenGL, manuelles Rendering + * * Version 1.0.1 (26.10.2023) - * Möglichkeit, das Fenster in kleinerer Auflösung anzuzeigen - * Möglichkeit, die Punktwolke farbig anzuzeigen - -> Achtung: sehr langsam, nur mit kleinem Bild möglich! + * Möglichkeit, das Fenster in kleinerer Auflösung anzuzeigen + * Möglichkeit, die Punktwolke farbig anzuzeigen + -> Achtung: sehr langsam, nur mit kleinem Bild möglich! * Version 1.0 (20.10.2023) \ No newline at end of file diff --git a/Window.java b/Window.java index 8655e47..5b1718b 100644 --- a/Window.java +++ b/Window.java @@ -1,3 +1,7 @@ +/** + * BITTE NICHT MEHR NUTZEN -> stattdessen OGL_Window verwenden! + */ + import ChartDirector.*; import javax.swing.*; import java.awt.*; diff --git a/inputs/gras_gross.jpg b/inputs/gras_gross.jpg new file mode 100644 index 0000000..721b8ef Binary files /dev/null and b/inputs/gras_gross.jpg differ diff --git a/outputs/output4.jpg b/outputs/output4.jpg new file mode 100644 index 0000000..c29966c Binary files /dev/null and b/outputs/output4.jpg differ