Code covered by the MathWorks Limited License

Highlights from
GPU Julia Set Explorer

image thumbnail

GPU Julia Set Explorer

by

 

10 Oct 2011 (Updated )

Explore the Julia Set of the Mandelbrot Set using MATLAB and a capable GPU.

juliaSetExplorer(z0)
function figh = juliaSetExplorer(z0)
%juliaSetExplorer  An interface for exploring the Mandelbrot Julia Set
%
%   juliaSetExplorer() launches a simple interface that allows the Julia
%   Set for any location in the Mandelbrot set to be viewed. The location
%   can be varied by clicking or dragging in a small overview window in the
%   top left. Close the window to exit.
%
%   figh = juliaSetExplorer() also returns a handle to the interface window
%   so that it can be captured or close programmatically.

%   Author: Ben Tordoff
%   Copyright 2010-2011 The MathWorks, Inc.

% First make sure we are capable of running this
matlabVersionCheck()

% Create the shared data structure
data = struct();
data.WriteVideo = false;
data.DoAnimation = false;
data.LocationList = makeLocationList();
data.NextLocation = 2;
if nargin>0
    data.z0 = z0;
else
    data.z0 = data.LocationList(1);
end
x = gpuArray.linspace( -2, 2, 800 );
y = gpuArray.linspace( -2, 2, 600 );
[x,y] = meshgrid(x, y);
data.z = complex( x, y );
clear x y;

% Build the interface
gui = createGUI();

% Are we capturing video
if data.WriteVideo
    data.VideoWriter = VideoWriter('juliaSetExplorer.avi');
    data.VideoWriter.FrameRate = 20;
    data.VideoWriter.Quality = 90;
    open( data.VideoWriter );
end

% Set the initial view
drawMandelbrot( gui.MandelImage );
onLimitsChanged();

% Animate a set path
if data.DoAnimation
    doAnimation();
end

% Return a handle to the figure if requested
if nargout > 0
    figh = gui.Window;
end


    function gui = createGUI()
        gui = struct();
        gui.Window = figure( ...
            'Name', 'Julia Set explorer v1.1', ...
            'Renderer', 'ZBuffer', ...
            'NumberTitle', 'off', ...
            'HandleVisibility', 'off', ...
            'MenuBar', 'none', ...
            'ToolBar', 'figure', ...
            'CloseRequestFcn', @onFigureClose );
        
        gui.JuliaAxes = axes( ...
            'Parent', gui.Window, ...
            'Position', [0 0 1 1], ...
            'XLim', [-2 2], 'YLim', [-2 2], ...
            'XTick', [], 'YTick', [], ...
            'DataAspectRatio', [1 1 1] );
        % Add listeners so that we can redraw when the axes are moved
        axHandle = handle( gui.JuliaAxes );
        gui.Listeners = [
            handle.listener( axHandle, findprop( axHandle, 'YLim' ), 'PropertyPostSet', @onLimitsChanged )
            ]; %#ok<NBRAK>
        
        % Also add a small set of axes for showing the Mandelbrot set
        gui.MandelAxes = axes( ...
            'Parent', gui.Window, ...
            'Position', [0 0.8 0.2 0.2], ...
            'XLim', [-2 0.5], 'YLim', [-1.25 1.25], ...
            'XTick', [], 'YTick', [], ...
            'ButtonDownFcn', @onMandelButtonDown );
        
        gui.JuliaImage = imagesc( 'Parent', gui.JuliaAxes, ...
            'CData', nan, ...
            'XData', [-2 2], 'YData', [-2 2] );
        colormap( gui.JuliaAxes, jet2(1000) );
        % Add a line so that zooming works. Strange but true.
        line( 'Parent', gui.JuliaAxes, ...
        'XData', [-2 2], 'YData', [-2 2], ...
            'Visible', 'off', ...
            'HitTest', 'off' );
        
        
        gui.MandelImage = imagesc( 'Parent', gui.MandelAxes, 'CData', nan, ...
            'XData', [-2 0.5], 'YData', [-1.25 1.25], 'HitTest', 'off' );
        gui.MandelCrosshair = [
            line( 'Parent', gui.MandelAxes, 'XData', [-2 0.5], 'YData', [0 0], 'Color', 'w', ...
            'HitTest', 'off', 'Tag', 'CrossHairH' );
            line( 'Parent', gui.MandelAxes, 'YData', [-1.25 1.25], 'XData', [0 0], 'Color', 'w', ...
            'HitTest', 'off', 'Tag', 'CrossHairV' );
            ];
        
        % Remove some things we don't want from the toolbar and add a
        % toggle to the toolbar to hide the mandelbrot view
        tb = findall( gui.Window, 'Type', 'uitoolbar' );
        delete( findall( tb, 'Tag', 'Standard.FileOpen' ) );
        delete( findall( tb, 'Tag', 'Standard.NewFigure' ) );
        delete( findall( tb, 'Tag', 'Standard.EditPlot' ) );
        delete( findall( tb, 'Tag', 'Exploration.Brushing' ) );
        delete( findall( tb, 'Tag', 'Exploration.DataCursor' ) );
        delete( findall( tb, 'Tag', 'Exploration.Rotate' ) );
        delete( findall( tb, 'Tag', 'DataManager.Linking' ) );
        delete( findall( tb, 'Tag', 'Plottools.PlottoolsOn' ) );
        delete( findall( tb, 'Tag', 'Plottools.PlottoolsOff' ) );
        delete( findall( tb, 'Tag', 'Annotation.InsertLegend' ) );
        delete( findall( tb, 'Tag', 'Annotation.InsertColorbar' ) );
        gui.ShowMandelToggle = uitoggletool( ...
            'Parent', tb, ...
            'CData', readIcon( 'icon_mandel.png' ), ...
            'TooltipString', 'Show/hide the Mandelbrot view', ...
            'State', 'on', ...
            'Separator', 'on', ...
            'ClickedCallback', @onMandelTogglePressed );
        gui.AnimToggle = uitoggletool( ...
            'Parent', tb, ...
            'CData', readIcon( 'icon_play.png' ), ...
            'TooltipString', 'Play an animation', ...
            'State', 'off', ...
            'ClickedCallback', @onAnimTogglePressed );
        % Set the resize function (we can't do this on construction as it
        % would fire!
        set( gui.Window, 'ResizeFcn', @onFigureResize );
    end % createGUI

    function cdata = readIcon( filename )
        [cdata,~,alpha] = imread( filename );
        idx = find( ~alpha );
        page = size(cdata,1)*size(cdata,2);
        cdata = double( cdata ) / 255;
        cdata(idx) = nan;
        cdata(idx+page) = nan;
        cdata(idx+2*page) = nan;
    end % readIcon

    function onMandelButtonDown( ~, ~ )
        pos = get( gui.MandelAxes, 'CurrentPoint' );
%         fprintf( 'Click at (%f,%f)\n', pos(1,1), pos(1,2) );
        set( gui.Window, ...
            'WindowButtonMotionFcn', @onMandelButtonMotion, ...
            'WindowButtonUpFcn', @onMandelButtonUp );
        updatePosition( complex( pos(1,1), pos(1,2) ) )
    end % onMandelButtonDown

    function onMandelButtonMotion( ~, ~ )
        pos = get( gui.MandelAxes, 'CurrentPoint' );
        updatePosition( complex( pos(1,1), pos(1,2) ) )
    end % onMandelButtonMotion

    function onMandelButtonUp( ~, ~ )
        pos = get( gui.MandelAxes, 'CurrentPoint' );
        set( gui.Window, ...
            'WindowButtonMotionFcn', [], ...
            'WindowButtonUpFcn', [] );
        updatePosition( complex( pos(1,1), pos(1,2) ) )
    end % onMandelButtonUp

    function updatePosition( z0 )
        if ~ishandle( gui.Window )
            return;
        end
        data.z0 = z0;
        drawMandelbrotCrosshair( gui.MandelCrosshair, data.z0 );
        drawJulia( gui.JuliaImage, gui.JuliaAxes, data.z, data.z0, data.DoAnimation );
        % Capture!
        if data.WriteVideo
            currFrame = getframe( gui.Window );
            writeVideo( data.VideoWriter, currFrame );
        else
            drawnow();
        end
    end % updatePosition

    function onLimitsChanged( ~, ~ )
        % To work out what to draw and at what resolution we need the axis
        % limits and pixel counts.
        xlim = get(gui.JuliaAxes,'XLim');
        ylim = get(gui.JuliaAxes,'YLim');
        pixpos = getpixelposition( gui.JuliaAxes );
        x = gpuArray.linspace( xlim(1), xlim(2), pixpos(3) );
        y = gpuArray.linspace( ylim(1), ylim(2), pixpos(4) );
        [x,y] = meshgrid(x, y);
        data.z = complex( x, y );
        set( gui.JuliaImage, 'XData', xlim, 'YData', ylim );
        % Use "update position to force a redraw"
        updatePosition( data.z0 );
    end % onLimitsChanged

    function onFigureClose( ~, ~ )
        % Clear up
        if data.WriteVideo
            close( data.VideoWriter );
        end
        delete( gui.Window );
    end % onFigureClose

    function onFigureResize( ~, ~ )
        pos = getpixelposition( gui.JuliaAxes );
        aspect = pos(4)/pos(3);
        
        % Make sure the Mandelbrot preview is square and not too big
        mandelX = min( 0.2, 200/pos(3) );
        mandelY = mandelX/aspect;
        set( gui.MandelAxes, 'Position', [0 0 mandelX mandelY] );
        
        % Set the Julia-set axes to exactly fill the figure and the
        % resolution to match the axes size
        pos = get( gui.Window, 'Position' );
        xlim = get( gui.JuliaAxes, 'XLim' );
        ylim = get( gui.JuliaAxes, 'YLim' );
        delta_ylim = ( diff( xlim )*pos(4)/pos(3) - diff( ylim ) ) / 2;
        data.WindowPixelSize = pos(3:4);
        % Set the YLim to give the correct aspect. This will trigger a
        % redraw
        set( gui.JuliaAxes, 'YLim', ylim + delta_ylim*[-1 1] );
    end % onFigureResize

    function onMandelTogglePressed( ~, ~ )
        % Toggle the Mandelbrot view on and off
        state = get( gui.ShowMandelToggle, 'State' );
        set( gui.MandelAxes, 'Visible', state, 'HitTest', state );
        set( gui.MandelImage, 'Visible', state );
        set( gui.MandelCrosshair, 'Visible', state );
    end

    function onAnimTogglePressed( ~, ~ )
        % Toggle the Mandelbrot view on and off
        data.DoAnimation = strcmpi( get( gui.AnimToggle, 'State' ), 'on' );
        if data.DoAnimation
            doAnimation();
        end
    end

    function doAnimation()
        while data.DoAnimation
            if ~ishandle( gui.Window )
                return;
            end
            updatePosition( data.LocationList(data.NextLocation) );
            data.NextLocation = mod( data.NextLocation, numel( data.LocationList ) ) + 1;
        end
        % Do a final redraw with animation off
        updatePosition( data.LocationList(data.NextLocation) );
    end  % doAnimation

end


function drawMandelbrot( imh )
escapeRadius2 = 400; % Square of escape radius
maxIters = 500;
x = gpuArray.linspace( -2, 0.5, 500 );
y = gpuArray.linspace( -1.2, 1.2, 500 );
[x0,y0] = meshgrid(x, y);

logCount = arrayfun( @processMandelbrotElement, x0, y0, escapeRadius2, maxIters );

set( imh, ...
    'CData', gather(logCount), ...
    'XData', [-2 0.5], 'YData', [-1.2 1.2] );
set( ancestor( imh, 'axes' ), 'XLim', [-2 0.5], 'YLim', [-1.2 1.2] );
end % drawMandelbrot


function drawMandelbrotCrosshair( l, z0 )
hline = l(1);
vline = l(2);

set( vline, 'XData', real(z0)*[1 1] );
set( hline, 'YData', imag(z0)*[1 1] );

end % drawMandelbrotCrosshair

function drawJulia( imh, axh, z, z0, animating )
maxIterations = 200;
escapeRadius2 = 100; % Square of escape radius

if animating && numel(z)>1e6
    z = z(1:2:end,1:2:end);
end

logCount = arrayfun( @processJuliaSetElement, z, z0, escapeRadius2, maxIterations );

set( imh, 'CData', gather( logCount ) );
set( axh, 'CLim', [1 log(maxIterations)] );

end % drawJulia

function [logCount,t] = processMandelbrotElement( x0, y0, escapeRadius2, maxIterations )
% Evaluate the Mandelbrot function for a single element
t = 1;
z0 = complex( x0, y0 );
z = z0;
count = 0;
while count <= maxIterations && (z*conj(z) <= escapeRadius2)
    z = z*z + z0;
    count = count + 1;
end
magZ2 = max(real(z).^2 + imag(z).^2,escapeRadius2);
logCount = log( count + 1 - log( log( magZ2 ) / 2 ) / log(2) );
end % processMandelbrotElement

function logCount = processJuliaSetElement( z, z0, escapeRadius2, maxIterations )
% Evaluate the Julia set function for a single element

count = 0;
magZ2 = real(z)^2 + imag(z)^2;
while count <= maxIterations && (magZ2 <= escapeRadius2)
    z = z*z + z0;
    count = count + 1;
    magZ2 = real(z)^2 + imag(z)^2;
end
% Iterate twice more to help smoothing
z = z.*z + z0;  z = z.*z + z0;  count = count + 2;
% Now adjust the count using the magnitude to get smoothing
magZ2 = max( real(z)^2 + imag(z)^2, escapeRadius2 );
logCount = log( count + 2 - log( log( magZ2 ) / 2 ) / log(2) );
end % processJuliaSetElement

function matlabVersionCheck()
% R2011b is v7.13
majorMinor = sscanf( version, '%d.%d' );
if (majorMinor(1)<7) || (majorMinor(1)==7 && majorMinor(2)<13)
    error( 'mandelbrotViewer:MATLABTooOld', 'mandelbrotViewer requires MATLAB R2011b or above.' );
end
end % matlabVersionCheck

function cmap = jet2(m)
% Jet colormap with added fade to black

% A list of break-point colors
colors = [
    0.0  0.0  0.5
    0.0  0.0  1.0
    0.0  0.5  1.0
    0.0  1.0  1.0
    0.5  1.0  0.5
    1.0  1.0  0.0
    1.0  0.5  0.0
    1.0  0.0  0.0
    0.5  0.0  0.0
    0.5  0.0  0.0
    1.0  0.0  0.0
    1.0  0.5  0.0
    1.0  1.0  0.0
    0.5  1.0  0.5
    0.0  1.0  1.0
    0.0  0.5  1.0
    0.0  0.0  1.0
    0.0  0.0  0.5
    0.0  0.0  0.0
    ];

% Now work out the indices into the map
N = size( colors, 1 );
idxIn = 1:N;
idxOut = linspace( 1, N, m );
cmap = [
    interp1( idxIn, colors(:,1), idxOut )
    interp1( idxIn, colors(:,2), idxOut )
    interp1( idxIn, colors(:,3), idxOut )
    ]';
end % jet2


function locations = makeLocationList()
breakpoints = [
    0.29
    0.47 + 0.157143i
    0.4442 + 0.3286i
    0.354911 + 0.585714i
    0.2103 + 0.5551i
    -0.0827 + 0.8355i
    -0.158482 + 1.042857i
    -0.5826 + 0.6143i
    -0.7533 + 0.1178i
    -1.008216 + 0.35i
    -1.25 + 0.2i
    -1.25
    0.29
    ];
numSteps = 10000;
N = numel(breakpoints);
re = interp1( 1:N, real( breakpoints ), linspace( 1, N, numSteps ) );
im = interp1( 1:N, imag( breakpoints ), linspace( 1, N, numSteps ) );
locations = complex( re, im );
end

Contact us