分形/fractalzoomer
分形缩放器是 一个分形程序,由 hrkalona (Kalonakis Christos ) 开发。
- 超过 150 种 分形函数
- 平面变换
- 颜色选项
- 图像滤镜
- 朱利亚集和朱利亚图
- 轨道追踪
- 3D 高度图
- 使用 边界追踪算法 进行更快的计算
- 用户公式
- 用户可编译函数
- 域着色
- 极坐标投影
- 跨平台(基于 Java)
Windows
- 下载 exe 文件
- 确保你已安装 JRE 1.8。
- 以管理员身份运行(为了复制一些 lib 文件)
Linux
- 下载 jar 文件
- 确保你已安装 openjdk-jre。
- 如果你想使用编辑/编译代码功能,你需要安装 openjdk-jdk。
- 要执行,在终端中使用:java -jar [JarFileName.jar]
java -jar ./FractalZoomer.jar
为了创建图像,我们首先需要将像素坐标转换为复数。
为了我们的目的,假设图像在每个维度上具有相同的大小。
然后我们需要迭代复数,并根据最终结果,必须为像素分配颜色。
void createImage(double xCenter, double yCenter, double size, int image_size, int maxIterations)
{
double dx = size / image_size;
double dy = size / image_size;
double xCenterOffset = xCenter - size * 0.5;
double yCenterOffset = yCenter + size * 0.5;
for(int y = 0; y < image_size; y++) {
for(int x = 0; x < image_size; x++) {
double result = Iterate(new Complex(xCenterOffset + dx * x, yCenterOffset - dy * y), maxIterations);
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
//set the color to the image at (y,x)
}
}
}
double Iterate(Complex value, int maxIterations)
{
Complex z = value;
for(int iterations = 0; iterations < maxIterations; iterations++)
{
if(triggeredCriterion(z) == true) // either escaped or converged
{
return outcoloring_algorithm.getResult(); //add the required arguments
}
z = function(z); // update the value based on the fractal type e.g. z = z^2 + c
}
return incoloring_algorithm.getResult(); //add the required arguments
}
例如,这是逃逸类型分形的通用代码。
double calculateFractal(Complex pixel) {
return calculateFractalWithPeriodicity(plane.transform(rotation.rotate(pixel))); // apply plane transformation and rotation
}
double calculateFractalWithoutPeriodicity(Complex pixel) {
int iterations = 0;
Complex tempz = new Complex(pertur_val.getValue(init_val.getValue(pixel))); // initial value and perturbation
Complex[] complex = new Complex[2];
complex[0] = tempz;//z
complex[1] = new Complex(pixel);//c
Complex zold = new Complex(); // zold = 0, will store z(n-1)
Complex zold2 = new Complex(); // zold2 = 0, will store z(n-2)
Complex start = new Complex(complex[0]);
Complex[] vars = createGlobalVars();
for(; iterations < max_iterations; iterations++) {
if(bailout_algorithm.escaped(complex[0], zold, zold2, iterations, complex[1], start, vars)) { // check if escaped
Object[] object = {iterations, complex[0], zold, zold2, complex[1], start, vars};
return out_color_algorithm.getResult(object);
}
zold2.assign(zold); // zold2 = zold
zold.assign(complex[0]); // zold = z
function(complex); // z = ...., for example z^2 + c
}
Object[] object = {complex[0], zold, zold2, complex[1], start, vars};
return in_color_algorithm.getResult(object);
}
这是收敛类型分形的通用代码。
double calculateFractalWithoutPeriodicity(Complex pixel) {
int iterations = 0;
double temp = 0;
Complex[] complex = new Complex[1];
complex[0] = new Complex(pixel);//z
Complex zold = new Complex(); // zold = 0, will store z(n-1)
Complex zold2 = new Complex(); // zold2 = 0, will store z(n-2)
Complex start = new Complex(complex[0]);
Complex[] vars = createGlobalVars();
for (; iterations < max_iterations; iterations++) {
if((temp = complex[0].distance_squared(zold)) <= convergent_bailout) { // check if converged
Object[] object = {iterations, complex[0], temp, zold, zold2, pixel, start, vars};
return out_color_algorithm.getResult(object);
}
zold2.assign(zold); // zold2 = zold
zold.assign(complex[0]); // zold = z
function(complex); // z = ...., for example z - (z^3-1)/(3*z^2)
}
Object[] object = {complex[0], zold, zold2, pixel, start, vars};
return in_color_algorithm.getResult(object);
}
该算法基于 Jos Leys 的论文中描述的算法[1] 该代码可以在 Kleinian.java 类中找到[2]
检查是否超过某个预定义边界的分形通常使用此逃逸检查标准 .
boolean escaped(Complex z, double bailout)
{
if(z.norm() >= bailout)
{
return true;
}
return false;
}
在大多数情况下,使用欧几里得范数(2),但你也可以使用任何其他范数。
通常,收敛到复数的判断,使用以下收敛标准 .
boolean converged(Complex z, Complex z_old, double error)
{
if(z.sub(z_old).norm() <= error)
{
return true;
}
return false;
}
收敛标准用于求根方法,用于确定是否找到根。
下面介绍了一些求根方法的例子。所有示例图像都使用 作为它们的函数。
为了选择分母的符号,您应该检查 和 ,以确定具有较大范数的那个。
为了选择分母的符号,您应该检查两个 和 ,以确定哪个具有较大的范数。
其中 deg 是度数,可以是任何复数。
为了选择分母的符号,您应该检查两个 和 ,以确定哪个具有较大的范数。
逃逸条件 定义为停止迭代的标准。第二个停止迭代标准是当迭代次数超过最大迭代次数时。
在软件的当前实现中,它只能更改逃逸类型分形,但概括显然适用于任何迭代方法。
如果您想为收敛类型分形设置逃逸条件,例如牛顿法用于 ,您需要将用户公式设置为 z - (z^3-1) / 3*z^2,并将算法设置为逃逸类型。
此时,您可以使用首选的停止条件设置用户逃逸算法。
boolean circleCriterion(Complex z, bouble bailout)
{
if(z.norm() >= bailout)
{
return true;
}
return false;
}
boolean squareCriterion(Complex z, bouble bailout)
{
if(Math.max(z.getAbsRe(), z.getAbsIm()) >= bailout)
{
return true;
}
return false;
}
boolean rhombusCriterion(Complex z, bouble bailout)
{
if(z.getAbsRe() + z.getAbsIm() >= bailout)
{
return true;
}
return false;
}
boolean nNormCriterion(Complex z, bouble bailout, double n_norm)
{
if(Math.pow(Math.pow(z.getAbsRe(), n_norm) + Math.pow(z.getAbsIm(), n_norm), 1 / n_norm) >= bailout)
{
return true;
}
return false;
}
boolean stripCriterion(Complex z, bouble bailout)
{
if(z.getAbsRe() >= bailout)
{
return true;
}
return false;
}
boolean halfplaneCriterion(Complex z, bouble bailout)
{
if(z.getRe() >= bailout)
{
return true;
}
return false;
}
boolean fieldLinesCriterion(Complex z, Complex zold, bouble bailout)
{
if(z.getRe() / zold.getRe() >= bailout&& z.getIm() / zold.getIm() >= bailout)
{
return true;
}
return false;
}
其中 zold 是 z 的前一个值。
旋转在技术上类似于平面变换。在我们从像素坐标获得复数后,我们可以使用旋转函数对复数进行变换。
使用欧拉公式
我们也可以围绕任意点旋转。在这种情况下,旋转定义为
Complex rotate(Complex value, double angle, Complex rotationCenter) {
Complex temp = value.sub(rotationCenter); // subtract the rotation center
Complex rotation = new Complex(Math.cos(angle), Math.sin(angle));
return temp.times(rotation).plus(center); // multiply by the rotation and re-add the rotation center
}
Fractal Zoomer 的极坐标投影是使用 pfract 的实现 实现的。它是一个 指数映射 的例子,它将复平面映射到半径和角度。
void createPolarProjectionImage(double xCenter, double yCenter, double size, int image_size, int maxIterations)
{
double center = Math.log(size);
double dy = (2 * Math.PI) / image_size;
double dx = dy;
double start = center - dx * image_size * 0.5;
for(int y = 0; y < image_size; y++) {
for(int x = 0; x < image_size; x++) {
double sf = Math.sin(y * dy);
double cf = Math.cos(y * dy);
double r = Math.exp(x * dx + start);
double result = Iterate(new Complex(xCenter + r * cf, yCenter + r * sf), maxIterations);
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
//set the color to the image at (y,x)
}
}
}
如果你想在 Fractal Zoomer 中看到这个大图像的一部分,将实坐标设置为 -1.786440255563638。如果你开始缩放,你会发现缩放类似于 https://commons.wikimedia.org/wiki/File:Mandelbrot_set_exponential_mapping_c%3D-1.7864402555636389.png
为了获得另一个图像,你需要将旋转设置为 180,并将旋转中心设置为当前中心。
为了渲染大型极坐标图像(墨卡托地图),你需要使用图像扩展器,并设置要拼接在一起的方形图像数量。
- 颜色映射文件 = 扩展名为 map 的文件,与 fractint 映射文件 格式相同
- 描述 (颜色) 调色板的数组 Color[]
调色板 被定义为一组颜色。它通常存储在一个数组中。
Color[] palette = new Color[N];
其中 N 是调色板中的颜色数量。
外着色或内着色算法将生成一个数字作为其结果,该数字将被转换为调色板索引,然后转换为颜色。
如果我们不关心连续颜色(平滑),简单的转换方法是将结果强制转换为整数,然后将其用作索引。
出于我们的目的,假设方法 Iterate 负责根据所选的内着色和外着色方法创建其结果。
如果 Iterate 返回最大迭代次数作为结果,我们可以选择指定的颜色,例如黑色。
Complex number = new Complex(2, 3);
double result = Iterate(number);
Color color = getColor(result, maxIterations);
Color getColor(double result, int maxIterations)
{
if(result == maxIterations)
{
return Color.BLACK;
}
Color color = palette[((int)result) % N];
return color;
}
如果我们想要连续颜色(平滑),Iterate 方法必须创建一个结果,该结果包含一个分数部分(例如 100.73)。
我们需要根据结果的整数部分获取两个连续的颜色,然后使用小数部分对这些颜色进行插值。
简单的插值方法是线性插值,但也可以使用其他方法,例如余弦插值等。
Complex number = new Complex(2, 3);
double result = Iterate(number);
Color color = getColor(result, maxIterations);
Color getColor(double result, int maxIterations)
{
if(result == maxIterations)
{
return Color.BLACK;
}
Color color1 = palette[((int)result) % N];
Color color1 = palette[((int)result + 1) % N];
double fractional = result - (int)result;
Color finalColor = new Color((int)(color1.getRed() + (color2.getRed() - color1.getRed()) * fractional + 0.5),
(int)(color1.getGreen() + (color2.getGreen() - color1.getGreen()) * fractional + 0.5),
(int)(color1.getBlue() + (color2.getBlue() - color1.getBlue()) * fractional + 0.5));
return finalColor;
}
不逃逸预定义边界或不收敛的复数属于集合(集合内),因此使用“内着色”这个术语。
在大多数介绍的算法中,使用复数的最后一个值,在某些特殊情况下,使用倒数第二个和第三个最后一个值。
如果使用用户内着色算法,可以进一步自定义算法。
double maximumIterations(int maxIterations)
{
return maxIterations; //max iterations
}
double normZ(Complex z, int maxIterations)
{
return z.norm_squared() * (maxIterations / 3.0);
}
double decompositionLike(Complex z)
{
return Math.abs((z.arg() / 2 * Math.PI + 0.75) * 59 * Math.PI);
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
double ReDivideIm(Complex z)
{
return Math.abs(z.getRe() / z.getIm()) * 8;
}
double CosNormZ(Complex z)
{
if((int)(z.norm_squared() * 10) % 2 == 1)
{
return Math.abs(Math.cos(z.getRe() * z.getIm() * z.getAbsRe() * z.getAbsIm()) * 400 + 50;
}
return Math.abs(Math.sin(z.getRe() * z.getIm() * z.getAbsRe() * z.getAbsIm())) * 400;
}
double NormZCosReSquared(Complex z)
{
return z.norm_squared() * Math.abs(Math.cos(z.getRe() * z.getRe())) * 400;
}
double SinReSquaredMinusImSquared(Complex z)
{
return Math.abs(Math.sin(z.getRe() * z.getRe() - z.getIm() * z.getIm())) * 400;
}
double AtanReTimesImTimesAbsReTimesAbsIm(Complex z)
{
return Math.abs(Math.atan(z.getRe() * z.getIm() * z.getAbsRe() * z.getAbsIm())) * 400;
}
double Squares(Complex z)
{
if(((Math.abs((int)(z.getRe() * 40)) % 2) ^ (Math.abs((int)(z.getIm() * 40)) % 2)) == 1)
{
return Math.abs((Math.atan2(z.getIm(), z.getRe()) / (Math.PI * 2) + 0.75) * Math.PI * 59);
}
return Math.abs((Math.atan2(z.getRe(), z.getIm() / (Math.PI * 2) + 0.75) * Math.PI * 59);
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
double Squares2(Complex z)
{
double x = z.getRe() * 16;
double y = z.getIm() * 16;
double dx = Math.abs(x - Math.floor(x));
double dy = Math.abs(y - Math.floor(y));
if((dx < 0.5 && dy < 0.5) || (dx > 0.5 && dy > 0.5))
{
return 50; // a palette offset
}
return 0;
}
逃逸预定义边界或收敛的复数不属于集合(集合外),因此使用“外着色”这个术语。
在大多数介绍的算法中,使用复数的最后一个值,在某些特殊情况下,使用倒数第二个和第三个最后一个值。
如果使用用户外着色算法,可以进一步自定义算法。
逃逸时间算法 计算分形逃逸预定义边界条件(逃逸)或收敛到某个值(阈值很小)所花费的迭代次数。
如果未启用连续颜色(平滑),我们只需要返回迭代次数,它将是整数。
double escapeTime(int n)
{
return n; //iterations
}
如果启用 continuous colors(平滑),我们需要生成一个结果,该结果将包含迭代次数加上一个小数部分。
计算逃逸类型分形的连续迭代次数
- 方法 1
请记住,所有范数都被平方,这样我们就可以避免计算平方根,范数定义为
- 方法 2
增加逃逸值将产生更平滑的图像。
计算收敛类型分形的连续迭代次数
- 方法 1
- 方法 2
以上 escapeTime 函数应调整为包含所需参数。如您所见,它必须始终返回浮点数作为结果。
为了便于参考,在以下一些外着色方法中,我们将使用 **A**、**B**、**C** 和 **D** 这些名称。
在特殊情况下,例如使用逃逸和收敛方法的磁铁函数,我们必须使用相应的平滑方法。
如果最终的复数值逃逸,请使用逃逸平滑方法。如果最终的复数值收敛,请使用收敛平滑方法。
如果您知道确切的根或某个点可以收敛到,您可以更改平滑方法以合并此根,以获得更好的结果。
例如,在磁铁函数中,根位于 .
- 方法 1
- 方法 2
二进制分解 基于最终的复数,我们可以向迭代次数添加偏移量。
连续迭代次数也可以用于此算法。
double BinaryDecomposition(int n, Complex z)
{
if(z.getIm() < 0)
{
return n + 50; //You can add A or B or C or D to include Continuous Iteration Count
}
return n; //You can add A or B or C or D to include Continuous Iteration Count
}
基于最终的复数,我们可以向迭代次数添加偏移量。
连续迭代次数也可以用于此算法。
double BinaryDecomposition2(int n, Complex z)
{
if(z.getRe() < 0)
{
return n + 50; //You can add A or B or C or D to include Continuous Iteration Count
}
return n; //You can add A or B or C or D to include Continuous Iteration Count
}
double EscapeTimePlusRe(int n, Complex z)
{
return n + z.getRe();
}
double EscapeTimePlusIm(int n, Complex z)
{
return n + z.getIm();
}
double EscapeTimePlusReDivideIm(int n, Complex z)
{
return n + z.getRe() / z.getIm();
}
double EscapeTimePlusRePlusImPlusReDivideIm(int n, Complex z)
{
return n + z.getRe() + z.getIm() + z.getRe() / z.getIm();
}
基于最终的复数,我们可以向迭代次数添加偏移量。
连续迭代次数也可以用于此算法。
double Biomorph(int n, Complex z, double bailout)
{
if(z.getRe() > -bailout && z.getRe() < bailout
|| z.getIm() > -bailout && z.getIm() < bailout)
{
return n; //You can add A or B to include Continuous Iteration Count
}
return n + 50; //You can add A or B to include Continuous Iteration Count
}
double ColorDecomposition(int n, Complex z)
{
return Math.abs((z.arg() / 2 * Math.PI + 0.75) * 59 * Math.PI);
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
此算法针对收敛型分形稍作修改,以便不同的根可以有不同的颜色。显然,所使用的方法无法将复数映射到实数,但结果可用。
double ColorDecomposition(int n, Complex z)
{
double re = Math.floor(1000 * (z.getRe() + 0.5) / 1000; // rounding
double im = Math.floor(1000 * (z.getRe() + 0.5) / 1000; // rounding
return Math.abs((Math.atan2(im, re) / Math.PI * 2 + 0.75) * Math.PI * 59 + (re * re + im * im) * 2.5);
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
double EscapeTimeColorDecomposition(int n, Complex z)
{
return n + Math.abs((z.arg() / 2 * Math.PI + 0.75) * 59 * Math.PI);
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
此算法针对收敛型分形稍作修改,以便不同的根可以有不同的颜色。显然,所使用的方法无法将复数映射到实数,但结果可用。
连续迭代次数也可以用于此算法。
double EscapeTimeColorDecomposition(int n, Complex z)
{
double re = Math.floor(1000 * (z.getRe() + 0.5) / 1000; // rounding
double im = Math.floor(1000 * (z.getRe() + 0.5) / 1000; // rounding
return n + Math.abs((Math.atan2(im, re) / Math.PI * 2 + 0.75) * Math.PI * 59 + (re * re + im * im) * 2.5); //You can add C or D to include Continuous Iteration Count
}
所使用的常数,如 **0.75** 或 **59π** 完全是任意的。
double EscapeTimeGaussianInteger(int n, Complex z)
{
return n + z.sub(z.gaussian_integer()).norm_squared() * 90;
}
可以使用此函数获得高斯整数
Complex gaussian_integer()
{
return new Complex((int)(re < 0 ? re - 0.5 : re + 0.5), (int)(im < 0 ? im - 0.5 : im + 0.5));
}
double EscapeTimeGaussianInteger2(int n, Complex z)
{
Complex temp = z.sub(z.gaussian_integer());
return n + Math.abs(Math.atan(temp.getIm() / temp.getRe())) * 5;
}
double EscapeTimeGaussianInteger3(int n, Complex z)
{
Complex temp = z.sub(z.gaussian_integer());
return Math.abs(n + temp.getRe());
}
double EscapeTimeGaussianInteger4(int n, Complex z)
{
Complex temp = z.sub(z.gaussian_integer());
return Math.abs(n + temp.getRe() + temp.getIm());
}
double EscapeTimeGaussianInteger5(int n, Complex z)
{
Complex temp = z.sub(z.gaussian_integer());
return Math.abs(n + temp.getRe() + temp.getIm() + temp.getRe() / temp.getIm());
}
double EscapeTimePlusAlgorithm(int n, Complex z, Complex z_old)
{
Complex temp = z.sub(z_old);
return n + Math.abs(Math.atan(temp.getIm() / temp.getRe())) * 4;
}
double EscapeTimePlusAlgorithm2(int n, Complex z)
{
Complex temp = z.sub(z.sin());
return n + Math.abs(Math.atan(temp.getIm() / temp.getRe())) * 8;
}
double EscapeTimeEscapeRadius(int n, Complex z, double bailout)
{
double zabs = Math.log(z.norm_squared()) / Math.log(bailout * bailout) - 1.0;
double zarg = (z.arg() / (2 * Math.PI) + 1.0) % 1.0;
return n + zabs + zarg;
}
double EscapeTimeGrid(int n, Complex z, double bailout)
{
double zabs = Math.log(z.norm_squared()) / Math.log(bailout * bailout) - 1.0;
double zarg = (z.arg() / (2 * Math.PI) + 1.0) % 1.0;
if(0.05 < zabs && zabs < 0.95 && 0.05 < zarg && zarg < 0.95)
{
return n; //You can add A or B to include Continuous Iteration Count
}
return n + 50; //You can add A or B to include Continuous Iteration Count
}
为了使网格线更平滑,可以使用以下版本。
double EscapeTimeGrid(int n, Complex z, double bailout)
{
double zabs = Math.log(z.norm_squared()) / Math.log(bailout * bailout) - 1.0;
double zarg = (z.arg() / (2 * Math.PI) + 1.0) % 1.0;
double k = Math.pow(0.5, 0.5 - zabs);
double grid_weight = 0.05;
if(grid_weight < zabs && zabs < (1.0 - grid_weight) && (grid_weight * k) < zarg && zarg < (1.0 - grid_weight * k))
{
return n; //You can add A or B to include Continuous Iteration Count
}
return n + 50; //You can add A or B to include Continuous Iteration Count
}
double Banded(int n, Complex z)
{
return n + Math.abs((Math.log(Math.log(z.norm_squared())) / Math.log(2)) * 2.4);
}
double EscapeTimeFieldLines(int n, Complex z, double bailout)
{
double lineWidth = 0.008;
double fx = (z.arg() / (2 * Math.PI); // angle within cell
double fy = Math.log(z.norm_squared()) / Math.log(bailout * bailout);
double fz = Math.pow(0.5, -fy);
if(Math.abs(fx) > lineWidth * fz)
{
return n; //You can add A or B to include Continuous Iteration Count
}
return n + 50; //You can add A or B to include Continuous Iteration Count
}
double EscapeTimeFieldLines2(int n, Complex z, double bailout)
{
double lineWidth = 0.07;
double fx = (z.arg() / 2) * Math.PI;
double fy = Math.log(z.norm_squared()) / Math.log(bailout * bailout);
double fz = Math.pow(0.5, -fy);
if(Math.abs(fx) < (1.0 - lineWidth) * fz && lineWidth * fz < Math.abs(fx))
{
return n; //You can add A or B to include Continuous Iteration Count
}
return n + 50; //You can add A or B to include Continuous Iteration Count
}
"通过计算局部熵来渲染 M 集。对于每个点,我计算了该点周围 2n+1×2n+1 网格上归一化迭代次数的熵,在下面的示例中,n=2。正如预期的那样,它的行为有点像距离估计,靠近边界的点更加无序。但除此之外,它还显示了有趣的“太阳耀斑”,它们来自迭代次数的归一化,即它们捕获了关于逃逸的信息。"[3]
由于每个像素的着色完全独立于彼此,因此我们可以通过将图像分成多个片段并为每个片段分配不同的线程来加快处理速度。
有很多不同的方法来分割图像,例如水平、垂直、网格等。
假设图像被分割成一个网格。(1x1, 2x2, 3x3, ...)
每个线程将分配不同的 gridI 和 gridJ。gridSize 将是 1 或 2 或 3...
void createImage(int gridI, int gridJ, int gridSize, double xCenter, double yCenter, double size, int image_size, int maxIterations)
{
double dx = size / image_size;
double dy = size / image_size;
double xCenterOffset = xCenter - size * 0.5;
double yCenterOffset = yCenter + size * 0.5;
int fromY = gridI * image_size / gridSize;
int toY = (gridI + 1) * image_size / gridSize;
int fromX = gridJ * image_size / gridSize;
int toX = (gridJ + 1) * image_size / gridSize;
for(int y = fromY; y < toY; y++) {
for(int x = fromX; x < toX; x++) {
double result = Iterate(new Complex(xCenterOffset + dx * x, yCenterOffset - dy * y), maxIterations);
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
//set the color to the image at (y,x)
}
}
}
以下是使用线程优化后的极坐标投影代码
void createPolarProjectionImage(int gridI, int gridJ, int gridSize, double xCenter, double yCenter, double size, int image_size, int maxIterations)
{
double center = Math.log(size);
double dy = (2 * Math.PI) / image_size;
double dx = dy;
double start = center - dx * image_size * 0.5;
int fromY = gridI * image_size / gridSize;
int toY = (gridI + 1) * image_size / gridSize;
int fromX = gridJ * image_size / gridSize;
int toX = (gridJ + 1) * image_size / gridSize;
for(int y = fromY; y < toY; y++) {
for(int x = fromX; x < toX; x++) {
double sf = Math.sin(y * dy);
double cf = Math.cos(y * dy);
double r = Math.exp(x * dx + start);
double result = Iterate(new Complex(xCenter + r * cf, yCenter + r * sf), maxIterations);
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
//set the color to the image at (y,x)
}
}
}
这些类型的算法试图通过跳过某些区域的所有像素来减少计算的像素。该区域必须与相同颜色的边界相邻。
这些算法可以显着加快绘制过程,但它们可能会引入错误。
-
边界追踪
-
边界追踪,多线程
此算法遵循单色边界,然后使用泛洪填充算法来绘制该边界中包含的所有像素。
-
迭代
-
多线程
通过将图像迭代地细分为四个部分(使用十字),此算法检查矩形边界以跳过整个区域。
此算法也称为Mariani/Silver 算法。
为了避免对集合中的点进行大量迭代,可以执行周期性检查。检查迭代像素时是否之前已经到达过某个点。如果是,则该像素不可能发散,并且一定在集合中。
如果周期性检查成功,可以安全地假设我们将达到最大迭代次数,因此可以停止迭代。
Complex period = new Complex(); // period = 0
int check = 3;
int check_counter = 0;
int update = 10;
int update_counter = 0;
boolean periodicityCheck(Complex z) {
//Check for period
if(z.distance_squared(period) < 1e-13) { // |z-period|^2
return true;
}
//Update history
if(check == check_counter) {
check_counter = 0;
//Double the value of check
if(update == update_counter) {
update_counter = 0;
check <<= 1;
}
update_counter++;
period.assign(z); // period = z
} //End of update history
check_counter++;
return false;
}
超采样[4] 是一种空间抗锯齿方法,即用于消除图像中锯齿(锯齿状和像素化边缘,俗称“锯齿”)的方法。
基本上,我们创建一个包含更多细节的更大的图像,然后可以将其缩放到更小的尺寸。缩小是通过平均颜色来实现的。这样,包含大量噪声的区域就会变得平滑。
下面介绍的方法不存储更大图像的数据,以节省空间。
对于原始图像的每个像素,它还通过使用更小的步长来计算一些附近的点。
此方法还包括线程优化。
void createImageSuperSampling(int gridI, int gridJ, int gridSize, double xCenter, double yCenter, double size, int image_size, int maxIterations, int samples)
{
double dx = size / image_size;
double dy = size / image_size;
double xCenterOffset = xCenter - size * 0.5;
double yCenterOffset = yCenter + size * 0.5;
int fromY = gridI * image_size / gridSize;
int toY = (gridI + 1) * image_size / gridSize;
int fromX = gridJ * image_size / gridSize;
int toX = (gridJ + 1) * image_size / gridSize;
double a = size * 0.25;
double totalSamples = samples + 1;
double x[] = {-a, a, a, -a,
-a, a, 0, 0,
-2*a, -2*a, -2*a, 0, 0, 2*a, 2*a, 2*a,
-2*a, -2*a, -a, -a, a, a, 2*a, 2*a};
double y[] = {-a, -a, a, a,
0, 0, -a, a,
-2*a, 0, 2*a, -2*a, 2*a, -2*a, 0, 2*a,
-a, a, -2*a, 2*a, -2*a, 2*a, -a, a};
for(int y = fromY; y < toY; y++) {
for(int x = fromX; x < toX; x++) {
double x0 = xCenterOffset + dx * x;
double y0 = yCenterOffset - dy * y;
double result = Iterate(new Complex(x0, y0), maxIterations); //calculate for the center value
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
for(int i = 0; i < samples; i++)
{
result = Iterate(new Complex(x0 + x[i], y0 + y[i]), maxIterations); //calculate for the extra samples around center
color = getColor(result, maxIterations);
/* Sum all the samples */
red += color.getRed();
green += color.getGreen();
blue += color.getBlue();
}
Color finalColor = new Color((int)(red / totalSamples + 0.5),
(int)(green / totalSamples + 0.5),
(int)(blue / totalSamples + 0.5)); //average the color
//set the final color to the image at (y,x)
}
}
}
下面介绍使用超采样和线程优化的极坐标投影代码。
void createPolarProjectionImageSuperSampling(int gridI, int gridJ, int gridSize, double xCenter, double yCenter, double size, int image_size, int maxIterations, int samples)
{
double center = Math.log(size);
double dy = (2 * Math.PI) / image_size;
double dx = dy;
double start = center - dx * image_size * 0.5;
int fromY = gridI * image_size / gridSize;
int toY = (gridI + 1) * image_size / gridSize;
int fromX = gridJ * image_size / gridSize;
int toX = (gridJ + 1) * image_size / gridSize;
double a = dy * 0.25;
double totalSamples = samples + 1;
double x[] = {-a, a, a, -a,
-a, a, 0, 0,
-2*a, -2*a, -2*a, 0, 0, 2*a, 2*a, 2*a,
-2*a, -2*a, -a, -a, a, a, 2*a, 2*a};
double y[] = {-a, -a, a, a,
0, 0, -a, a,
-2*a, 0, 2*a, -2*a, 2*a, -2*a, 0, 2*a,
-a, a, -2*a, 2*a, -2*a, 2*a, -a, a};
for(int y = fromY; y < toY; y++) {
for(int x = fromX; x < toX; x++) {
double sf = Math.sin(y * dy);
double cf = Math.cos(y * dy);
double r = Math.exp(x * dx + start);
double result = Iterate(new Complex(xCenter + r * cf, yCenter + r * sf), maxIterations);
imageIterations[y][x] = result; //save the iteration data for later use
Color color = getColor(result, maxIterations);
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();
for(int i = 0; i < samples; i++)
{
sf = Math.sin(y * dy + y[i]);
cf = Math.cos(y * dy + y[i]);
r = Math.exp(x * dx + start + x[i]);
result = Iterate(new Complex(xCenter + r * cf, yCenter + r * sf), maxIterations); //calculate for the extra samples around center
color = getColor(result, maxIterations);
/* Sum all the samples */
red += color.getRed();
green += color.getGreen();
blue += color.getBlue();
}
Color finalColor = new Color((int)(red / totalSamples + 0.5),
(int)(green / totalSamples + 0.5),
(int)(blue / totalSamples + 0.5)); //average the color
//set the color to the image at (y,x)
}
}
}
如果你想在超采样时尽量减少对三角函数和指数函数的使用,你可以使用一些函数的恒等式。
对于指数函数
我们将使用以下恒等式
所以
你必须预先计算一次 ,然后使用这些恒等式将其替换到所有表达式中。
对于三角函数
我们将使用以下恒等式
所以
您必须预先计算 , 一次,并使用这些恒等式将它们替换到所有表达式中。
文件类型
资源库