JMandelbrot.java revision 13978:1993af50385d
1/* 2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23package com.sun.swingset3.demos.spinner; 24 25import javax.swing.*; 26import java.awt.*; 27import java.awt.event.*; 28import java.awt.geom.Point2D; 29import java.awt.image.BufferedImage; 30import java.awt.image.DataBufferInt; 31import java.util.List; 32 33import com.sun.swingset3.demos.ResourceManager; 34 35/** 36 * @author Mikhail Lapshin 37 */ 38public class JMandelbrot extends JComponent { 39 40 private static final double EPSILON = 1E-16; 41 private static final int MIN_WIDTH = 50; 42 private static final int MIN_HEIGHT = 50; 43 private static final double ZOOM_RATE = 3; 44 private static final int NUM_OF_THREADS = 4; 45 46 private Point2D center; 47 public static final String CENTER_PROPERTY_NAME = "center"; 48 49 private int maxIteration = 300; 50 public static final String MAX_ITERATION_PROPERTY_NAME = "maxIteration"; 51 52 private Palette palette; 53 public static final String PALETTE_PROPERTY_NAME = "palette"; 54 55 private BufferedImage buffer; 56 private final MandelbrotCalculator[] calculators 57 = new MandelbrotCalculator[NUM_OF_THREADS]; 58 59 private double xLowLimit = -2; 60 private double xHighLimit = 2; 61 private double yLowLimit = -2; 62 private double yHighLimit = 2; 63 private double xScale = 100; 64 private double yScale = 100; 65 private int oldComponentWidth = (int) (xScale * (xHighLimit - xLowLimit)); 66 private int oldComponentHeight = (int) (yScale * (yHighLimit - yLowLimit)); 67 68 public JMandelbrot(int width, int height, Palette palette, 69 ResourceManager resourceManager) { 70 setPreferredSize(new Dimension(width, height)); 71 setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); 72 calcConstants(width, height); 73 setPalette(palette); 74 setToolTipText(resourceManager.getString("SpinnerDemo.toolTip")); 75 installListeners(); 76 } 77 78 private void calcConstants() { 79 calcConstants(getWidth(), getHeight()); 80 } 81 82 private void calcConstants(int width, int height) { 83 if ((width >= MIN_WIDTH) && (height >= MIN_HEIGHT)) { 84 double oldIntervalWidth = xHighLimit - xLowLimit; 85 double oldIntervalHeight = yHighLimit - yLowLimit; 86 double newIntervalWidth 87 = width * oldIntervalWidth / oldComponentWidth; 88 double newIntervalHeight 89 = height * oldIntervalHeight / oldComponentHeight; 90 double xDiff = newIntervalWidth - oldIntervalWidth; 91 double yDiff = newIntervalHeight - oldIntervalHeight; 92 xLowLimit -= xDiff / 2; 93 xHighLimit += xDiff / 2; 94 yLowLimit -= yDiff / 2; 95 yHighLimit += yDiff / 2; 96 buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 97 oldComponentWidth = width; 98 oldComponentHeight = height; 99 setCenter(calcCenter()); 100 } 101 } 102 103 private void installListeners() { 104 addMouseListener(new MouseAdapter() { 105 @Override 106 public void mousePressed(MouseEvent e) { 107 int xCoord = e.getX(); 108 int yCoord = e.getY(); 109 double intervalWidth = xHighLimit - xLowLimit; 110 double intervalHeight = yHighLimit - yLowLimit; 111 double x = intervalWidth * xCoord / getWidth() + xLowLimit; 112 double y = intervalHeight * yCoord / getHeight() + yLowLimit; 113 114 double newIntervalWidth; 115 double newIntervalHeight; 116 if (e.getButton() == MouseEvent.BUTTON1) { 117 boolean limitReached = false; 118 newIntervalWidth = intervalWidth / ZOOM_RATE; 119 if ((newIntervalWidth / getWidth()) < EPSILON) { 120 newIntervalWidth = intervalWidth; 121 limitReached = true; 122 } 123 newIntervalHeight = intervalHeight / ZOOM_RATE; 124 if ((newIntervalHeight / getHeight()) < EPSILON) { 125 newIntervalHeight = intervalHeight; 126 limitReached = true; 127 } 128 if (!limitReached) { 129 xLowLimit = x - (x - xLowLimit) / ZOOM_RATE; 130 yLowLimit = y - (y - yLowLimit) / ZOOM_RATE; 131 } 132 } else { 133 newIntervalWidth = intervalWidth * ZOOM_RATE; 134 newIntervalHeight = intervalHeight * ZOOM_RATE; 135 xLowLimit = x - (x - xLowLimit) * ZOOM_RATE; 136 yLowLimit = y - (y - yLowLimit) * ZOOM_RATE; 137 } 138 139 xHighLimit = xLowLimit + newIntervalWidth; 140 yHighLimit = yLowLimit + newIntervalHeight; 141 142 setCenter(calcCenter()); 143 144 xScale = getWidth() / newIntervalWidth; 145 yScale = getHeight() / newIntervalHeight; 146 147 calculatePicture(); 148 } 149 }); 150 151 addComponentListener(new ComponentListener() { 152 @Override 153 public void componentResized(ComponentEvent e) { 154 calcConstants(); 155 calculatePicture(); 156 repaint(); 157 } 158 159 @Override 160 public void componentMoved(ComponentEvent e) { 161 } 162 163 @Override 164 public void componentShown(ComponentEvent e) { 165 } 166 167 @Override 168 public void componentHidden(ComponentEvent e) { 169 } 170 }); 171 } 172 173 //<snip>Use SwingWorker to asynchronously calculate parts of the picture 174 public void calculatePicture() { 175 int yStep = getHeight() / NUM_OF_THREADS; 176 int yStart = 0; 177 for (int i = 0; i < calculators.length; i++) { 178 if ((calculators[i] != null) && !calculators[i].isDone()) { 179 calculators[i].cancel(true); 180 } 181 int yEnd = i == calculators.length - 1 ? getHeight() : yStart + yStep; 182 calculators[i] = new MandelbrotCalculator(yStart, yEnd); 183 calculators[i].execute(); 184 yStart = yEnd; 185 } 186 } 187 //</snip> 188 189 private Point2D calcCenter() { 190 return new Point2D.Double(xLowLimit + (xHighLimit - xLowLimit) / 2, 191 yLowLimit + (yHighLimit - yLowLimit) / 2); 192 } 193 194 @Override 195 protected void paintComponent(Graphics g) { 196 super.paintComponent(g); 197 g.drawImage(buffer, 0, 0, null); 198 } 199 200 //<snip>Use SwingWorker to asynchronously calculate parts of the picture 201 private class MandelbrotCalculator extends SwingWorker<Object, Object> { 202 203 private final int yStart; 204 private final int yEnd; 205 206 public MandelbrotCalculator(int yStart, int yEnd) { 207 this.yStart = yStart; 208 this.yEnd = yEnd; 209 } 210 211 @Override 212 protected Object doInBackground() throws Exception { 213 int[] data = ((DataBufferInt) buffer.getRaster().getDataBuffer()).getData(); 214 215 double xArr[] = new double[buffer.getWidth()]; 216 217 for (int i = 0; i < xArr.length; i++) { 218 xArr[i] = i / xScale + xLowLimit; 219 } 220 221 double yArr[] = new double[yEnd - yStart]; 222 223 for (int i = 0; i < yArr.length; i++) { 224 yArr[i] = (yStart + i) / yScale + yLowLimit; 225 } 226 227 int i = yStart * buffer.getWidth(); 228 229 for (double y : yArr) { 230 for (double x : xArr) { 231 int value = calcValue(x, y); 232 233 data[i] = value == maxIteration ? 0 : palette.getRgbColor(value); 234 235 i++; 236 } 237 if (Thread.currentThread().isInterrupted()) { 238 return null; 239 } 240 publish(); 241 } 242 return null; 243 } 244 245 private int calcValue(double x, double y) { 246 double x0 = x; 247 double y0 = y; 248 249 for (int i = 0; i < maxIteration; i++) { 250 double x2 = x * x; 251 double y2 = y * y; 252 253 if (x2 + y2 > 4) { 254 return i; 255 } 256 257 y = 2 * x * y + y0; 258 x = x2 - y2 + x0; 259 } 260 261 return maxIteration; 262 } 263 264 @Override 265 protected void process(List<Object> chunks) { 266 repaint(); 267 } 268 } 269 //</snip> 270 271 // Getters and Setters 272 public int getMaxIteration() { 273 return maxIteration; 274 } 275 276 public void setMaxIteration(int maxIteration) { 277 int oldValue = this.maxIteration; 278 this.maxIteration = maxIteration; 279 firePropertyChange(MAX_ITERATION_PROPERTY_NAME, oldValue, maxIteration); 280 palette.setSize(maxIteration); 281 } 282 283 public double getXHighLimit() { 284 return xHighLimit; 285 } 286 287 public double getXLowLimit() { 288 return xLowLimit; 289 } 290 291 public double getYLowLimit() { 292 return yLowLimit; 293 } 294 295 public double getYHighLimit() { 296 return yHighLimit; 297 } 298 299 public Point2D getCenter() { 300 return center; 301 } 302 303 public void setCenter(Point2D coords) { 304 Point2D oldValue = this.center; 305 this.center = coords; 306 307 double width = xHighLimit - xLowLimit; 308 double height = yHighLimit - yLowLimit; 309 310 xLowLimit = coords.getX() - width / 2; 311 xHighLimit = xLowLimit + width; 312 yLowLimit = coords.getY() - height / 2; 313 yHighLimit = yLowLimit + height; 314 315 firePropertyChange(CENTER_PROPERTY_NAME, oldValue, coords); 316 } 317 318 public Palette getPalette() { 319 return palette; 320 } 321 322 public void setPalette(Palette palette) { 323 Palette oldValue = this.palette; 324 palette.setSize(maxIteration); 325 this.palette = palette; 326 firePropertyChange(PALETTE_PROPERTY_NAME, oldValue, palette); 327 } 328} 329