UI Menu Visual Glitch (Flickering) (Mandelbrot Fractal)

I am trying to create this mandelbrot fractal with a UI but as you can see the UI has a weird visual glitch where it flickers the colors of the Mandelbrot fractal i can’t find the cause of this

// MandelbrotStandalone.pde

// Mandelbrot specific parameters
int mandelbrotIterations = 10; // Default to 10 iterations
float mandelbrotZoom = 1;
float mandelbrotOffsetX = 0;
float mandelbrotOffsetY = 0;

PGraphics pg;
boolean rendering = false;
int activeSlider = -1;

void setup() {
  size(1920, 1080);
  pg = createGraphics(width, height);
}

void draw() {
  background(255);
  
  if (!rendering) {
    rendering = true;
    thread("renderMandelbrot");
  }
  
  image(pg, 0, 0);
  drawUI();
}

void renderMandelbrot() {
  pg.beginDraw();
  pg.loadPixels();
  for (int x = 0; x < pg.width; x++) {
    for (int y = 0; y < pg.height; y++) {
      float a = map(x, 0, pg.width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
      float b = map(y, 0, pg.height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);
      float ca = a;
      float cb = b;
      int n = 0;
      while (n < mandelbrotIterations) {
        float aa = a * a - b * b;
        float bb = 2 * a * b;
        a = aa + ca;
        b = bb + cb;
        if (abs(a + b) > 16) {
          break;
        }
        n++;
      }
      float bright = map(n, 0, mandelbrotIterations, 0, 255);
      if (n == mandelbrotIterations) {
        bright = 0;
      }
      pg.pixels[x + y * pg.width] = color(bright);
    }
  }
  pg.updatePixels();
  pg.endDraw();
  
  rendering = false;
}

void drawUI() {
  fill(40);
  noStroke();
  rect(0, 0, 300, height);

  fill(255);
  textAlign(LEFT, CENTER);
  textSize(14);
  
  // Iterations slider
  fill(255);
  text("Iterations: " + mandelbrotIterations, 20, 30);
  fill(200);
  rect(20, 40, 260, 10, 5);
  fill(100);
  float iterKnobX = map(mandelbrotIterations, 1, 100, 20, 280);
  rect(iterKnobX - 5, 35, 10, 20, 5);
  
  // Removed the zoom slider
}

void mousePressed() {
  // Handling mouse interaction for Mandelbrot-specific sliders
  if (mouseX < 300) {
    if (mouseY > 25 && mouseY < 45) {
      activeSlider = 0; // Iterations slider
    }
  }
}

void mouseDragged() {
  // Update slider values based on dragging
  if (activeSlider == 0) {
    mandelbrotIterations = int(map(constrain(mouseX, 20, 280), 20, 280, 1, 100));
  }
}

void mouseReleased() {
  activeSlider = -1;
}

void mouseWheel(MouseEvent event) {
  // Handle zooming with the mouse wheel
  float e = event.getCount();
  float zoomFactor = 1.1;

  // Calculate the mouse position in world coordinates
  float mouseXWorld = map(mouseX, 0, width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
  float mouseYWorld = map(mouseY, 0, height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);

  if (e < 0) {
    mandelbrotZoom *= zoomFactor;
  } else {
    mandelbrotZoom /= zoomFactor;
  }

  // Calculate the new mouse position in world coordinates after zooming
  float newMouseXWorld = map(mouseX, 0, width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
  float newMouseYWorld = map(mouseY, 0, height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);

  // Adjust offsets to keep the zoom centered around the mouse
  mandelbrotOffsetX += mouseXWorld - newMouseXWorld;
  mandelbrotOffsetY += mouseYWorld - newMouseYWorld;
}

So, the problem was that the menu and the fractal were interfering with each other when being drawn. The fractal rendering was running in its own thread, which was causing some conflicts with the shared graphics buffer (PGraphics pg). To solvr this, I did a few things:

Making It Thread-Safe: I used a synchronized block to ensure that only one thread could access and modify the graphics buffer at a time. This prevented the fractal rendering thread and the main drawing thread from stepping on each other’s toes, which was causing the glitches.

Using a Temporary Buffer: I created a temporary graphics buffer for all the fractal rendering. Once the fractal was fully rendered on this temporary buffer, I then updated the main graphics buffer. Doing it this way kept the fractal rendering isolated until it was completely done, which reduced any chance of glitches.

Redrawing the UI Last: I made sure to always redraw the menu after drawing the fractal. This way, the menu was always on top and wouldn’t geet messed up by the fractal rendering.

After making these changes, the glitches were gone…

3 Likes

It would be nice to see how much code you had to add to solve the thread problem. How much work would it be to add color?

For whatever it’s worth, the following is another approach using a Swing slider with the default AWT canvas which may be a little less complicated:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

int _wndW = 1920;
int _wndH = 1080;

PGraphics pg;

// Mandelbrot specific parameters
int mandelbrotIterations = 10; // Default to 10 iterations
float mandelbrotZoom = 1;
float mandelbrotOffsetX = 0;
float mandelbrotOffsetY = 0;

void renderMandelbrot() {
  pg.beginDraw();
  pg.loadPixels();
  for (int x = 0; x < pg.width; x++) {
    for (int y = 0; y < pg.height; y++) {
      float a = map(x, 0, pg.width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
      float b = map(y, 0, pg.height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);
      float ca = a;
      float cb = b;
      int n = 0;
      while (n < mandelbrotIterations) {
        float aa = a * a - b * b;
        float bb = 2 * a * b;
        a = aa + ca;
        b = bb + cb;
        if (abs(a + b) > 16) {
          break;
        }
        n++;
      }
      float bright = map(n, 0, mandelbrotIterations, 0, 255);
      if (n == mandelbrotIterations) {
        bright = 0;
      }
      pg.pixels[x + y * pg.width] = color(bright);
    }
  }
  pg.updatePixels();
  pg.endDraw();
}

void buildWnd() {
  JSlider slider = new JSlider(JSlider.HORIZONTAL, 3, 100, 10);
  slider.setBounds(60, 10, 280, 30);
  frame.add(slider);
  // **** Action **** //
  slider.addChangeListener(new ChangeListener() {
    void stateChanged(ChangeEvent changeEvent) {
      mandelbrotIterations = slider.getValue();
      renderMandelbrot();
    }
  }
  );
  frame.setVisible(true);
  renderMandelbrot();
}

void setup() {
  size(_wndW, _wndH);
  pg = createGraphics(_wndW, _wndH-50);
  surface.setTitle("Swing Component with Default AWT Canvas");
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(0, 0, _wndW, _wndH); // Makes it possible to add swing components
  canvas.setBounds(0, 50, _wndW, _wndH - 50); // Default canvas used for draw()
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread
    }
  }
  );
}

void draw() {
  image(pg, 0, 0);
}
1 Like

the reason i needed that spesific UI type is because it’s a part of a larger program i am working on to render different types of fractals and control them, the UI is dynamic and changes it’s sliders with each different fractal also see this if you have a creative idea …

this is my updated code with the ui fixed as some very basic color implementation

PGraphics pg; // Declare pg as a global variable
boolean rendering = false; // Declare rendering as a global variable
int activeSlider = -1; // Declare activeSlider as a global variable

// Mandelbrot specific parameters
float mandelbrotZoom = 1;
float mandelbrotOffsetX = 0;
float mandelbrotOffsetY = 0;
int mandelbrotIterations = 10; // Default to 10 iterations

void setup() {
  size(1920, 1080);
  pg = createGraphics(width, height); // Create a separate graphics buffer
}

void draw() {
  background(255); // Clear the main canvas

  synchronized (pg) {
    image(pg, 0, 0); // Draw the Mandelbrot fractal from the graphics buffer
  }

  drawUI(); // Draw the UI on top of everything else

  if (!rendering) {
    rendering = true;
    thread("renderMandelbrot");
  }
}

void renderMandelbrot() {
  PGraphics tempPg;
  synchronized (pg) {
    tempPg = createGraphics(pg.width, pg.height);
    tempPg.beginDraw();
    tempPg.colorMode(HSB, 360, 100, 100); // Set color mode to HSB (Hue, Saturation, Brightness)
    tempPg.background(0); // Set background to black
    tempPg.loadPixels();
    for (int x = 0; x < tempPg.width; x++) {
      for (int y = 0; y < tempPg.height; y++) {
        float a = map(x, 0, tempPg.width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
        float b = map(y, 0, tempPg.height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);
        float ca = a;
        float cb = b;
        int n = 0;
        while (n < mandelbrotIterations) {
          float aa = a * a - b * b;
          float bb = 2 * a * b;
          a = aa + ca;
          b = bb + cb;
          if (abs(a + b) > 16) {
            break;
          }
          n++;
        }
        // Extended rainbow effect: map iterations to a wider range of hues
        float hue = map(n, 0, mandelbrotIterations, 0, 360); // Full spectrum of hues from 0 to 360
        float brightness = (n == mandelbrotIterations) ? 0 : 100; // Set brightness to 0 if max iterations reached
        tempPg.pixels[x + y * tempPg.width] = color(hue, 100, brightness);
      }
    }
    tempPg.updatePixels();
    tempPg.endDraw();
    pg = tempPg; // Update the main graphics buffer with the new rendering
  }

  rendering = false;
}

void drawUI() {
  // Draw the UI on the main canvas after the fractal is drawn
  fill(40, 255); // Set alpha to 255 for full opacity
  noStroke();
  rect(0, 0, 300, height);

  fill(255);
  textAlign(LEFT, CENTER);
  textSize(14);
  
  // Iterations slider
  fill(255);
  text("Iterations: " + mandelbrotIterations, 20, 30);
  fill(200);
  rect(20, 40, 260, 10, 5);
  fill(100);
  float iterKnobX = map(mandelbrotIterations, 1, 100, 20, 280);
  rect(iterKnobX - 5, 35, 10, 20, 5);
}

void mousePressed() {
  // Handling mouse interaction for Mandelbrot-specific sliders
  if (mouseX < 300) {
    if (mouseY > 25 && mouseY < 45) {
      activeSlider = 0; // Iterations slider
    }
  }
}

void mouseDragged() {
  // Update slider values based on dragging
  if (activeSlider == 0) {
    mandelbrotIterations = int(map(constrain(mouseX, 20, 280), 20, 280, 1, 100));
  }
}

void mouseReleased() {
  activeSlider = -1;
}

void mouseWheel(MouseEvent event) {
  // Handle zooming with the mouse wheel
  float e = event.getCount();
  float zoomFactor = 1.1;

  // Calculate the mouse position in world coordinates
  float mouseXWorld = map(mouseX, 0, width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
  float mouseYWorld = map(mouseY, 0, height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);

  if (e < 0) {
    mandelbrotZoom *= zoomFactor;
  } else {
    mandelbrotZoom /= zoomFactor;
  }

  // Calculate the new mouse position in world coordinates after zooming
  float newMouseXWorld = map(mouseX, 0, width, -2.5 / mandelbrotZoom + mandelbrotOffsetX, 1 / mandelbrotZoom + mandelbrotOffsetX);
  float newMouseYWorld = map(mouseY, 0, height, -1 / mandelbrotZoom + mandelbrotOffsetY, 1 / mandelbrotZoom + mandelbrotOffsetY);

  // Adjust offsets to keep the zoom centered around the mouse
  mandelbrotOffsetX += mouseXWorld - newMouseXWorld;
  mandelbrotOffsetY += mouseYWorld - newMouseYWorld;
}

Updated with the ability to zoom in and some rainbow colors

3 Likes