Very fast mandelbrot explorer

So I had an idea to make a random coloring thing just to be a pretty effect and whatnot, and I’ve gotten one that works somewhat okayish, but it doesn’t work CORRECTLY. It’s supposed to sample already drawn pixels and average them to come up with its own color, but as you can see with the first line alone, it doesn’t work. Then the rest of the lines somewhat work, but they don’t sample from the first line and there are random lightly colored ones scattered about (on the bright side, the sampling works properly from the random light ones). Below is the code

function setup()
    img = image(300,300)
    co = coroutine.create(makerand)
end
function draw() 
    noSmooth()
    background(40, 40, 50)
    coroutine.resume(co)
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
end
function makerand()
    for x=1,img.width do
        for y=1,img.height do
            local cols = {}
            local colsx = {x,x-1,x-2}
            local colsy = {y-1,y+1,y-2,y+2}
            for a,b in pairs(colsx) do
                for q,l in pairs(colsy) do
                    if l > 1 and l < img.height and b > 1 then
                        local tempcol = color(0,0,0,0)
                        tempcol.r,tempcol.g,tempcol.b,tempcol.a = img:get(b,l)
                        if tempcol.a > 0 then
                            cols[#cols+1]=tempcol
                        end
                    end
                end
            end
            local finalcol
            if not cols[1] then
                finalcol = color(math.random(255),math.random(255),math.random(255),255)
            else
                finalcol = color(0,0,0,255)
                for z,v in pairs(cols) do
                    finalcol.r = finalcol.r + v.r +math.random(-2,2)
                    finalcol.g = finalcol.g + v.g +math.random(-2,2)
                    finalcol.b = finalcol.b + v.b +math.random(-2,2)
                end
                finalcol.r = finalcol.r / #cols
                finalcol.g = finalcol.g / #cols
                finalcol.b = finalcol.b / #cols 
            end
            img:set(x,y,finalcol)
        end
        if x%3==0 then coroutine.yield() end
    end
end

@Monkeyman32123 Nice idea. I didn’t feel like trying to figure out why you’re code doesn’t work, so I wrote my own similar to yours. This is what yours might look like.

EDIT: Made minor changes.


displayMode(FULLSCREEN)

function setup()
    img=image(500,500)
    co = coroutine.create(getAverage)
end

function draw()
    background(0)
    coroutine.resume(co)
    sprite(img,WIDTH/2,HEIGHT/2)
end
    
function getAverage()
    for x=1,img.width do
        for y=1,img.height do
            colsx = {x-1,x-2}
            colsy = {y-1,y+1,y-2,y+2}
            if x<3 then
                img:set(x,y,math.random(255),math.random(255),math.random(255))  
            else
                ra,ga,ba=0,0,0
                for xx=1,#colsx do
                    for yy=1,#colsy do
                        cx=colsx[xx]
                        cy=colsy[yy]
                        if cx>0 and cx<=img.width and cy>0 and cy<=img.width then
                            r,g,b=img:get(cx,cy)
                            ra=ra+r
                            ga=ga+g
                            ba=ba+b
                        end
                    end
                end
                img:set(x,y,math.floor(ra/8),math.floor(ga/8),math.floor(ba/8))
            end
        end
        if x%5==0 then 
            coroutine.yield() 
        end
    end
end
                

@Monkeyman32123 Here’s your code corrected. I added displayMode, and changed 2 lines. Those lines are marked with the comment – changed.

EDIT: Writing my own code first helped me debug the original code since I knew what was being done.


displayMode(FULLSCREEN) -- added
    
function setup()
    img = image(300,300)
    co = coroutine.create(makerand)
end
function draw() 
    noSmooth()
    background(40, 40, 50)
    coroutine.resume(co)
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
end
function makerand()
    for x=1,img.width do
        for y=1,img.height do
            local cols = {}
            local colsx = {x-1,x-2} -- changed
            local colsy = {y-1,y+1,y-2,y+2}
            for a,b in pairs(colsx) do
                for q,l in pairs(colsy) do
                    if l > 1 and l < img.height and b > 1 then
                        local tempcol = color(0,0,0,0)
                        tempcol.r,tempcol.g,tempcol.b,tempcol.a = img:get(b,l)
                        if tempcol.a > 0 then
                            cols[#cols+1]=tempcol
                        end
                    end
                end
            end
            local finalcol
            if x<3 then -- changed
                finalcol = color(math.random(255),math.random(255),math.random(255),255)
            else
                finalcol = color(0,0,0,255)
                for z,v in pairs(cols) do
                    finalcol.r = finalcol.r + v.r +math.random(-2,2)
                    finalcol.g = finalcol.g + v.g +math.random(-2,2)
                    finalcol.b = finalcol.b + v.b +math.random(-2,2)
                end
                finalcol.r = finalcol.r / #cols
                finalcol.g = finalcol.g / #cols
                finalcol.b = finalcol.b / #cols 
            end
            img:set(x,y,finalcol)
        end
        if x%3==0 then coroutine.yield() end
    end
end

Thank you for both! Both exactly what I meant to do! If you don’t mind explaining, what made those corrections make it work?

Also, here is another version that, instead of a side bleed, uses points about the image to color the pixels. Makes a nice lava Lampy effect


displayMode(FULLSCREEN) -- added

function setup()
    img = image(500,500)
    co = coroutine.create(makerand)
    ps,pc = {},{}
end
function draw() 
    noSmooth()
    background(40, 40, 50)
    coroutine.resume(co)
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
end
function makerand()
    makepointsources()
    for x=1,img.width do
        for y=1,img.height do
            local r = 0
            local g = 0
            local b = 0
            local usenum = 0
            for i=1,#ps do
                local pcol,ppos = pc[i],ps[i]
                dist = vec2(x,y):dist(vec2(ppos.x,ppos.y))
                if dist > img.width/4 then 
                    dist = img.width/4
                else
                    usenum = usenum + (img.width/4-dist)
                end
                dist = img.width/4-dist
                r = r + (dist*pcol.r)
                g = g + (dist*pcol.g)
                b = b + (dist*pcol.b)
            end
            --print(r,g,b)
            r=r/usenum
            g=g/usenum
            b=b/usenum
            img:set(x,y,r,g,b,255)
        end
        coroutine.yield()
    end
end
function makepointsources()
    for i=1,math.pow(img.width,7/12) do
        local x1 = math.random(img.width)
        local y1 = math.random(img.height)
        ps[i] = {x=x1,y=y1}
        pc[i] = color(math.random(255),math.random(255),math.random(255),255)
        img:set(x1,y1,pc[i])
    end
end

Not the same but I didn’t want to make a new thread ( I have enough threads already). I made a very simple mandelbrot explorer. It would be nice to implement double the precision into the OpenGL half of the process so that it can zoom further (by using two floats to represent each number), but the way to do that is far beyond my comprehension. The mandelbrot set has been a fascination of mine for a while, so I researched algorithms and decided to use this one (and took the method for coloring from the codea built in example. Tap to zoom in where you tapped, zoom out action parameter to zoom out. The maxiter parameter is actually 2^maxiter (so it runs very slowly at higher numbers). If any of you know how to implement double the precision into the GLSL half and are also very bored (or just love the fractal as much as I do) I would literally love you forever (I could explore mandelbrot for days as is, but I always get depressed once I reach what I call the “pixel point”). Anyways, rant over, enjoy :slight_smile:

-- Mandelshader
displayMode(OVERLAY)
-- Use this function to perform your initial setup
function setup()
    m = mesh()
    img = image(WIDTH,HEIGHT)
    m.shader = shader(mandelshader.v,mandelshader.f)
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m.texture = img
    m.shader.coords,c1 = vec2(-2,-1),vec2(-2,-1)
    m.shader.coords2,c2 = vec2(.5,1),vec2(.5,1)
    m.shader.maxiter = 128
    c3 = c2-c1
    refresh = true
    parameter.action("zoom_out",function() touched({x=WIDTH/2,y=HEIGHT/2,state=ENDED},1.5) end)
    parameter.integer("maxiter",1,10,7,function() m.shader.maxiter = 2^maxiter; refresh = true end)
    parameter.integer("rn",1,33,3,function() m.shader.rn = rn; refresh = true end)
    parameter.integer("gn",1,33,5,function() m.shader.gn = gn; refresh = true end)
    parameter.integer("bn",1,33,7,function() m.shader.bn = bn; refresh = true end)
end
function draw()
    background(40, 40, 50)
    if refresh then
        setContext(img)
        m:draw()
        setContext()
        refresh  = false
    end
    
    sprite(img,WIDTH/2,HEIGHT/2)
    
end
function touched(t,diff)
    if t.state == ENDED then
        local x = c1.x+c3.x*(t.x/WIDTH)
        local y = c1.y+c3.y*(t.y/HEIGHT)
        diff = c3*(diff or (1/4))
        c1 = vec2(x-diff.x,y-diff.y)
        c2 = vec2(x+diff.x,y+diff.y)
        c3 = c2-c1
        m.shader.coords = c1
        m.shader.coords2 = c2
        refresh = true
    end
end
mandelshader = {
f=[[
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform highp vec2 coords;
uniform highp vec2 coords2;
uniform int maxiter;
uniform float rn;
uniform float gn;
uniform float bn;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
void main()
{
    highp vec2 coords3 = coords2-coords;
    float x0 = coords.x+coords3.x*(vTexCoord.x);
    float y0 = coords.y+coords3.y*(vTexCoord.y);
    float x = x0;
    float y = y0;
    int iteration = 0;
    while ( ((x*x + y*y) < 4.) && (iteration < maxiter)) {
        highp float xtemp = x*x - y*y + x0;
        y = 2.*x*y + y0;
        x = xtemp;
        iteration = iteration + 1;
    }
    float c = float(iteration)/float(maxiter);
    //float r = mod(c*3.,1.);
    //float g = mod(c*5.,1.);
    //float b = mod(c*7.,1.);
    float r = mod(c*rn,1.);
    float g = mod(c*gn,1.);
    float b = mod(c*bn,1.);
    lowp vec4 col = vec4(r,g,b,1.);
    //imag:set(xa, ya, col)
    //Sample the texture at the interpolated coordinate
    //lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;

    //Set the output color to the texture color
    gl_FragColor = col;
}
]],
v=[[
    //
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}

]]
}

Here’s a (much slower) version with emulated double precision. Took me awhile, but it is much prettier.

supportedOrientations(LANDSCAPE_ANY)
displayMode(OVERLAY)
function setup()
    m = mesh()
    sf = 1 --change to 2 for higher detail at the cost of 4x the load time
    img = image(WIDTH*sf,HEIGHT*sf)
    m.shader = shader(mandelshader.v,mandelshader.f)
    m:addRect(WIDTH/2*sf,HEIGHT/2*sf,WIDTH*sf,HEIGHT*sf)
    c1 = vec2(-2,-1)
    c2 = vec2(.5,1)
    c3 = c2-c1
    m.shader.coordsx,m.shader.coordsy = vec2(-2.0,0.0),vec2(-1,0.0)
    m.shader.coords2x,m.shader.coords2y = vec2(.5,0.),vec2(1,0.0)
    refresh(0)
    parameter.action("zoom_out",function() touched({x=WIDTH/2,y=HEIGHT/2,state=ENDED},1.5) end)
    parameter.integer("maxiter",2,11,2,function() m.shader.maxiter = 2^maxiter; refresh(0) end)
    parameter.integer("rn",1,33,3,function() m.shader.rn = rn; refresh(0) end)
    parameter.integer("gn",1,33,5,function() m.shader.gn = gn; refresh(0) end)
    parameter.integer("bn",1,33,7,function() m.shader.bn = bn; refresh(0) end)
    print("maxiter is actually 2^maxiter \
\
rn,gn,and bn are the amount of red, green, and blue respectively\
\
tap to zoom in on the touch point")
end
function draw()
    background(40, 40, 50)
    if ref then
        setContext(img)
        m:draw()
        setContext()
    end
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    refresh()
end
function refresh(set)
    if set then
        rnum = set
        ref = true
    else
        m.shader.num = (rnum-1)/(2^maxiter*sf)
        m.shader.num2 = (rnum)/(2^maxiter*sf)
        rnum = rnum + 1
        if rnum == (2^maxiter*sf)+2 then
            ref = false
        end
    end
end
function touched(t,diff)
    if t.state == ENDED then
        local x = c1.x+c3.x*(t.x/WIDTH)
        local y = c1.y+c3.y*(t.y/HEIGHT)
        diff = c3*(diff or (1/4))
        c1 = vec2(x-diff.x,y-diff.y)
        c2 = vec2(x+diff.x,y+diff.y)
        c3 = c2-c1
        m.shader.coordsx,m.shader.coordsy = vec2(c1.x,0.0),vec2(c1.y,0.0)
        m.shader.coords2x,m.shader.coords2y = vec2(c2.x,0.0),vec2(c2.y,0.0)
        refresh(1)
    end
end
mandelshader = {
f=[[
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform highp vec2 coordsx,coordsy;
uniform highp vec2 coords2x,coords2y;
uniform int maxiter;
uniform float rn;
uniform float gn;
uniform float bn;
uniform float num;
uniform float num2;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
vec2 dsadd (vec2 dsa, vec2 dsb)
{
vec2 dsc;
float t1, t2, e;

 t1 = dsa.x + dsb.x;
 e = t1 - dsa.x;
 t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;

 dsc.x = t1 + t2;
 dsc.y = t2 - (dsc.x - t1);
 return dsc;
}
vec2 dsmultiply (vec2 dsa, vec2 dsb)
{
vec2 dsc;
float c11, c21, c2, e, t1, t2;
float a1, a2, b1, b2, cona, conb, split = 8193.;
 cona = dsa.x * split;
 conb = dsb.x * split;
 a1 = cona - (cona - dsa.x);
 b1 = conb - (conb - dsb.x);
 a2 = dsa.x - a1;
 b2 = dsb.x - b1;
 c11 = dsa.x * dsb.x;
 c21 = a2 * b2 + (a2 * b1 + (a1 * b2 + (a1 * b1 - c11)));
 c2 = dsa.x * dsb.y + dsa.y * dsb.x;
 t1 = c11 + c2;
 e = t1 - c11;
 t2 = dsa.y * dsb.y + ((c2 - e) + (c11 - (t1 - e))) + c21;
 dsc.x = t1 + t2;
 dsc.y = t2 - (dsc.x - t1);
 return dsc;
}
vec2 dssub (vec2 dsa, vec2 dsb)
{
vec2 dsc;
float e, t1, t2;
 t1 = dsa.x - dsb.x;
 e = t1 - dsa.x;
 t2 = ((-dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y - dsb.y;
 dsc.x = t1 + t2;
 dsc.y = t2 - (dsc.x - t1);
 return dsc;
}
// Compare: res = -1 if a < b
//              = 0 if a == b
//              = 1 if a > b
float dscompare(vec2 dsa, vec2 dsb)
{
 if (dsa.x < dsb.x) return -1.;
 else if (dsa.x == dsb.x) 
	{
	if (dsa.y < dsb.y) return -1.;
	else if (dsa.y == dsb.y) return 0.;
	else return 1.;
	}
 else return 1.;
}
void main()
{
    if (vTexCoord.x > num && vTexCoord.x < num2) {
    highp vec2 coords3x = dssub(coords2x,coordsx);
    highp vec2 coords3y = dssub(coords2y,coordsy);
    highp vec2 x0 = dsadd(coordsx,dsmultiply(coords3x,vec2(vTexCoord.x,0.0)));
    highp vec2 y0 = dsadd(coordsy,dsmultiply(coords3y,vec2(vTexCoord.y,0.0)));
    highp vec2 x = x0;
    highp vec2 y = y0;
    int iteration = 0;
    while ( (dscompare(dsadd(dsmultiply(x,x),dsmultiply(y,y)),vec2(4.,0.)) == -1.) && (iteration < maxiter)) {
        highp vec2 xtemp = dsadd(dssub(dsmultiply(x,x),dsmultiply(y,y)) , x0);
        y = dsadd(dsmultiply(dsmultiply(vec2(2.,0.),x),y), y0);
        x = xtemp;
        iteration = iteration + 1;
    }
    float c = float(iteration)/float(maxiter);
    float r = mod(c*rn,1.);
    float g = mod(c*gn,1.);
    float b = mod(c*bn,1.);
    lowp vec4 col = vec4(r,g,b,1.);
    gl_FragColor = col;
    }
    else {
    discard;
    }
}
]]
,
v=[[
    //
// A basic vertex shader
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;   
    //multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]]
}