
// SeqMandelbrot.java
// Yohan Liyanage, 2008
// Modified by Andrew Davison, ad@coe.psu.ac.th, May 2023

/* Compute a Mandelbrot image: given an (x, y) coordinate in the 
   screen image, compute an integer value for that pixel and 
   map it to a color. 

   Each pixel can be computed independently of the rest, and fairly 
   quickly because the computation isn't very complex. This means
   that when we move to the concurrent version with workers that we
   should allocate multiple pixel calculations to each worker to 
   give them something substantial to do.

   To prepare for this, the image is sequentially rendered using 
   calcMandelbrot(int start, int numLines) which calculates
   *lines* of the image starting from line == start.

   Based on code from:
      JavaSpaces Principles, Patterns, and Practice
      Eric Freeman, Susanne Hupfer, and Ken Arnold
      Addison Wesley, 1999
      ch.6, Sec. 6.1.1., p.155

  Usage:
    > javac SeqMandelbrot.java
    > java SeqMandelbrot

  Typical Execution:
    Starting Computation...
    Waiting
    Notified
    Time taken:  2021ms

  The concurrent version takes about 400 ms.

  Other version: 
    * MandelSpace.java
*/

import javax.swing.*;
import java.awt.*;


public class SeqMandelbrot extends JFrame
{
  private static final int NUM_ITERS = 10000;
                                // was 1000000

  private static final int MIN_LINES = 3;   
    // min no. of lines per task (>= 1)

  private static final int MAX_LINES = 10;   
      // max no. of lines per task (>= MIN_LINES)

  // dimensions of window
  private static final int WIDTH = 600;
  private static final int HEIGHT = 600;

  // region for which Mandelbrot is being computed
  private double x1 = -2.25;   // (x1,y1) to (x2, y2) 
  private double x2 = 3.0;
  private double y1 = -1.8;
  private double y2 = 3.3;

  // used to determine no. of lines used in a calculation
  private int yMid = HEIGHT/2;

  // image and its graphic
  private Image mandelIm;
  private Graphics mandelGraphic;

  private boolean isFinished = false;


  public SeqMandelbrot() 
  {
    super("Mandelbrot Rendering...");
    setSize(WIDTH, HEIGHT);
    setResizable(false);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setVisible(true);

    // create image for drawing the Mandelbrot set
    mandelIm = createImage(WIDTH, HEIGHT);
    mandelGraphic = mandelIm.getGraphics();
    mandelGraphic.setColor(Color.BLACK);  // black background
    mandelGraphic.fillRect(0, 0, WIDTH, HEIGHT);

    repaint();

    new Thread(() -> render()).start();
        /* render in a separate thread, so it doesn't impact
           the drawing done by Swing */
  }  // end of SeqMandelbrot()



  public void paint(Graphics g)
  // draw the current version of the Mandelbrot image;
  // this may only be partially completed
  {
    g.drawImage(mandelIm, 0, 0, null);
    if (isFinished)
      setTitle("Mandelbrot Completed");
  } /* paint */


  private void render()
  /* Divide the rendering into groups of lines.

     Rather than doing an equal division, the number
     of lines passed to calcMandelbrot() will depend
     on the line position. At the top and bottom of
     the image, the number will be close to MAX_LINES,
     while in the center the number will be smaller,
     closer to MIN_LINES. The reason for this is that
     calculating a pixel value is more work at the
     center of the Mandelbrot.
  */
  { long startTime = System.currentTimeMillis();

    int numLines = 0;
    for (int i = 0; i < HEIGHT; i += numLines) {
      double h = calcLinesHeight(i);
      numLines = (int) ((MAX_LINES - MIN_LINES)*h + MIN_LINES);
      // System.out.println("render i, numLines: " + i + " " + numLines);
      int[][] pixels = calcMandelbrot(i, numLines);
      updateImage(pixels, i, numLines);
    }
    isFinished = true;

    long elapsedTime = System.currentTimeMillis() - startTime;
    System.out.println("Time taken:  " + elapsedTime + "ms");
  } /* render */



  private double calcLinesHeight(int i)
  {  return Math.abs((double) (i - yMid)) / yMid;   }


  private int[][] calcMandelbrot(int start, int numLines)
  /* calculate the Mandelbrot pixel values starting
     from line == start, and for numLines
  */
  {
    double x, y, xx, a;

    int end = start + numLines;
    double da = x2/WIDTH;
    double db = y2/HEIGHT;

    double b = y1;

    int[][] pixels = new int[WIDTH][numLines];

    for (int i = 0; i < start; i++)
      b = b + db;

    int k = 0;
    for (int i = start; i < end; i++, k++) {
      a = x1;
      for (int j = 0; j < WIDTH; j++) {
        int n = 0;
        x = 0.0;
        y = 0.0;
        while ((n < NUM_ITERS) && ((x*x)+(y*y) < 4.0)) {
          xx = x*x - y*y + a;
          y = 2*x*y + b;
          x = xx;
          n++;
        }
        pixels[j][k] = n;
        a = a + da;
      }
      b = b + db;
    }
    return pixels;
  } /* calcMandelbrot */



  private void updateImage(int[][] pixels, int start, int numLines)
  /* add the pixel colors to the image starting
     from line == start, and for numLines
  */
  {
    int y = 0;   // first line of pixels array
    for (int line = start; y < numLines; y++, line++)
      for (int x = 0; x < WIDTH; x++) {
        int pixel = pixels[x][y];
        Color c = getPixelColor(pixel);
        mandelGraphic.setColor(c);
        mandelGraphic.fillRect(x, line, 1, 1);
      }

    repaint();   // show the ongoing rendering
  } /* updateImage */


  private Color getPixelColor(int pixel)
  // map a pixel to a mix of red and blue
  {
    Color c;
    int val = pixel % 101;   // 0-100
    if (val == 100)
      c = new Color(0, 0, 0);  // black
    else if (val > 90)
      c = new Color(val*2, 0, (val - 90)*25);
    else if (val > 80)
      c = new Color(val*2, 0, 0);
    else if (val > 60)
      c = new Color(val*3, 0, val);
    else if (val > 20)
      c = new Color(val*4, 0, val*2);
    else if (val > 10)
      c = new Color(val*5, 0, val*10);
    else
      c = new Color(0, 0, val*20);
    return c;
  } /* getPixelColor */



  // ---------------------------------------

  public static void main(String[] args)
  {  new SeqMandelbrot();  } 


}  // end of SeqMandelbrot class