| /* |
| * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.swingset3.demos.spinner; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.awt.geom.Point2D; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.DataBufferInt; |
| import java.util.List; |
| |
| import com.sun.swingset3.demos.ResourceManager; |
| |
| /** |
| * @author Mikhail Lapshin |
| */ |
| public class JMandelbrot extends JComponent { |
| |
| private static final double EPSILON = 1E-16; |
| private static final int MIN_WIDTH = 50; |
| private static final int MIN_HEIGHT = 50; |
| private static final double ZOOM_RATE = 3; |
| private static final int NUM_OF_THREADS = 4; |
| |
| private Point2D center; |
| public static final String CENTER_PROPERTY_NAME = "center"; |
| |
| private int maxIteration = 300; |
| public static final String MAX_ITERATION_PROPERTY_NAME = "maxIteration"; |
| |
| private Palette palette; |
| public static final String PALETTE_PROPERTY_NAME = "palette"; |
| |
| private BufferedImage buffer; |
| private final MandelbrotCalculator[] calculators |
| = new MandelbrotCalculator[NUM_OF_THREADS]; |
| |
| private double xLowLimit = -2; |
| private double xHighLimit = 2; |
| private double yLowLimit = -2; |
| private double yHighLimit = 2; |
| private double xScale = 100; |
| private double yScale = 100; |
| private int oldComponentWidth = (int) (xScale * (xHighLimit - xLowLimit)); |
| private int oldComponentHeight = (int) (yScale * (yHighLimit - yLowLimit)); |
| |
| public JMandelbrot(int width, int height, Palette palette, |
| ResourceManager resourceManager) { |
| setPreferredSize(new Dimension(width, height)); |
| setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); |
| calcConstants(width, height); |
| setPalette(palette); |
| setToolTipText(resourceManager.getString("SpinnerDemo.toolTip")); |
| installListeners(); |
| } |
| |
| private void calcConstants() { |
| calcConstants(getWidth(), getHeight()); |
| } |
| |
| private void calcConstants(int width, int height) { |
| if ((width >= MIN_WIDTH) && (height >= MIN_HEIGHT)) { |
| double oldIntervalWidth = xHighLimit - xLowLimit; |
| double oldIntervalHeight = yHighLimit - yLowLimit; |
| double newIntervalWidth |
| = width * oldIntervalWidth / oldComponentWidth; |
| double newIntervalHeight |
| = height * oldIntervalHeight / oldComponentHeight; |
| double xDiff = newIntervalWidth - oldIntervalWidth; |
| double yDiff = newIntervalHeight - oldIntervalHeight; |
| xLowLimit -= xDiff / 2; |
| xHighLimit += xDiff / 2; |
| yLowLimit -= yDiff / 2; |
| yHighLimit += yDiff / 2; |
| buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
| oldComponentWidth = width; |
| oldComponentHeight = height; |
| setCenter(calcCenter()); |
| } |
| } |
| |
| private void installListeners() { |
| addMouseListener(new MouseAdapter() { |
| @Override |
| public void mousePressed(MouseEvent e) { |
| int xCoord = e.getX(); |
| int yCoord = e.getY(); |
| double intervalWidth = xHighLimit - xLowLimit; |
| double intervalHeight = yHighLimit - yLowLimit; |
| double x = intervalWidth * xCoord / getWidth() + xLowLimit; |
| double y = intervalHeight * yCoord / getHeight() + yLowLimit; |
| |
| double newIntervalWidth; |
| double newIntervalHeight; |
| if (e.getButton() == MouseEvent.BUTTON1) { |
| boolean limitReached = false; |
| newIntervalWidth = intervalWidth / ZOOM_RATE; |
| if ((newIntervalWidth / getWidth()) < EPSILON) { |
| newIntervalWidth = intervalWidth; |
| limitReached = true; |
| } |
| newIntervalHeight = intervalHeight / ZOOM_RATE; |
| if ((newIntervalHeight / getHeight()) < EPSILON) { |
| newIntervalHeight = intervalHeight; |
| limitReached = true; |
| } |
| if (!limitReached) { |
| xLowLimit = x - (x - xLowLimit) / ZOOM_RATE; |
| yLowLimit = y - (y - yLowLimit) / ZOOM_RATE; |
| } |
| } else { |
| newIntervalWidth = intervalWidth * ZOOM_RATE; |
| newIntervalHeight = intervalHeight * ZOOM_RATE; |
| xLowLimit = x - (x - xLowLimit) * ZOOM_RATE; |
| yLowLimit = y - (y - yLowLimit) * ZOOM_RATE; |
| } |
| |
| xHighLimit = xLowLimit + newIntervalWidth; |
| yHighLimit = yLowLimit + newIntervalHeight; |
| |
| setCenter(calcCenter()); |
| |
| xScale = getWidth() / newIntervalWidth; |
| yScale = getHeight() / newIntervalHeight; |
| |
| calculatePicture(); |
| } |
| }); |
| |
| addComponentListener(new ComponentListener() { |
| @Override |
| public void componentResized(ComponentEvent e) { |
| calcConstants(); |
| calculatePicture(); |
| repaint(); |
| } |
| |
| @Override |
| public void componentMoved(ComponentEvent e) { |
| } |
| |
| @Override |
| public void componentShown(ComponentEvent e) { |
| } |
| |
| @Override |
| public void componentHidden(ComponentEvent e) { |
| } |
| }); |
| } |
| |
| //<snip>Use SwingWorker to asynchronously calculate parts of the picture |
| public void calculatePicture() { |
| int yStep = getHeight() / NUM_OF_THREADS; |
| int yStart = 0; |
| for (int i = 0; i < calculators.length; i++) { |
| if ((calculators[i] != null) && !calculators[i].isDone()) { |
| calculators[i].cancel(true); |
| } |
| int yEnd = i == calculators.length - 1 ? getHeight() : yStart + yStep; |
| calculators[i] = new MandelbrotCalculator(yStart, yEnd); |
| calculators[i].execute(); |
| yStart = yEnd; |
| } |
| } |
| //</snip> |
| |
| private Point2D calcCenter() { |
| return new Point2D.Double(xLowLimit + (xHighLimit - xLowLimit) / 2, |
| yLowLimit + (yHighLimit - yLowLimit) / 2); |
| } |
| |
| @Override |
| protected void paintComponent(Graphics g) { |
| super.paintComponent(g); |
| g.drawImage(buffer, 0, 0, null); |
| } |
| |
| //<snip>Use SwingWorker to asynchronously calculate parts of the picture |
| private class MandelbrotCalculator extends SwingWorker<Object, Object> { |
| |
| private final int yStart; |
| private final int yEnd; |
| |
| public MandelbrotCalculator(int yStart, int yEnd) { |
| this.yStart = yStart; |
| this.yEnd = yEnd; |
| } |
| |
| @Override |
| protected Object doInBackground() throws Exception { |
| int[] data = ((DataBufferInt) buffer.getRaster().getDataBuffer()).getData(); |
| |
| double xArr[] = new double[buffer.getWidth()]; |
| |
| for (int i = 0; i < xArr.length; i++) { |
| xArr[i] = i / xScale + xLowLimit; |
| } |
| |
| double yArr[] = new double[yEnd - yStart]; |
| |
| for (int i = 0; i < yArr.length; i++) { |
| yArr[i] = (yStart + i) / yScale + yLowLimit; |
| } |
| |
| int i = yStart * buffer.getWidth(); |
| |
| for (double y : yArr) { |
| for (double x : xArr) { |
| int value = calcValue(x, y); |
| |
| data[i] = value == maxIteration ? 0 : palette.getRgbColor(value); |
| |
| i++; |
| } |
| if (Thread.currentThread().isInterrupted()) { |
| return null; |
| } |
| publish(); |
| } |
| return null; |
| } |
| |
| private int calcValue(double x, double y) { |
| double x0 = x; |
| double y0 = y; |
| |
| for (int i = 0; i < maxIteration; i++) { |
| double x2 = x * x; |
| double y2 = y * y; |
| |
| if (x2 + y2 > 4) { |
| return i; |
| } |
| |
| y = 2 * x * y + y0; |
| x = x2 - y2 + x0; |
| } |
| |
| return maxIteration; |
| } |
| |
| @Override |
| protected void process(List<Object> chunks) { |
| repaint(); |
| } |
| } |
| //</snip> |
| |
| // Getters and Setters |
| public int getMaxIteration() { |
| return maxIteration; |
| } |
| |
| public void setMaxIteration(int maxIteration) { |
| int oldValue = this.maxIteration; |
| this.maxIteration = maxIteration; |
| firePropertyChange(MAX_ITERATION_PROPERTY_NAME, oldValue, maxIteration); |
| palette.setSize(maxIteration); |
| } |
| |
| public double getXHighLimit() { |
| return xHighLimit; |
| } |
| |
| public double getXLowLimit() { |
| return xLowLimit; |
| } |
| |
| public double getYLowLimit() { |
| return yLowLimit; |
| } |
| |
| public double getYHighLimit() { |
| return yHighLimit; |
| } |
| |
| public Point2D getCenter() { |
| return center; |
| } |
| |
| public void setCenter(Point2D coords) { |
| Point2D oldValue = this.center; |
| this.center = coords; |
| |
| double width = xHighLimit - xLowLimit; |
| double height = yHighLimit - yLowLimit; |
| |
| xLowLimit = coords.getX() - width / 2; |
| xHighLimit = xLowLimit + width; |
| yLowLimit = coords.getY() - height / 2; |
| yHighLimit = yLowLimit + height; |
| |
| firePropertyChange(CENTER_PROPERTY_NAME, oldValue, coords); |
| } |
| |
| public Palette getPalette() { |
| return palette; |
| } |
| |
| public void setPalette(Palette palette) { |
| Palette oldValue = this.palette; |
| palette.setSize(maxIteration); |
| this.palette = palette; |
| firePropertyChange(PALETTE_PROPERTY_NAME, oldValue, palette); |
| } |
| } |