/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jenetics.example.image;
import static java.lang.String.format;
import static javax.swing.SwingUtilities.invokeLater;
import static org.jenetics.example.image.EvolvingImagesCmd.writeImage;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.jenetics.Genotype;
import org.jenetics.engine.EvolutionResult;
/**
* This example shows a more advanced use of a genetic algorithm: approximate a
* raster image with ~100 semi-transparent polygons of length 6.
* <p>
* The fitness function is quite simple yet expensive to compute:
* <ul>
* <li>draw the polygons of a chromosome to an image
* <li>compare each pixel with the corresponding reference image
* </ul>
* <p>
* To improve the speed of the calculation, we calculate the fitness not on the
* original image size, but rather on a scaled down version, which is sufficient
* to demonstrate the power of such a genetic algorithm.
*
* @see <a href="http://www.nihilogic.dk/labs/evolving-images/">
* Evolving Images with JavaScript and canvas (Nihilogic)</a>
*/
public final class EvolvingImages extends JFrame {
// Additional Swing components.
private final NumberFormat _fitnessFormat = NumberFormat.getNumberInstance();
private final ImagePanel _imagePanel;
private final PolygonPanel _polygonPanel;
private volatile EvolvingImagesWorker _worker;
/**
* Creates new form ImageEvolution
*/
public EvolvingImages() {
_imagePanel = new ImagePanel();
_polygonPanel = new PolygonPanel();
initComponents();
init();
}
private void init() {
setIconImage(
Toolkit.getDefaultToolkit().getImage(getClass().getResource(
"/org/jenetics/example/image/monalisa.png"))
);
origImagePanel.add(_imagePanel);
polygonImagePanel.add(_polygonPanel);
engineParamPanel.setEngineParam(engineParam());
_fitnessFormat.setMaximumIntegerDigits(1);
_fitnessFormat.setMinimumIntegerDigits(1);
_fitnessFormat.setMinimumFractionDigits(5);
_fitnessFormat.setMaximumFractionDigits(5);
imageSplitPane.setDividerLocation(0.5);
startButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
saveButton.setEnabled(false);
try (InputStream in = getClass()
.getClassLoader()
.getResourceAsStream("org/jenetics/example/image/monalisa.png"))
{
update(ImageIO.read(in));
} catch (IOException e) {
throw new AssertionError(e);
}
}
private void update(final BufferedImage image) {
_imagePanel.setImage(image);
_polygonPanel.setDimension(image.getWidth(), image.getHeight());
}
private EngineParam getEngineParam() {
final EngineParam param = engineParamPanel.getEngineParam();
engineParam(param);
return param;
}
private BufferedImage getImage() {
return _imagePanel.getImage();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
imagePanel = new javax.swing.JPanel();
imageSplitPane = new javax.swing.JSplitPane();
origImagePanel = new javax.swing.JPanel();
polygonImagePanel = new javax.swing.JPanel();
buttonPanel = new javax.swing.JPanel();
startButton = new javax.swing.JButton();
stopButton = new javax.swing.JButton();
openButton = new javax.swing.JButton();
pauseButton = new javax.swing.JButton();
saveButton = new javax.swing.JButton();
resultPanel = new javax.swing.JPanel();
bestEvolutionResultPanel = new org.jenetics.example.image.EvolutionResultPanel();
currentevolutionResultPanel = new org.jenetics.example.image.EvolutionResultPanel();
engineParamPanel = new org.jenetics.example.image.EngineParamPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Evolving images");
imagePanel.setBackground(new java.awt.Color(153, 153, 153));
imagePanel.setLayout(new java.awt.GridLayout(1, 1));
imageSplitPane.setDividerLocation(300);
origImagePanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Source image"));
origImagePanel.setName(""); // NOI18N
origImagePanel.addComponentListener(new java.awt.event.ComponentAdapter() {
public void componentResized(java.awt.event.ComponentEvent evt) {
origImagePanelComponentResized(evt);
}
});
origImagePanel.setLayout(new java.awt.BorderLayout());
imageSplitPane.setLeftComponent(origImagePanel);
polygonImagePanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Polygon image"));
polygonImagePanel.setLayout(new java.awt.GridLayout(1, 1));
imageSplitPane.setRightComponent(polygonImagePanel);
imagePanel.add(imageSplitPane);
startButton.setText("Start");
startButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
startButtonActionPerformed(evt);
}
});
stopButton.setText("Stop");
stopButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
stopButtonActionPerformed(evt);
}
});
openButton.setText("Open");
openButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
openButtonActionPerformed(evt);
}
});
pauseButton.setText("Pause");
pauseButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pauseButtonActionPerformed(evt);
}
});
saveButton.setText("Save");
saveButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
saveButtonActionPerformed(evt);
}
});
javax.swing.GroupLayout buttonPanelLayout = new javax.swing.GroupLayout(buttonPanel);
buttonPanel.setLayout(buttonPanelLayout);
buttonPanelLayout.setHorizontalGroup(
buttonPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(buttonPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(buttonPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(startButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(stopButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(openButton, javax.swing.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
.addComponent(pauseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(saveButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
buttonPanelLayout.setVerticalGroup(
buttonPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(buttonPanelLayout.createSequentialGroup()
.addContainerGap()
.addComponent(startButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(stopButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 196, Short.MAX_VALUE)
.addComponent(openButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(saveButton)
.addContainerGap())
);
resultPanel.setLayout(new java.awt.GridBagLayout());
bestEvolutionResultPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Best"));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
resultPanel.add(bestEvolutionResultPanel, gridBagConstraints);
currentevolutionResultPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Current"));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
resultPanel.add(currentevolutionResultPanel, gridBagConstraints);
engineParamPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Engine parameter"));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
resultPanel.add(engineParamPanel, gridBagConstraints);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(resultPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 788, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(imagePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(buttonPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(imagePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(resultPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void startButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startButtonActionPerformed
_worker = EvolvingImagesWorker.of(getEngineParam(), getImage());
_worker.start(this::onNewResult);
// Enable/Disable UI controls.
startButton.setEnabled(false);
stopButton.setEnabled(true);
pauseButton.setEnabled(true);
openButton.setEnabled(false);
saveButton.setEnabled(true);
engineParamPanel.setEnabled(false);
}//GEN-LAST:event_startButtonActionPerformed
private void stopButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopButtonActionPerformed
_worker.stop();
startButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
pauseButton.setText("Pause");
openButton.setEnabled(true);
engineParamPanel.setEnabled(true);
}//GEN-LAST:event_stopButtonActionPerformed
private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed
final File dir = lastOpenDirectory();
final JFileChooser chooser = dir != null
? new JFileChooser(dir)
: new JFileChooser();
chooser.setDialogTitle("Choose Image");
chooser.setDialogType(JFileChooser.OPEN_DIALOG);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.addChoosableFileFilter(new FileNameExtensionFilter(
format(
"Images (%s)",
Stream.of(ImageIO.getReaderFileSuffixes())
.map(s -> format(" *.%s", s))
.collect(Collectors.joining(","))
),
ImageIO.getReaderFileSuffixes()
));
final int returnVal = chooser.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
final File imageFile = chooser.getSelectedFile();
try {
update(ImageIO.read(imageFile));
if (imageFile.getParentFile() != null) {
lastOpenDirectory(imageFile.getParentFile());
}
} catch (IOException e) {
JOptionPane.showMessageDialog(
rootPane,
format("Error while loading image '%s'.", imageFile),
e.toString(),
JOptionPane.ERROR_MESSAGE
);
}
}
}//GEN-LAST:event_openButtonActionPerformed
private void origImagePanelComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_origImagePanelComponentResized
origImagePanel.repaint();
polygonImagePanel.repaint();
}//GEN-LAST:event_origImagePanelComponentResized
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
switch (pauseButton.getText()) {
case "Pause":
_worker.pause();
pauseButton.setText("Resume");
break;
case "Resume":
_worker.resume();
pauseButton.setText("Pause");
break;
default:
}
}//GEN-LAST:event_pauseButtonActionPerformed
private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveButtonActionPerformed
final File dir = lastSaveDirectory();
final JFileChooser chooser = dir != null
? new JFileChooser(dir)
: new JFileChooser();
chooser.setDialogTitle("Save Polygon Image");
chooser.setDialogType(JFileChooser.SAVE_DIALOG);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.addChoosableFileFilter(new FileNameExtensionFilter(
"Images (*.png)",
ImageIO.getReaderFileSuffixes()
));
final int returnVal = chooser.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
final String name = chooser.getSelectedFile().getAbsolutePath();
final File imageFile = name.toLowerCase().endsWith(".png")
? new File(name)
: new File(name + ".png");
try {
final Dimension dim = _polygonPanel.getDimension();
final PolygonChromosome ch = _polygonPanel.getChromosome();
writeImage(imageFile, ch, dim.width, dim.height);
if (imageFile.getParentFile() != null) {
lastSaveDirectory(imageFile.getParentFile());
}
} catch (Exception e) {
JOptionPane.showMessageDialog(
rootPane,
format("Error while saving image '%s'.", imageFile),
e.toString(),
JOptionPane.ERROR_MESSAGE
);
}
}
}//GEN-LAST:event_saveButtonActionPerformed
private void onNewResult(
final EvolutionResult<PolygonGene, Double> current,
final EvolutionResult<PolygonGene, Double> best
) {
invokeLater(() -> {
final Genotype<PolygonGene> gt = best
.getBestPhenotype()
.getGenotype();
bestEvolutionResultPanel.update(best);
currentevolutionResultPanel.update(current);
_polygonPanel.setChromosome((PolygonChromosome)gt.getChromosome());
_polygonPanel.repaint();
});
}
/* *************************************************************************
* Application preferences.
**************************************************************************/
private static final String ENGINE_PARAM_NODE = "engine_param";
private static final String LAST_OPEN_DIRECTORY_PREF = "last_open_directory";
private static final String LAST_SAVE_DIRECTORY_PREF = "last_save_directory";
private EngineParam engineParam() {
return EngineParam.load(appPref().node(ENGINE_PARAM_NODE));
}
private void engineParam(final EngineParam param) {
param.store(appPref().node(ENGINE_PARAM_NODE));
}
private File lastOpenDirectory() {
final String dirName = appPref().get(LAST_OPEN_DIRECTORY_PREF, null);
return dirName != null ? new File(dirName) : null;
}
private void lastOpenDirectory(final File dir) {
appPref().put(LAST_OPEN_DIRECTORY_PREF, dir.getAbsolutePath());
}
private File lastSaveDirectory() {
final String dirName = appPref().get(LAST_SAVE_DIRECTORY_PREF, null);
return dirName != null ? new File(dirName) : null;
}
private void lastSaveDirectory(final File dir) {
appPref().put(LAST_SAVE_DIRECTORY_PREF, dir.getAbsolutePath());
}
private static Preferences appPref() {
return Preferences.userRoot().node("org/jenetics/example/image");
}
private static void prefFlush() {
try {
appPref().flush();
} catch (BackingStoreException ex) {
Logger.getLogger(EvolvingImages.class.getName())
.log(Level.SEVERE, null, ex);
}
}
/**
* @param args the command line arguments
*/
public static void main(final String args[]) {
// Start command line version if the right parameters are given.
if (new EvolvingImagesCmd(args).run()) return;
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(EvolvingImages.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(EvolvingImages.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(EvolvingImages.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(EvolvingImages.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
//</editor-fold>
//</editor-fold>
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(() -> {
new EvolvingImages().setVisible(true);
prefFlush();
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.jenetics.example.image.EvolutionResultPanel bestEvolutionResultPanel;
private javax.swing.JPanel buttonPanel;
private org.jenetics.example.image.EvolutionResultPanel currentevolutionResultPanel;
private org.jenetics.example.image.EngineParamPanel engineParamPanel;
private javax.swing.JPanel imagePanel;
private javax.swing.JSplitPane imageSplitPane;
private javax.swing.JButton openButton;
private javax.swing.JPanel origImagePanel;
private javax.swing.JButton pauseButton;
private javax.swing.JPanel polygonImagePanel;
private javax.swing.JPanel resultPanel;
private javax.swing.JButton saveButton;
private javax.swing.JButton startButton;
private javax.swing.JButton stopButton;
// End of variables declaration//GEN-END:variables
}