Jump to my Home Page Send me a message Check out stuff on GitHub Check out my photography on Instagram Check out my profile on LinkedIn Check out my profile on reddit Check me out on Facebook

Mitch Richling: Julia Sets

Author: Mitch Richling
Updated: 2024-10-31

top_10.png

Table of Contents

1. Introduction

Julia sets are generated in much the same way as the Mandelbrot Set. In both cases sample a rectangular part of the complex plane at a discreet grid of points, iterate a function at each point, and the color the point according to some aspect of the sequence behavior. For the Mandelbrot Set we set \(c\) in the following formula to the grid point, set \(z=0\), and iterate the function.

\[f(z)=z^2+c\]

For Julia sets we fix the value of \(c\) in the same formula to a constant. Then for each grid point, we set \(z\) equal to the grid point, and iterate the function. Note that we have a Julia set associated with each point in the complex plane!

2. Drawing A Picture

Like Mandelbrot Set, the most common choice for how to assign colors is to use the \(L\) function. Here is a C++ program that draws a Julia set for the point \(\frac{2}{5}+\frac{3}{5}i\):

#include "ramCanvas.hpp"

typedef mjr::ramCanvas3c8b::colorType ct;

int main(void) {
  std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
  const int    IMGSIZ = 7680;
  const int    NUMITR = 1024;
  const double MAXZ   = 4.0;
  mjr::ramCanvas3c8b theRamCanvas(IMGSIZ, IMGSIZ, -2.2, 2.2, -2.2, 2.2);

  std::complex<double> c(-0.4, 0.6);
  for(int y=0;y<theRamCanvas.getNumPixY();y++) {
    for(int x=0;x<theRamCanvas.getNumPixX();x++) {
      std::complex<double> z = theRamCanvas.int2real(x, y);
      int count = 0;
      while((std::norm(z)<MAXZ) && (count<=NUMITR)) {
        z=std::pow(z, 2) + c;
        count++;
      }
      if(count < NUMITR)
        theRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20)));
    }
  }
  theRamCanvas.writeTIFFfile("julia.tiff");
  std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime;
  std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl;
}

The above program will generate this picture:

julia_10.png

3. Making A Movie

That's pretty nice, but every point in the complex plane has one of these sets. So let's move a point along one of the Mandelbrot curves around the Mandelbrot Set, and make a movie of the corresponding Julia sets. Here's some code:

#include "ramCanvas.hpp"

typedef mjr::ramCanvas3c8b::colorType ct;

int main(void) {
  std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
  const int    JULIA_MAXITR = 1024;
  const double JULIA_MAXZSQ = 4.0;
  const int    IMGSIZ = 7680/8;
  const int    MANDL_MAXITR = 1024;
  const double MANDL_MAXZSQ = 4.0;
  mjr::ramCanvas3c8b manRamCanvas(IMGSIZ, IMGSIZ, -2.2, 0.8, -1.5, 1.5);

  // Read in our curve -- not much error checking here...
  std::vector<double> mcX;
  std::vector<double> mcY;
  char strBuf[256];
  std::ifstream iStream("../data/mandelbrot_curve_10.txt", std::ios_base::binary);
  if (!(iStream.good()))
    return 1;
  while(iStream.good()) {
    iStream.getline(strBuf, 100, '\n');
    std::string strBufS(strBuf);
    if (strBufS.length() > 0) {
      std::string::size_type pos;
      std::string::size_type tagIdx = strBufS.find_first_of("\t ,");
      mcX.push_back(std::stod(strBufS.substr(0, tagIdx), &pos));
      mcY.push_back(std::stod(strBufS.substr(tagIdx+1), &pos));
    }
  }
  int numPts = static_cast<int>(mcX.size());

  // Draw a refrence mandelbrot set
  for(int y=0;y<manRamCanvas.getNumPixY();y++) {
    for(int x=0;x<manRamCanvas.getNumPixX();x++) {
      std::complex<double> c = manRamCanvas.int2real(x, y);
      std::complex<double> z(0.0, 0.0);
      int count = 0;
      while((std::norm(z)<MANDL_MAXZSQ) && (count<=MANDL_MAXITR)) {
        z=std::pow(z, 2) + c;
        count++;
      }
      if(count < MANDL_MAXITR)
        manRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20)));
    }
  }
  // Draw our curve on the refrence mandelbrot set image
  for(int frame=0; frame<numPts; frame++)
    manRamCanvas.drawLine(mcX[frame], mcY[frame], mcX[(frame+1)%numPts], mcY[(frame+1)%numPts], "white");

  // Draw frames
  for(int frame=0; frame<numPts; frame++) {
    // Draw julia curve frames
    mjr::ramCanvas3c8b julRamCanvas(IMGSIZ, IMGSIZ, -2.2, 2.2, -2.2, 2.2);
    std::cout << "Frame: " << frame << " of " << numPts << std::endl;
    std::complex<double> c = std::complex<double>(mcX[frame], mcY[frame]);
    for(int y=0;y<julRamCanvas.getNumPixY();y++) {
      for(int x=0;x<julRamCanvas.getNumPixX();x++) {
        std::complex<double> z(julRamCanvas.int2realX(x), julRamCanvas.int2realY(y));
        int count = 0;
        while((std::norm(z)<JULIA_MAXZSQ) && (count<=JULIA_MAXITR)) {
          z=std::pow(z, 2) + c;
          count++;
        }
        if(count < JULIA_MAXITR)
          julRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20)));
      }
    }
    // Draw inset image
    mjr::ramCanvas3c8b setRamCanvas(manRamCanvas);
    setRamCanvas.drawFillCircle(c, 0.04, "white");
    setRamCanvas.scaleDownMean(4);
    julRamCanvas.insertCanvas(setRamCanvas, 0, 0);
    julRamCanvas.drawRectangle(0, 0, IMGSIZ/4, IMGSIZ/4, "white");
    // Label the frame
    julRamCanvas.drawString("MJR 2022", mjr::hershey::font::ROMAN_SL_SANSERIF,  IMGSIZ-IMGSIZ/6, 20, "white", 1, 20);
    // Dump out frame
    julRamCanvas.writeTIFFfile("juliaM2_" + mjr::math::str::fmt_int(frame, 3, '0') + ".tiff");
  }

  std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime;
  std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl;
}

And our movie:

juliaM2_50.gif

4. References

Check out the fractals section of my reading list.

All the code used to generate everything on this page may be found on github.