javascript - 生成的 Julia 集和维基百科图像之间的差异

标签 javascript image canvas html5-canvas fractals

我编写了一个小型 JavaScript 程序来查看 Julia 设置图像 [link] 。我一直在实现多种迭代算法(不仅仅是 z^2 + c),并且最近实现了 exp(z)。但是,我的输出与维基百科页面图像不同。以下是 c 值 -0.65 的比较:

我的图片: My julia set

维基百科图像enter image description here

显然,他们的输出更加丰富多彩,并且显示了更多细节!我在想这可能是我的实现中的错误,或者可能是我的着色算法?或者维基百科的结果是通过某种不同的方式生成的?下面是一个仅包含 e^z + c 算法和相关函数的 MVE。如果您想随意体验整个程序,它是托管的 on my site

//globals
var MAXITERATION = 2500;
var BOUNDARY = 4;
var CANVASID = "juliaDraw";
var CANVAS = document.getElementById("juliaDraw");
var CONTEXT = document.getElementById("juliaDraw").getContext('2d');
var HEIGHT = 750;
var WIDTH = 750;
var CONVERGENCEITERCOUNT = 2500;

/** Complex number functions **/

function complexNum(real, imaginary) {
  this.real = real;
  this.imaginary = imaginary;
  return this;
}

// This is the bit that might be a problem but it seems to return correct results? 
function raiseNumberToComplexPower(x, c) {
  var s = Math.pow(x, c.real);
  var pow = c.imaginary * Math.log(x);
  var num = new complexNum(Math.cos(pow), Math.sin(pow));
  return scalarComplex(s, num);
}

function addComplex(c1, c2) {
  var real = c1.real + c2.real;
  var imaginary = c1.imaginary + c2.imaginary;
  return new complexNum(real, imaginary);
}

function multComplex(c1, c2) {
  var real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
  var imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
  return new complexNum(real, imaginary);
}

function scalarComplex(s, c) {
  return new complexNum(c.real * s, c.imaginary * s);
}

function getComplexModulus(c) {
	return Math.sqrt((c.real * c.real) + (c.imaginary * c.imaginary));
}

/** Drawing and manipulation **/

function createArray(length) {
  var arr = new Array(length || 0),
    i = length;
  if (arguments.length > 1) {
    var args = Array.prototype.slice.call(arguments, 1);
    while (i--) arr[length - 1 - i] = createArray.apply(this, args);
  }
  return arr;
}

function drawJulia() {
  CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
  var start = new complexNum(-2, 2);
  var c = new complexNum(readInput('realValue') * 1, readInput('imagValue') * 1);
  STARTPOS = {
    real: -2,
    imaginary: 2
  }
  RANGE = 4;
  plotJuliaSet(CANVASID, c);
}

function plotJuliaSet(canvasID, c) {
  var complexNumberArray = createArray(WIDTH + 1, HEIGHT + 1);
  var doesPointEscapeArray = createArray(WIDTH + 1, HEIGHT + 1);
  ITERALGO = exponential;
  for (var x = 0; x <= WIDTH; x++) {
    for (var y = 0; y <= HEIGHT; y++) {
      complexNumberArray[x][y] = new coordsToComplex({
        x: x,
        y: y
      });
      complexNumberArray[x][y] = complexNumberArray[x][y];
      doesPointEscapeArray[x][y] = doesPointEscape(c, complexNumberArray[x][y]);
      if (doesPointEscapeArray[x][y] >= 0) {
        drawPointOnCanvas(x, y, getColor(doesPointEscapeArray[x][y]));
      } else {
        drawPointOnCanvas(x, y, 'black');
      }
    }
  }
  console.log('done');
}

function doesPointEscape(c, complexNum) {
  var iterations = 0;
  var iterationsToEscape = -1;
  var escaped = false;
  while ((!escaped) && (iterations < MAXITERATION)) {
    if (getComplexModulus(complexNum) > BOUNDARY) {
      escaped = true;
      iterationsToEscape = iterations;
    }
    complexNum = ITERALGO(complexNum, c);
    iterations++;
  }
  return iterationsToEscape;
}

function exponential(complexNum, c) {
  // e^z + c
  return addComplex(raiseNumberToComplexPower(Math.E, complexNum), c);
}

function drawPointOnCanvas(x, y, color) {
  CONTEXT.fillStyle = color;
  CONTEXT.fillRect(x, y, 1, 1);
}

function getColor(iterations) {
  //console.log("Iterations: "+getBaseLog(iterations+1,255));
  var color = "rgb(" + Math.floor((8 * iterations) % 255) + "," + Math.floor(2 * iterations % 255) + "," + Math.floor(255 - ((8 * iterations) % 255)) + ")";
  //console.log(color);
  return color;
}

function coordsToComplex(coordinates) {
  return {
    real: ((coordinates.x / WIDTH) * RANGE + STARTPOS.real),
    imaginary: ((coordinates.y / HEIGHT) * -RANGE + STARTPOS.imaginary)
  };
}

function complexToCoords(c) {
  return {
    x: ((c.real - STARTPOS.real) / (RANGE)) * WIDTH,
    y: ((c.imaginary - STARTPOS.imaginary) / -(RANGE)) * HEIGHT
  };
}

function readInput(inputID) {
  return document.getElementById(inputID).value;
}
.desc {
  float: right;
  width: 300px;
}
#juliaDraw {
  border: 1px dotted;
  float: left;
}
.canvasWrapper canvas {
  position: absolute;
  top: 0;
  left: 0;
}
<div class="desc">
  <h1>Julia Set Viewer</h1> 
  <form>
    <label>Real:
      <input type="text" id="realValue" value="-0.65">
    </label>
    <br>
    <label>Imag:
      <input type="text" id="imagValue" value="0">
    </label>
    <input type="button" onClick="drawJulia()" value="Draw">
  </form>
</div>
<canvas id="juliaDraw" width=750 height=750 onClick="drawZoomJulia()"></canvas>

最佳答案

这个问题实际上是一个数学问题。具体来说,您正在使用为指数函数上的多项式构建的转义准则。在所有情况下,您都会进行迭代,直到迭代的绝对值超过 BOUNDARY 并且 BOUNDARY 在开始时设置为 4。维基百科图像显然使用了更大的转义值。在下面的两张图中,我们比较了逃逸半径 4 和逃逸半径 100;较大的逃逸半径更像维基百科的图像:

enter image description here

但是,坦率地说,维基百科的图像也是不正确的。 Julia 集合图片背后的全部要点是尝试将复杂平面分解为两个集合:一组动态简单,另一组动态复杂。我们迭代多项式直到绝对值很大,因为所有具有大绝对值的点都会逃逸到无穷大。对于您的函数 exp(z)-0.65 来说,情况并非如此。例如,如果 z=-100,则 abs(z) 相当大,但 exp(-100)-0.65 非常接近-0.65。就指数函数的绝对值而言,没有好的逃逸标准。

最有效的方法是迭代您的函数,直到其真实部分变大。就好像复杂平面的右侧有一个逃生 channel 。如果我们迭代你的函数直到实部超过 100,我们会得到如下结果:

enter image description here

关于javascript - 生成的 Julia 集和维基百科图像之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40380709/

相关文章:

algorithm - 随机生成弯曲/波浪路径

javascript - 将 excel 格式的数据从 Web 复制到剪贴板

Javascript RegExp 匹配怪异

c# - 合并 2 种颜色以制作透明叠加层?

css - 带有 glsl 着色器的 Canvas 在某些 css 变换比例下中断

jquery - 如果使用jquery验证和signature_pad输入签名,如何添加检查?

javascript - 找到类(class)的高度并申请另一个类(class)

javascript - Fetch 没有给我回复

php - 使用 JQuery 或 Javascript 刷新图像

javascript - 无法读取express.js中上传的图像