In addition to plotting the set, a variety of algorithms have been developed to
general coloring algorithms: ( feature of the pixel -> color )
One thing we may want to consider is avoiding having to deal with a palette or color blending at all. There are actually a handful of methods we can leverage to generate smooth, consistent coloring by constructing the color on the spot.
Here
A naive method for generating a color in this way is by directly scalingv to 255 and passing it into RGB as such
rgb = [v * 255, v * 255, v * 255]
One flaw with this is that RGB is non-linear due to gamma; consider linear sRGB instead. Going from RGB to sRGB uses an inverse companding function on the channels. This makes the gamma linear, and allows us to properly sum the colors for sampling.
srgb = [v * 255, v * 255, v * 255]
The escape time algorithm is popular for its simplicity. However, it creates bands of color, which, as a type ofaliasing, can detract from an image's aesthetic value. This can be improved using an algorithm known as "normalized iteration count",[1][2] which provides a smooth transition of colors between iterations. The algorithm associates a real number with each value ofz by using the connection of the iteration number with thepotential function. This function is given by
wherezn is the value aftern iterations andP is the power for whichz is raised to in the Mandelbrot set equation (zn+1 = znP + c,P is generally 2).
If we choose a large bailout radiusN (e.g., 10100), we have that
for some real number, and this is
and asn is the first iteration number such that |zn| >N, the number we subtract fromn is in the interval [0, 1).
For the coloring we must have a cyclic scale of colors (constructed mathematically, for instance) and containingH colors numbered from 0 toH − 1 (H = 500, for instance). We multiply the real number by a fixed real number determining the density of the colors in the picture, take the integral part of this number modulo H, and use it to look up the corresponding color in the color table.
For example, modifying the above pseudocode and also using the concept ofw:linear interpolation would yield
for each pixel (Px, Py) on the screendo x0:= scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2.5, 1)) y0:= scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1)) x:= 0.0 y:= 0.0 iteration:= 0 max_iteration:= 1000// Here N = 2^8 is chosen as a reasonable bailout radius.while x*x + y*y ≤ (1 << 16)and iteration < max_iterationdo xtemp:= x*x - y*y + x0 y:= 2*x*y + y0 x:= xtemp iteration:= iteration + 1// Used to avoid floating point issues with points inside the set.if iteration < max_iterationthen// sqrt of inner term removed using log simplification rules. log_zn:= log(x*x + y*y) / 2 nu:= log(log_zn / log(2)) / log(2)// Rearranging the potential function.// Dividing log_zn by log(2) instead of log(N = 1<<8)// because we want the entire palette to range from the// center to radius 2, NOT our bailout radius. iteration:= iteration + 1 - nu color1:= palette[floor(iteration)] color2:= palette[floor(iteration) + 1]// iteration % 1 = fractional part of iteration. color:= linear_interpolate(color1, color2, iteration % 1) plot(Px, Py, color)
Typically when we render a fractal, the range of where colors from a given palette appear along the fractal is static. If we desire to offset the location from the border of the fractal, or adjust their palette to cycle in a specific way, there are a few simple changes we can make when taking the final iteration count before passing it along to choose an item from our palette.
When we have obtained the iteration count, we can make the range of colors non-linear. Raising a value normalized to the range [0,1] to a powern, maps a linear range to an exponential range, which in our case can nudge the appearance of colors along the outside of the fractal, and allow us to bring out other colors, or push in the entire palette closer to the border.
where:
This scales the iter count non-linearly and scales the palette to cycle approximately proportionally to the zoom.
We can then plug v into whatever algorithm we desire for generating a color.
HSV Coloring can be accomplished by mapping iter count from [0,max_iter) to [0,360), taking it to the power of 1.5, and then modulo 360.
We can then simply take the exponentially mapped iter count into the value and return
hsv = [powf((i / max) * 360, 1.5) % 360, 100, (i / max) * 100]
This method applies to HSL as well, except we pass a saturation of 50% instead.
hsl = [powf((i / max) * 360, 1.5) % 360, 50, (i / max) * 100]
where:
One of the most perceptually uniform coloring methods involves passing in the processed iter count into LCH. If we utilize the exponentially mapped and cyclic method above, we can take the result of that into the Luma and Chroma channels. We can also exponentially map the iter count and scale it to 360, and pass this modulo 360 into the hue.
One issue we wish to avoid here is out-of-gamut colors. This can be achieved with a little trick based on the change in in-gamut colors relative to luma and chroma. As we increase luma, we need to decrease chroma to stay within gamut.
s = iters/max_i;v = 1.0 - powf(cos(pi * s), 2.0);LCH = [75 - (75 * v), 28 + (75 - (75 * v)), powf(360 * s, 1.5) % 360];
A more complex coloring method involves using ahistogram which pairs each pixel with said pixel's maximum iteration count before escape/bailout. This method will equally distribute colors to the same overall area, and, importantly, is independent of the maximum number of iterations chosen.[3]
This algorithm has four passes. The first pass involves calculating the iteration counts associated with each pixel (but without any pixels being plotted). These are stored in an array: IterationCounts[x][y], where x and y are the x and y coordinates of said pixel on the screen respectively.
The first step of the second pass is to create an array of sizen, which is the maximum iteration count: NumIterationsPerPixel. Next, one must iterate over the array of pixel-iteration count pairs, IterationCounts[][], and retrieve each pixel's saved iteration count,i, via e.g.i = IterationCounts[x][y]. After each pixel's iteration counti is retrieved, it is necessary to index the NumIterationsPerPixel byi and increment the indexed value (which is initially zero) -- e.g. NumIterationsPerPixel[i] = NumIterationsPerPixel[i] + 1 .
for (x = 0; x < width; x++)dofor (y = 0; y < height; y++)do i:= IterationCounts[x][y] NumIterationsPerPixel[i]++
The third pass iterates through the NumIterationsPerPixel array and adds up all the stored values, saving them intotal. The array index represents the number of pixels that reached that iteration count before bailout.
total: = 0for (i = 0; i < max_iterations; i++)do total += NumIterationsPerPixel[i]
After this, the fourth pass begins and all the values in the IterationCounts array are indexed, and, for each iteration counti, associated with each pixel, the count is added to a global sum of all the iteration counts from 1 toi in the NumIterationsPerPixel array . This value is then normalized by dividing the sum by thetotal value computed earlier.
hue[][]:= 0.0for (x = 0; x < width; x++)dofor (y = 0; y < height; y++)do iteration:= IterationCounts[x][y]for (i = 0; i <= iteration; i++)do hue[x][y] += NumIterationsPerPixel[i] / total/* Must be floating-point division. */...color = palette[hue[m, n]]...
Finally, the computed value is used, e.g. as an index to a color palette.
This method may be combined with the smooth coloring method below for more aesthetically pleasing images.
color From the Mandelbrot Set Glossary and Encyclopedia, by Robert Munafo, (c) 1987-2023
Java script version byAlbert Lobo
// https://github.com/llop/mandelbrot-viewer-js/blob/master/mandelbrot-viewer.js// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c_hsvToRgb(h,s,v){letr,g,b;consti=Math.floor(h*6);constf=h*6-i;constp=v*(1-s);constq=v*(1-f*s);constt=v*(1-(1-f)*s);switch(i%6){case0:r=v,g=t,b=p;break;case1:r=q,g=v,b=p;break;case2:r=p,g=v,b=t;break;case3:r=p,g=q,b=v;break;case4:r=t,g=p,b=v;break;case5:r=v,g=p,b=q;break;}return[r*255,g*255,b*255];}// https://mrob.com/pub/muency/color.html_colorCheckered(k){if(this.ns[k]>=this.maxN)return[255,255,255];constdwell=Math.floor(this.dwell[k]);constfinalrad=this.dwell[k]-Math.floor(this.dwell[k]);constdscale=Math.log2(this.dist[k]/this.inc);letvalue=0.0;if(dscale>0.0)value=1.0;elseif(dscale>-10.0)value=(10.0+dscale)/10.0;letp=Math.log(dwell)/Mandelbrot.LOG_BIG_NUM;letangle=0.0;letradius=0.0;if(p<0.5){p=1.0-1.5*p;angle=1.0-p;radius=Math.sqrt(p);}else{p=1.5*p-0.5;angle=p;radius=Math.sqrt(p);}if(dwell%2){value*=0.85;radius*=0.667;}if(this.finalang[k]>0.0){angle+=0.02;}angle+=0.0001*finalrad;lethue=angle*10.0;hue-=Math.floor(hue);letsaturation=radius-Math.floor(radius);returnthis._hsvToRgb(hue,saturation,value);}// b&w version of checkerboard_colorCheckeredBlackAndWhite(k){constcolor=this._colorCheckered(k);constgray=Math.round(color[0]*0.299+color[1]*0.587+color[2]*0.114);return[gray,gray,gray];}// color pixel k_color(k){if(this.ns[k]==0)return[0,0,0];// C not yet processed: color blackthis.pix[k]=0;// mark pixel as coloredreturnthis.colorFunc(k);// return color for pixel}// paint what we have on the canvasrender(){if(!this.scanDone){letoffset=0;for(letk=0;k<this.imgSize;++k,offset+=4){if(this.pix[k]){constcolor=this._color(k);this.image.data[offset]=color[0];this.image.data[offset+1]=color[1];this.image.data[offset+2]=color[2];this.image.data[offset+3]=255;}}if(!this.scanning)this.scanDone=true;}this.context.clearRect(0,0,this.imgWidth,this.imgHeight);this.context.putImageData(this.image,0,0);}// sleep function. use 'await this.sleep()' in async functions_sleep(){returnnewPromise(requestAnimationFrame);}}
{{cite journal}}:Cite journal requires|journal= (help)