Add code
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
|
||||
This class does not contain all the methods used
|
||||
when handling complex numbers, only the ones
|
||||
needed for calculating Julia sets:
|
||||
add, modulus, square
|
||||
|
||||
*/
|
||||
public class ComplexNumber {
|
||||
public double real;
|
||||
public double imaginary;
|
||||
|
||||
// Initialize both parts of number to zero
|
||||
// if no input is given
|
||||
ComplexNumber()
|
||||
{
|
||||
real = 0.0;
|
||||
imaginary = 0.0;
|
||||
}
|
||||
|
||||
//Initialize with user input
|
||||
ComplexNumber(double real, double imaginary)
|
||||
{
|
||||
this.real = real;
|
||||
this.imaginary = imaginary;
|
||||
}
|
||||
|
||||
// Add two complex numbers
|
||||
// Result gets saved to the current complex number
|
||||
public void add(ComplexNumber cN)
|
||||
{
|
||||
this.real = this.real + cN.real;
|
||||
this.imaginary = this.imaginary + cN.imaginary;
|
||||
}
|
||||
|
||||
// The modulus of a complex number describes the distance from (0,0)
|
||||
// Result is always a real number
|
||||
public double modulus()
|
||||
{
|
||||
return Math.sqrt(Math.pow(this.real, 2.0) + Math.pow(this.imaginary, 2.0));
|
||||
}
|
||||
|
||||
public void square()
|
||||
{
|
||||
double temp_real = Math.pow(this.real, 2.0) - Math.pow(this.imaginary, 2.0);
|
||||
double temp_imaginary = this.real * this.imaginary * 2;
|
||||
this.real = temp_real;
|
||||
this.imaginary = temp_imaginary;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class JuliaDrawThread extends Thread {
|
||||
|
||||
private final int threadNumber;
|
||||
private final int totalThreads;
|
||||
private final int imageSectionHeight;
|
||||
private final BufferedImage partialImage;
|
||||
private final JuliaSet obj;
|
||||
|
||||
public void run() {
|
||||
generateImagePart();
|
||||
}
|
||||
|
||||
public BufferedImage getPartialImage() {
|
||||
return this.partialImage;
|
||||
}
|
||||
|
||||
private void generateImagePart() {
|
||||
float pixelBrightness;
|
||||
double complexX, complexY;
|
||||
ComplexNumber z;
|
||||
int i;
|
||||
|
||||
int startCoordinate = (int) Math.floor(((float) this.obj.imageRectY / totalThreads)) * this.threadNumber;
|
||||
|
||||
int endCoordinate = startCoordinate + imageSectionHeight;
|
||||
|
||||
double aspectRatio = (double) this.obj.imageRectY / (double) this.obj.imageRectX;
|
||||
|
||||
double mouseX = obj.mouseOffsetX / (double) obj.imageRectX / obj.zoom;
|
||||
double mouseY = obj.mouseOffsetY / (double) obj.imageRectY / obj.zoom;
|
||||
|
||||
// Iterate over every pixel
|
||||
for (int x = 0; x < this.obj.imageRectX; x++) {
|
||||
for (int y = startCoordinate; y < endCoordinate; y++) {
|
||||
pixelBrightness = 0.0 f;
|
||||
|
||||
complexX = (((((double) obj.imageRectX / 2) - x) / (((double) obj.imageRectX / 2) / 2)) / obj.zoom) + (mouseX * obj.zoom);
|
||||
complexY = aspectRatio * ((((y - ((double) obj.imageRectY / 2)) / (((double) obj.imageRectY / 2) / 2))) / obj.zoom) - (mouseY * obj.zoom);
|
||||
|
||||
z = new ComplexNumber(complexX, complexY);
|
||||
|
||||
// Loop until we've reached the maximum number of iterations
|
||||
for (i = 0; i < obj.juliaIterations; i++) {
|
||||
z.square();
|
||||
z.add(new ComplexNumber(obj.juliaCX, obj.juliaCY));
|
||||
|
||||
// Break out of the loop
|
||||
if (z.modulus() > 2) {
|
||||
// Set brightness to max
|
||||
pixelBrightness = 1.0 f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Brings the color value from e.g 0-300 to somewhere between 0-1
|
||||
float colorValue = (i % this.obj.juliaIterations) / ((float) this.obj.juliaIterations - 1);
|
||||
|
||||
// Variant 1
|
||||
if (this.obj.selectedColorMethod == 1) {
|
||||
partialImage.setRGB(x, y - startCoordinate, Color.getHSBColor(colorValue, 1, pixelBrightness).getRGB());
|
||||
}
|
||||
|
||||
// Variant 2: One colour
|
||||
else if (this.obj.selectedColorMethod == 2) {
|
||||
if (colorValue < obj.blackThresholdSliderFactor) {
|
||||
partialImage.setRGB(x, y - startCoordinate, Color.getHSBColor((float) obj.colorHueSliderFactor / 100, 1, 0).getRGB());
|
||||
} else {
|
||||
partialImage.setRGB(x, y - startCoordinate, Color.getHSBColor((float) obj.colorHueSliderFactor / 100, 1, Math.min(colorValue * (float)(obj.colorBrightnessSliderFactor), 1.0 f)).getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JuliaDrawThread(int threadNr, int totalThreads, JuliaSet obj) {
|
||||
this.threadNumber = threadNr;
|
||||
this.totalThreads = totalThreads;
|
||||
this.obj = obj;
|
||||
|
||||
if (this.threadNumber != this.totalThreads - 1) {
|
||||
this.imageSectionHeight = (int) Math.floor((float) this.obj.imageRectY / this.totalThreads);
|
||||
} else {
|
||||
this.imageSectionHeight = (int) Math.floor((float) this.obj.imageRectY / this.totalThreads) + (this.obj.imageRectY % this.totalThreads) + 1;
|
||||
}
|
||||
|
||||
partialImage = new BufferedImage(obj.imageRectX, this.imageSectionHeight, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
}
|
||||
+544
@@ -0,0 +1,544 @@
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class JuliaSet {
|
||||
public JFrame frame;
|
||||
public JPanel panel;
|
||||
public JPanel controlPanel1;
|
||||
public JPanel controlPanel2;
|
||||
public JPanel imagePanel;
|
||||
public JPanel borderPanel1;
|
||||
public JPanel borderPanel2;
|
||||
public JPanel borderPanel3;
|
||||
|
||||
JTextField textFieldCY;
|
||||
JTextField textFieldIterations;
|
||||
JLabel labelZoom;
|
||||
JLabel labelColorMethod;
|
||||
JLabel imageLabel;
|
||||
JLabel hueSliderLabel;
|
||||
JLabel brightnessSliderLabel;
|
||||
JLabel blackThresholdLabel;
|
||||
JSlider sliderZoom;
|
||||
JSlider sliderColorHue;
|
||||
JSlider sliderColorBrightness;
|
||||
JSlider sliderBlackThreshold;
|
||||
|
||||
BufferedImage juliaSetImage;
|
||||
|
||||
int sliderFactor;
|
||||
int imageRectX;
|
||||
int imageRectY;
|
||||
int colorHueSliderFactor;
|
||||
double colorBrightnessSliderFactor;
|
||||
double blackThresholdSliderFactor;
|
||||
|
||||
public int juliaIterations;
|
||||
public double juliaCX;
|
||||
public double juliaCY;
|
||||
public double juliaXAxisMin;
|
||||
public double juliaXAxisMax;
|
||||
public double juliaYAxisMin;
|
||||
public double juliaYAxisMax;
|
||||
|
||||
public double distanceMinMaxX;
|
||||
public double distanceMinMaxY;
|
||||
public double offsetX;
|
||||
public double offsetY;
|
||||
public double zoom;
|
||||
public double stretchFactorX;
|
||||
public double stretchFactorY;
|
||||
public double mouseOffsetX;
|
||||
public double mouseOffsetY;
|
||||
|
||||
// Rounding to two decimal points
|
||||
// Used for the slider
|
||||
static final DecimalFormat df = new DecimalFormat("0.00");
|
||||
|
||||
// For the color picker
|
||||
public int selectedColorMethod;
|
||||
|
||||
static ButtonGroup colorMethods;
|
||||
static JRadioButton colorMethod1;
|
||||
static JRadioButton colorMethod2;
|
||||
|
||||
// for the resize event
|
||||
private javax.swing.Timer waitingTimer;
|
||||
|
||||
JuliaSet() {
|
||||
initWindow();
|
||||
initGuiElements();
|
||||
showGUI();
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
public void drawJuliaSet() {
|
||||
// Start timer
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// Because the JPanel with the image on it doesn't want to reveal its own size...
|
||||
int trueHeight = this.frame.getContentPane().getComponent(0).getHeight() - (this.controlPanel1.getHeight() + this.controlPanel2.getHeight() + 3 * 10);
|
||||
|
||||
this.imageRectX = this.imagePanel.getBounds().width;
|
||||
this.imageRectY = trueHeight;
|
||||
|
||||
// If the user switches the variables around
|
||||
double temp;
|
||||
if (juliaXAxisMin > juliaXAxisMax) {
|
||||
temp = juliaXAxisMax;
|
||||
juliaXAxisMax = juliaXAxisMin;
|
||||
juliaXAxisMin = temp;
|
||||
}
|
||||
if (juliaYAxisMin > juliaYAxisMax) {
|
||||
temp = juliaYAxisMax;
|
||||
juliaYAxisMax = juliaYAxisMin;
|
||||
juliaYAxisMin = temp;
|
||||
}
|
||||
|
||||
// All the calculations are done beforehand
|
||||
// to save time. No need to calculate them
|
||||
// every single time...
|
||||
|
||||
// zoom = slider value ^ 3
|
||||
zoom = Math.pow((double) sliderFactor / 10.0, 3);
|
||||
|
||||
distanceMinMaxX = Math.abs(juliaXAxisMax - juliaXAxisMin);
|
||||
distanceMinMaxY = Math.abs(juliaYAxisMax - juliaYAxisMin);
|
||||
|
||||
offsetX = (juliaXAxisMin + (Math.abs(distanceMinMaxX) / 2));
|
||||
offsetY = (juliaYAxisMin + (Math.abs(distanceMinMaxY) / 2));
|
||||
|
||||
stretchFactorX = distanceMinMaxX / 2;
|
||||
stretchFactorY = distanceMinMaxY / 2;
|
||||
|
||||
// Create image to draw on
|
||||
juliaSetImage = new BufferedImage(this.imageRectX, this.imageRectY, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
int systemThreads = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
JuliaDrawThread[] jA = new JuliaDrawThread[systemThreads];
|
||||
|
||||
for (int t = 0; t < systemThreads; t++) {
|
||||
// Make sure that the reference is empty
|
||||
jA[t] = null;
|
||||
jA[t] = new JuliaDrawThread(t, systemThreads, this);
|
||||
}
|
||||
|
||||
// Create array of threads
|
||||
Thread[] tA = new Thread[systemThreads];
|
||||
|
||||
// here's where we
|
||||
for (int t = 0; t < systemThreads; t++) {
|
||||
tA[t] = new Thread(jA[t]);
|
||||
tA[t].start();
|
||||
}
|
||||
|
||||
BufferedImage[] partialImages = new BufferedImage[systemThreads];
|
||||
for (int t = 0; t < systemThreads; t++) {
|
||||
try {
|
||||
tA[t].join();
|
||||
partialImages[t] = jA[t].getPartialImage();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we've collected all the image data
|
||||
// Merge into one image
|
||||
int currentHeight = 0;
|
||||
Graphics2D g2d = juliaSetImage.createGraphics();
|
||||
for (BufferedImage pImage: partialImages) {
|
||||
g2d.drawImage(pImage, 0, currentHeight, null);
|
||||
currentHeight += pImage.getHeight();
|
||||
}
|
||||
|
||||
g2d.dispose();
|
||||
|
||||
// Draw the image here
|
||||
imageLabel.setIcon(new ImageIcon(juliaSetImage));
|
||||
|
||||
// End timer
|
||||
long endTime = System.currentTimeMillis();
|
||||
System.out.printf("Time: %dms%n", endTime - startTime);
|
||||
}
|
||||
|
||||
public void zoomSliderStateChanged(ChangeEvent e) {
|
||||
JSlider source = (JSlider) e.getSource();
|
||||
sliderFactor = source.getValue();
|
||||
|
||||
// Rounding the number before writing it to the label
|
||||
labelZoom.setText(df.format((Math.pow((double) sliderFactor / 10, 3))));
|
||||
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
public void colorHueSliderStateChanged(ChangeEvent e) {
|
||||
JSlider source = (JSlider) e.getSource();
|
||||
colorHueSliderFactor = source.getValue();
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
public void brightnessSliderStateChanged(ChangeEvent e) {
|
||||
JSlider source = (JSlider) e.getSource();
|
||||
colorBrightnessSliderFactor = (float) source.getValue();
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
public void blackThresholdSliderStateChanged(ChangeEvent e) {
|
||||
JSlider source = (JSlider) e.getSource();
|
||||
blackThresholdSliderFactor = (float) source.getValue() / 250;
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
// Universal text field changer
|
||||
// That way I don't have to re-write the float and integer parsing for every single text field
|
||||
// Using reflection we just look for a variable with a matching name
|
||||
|
||||
// Help for key listener: https://kodejava.org/how-do-i-add-key-listener-event-handler-to-jtextfield/
|
||||
public void textFieldChanged(JTextField jt, String variableToBeChanged) {
|
||||
try {
|
||||
// Get variable of object by name
|
||||
Field f = this.getClass().getDeclaredField(variableToBeChanged);
|
||||
if (f.getType().toString().equals("double")) {
|
||||
try {
|
||||
f.setDouble(this, Double.parseDouble(jt.getText()));
|
||||
// Value changed successfully, redraw Julia set
|
||||
drawJuliaSet();
|
||||
} catch (NumberFormatException ex) {
|
||||
jt.setText(Double.toString(f.getDouble(this)));
|
||||
JOptionPane.showConfirmDialog(null, "Please only enter floating point numbers", "Floats only", JOptionPane.DEFAULT_OPTION);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Just like the other exceptions these should never occur
|
||||
// because the user doesn't have control over them
|
||||
// Prints stack trace to the terminal, but the user can't see that *taps head*
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (f.getType().toString().equals("int")) {
|
||||
try {
|
||||
f.setInt(this, Integer.parseInt(jt.getText()));
|
||||
drawJuliaSet();
|
||||
} catch (NumberFormatException ex) {
|
||||
jt.setText(Integer.toString(f.getInt(this)));
|
||||
JOptionPane.showConfirmDialog(null, "Please only enter integers", "Integers only", JOptionPane.DEFAULT_OPTION);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
// These are never supposed to be hit
|
||||
// The user doesn't have control over any of this
|
||||
// They're just here so IntelliJ doesn't complain
|
||||
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
class ColorRadioButtonActionListener implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
JRadioButton button = (JRadioButton) event.getSource();
|
||||
|
||||
if (button == colorMethod1) {
|
||||
selectedColorMethod = 1;
|
||||
hueSliderLabel.setVisible(false);
|
||||
brightnessSliderLabel.setVisible(false);
|
||||
sliderColorBrightness.setVisible(false);
|
||||
sliderColorHue.setVisible(false);
|
||||
blackThresholdLabel.setVisible(false);
|
||||
sliderBlackThreshold.setVisible(false);
|
||||
} else if (button == colorMethod2) {
|
||||
selectedColorMethod = 2;
|
||||
hueSliderLabel.setVisible(true);
|
||||
brightnessSliderLabel.setVisible(true);
|
||||
sliderColorBrightness.setVisible(true);
|
||||
sliderColorHue.setVisible(true);
|
||||
blackThresholdLabel.setVisible(true);
|
||||
sliderBlackThreshold.setVisible(true);
|
||||
}
|
||||
|
||||
drawJuliaSet();
|
||||
}
|
||||
}
|
||||
|
||||
public void initGuiElements() {
|
||||
this.panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
|
||||
// Create panels to serve as borders
|
||||
borderPanel1 = new JPanel();
|
||||
borderPanel2 = new JPanel();
|
||||
borderPanel3 = new JPanel();
|
||||
|
||||
borderPanel1.setMinimumSize(new Dimension(10, 10));
|
||||
borderPanel1.setPreferredSize(new Dimension(10, 10));
|
||||
borderPanel2.setMinimumSize(new Dimension(10, 10));
|
||||
borderPanel2.setPreferredSize(new Dimension(10, 10));
|
||||
borderPanel3.setMinimumSize(new Dimension(10, 10));
|
||||
borderPanel3.setPreferredSize(new Dimension(10, 10));
|
||||
|
||||
borderPanel1.setLayout(new BoxLayout(borderPanel1, BoxLayout.X_AXIS));
|
||||
borderPanel2.setLayout(new BoxLayout(borderPanel2, BoxLayout.X_AXIS));
|
||||
borderPanel3.setLayout(new BoxLayout(borderPanel3, BoxLayout.X_AXIS));
|
||||
|
||||
// Create JPanels for the controls at the top
|
||||
this.controlPanel1 = new JPanel();
|
||||
this.controlPanel2 = new JPanel();
|
||||
|
||||
|
||||
this.controlPanel1.setLayout(new BoxLayout(this.controlPanel1, BoxLayout.X_AXIS));
|
||||
this.controlPanel2.setLayout(new BoxLayout(this.controlPanel2, BoxLayout.X_AXIS));
|
||||
|
||||
// Add space in-between control elements
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
JLabel labelParameterC = new JLabel("Parameter C:");
|
||||
this.controlPanel1.add(labelParameterC);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
JLabel labelCX = new JLabel("X");
|
||||
this.controlPanel1.add(labelCX);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
JTextField textFieldCX = new JTextField("0.0");
|
||||
textFieldCX.setMaximumSize(new Dimension(50, 30));
|
||||
juliaCX = 0.0 f;
|
||||
textFieldCX.addKeyListener(new KeyAdapter() {
|
||||
public void keyReleased(KeyEvent e) {
|
||||
textFieldChanged(textFieldCX, "juliaCX");
|
||||
}
|
||||
});
|
||||
textFieldCX.setPreferredSize(new Dimension(40, 30));
|
||||
textFieldCX.setMaximumSize(new Dimension(90, 30));
|
||||
this.controlPanel1.add(textFieldCX);
|
||||
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
JLabel labelCY = new JLabel("Y");
|
||||
this.controlPanel1.add(labelCY);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
|
||||
textFieldCY = new JTextField("0.0");
|
||||
textFieldCY.setPreferredSize(new Dimension(40, 30));
|
||||
textFieldCY.setMaximumSize(new Dimension(90, 30));
|
||||
juliaCY = 0.0 f;
|
||||
textFieldCY.addKeyListener(new KeyAdapter() {
|
||||
public void keyReleased(KeyEvent e) {
|
||||
textFieldChanged(textFieldCY, "juliaCY");
|
||||
}
|
||||
});
|
||||
|
||||
this.controlPanel1.add(textFieldCY);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
JLabel labelIterations = new JLabel("Iterations");
|
||||
this.controlPanel1.add(labelIterations);
|
||||
|
||||
textFieldIterations = new JTextField("300");
|
||||
textFieldIterations.setPreferredSize(new Dimension(40, 30));
|
||||
textFieldIterations.setMaximumSize(new Dimension(90, 30));
|
||||
juliaIterations = 300;
|
||||
textFieldIterations.addKeyListener(new KeyAdapter() {
|
||||
public void keyReleased(KeyEvent e) {
|
||||
textFieldChanged(textFieldIterations, "juliaIterations");
|
||||
}
|
||||
});
|
||||
this.controlPanel1.add(textFieldIterations);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
// Initial values to constrain the first drawn julia set
|
||||
juliaXAxisMin = -2.0 f;
|
||||
juliaXAxisMax = 2.0 f;
|
||||
juliaYAxisMax = 2.0 f;
|
||||
juliaYAxisMin = -2.0 f;
|
||||
|
||||
// Zoom label
|
||||
labelZoom = new JLabel("Zoom");
|
||||
this.controlPanel1.add(labelZoom);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
// Zoom slider
|
||||
// unfortunately it only takes integers, so we'll have to divide by ten
|
||||
// to get the number we actually want
|
||||
sliderZoom = new JSlider(1, 1000, 10);
|
||||
|
||||
// + 2 because the slider feels slightly off-center otherwise
|
||||
sliderZoom.addChangeListener(this::zoomSliderStateChanged);
|
||||
this.controlPanel1.add(sliderZoom);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
|
||||
// Text field right next to the slider
|
||||
// shows the zoom factor, is not writable
|
||||
labelZoom = new JLabel("1,0");
|
||||
sliderFactor = 10;
|
||||
this.controlPanel1.add(labelZoom);
|
||||
this.controlPanel1.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
labelZoom.setMinimumSize(new Dimension(50, 30));
|
||||
labelZoom.setPreferredSize(new Dimension(50, 30));
|
||||
|
||||
labelColorMethod = new JLabel("View:");
|
||||
this.controlPanel2.add(Box.createRigidArea(new Dimension(7, 0)));
|
||||
this.controlPanel2.add(labelColorMethod);
|
||||
|
||||
// Create buttons
|
||||
colorMethod1 = new JRadioButton();
|
||||
this.controlPanel2.add(colorMethod1);
|
||||
|
||||
// Set this one to the default
|
||||
// We do this before we add the item listener
|
||||
// No need to draw the set twice just because
|
||||
// we clicked a button
|
||||
colorMethod1.doClick();
|
||||
selectedColorMethod = 1;
|
||||
|
||||
colorMethod2 = new JRadioButton();
|
||||
this.controlPanel2.add(colorMethod2);
|
||||
|
||||
// Create group for the radio buttons
|
||||
colorMethods = new ButtonGroup();
|
||||
colorMethods.add(colorMethod1);
|
||||
colorMethods.add(colorMethod2);
|
||||
|
||||
// Add listener for the buttons
|
||||
ColorRadioButtonActionListener colorActionListener = new ColorRadioButtonActionListener();
|
||||
colorMethod1.addActionListener(colorActionListener);
|
||||
colorMethod2.addActionListener(colorActionListener);
|
||||
|
||||
// "H" for the slider
|
||||
hueSliderLabel = new JLabel("H ");
|
||||
this.controlPanel2.add(hueSliderLabel);
|
||||
hueSliderLabel.setVisible(false);
|
||||
|
||||
// Hue slider
|
||||
sliderColorHue = new JSlider(0, 100, 0);
|
||||
// + 2 because the slider feels slightly off-center otherwise
|
||||
sliderColorHue.addChangeListener(this::colorHueSliderStateChanged);
|
||||
sliderColorHue.setVisible(false);
|
||||
this.controlPanel2.add(sliderColorHue);
|
||||
|
||||
brightnessSliderLabel = new JLabel("B ");
|
||||
this.controlPanel2.add(brightnessSliderLabel);
|
||||
brightnessSliderLabel.setVisible(false);
|
||||
|
||||
// Brightness slider
|
||||
sliderColorBrightness = new JSlider(1, 100, 1);
|
||||
// + 2 because the slider feels slightly off-center otherwise
|
||||
sliderColorBrightness.addChangeListener(this::brightnessSliderStateChanged);
|
||||
sliderColorBrightness.setVisible(false);
|
||||
this.controlPanel2.add(sliderColorBrightness);
|
||||
colorBrightnessSliderFactor = 1;
|
||||
|
||||
// Black threshold label
|
||||
blackThresholdLabel = new JLabel("Threshold ");
|
||||
blackThresholdLabel.setVisible(false);
|
||||
this.controlPanel2.add(blackThresholdLabel);
|
||||
|
||||
// Black threshold label
|
||||
sliderBlackThreshold = new JSlider(0, 100, 0);
|
||||
sliderBlackThreshold.addChangeListener(this::blackThresholdSliderStateChanged);
|
||||
sliderBlackThreshold.setVisible(false);
|
||||
this.controlPanel2.add(sliderBlackThreshold);
|
||||
|
||||
// Draw image onto label
|
||||
// Every time setIcon() is called the image updates
|
||||
imagePanel = new JPanel();
|
||||
imageLabel = new JLabel();
|
||||
this.imagePanel.add(imageLabel);
|
||||
|
||||
System.gc();
|
||||
}
|
||||
|
||||
public void initWindow() {
|
||||
frame = new JFrame();
|
||||
frame.setTitle("Julia Set");
|
||||
|
||||
// Windows UI
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
frame.setMinimumSize(new Dimension(800, 800));
|
||||
|
||||
frame.setLocationRelativeTo(null);
|
||||
|
||||
// Actually make sure that the window gets closed when we press the X button
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent we) {
|
||||
we.getWindow().dispose(); // Throw away the window
|
||||
System.exit(0); // Exit JVM
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent ae) {
|
||||
if (ae.getSource() == this.waitingTimer) {
|
||||
//Stop timer, event has ended
|
||||
this.waitingTimer.stop();
|
||||
this.waitingTimer = null;
|
||||
|
||||
this.imagePanel.setPreferredSize(new Dimension(this.imagePanel.getBounds().width, this.imagePanel.getBounds().height));
|
||||
drawJuliaSet();
|
||||
}
|
||||
}
|
||||
|
||||
public void showGUI() {
|
||||
this.panel.add(borderPanel1);
|
||||
this.panel.add(controlPanel1);
|
||||
this.panel.add(borderPanel2);
|
||||
this.panel.add(controlPanel2);
|
||||
this.panel.add(borderPanel3);
|
||||
this.panel.add(imagePanel);
|
||||
this.frame.add(this.panel);
|
||||
this.frame.setVisible(true);
|
||||
|
||||
// Implement timer to wait until the resize event is over
|
||||
this.frame.addComponentListener(new ComponentAdapter() {
|
||||
public void componentResized(ComponentEvent componentEvent) {
|
||||
if (waitingTimer == null) {
|
||||
waitingTimer = new Timer(50, JuliaSet.this::actionPerformed);
|
||||
waitingTimer.start();
|
||||
} else {
|
||||
waitingTimer.restart();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MouseAdapter ma = new MouseAdapter() {
|
||||
private Point startPoint;
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
startPoint = e.getPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
Point p = e.getPoint();
|
||||
mouseOffsetX += (p.x - startPoint.x) / zoom;
|
||||
mouseOffsetY += (p.y - startPoint.y) / zoom;
|
||||
startPoint = null;
|
||||
drawJuliaSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
sliderZoom.setValue(sliderZoom.getValue() - e.getWheelRotation());
|
||||
}
|
||||
};
|
||||
|
||||
frame.addMouseListener(ma);
|
||||
frame.addMouseMotionListener(ma);
|
||||
frame.addMouseWheelListener(ma);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new JuliaSet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import javax.swing.*;
|
||||
|
||||
public class PaintImage extends JPanel {
|
||||
public PaintImage() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Julia set
|
||||
|
||||
## Overview
|
||||
|
||||
Computation is done on the CPU, uses multithreading. Use the mouse to move around and the scroll wheel to zoom in and out.
|
||||
|
||||
Offers two views:
|
||||
|
||||
- number of iterations determines the color
|
||||
|
||||
- number of iterations determines the brightness
|
||||
|
||||
The second mode also allows you to adjust the hue, brightness and black threshold.
|
||||
|
||||
## Demo
|
||||
|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Reference in New Issue
Block a user