diff options
author | Camil Staps | 2015-06-05 14:22:12 +0200 |
---|---|---|
committer | Camil Staps | 2015-06-05 14:22:12 +0200 |
commit | 6928ed87c0a9fa7f746c4331d0a1079d15f09051 (patch) | |
tree | aff3df7dbb53a2ffb3382d36334fcff993947415 /Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java | |
parent | Merge branch 'master' of github:camilstaps/OO1415 (diff) |
Everything works except for multiple swingworkers
Diffstat (limited to 'Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java')
-rw-r--r-- | Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java new file mode 100644 index 0000000..bf21b43 --- /dev/null +++ b/Week15 Mandelbrot/src/com/camilstaps/mandelbrot/Grid.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2015 Camil Staps + */ +package com.camilstaps.mandelbrot; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Observable; +import java.util.Observer; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.SwingWorker; + +/** + * + * @author camilstaps + */ +public class Grid extends JPanel implements Observer, MouseListener, MouseMotionListener { + + private final FractalModel fractalModel; + + private final BufferedImage image; + private final WritableRaster raster; + private final int width, height; + private static final int DEFAULT_WIDTH = 400, DEFAULT_HEIGHT = 400; + + private int pixelCounter; + private static final int COUNTER_MAX = 1000; + + private static final int REPETITIONS_MAX = 1000, STEPS = 50; + + private final int[] colors; + + private Updater updater; + + private int start_x, start_y, old_x, old_y; + private boolean dragging = false; + Graphics graphics; + + private ProgressView progressView; + + public Grid(FractalModel fractalModel) { + this.fractalModel = fractalModel; + fractalModel.addObserver(this); + + width = DEFAULT_WIDTH; + height = DEFAULT_HEIGHT; + + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + raster = image.getRaster(); + + setPreferredSize(new Dimension(width, height)); + + colors = new int[REPETITIONS_MAX + 1]; + for (int i = 0; i <=REPETITIONS_MAX; i++) { + colors[i] = Color.HSBtoRGB( + 0.07f, + 0.5f + 0.5f * (float) i / (float) REPETITIONS_MAX, + 1); + } + + addMouseListener(this); + addMouseMotionListener(this); + + update(fractalModel, null); + } + + public ProgressView getProgressView() { + if (progressView == null) { + progressView = new ProgressView(); + } + return progressView; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public synchronized void update(Observable o, Object o1) { + if (updater != null) { + updater.stop(); + updater.cancel(true); + } + updater = new Updater(); + updater.execute(); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + g.drawImage(image, 0, 0, null); + } + + public void setPixel(int x, int y, int[] rgb) { + raster.setPixel(x, y, rgb); + pixelCounter++; + if (pixelCounter > COUNTER_MAX) { + pixelCounter = 0; + repaint(); + } + } + + protected double getRealX(int pixel_x) { + return ((double) pixel_x * (fractalModel.getEndX() - fractalModel.getStartX()) / (double) width) + fractalModel.getStartX(); + } + + protected double getRealY(int pixel_y) { + return ((double) pixel_y * (fractalModel.getEndY() - fractalModel.getStartY()) / (double) height) + fractalModel.getStartY(); + } + + @Override + public void mouseClicked(MouseEvent me) { + if ((me.getModifiers() & InputEvent.SHIFT_MASK) != 0) { + zoomOut(me); + } else { + zoomIn(me); + } + } + + @Override + public void mousePressed(MouseEvent me) { + old_x = start_x = me.getX(); + old_y = start_y = me.getY(); + } + + @Override + public synchronized void mouseReleased(MouseEvent me) { + if (me.getX() != start_x || me.getY() != start_y) { + zoomBox(me); + } + + dragging = false; + notifyAll(); + } + + /** + * Zoom in with factor 2 + * @param me + */ + private void zoomIn(MouseEvent me) { + double offset_x = (fractalModel.getEndX() - fractalModel.getStartX()) / 4; + double offset_y = (fractalModel.getEndY() - fractalModel.getStartY()) / 4; + + double newCenterX = getRealX(me.getX()); + fractalModel.setStartX(newCenterX - offset_x); + fractalModel.setEndX(newCenterX + offset_x); + + double newCenterY = getRealY(me.getY()); + fractalModel.setStartY(newCenterY - offset_y); + fractalModel.setEndY(newCenterY + offset_y); + } + + /** + * Zoom out with factor 2 + * @param me + */ + private void zoomOut(MouseEvent me) { + double offset_x = fractalModel.getEndX() - fractalModel.getStartX(); + double offset_y = fractalModel.getEndY() - fractalModel.getStartY(); + + double newCenterX = getRealX(me.getX()); + fractalModel.setStartX(newCenterX - offset_x); + fractalModel.setEndX(newCenterX + offset_x); + + double newCenterY = getRealY(me.getY()); + fractalModel.setStartY(newCenterY - offset_y); + fractalModel.setEndY(newCenterY + offset_y); + } + + /** + * Zoom to the selected box. + * Intentionally chose to let the user only select squares, otherwise it's too easy to mess up the scale + * @param me + */ + private void zoomBox(MouseEvent me) { + double newStartX = getRealX(start_x), + newEndX = getRealX(me.getX()), + newStartY = getRealY(start_y), + newEndY = getRealY(start_y + me.getX() - start_x); + + fractalModel.setStartX(newStartX); + fractalModel.setEndX(newEndX); + fractalModel.setStartY(newStartY); + fractalModel.setEndY(newEndY); + } + + @Override + public void mouseEntered(MouseEvent me) {} + + @Override + public void mouseExited(MouseEvent me) {} + + @Override + public void mouseDragged(MouseEvent me) { + Graphics g = getSafeGraphics(); + if (g != null) { + if (dragging) { + eraseZoombox(); + } + g.drawRect(start_x, start_y, me.getX() - start_x, me.getX() - start_x); + old_y = old_x = me.getX(); + dragging = true; + } + } + + /** + * Semi-singleton construction for graphics + * @return + */ + private Graphics getSafeGraphics() { + if (graphics == null) { + graphics = getGraphics(); + graphics.setXORMode(Color.white); + } + return graphics; + } + + /** + * Erase the old zoombox if it exists + */ + private void eraseZoombox() { + if (start_x < 0 || start_y < 0 || old_x < 0 || old_y < 0) + return; + getSafeGraphics().drawRect(start_x, start_y, old_x - start_x, old_y - start_x); + } + + @Override + public void mouseMoved(MouseEvent me) {} + + protected class Updater extends SwingWorker<Map<Point,Integer>, Map<Point,Integer>> { + private boolean doneProcessing = true, stop = false; + + @Override + protected Map<Point, Integer> doInBackground() throws Exception { + Map<Point,Integer> results = new HashMap<>(); + + for (int repetitions = REPETITIONS_MAX / STEPS; repetitions <= REPETITIONS_MAX && !stop; repetitions += REPETITIONS_MAX / STEPS) { + for (int x = 0; x < width && !stop; x++) { + for (int y = 0; y < height && !stop; y++) { + Point p = new Point(x, y); + results.put(p, fractalModel.getMandelNumber(getRealX(x), getRealY(y), repetitions)); + } + } + + // Since we're always publishing the same object, there's no point in calling publish if process() wasn't called yet + if (doneProcessing && !stop) { + doneProcessing = false; + publish(results); + } + setProgress(repetitions * 100 / REPETITIONS_MAX); + } + + return results; + } + + @Override + protected synchronized void process(List<Map<Point,Integer>> results) { + if (progressView != null) { + progressView.setValue(getProgress()); + } + + for (Map<Point,Integer> resultMap : results) { + if (stop || dragging) + break; + + for (Entry<Point,Integer> result : resultMap.entrySet()) { + int rgbValue = colors[result.getValue()]; + int[] rgb = { (rgbValue >> 16) & 0xff, (rgbValue >> 8) & 0xff, rgbValue & 0xff }; + setPixel(result.getKey().x, result.getKey().y, rgb); + } + } + + doneProcessing = true; + } + + @Override + public void done() { + updater = null; + } + + public void stop() { + stop = true; + } + } + + protected class Point { + int x, y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object another) { + if (another == null || another.getClass() != Point.class) { + return false; + } + Point that = (Point) another; + return that.x == x && that.y == y; + } + + @Override + public int hashCode() { + return (x << 8) | y; + } + } + + protected class ProgressView extends JProgressBar { + + public ProgressView() { + setMaximum(100); + setMinimum(0); + } + + @Override + public void setValue(int n) { + setVisible(n != 100); + super.setValue(n); + } + + } + +} |