diff options
6 files changed, 261 insertions, 99 deletions
diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/FractalModel.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/FractalModel.java index dd09100..4c2499d 100644 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/FractalModel.java +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/FractalModel.java @@ -26,28 +26,34 @@ package com.camilstaps.mandelbrot; import java.util.Observable; /** - * - * @author camilstaps + * The FractalModel holds the bounds of the shown fractal + * @author Camil Staps */ public class FractalModel extends Observable { - private double start_x, start_y, end_x, end_y; + private double start_x = -1, end_x = 1, start_y = -1, end_y = 1; - public FractalModel() { - start_x = -1; - end_x = 1; - start_y = -1; - end_y = 1; + /** + * Get the Mandelbrot number for a specific point up to some maximum + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @param repetitions the maximum + * @return the mandelbrot number + */ + public int getMandelNumber(double x, double y, int repetitions) { + return MandelbrotFractal.mandelNumber(x, y, repetitions); } + /** + * Get the Mandelbrot number for a specific point up to some maximum + * @param p the point + * @param repetitions the maximum + * @return the mandelbrot number + */ public int getMandelNumber(MandelbrotFractal.Point p, int repetitions) { return MandelbrotFractal.mandelNumber(p, repetitions); } - public int getMandelNumber(double x, double y, int repetitions) { - return MandelbrotFractal.mandelNumber(x, y, repetitions); - } - public double getStartX() { return start_x; } @@ -64,7 +70,14 @@ public class FractalModel extends Observable { return end_y; } - public synchronized void setBorders(double start_x, double end_x, double start_y, double end_y) { + /** + * Set all bounds together + * @param start_x + * @param end_x + * @param start_y + * @param end_y + */ + public synchronized void setBounds(double start_x, double end_x, double start_y, double end_y) { if (start_x == this.start_x && end_x == this.end_x && start_y == this.start_y && end_y == this.end_y) return; diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java index 9fca89a..71c644a 100644 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java @@ -44,41 +44,83 @@ import javax.swing.JProgressBar; import javax.swing.SwingWorker; /** - * - * @author camilstaps + * The Grid is both a View and a Controller: it shows a graphical representation + * of the FractalModel, and allows the user to control that FractalModel by + * clicking (zoom in), shift-clicking (zoom out) and dragging (box zoom). + * @author Camil Staps */ public class Grid extends JPanel implements Observer, MouseListener, MouseMotionListener { + /** + * Hard maximum amount of repetitions and the amount of steps to take to get + * to that maximum + */ + private static final int REPETITIONS_MAX = 1000, STEPS = 50; + + /** + * Width and height of the grid + */ + private static final int WIDTH = 400, HEIGHT = 400; + + /** + * List of colours for visualisation + */ + private final int[] colors; + + /** + * The FractalModel to visualise + */ private final FractalModel fractalModel; + /** + * Used for visualisation + */ private final BufferedImage image; private final WritableRaster raster; - private final int width = 400, height = 400; + /** + * Count pixels changed to check if we need to repaint + */ private int pixelCounter; - private static final int REPETITIONS_MAX = 1000, STEPS = 50; - - private final int[] colors; - + /** + * Updating the view is done by SwingWorkers. The solution provides support + * for multiple SwingWorkers; Updaters; which are held in this list. + */ private final List<Updater> updaters = new ArrayList<>(); + /** + * Used for handling mouse events and zooming + */ private int start_x, start_y, old_x, old_y; private boolean dragging = false; Graphics graphics; + /** + * A progress bar + */ private ProgressView progressView; + /** + * Whether or not to use multiple SwingWorkers. + * Using multiple SwingWorkers is *not* faster. The application is already + * faster by using memory in the MandelbrotFractal class. Using multiple + * SwingWorkers does not aid in speed. + */ private boolean useMultipleSwingWorkers = false; + /** + * Create a new Grid to view and control a FractalModel + * @param fractalModel + */ public Grid(FractalModel fractalModel) { this.fractalModel = fractalModel; fractalModel.addObserver(this); - image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); raster = image.getRaster(); - setPreferredSize(new Dimension(width, height)); + setPreferredSize(new Dimension(WIDTH, HEIGHT)); colors = new int[REPETITIONS_MAX + 1]; for (int i = 0; i <=REPETITIONS_MAX; i++) { @@ -94,6 +136,10 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion update(fractalModel, null); } + /** + * Get the progress bar (create it if it doesn't exist yet) + * @return + */ public ProgressView getProgressView() { if (progressView == null) { progressView = new ProgressView(); @@ -103,18 +149,28 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion @Override public int getWidth() { - return width; + return WIDTH; } @Override public int getHeight() { - return height; + return HEIGHT; } + /** + * Change whether we use multiple SwingWorkers or not. This change will + * take effect when the FractalModel is updated. + * @param value + */ public void setUseMultipleSwingWorkers(boolean value) { useMultipleSwingWorkers = value; } + /** + * Stop the previous update and start rendering all over again. + * @param o + * @param o1 + */ @Override public final synchronized void update(Observable o, Object o1) { if (updaters != null) { @@ -128,20 +184,20 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion synchronized (updaters) { updaters.clear(); if (useMultipleSwingWorkers) { - Updater u1 = new Updater(0, width / 2, 0, height / 2); + Updater u1 = new Updater(0, WIDTH / 2, 0, HEIGHT / 2); u1.execute(); updaters.add(u1); - Updater u2 = new Updater(width / 2, width, 0, height / 2); + Updater u2 = new Updater(WIDTH / 2, WIDTH, 0, HEIGHT / 2); u2.execute(); updaters.add(u2); - Updater u3 = new Updater(0, width / 2, height / 2, height); + Updater u3 = new Updater(0, WIDTH / 2, HEIGHT / 2, HEIGHT); u3.execute(); updaters.add(u3); - Updater u4 = new Updater(width / 2, width, height / 2, height); + Updater u4 = new Updater(WIDTH / 2, WIDTH, HEIGHT / 2, HEIGHT); u4.execute(); updaters.add(u4); } else { - Updater u = new Updater(0, width, 0, height); + Updater u = new Updater(0, WIDTH, 0, HEIGHT); u.execute(); updaters.add(u); } @@ -154,23 +210,45 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion g.drawImage(image, 0, 0, null); } + /** + * Set a pixel on the grid + * @param x + * @param y + * @param rgb + */ public synchronized void setPixel(int x, int y, int[] rgb) { raster.setPixel(x, y, rgb); pixelCounter++; - if (pixelCounter == width * height) { + if (pixelCounter == WIDTH * HEIGHT) { pixelCounter = 0; repaint(); } } + /** + * Get the 'mathematical' x coordinate that belongs to a pixel's x coordinate + * @param pixel_x + * @return + */ protected double getRealX(int pixel_x) { - return ((double) pixel_x * (fractalModel.getEndX() - fractalModel.getStartX()) / (double) width) + fractalModel.getStartX(); + return ((double) pixel_x * (fractalModel.getEndX() - fractalModel.getStartX()) + / (double) WIDTH) + fractalModel.getStartX(); } + /** + * Get the 'mathematical' y coordinate that belongs to a pixel's y coordinate + * @param pixel_y + * @return + */ protected double getRealY(int pixel_y) { - return ((double) pixel_y * (fractalModel.getEndY() - fractalModel.getStartY()) / (double) height) + fractalModel.getStartY(); + return ((double) pixel_y * (fractalModel.getEndY() - fractalModel.getStartY()) + / (double) HEIGHT) + fractalModel.getStartY(); } + /** + * Click to zoom in; Shift-click to zoom out + * @param me + */ @Override public void mouseClicked(MouseEvent me) { if ((me.getModifiers() & InputEvent.SHIFT_MASK) != 0) { @@ -180,12 +258,20 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion } } + /** + * Keep track of mouse data for the box zoom + * @param me + */ @Override public void mousePressed(MouseEvent me) { old_x = start_x = me.getX(); old_y = start_y = me.getY(); } + /** + * If this was a box zoom, ... well, perform a box zoom. + * @param me + */ @Override public synchronized void mouseReleased(MouseEvent me) { if (me.getX() != start_x || me.getY() != start_y) { @@ -193,7 +279,6 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion } dragging = false; - notifyAll(); } /** @@ -253,6 +338,10 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion @Override public void mouseExited(MouseEvent me) {} + /** + * Draw the zoombox on dragging + * @param me + */ @Override public void mouseDragged(MouseEvent me) { Graphics g = getSafeGraphics(); @@ -290,11 +379,21 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion @Override public void mouseMoved(MouseEvent me) {} + /** + * A SwingWorker to update the graphics + */ protected class Updater extends SwingWorker<Map<Point,Integer>, Map<Point,Integer>> { private boolean doneProcessing = true, stop = false; private final int start_x, end_x, start_y, end_y; + /** + * Update only the given rectangle + * @param start_x + * @param end_x + * @param start_y + * @param end_y + */ public Updater(int start_x, int end_x, int start_y, int end_y) { super(); @@ -304,8 +403,12 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion this.end_y = end_y; } + /** + * Calculate all points in the rectangle using the FractalModel + * @return the resulting points + */ @Override - protected Map<Point, Integer> doInBackground() throws Exception { + protected Map<Point, Integer> doInBackground() { Map<Point,Integer> results = new HashMap<>(); for (int repetitions = REPETITIONS_MAX / STEPS; repetitions <= REPETITIONS_MAX && !stop; repetitions += REPETITIONS_MAX / STEPS) { @@ -327,6 +430,10 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion return results; } + /** + * Process a list of points, and update the progress bar + * @param results + */ @Override protected void process(List<Map<Point,Integer>> results) { if (progressView != null) { @@ -347,16 +454,17 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion doneProcessing = true; } - @Override - public void done() { - //updaters.remove(this); - } - + /** + * Tell the updater to stop + */ public void stop() { stop = true; } } + /** + * A point on the grid + */ protected class Point { int x, y; @@ -380,6 +488,10 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion } } + /** + * A special ProgressBar which is hidden when full, and takes the average + * of the progresses of all Updaters as its value + */ protected class ProgressView extends JProgressBar { public ProgressView() { @@ -394,6 +506,9 @@ public class Grid extends JPanel implements Observer, MouseListener, MouseMotion super.setValue(n); } + /** + * Calculate the value as the average of the progresses of all Updaters + */ public void setValue() { int sum = 0, count = 0; for (Updater updater : updaters) { diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotFractal.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotFractal.java index 43909fe..ec05939 100644 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotFractal.java +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotFractal.java @@ -39,11 +39,24 @@ public class MandelbrotFractal { */ private static final Map<Point,Result> mandelNumbers = new HashMap<>(); + /** + * Calculate the Mandelbrot number for a specific point up to some maximum + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @param repetitions the maximum + * @return the mandelbrot number + */ public synchronized static int mandelNumber(double x, double y, int repetitions) { Point p = new Point(x, y); return mandelNumber(p, repetitions); } + /** + * Calculate the Mandelbrot number for a specific point up to some maximum + * @param p the point + * @param repetitions the maximum + * @return the mandelbrot number + */ public synchronized static int mandelNumber(Point p, int repetitions) { if (mandelNumbers.containsKey(p)) { Result result = mandelNumbers.get(p); @@ -61,6 +74,12 @@ public class MandelbrotFractal { } } + /** + * Calculate the Mandelbrot number for a specifc point up to some maximum + * @param p the point + * @param repetitions the maximum + * @return the result + */ protected synchronized static Result calculateMandelNumber(Point p, int repetitions) { Result start = new Result(); start.x = p.x; @@ -69,6 +88,13 @@ public class MandelbrotFractal { return start; } + /** + * Calculate the Mandelbrot number for a specific point up to some maximum, + * starting from an earlier result + * @param p the point + * @param repetitions the maximum + * @param start the earlier result; will be updated with the new result + */ protected synchronized static void calculateMandelNumber(Point p, int repetitions, Result start) { int n = start.repetitions; @@ -82,6 +108,9 @@ public class MandelbrotFractal { start.mandelNumber = n; } + /** + * A point on a coordinate system + */ public static class Point { double x, y; @@ -113,6 +142,10 @@ public class MandelbrotFractal { } } + /** + * A Mandelbrot result consists of a point, the mandelbrot number and the + * maximum with which we calculated that result. + */ protected static class Result { int mandelNumber = -1, repetitions = 0; double x, y; diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotWindow.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotWindow.java index 81a4204..a7e6832 100644 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotWindow.java +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/MandelbrotWindow.java @@ -33,6 +33,9 @@ import javax.swing.JFrame; */ public class MandelbrotWindow extends JFrame { + /** + * Create a window with a grid, progress bar and textfields + */ private MandelbrotWindow() { super("Mandelbrot"); diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Textfields.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Textfields.java index d719c4a..5cd20f3 100644 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Textfields.java +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Textfields.java @@ -36,18 +36,32 @@ import javax.swing.JPanel; import javax.swing.JTextField; /** - * - * @author camilstaps + * The Textfields form a view and a controller for the FractalModel class, and + * a controller for the Grid class + * @author Camil Staps */ public class Textfields extends JPanel implements Observer { + /** + * The FractalModel to view and control + */ private final FractalModel fractalModel; + + /** + * The Grid to control + */ private final Grid grid; + /** + * Initial values of the text fields + */ private final String INITIAL_CENTER_X = "-0.46", INITIAL_CENTER_Y = "0", INITIAL_SCALE = "0.5"; + /** + * The components + */ private final JTextField field_centerX = new JTextField(INITIAL_CENTER_X, 6); private final JTextField field_centerY = new JTextField(INITIAL_CENTER_Y, 6); private final JTextField field_scale = new JTextField(INITIAL_SCALE, 6); @@ -55,6 +69,11 @@ public class Textfields extends JPanel implements Observer { private final JButton button_reset = new JButton("Reset"); private final JCheckBox checkbox_multiple_swingworkers = new JCheckBox("Multiple SwingWorkers"); + /** + * Create a new instance + * @param fractalModel + * @param grid + */ public Textfields(FractalModel fractalModel, Grid grid) { super(new BorderLayout()); @@ -68,6 +87,9 @@ public class Textfields extends JPanel implements Observer { button_redraw.doClick(); } + /** + * Setup the swing components with their listeners + */ private void setupControls() { JPanel panel = new JPanel(new GridLayout(9,1)); @@ -81,16 +103,24 @@ public class Textfields extends JPanel implements Observer { panel.add(field_scale); button_redraw.addActionListener(new ActionListener() { + /** + * Update the FractalModel's bounds + * @param ae + */ @Override public void actionPerformed(ActionEvent ae) { if (ae.getActionCommand().equals("Redraw")) { - fractalModel.setBorders(getStartX(), getEndX(), getStartY(), getEndY()); + fractalModel.setBounds(getStartX(), getEndX(), getStartY(), getEndY()); } } }); panel.add(button_redraw); button_reset.addActionListener(new ActionListener() { + /** + * Reset the textfields to their default values + * @param ae + */ @Override public void actionPerformed(ActionEvent ae) { field_centerX.setText(INITIAL_CENTER_X); @@ -103,6 +133,10 @@ public class Textfields extends JPanel implements Observer { panel.add(button_reset); checkbox_multiple_swingworkers.addActionListener(new ActionListener() { + /** + * Tell the Grid to use single or multiple SwingWorkers + * @param ae + */ @Override public void actionPerformed(ActionEvent ae) { grid.setUseMultipleSwingWorkers(checkbox_multiple_swingworkers.isSelected()); @@ -119,30 +153,51 @@ public class Textfields extends JPanel implements Observer { return 200; } + /** + * Get the desired low bound on x of the model + * @return + */ protected double getStartX() { double width = 1 / Double.parseDouble(field_scale.getText()); double center = Double.parseDouble(field_centerX.getText()); return center - width / 2; } + /** + * Get the desired high bound on x of the model + * @return + */ protected double getEndX() { double width = 1 / Double.parseDouble(field_scale.getText()); double center = Double.parseDouble(field_centerX.getText()); return center + width / 2; } + /** + * Get the desired low bound on y of the model + * @return + */ protected double getStartY() { double height = 1 / Double.parseDouble(field_scale.getText()); double center = Double.parseDouble(field_centerY.getText()); return center - height / 2; } + /** + * Get the desired high bound on y of the model + * @return + */ protected double getEndY() { double height = 1 / Double.parseDouble(field_scale.getText()); double center = Double.parseDouble(field_centerY.getText()); return center + height / 2; } + /** + * Update the text fields based on the FractalModel's bounds + * @param o + * @param o1 + */ @Override public final void update(Observable o, Object o1) { field_centerX.setText(Float.toString((float) diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/ZoomFrame.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/ZoomFrame.java deleted file mode 100644 index 071bb8c..0000000 --- a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/ZoomFrame.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2015 Camil Staps <info@camilstaps.nl> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.camilstaps.mandelbrot; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.Graphics; -import javax.swing.JFrame; - -/** - * A frame in which zooming is possible - * @author Camil Staps, s4498062 - */ -public class ZoomFrame extends JFrame { - - private Graphics graphics; - - private final Grid drawView; - - public ZoomFrame(String s, Grid drawView) { - super(s); - - this.drawView = drawView; - - setLayout(new BorderLayout()); - getContentPane().setPreferredSize(new Dimension(drawView.getWidth(), drawView.getHeight())); - pack(); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setResizable(false); - setLocationRelativeTo(null); - setVisible(true); - - add(drawView, BorderLayout.CENTER); - } - -} |