Flood-filling in an image

An example using the Flood-Fill technique

In this sample you will learn how to use the following OpenCV functions:

Sources:

function varargout = ffilldemo_gui(im)
    % load source image
    if nargin < 1
        src = imread(fullfile(mexopencv.root(),'test','fruits.jpg'));
    elseif ischar(im)
        src = imread(im);
    else
        src = im;
    end

    % make sure we start with a color image
    if size(src,3) == 1
        src = cv.cvtColor(src, 'GRAY2RGB');
    end

    % create the UI
    h = buildGUI(src);
    if nargout > 0, varargout{1} = h; end
end

function onClick(~,~,h)
    %ONCLICK  Event handler for mouse click on images

    % ignore anything but left mouse clicks
    if ~strcmp(get(h.fig,'SelectionType'), 'normal')
        return;
    end

    % current parameters
    p = getappdata(h.fig, 'params');

    % which axis is active (color or gray)
    if p.isColor
        idx = 1;
    else
        idx = 2;
    end

    % retrieve location of mouse click (in image coordinates)
    % and use it as flooding seed point
    seed = get(h.ax(idx), 'CurrentPoint');
    seed = seed(1,1:2);

    % flood fill options
    newVal = randi([0 255], [1 3]);
    if ~p.isColor
        newVal = newVal * [0.299; 0.587; 0.114];
    end
    if p.ffillMode == 's'
        lo = zeros(1,3);
        up = zeros(1,3);
    else
        lo = repmat(p.loDiff,1,3);
        up = repmat(p.upDiff,1,3);
    end
    flags = {'Connectivity',p.connectivity, ...
        'FixedRange',p.ffillMode=='f', 'MaskFillValue',p.newMaskVal};

    % apply flood fill on selected image (color or gray)
    dst = get(h.img(idx), 'CData');
    if p.useMask
        % update mask as well
        mask = get(h.img(3), 'CData');
        mask = cv.threshold(mask, 1, 'MaxValue',128, 'Type','Binary');
        [dst,~,area,mask] = cv.floodFill(dst, round(seed-1), newVal, ...
            'Mask',mask, 'LoDiff',lo, 'UpDiff',up, flags{:});
        set(h.img(3), 'CData',mask);
    else
        [dst,~,area] = cv.floodFill(dst, round(seed-1), newVal, ...
            'LoDiff',lo, 'UpDiff',up, flags{:});
    end

    % show result
    fprintf('%d pixels were repainted\n', area);
    set(h.img(idx), 'CData',dst);
    set(h.lin, 'XData',seed(1), 'YData',seed(2));
    drawnow;
end

function onType(~,e,h)
    %ONTYPE  Event handler for key press on figure

    % parameters
    p = getappdata(h.fig, 'params');

    % handle keys
    switch e.Key
        case {'q', 'escape'}
            disp('Exiting ...');
            close(h.fig);
            return;

        case 'h'
            usage([],[]);

        case 'c'
            % reset images
            resetImages(h);
            if p.isColor
                % use gray image
                stackAxes(h, 2, 1);
                disp('Grayscale mode is set');
            else
                % use color image
                stackAxes(h, 1, 2);
                disp('Color mode is set');
            end
            p.isColor = ~p.isColor;

        case 'm'
            p.useMask = ~p.useMask;

        case 'r'
            resetImages(h);
            disp('Original image is restored');

        case {'s', 'f', 'g'}
            p.ffillMode = e.Key;
            if e.Key == 's'
                disp('Simple floodfill mode is set');
            elseif e.Key == 'f'
                disp('Fixed Range floodfill mode is set');
            elseif e.Key == 'g'
                disp('Gradient (floating range) floodfill mode is set');
            end

        case {'4','8'}
            p.connectivity = str2double(e.Key);
            fprintf('%s-connectivity mode is set\n', e.Key);
    end

    % update/refresh
    setappdata(h.fig, 'params', p);
    updateModes(h);
    if ismember(e.Key, {'c','m','r'}), drawnow; end
end

function onChange(~,~,h)
    %ONCHANGE  Event handler for UI controls

    % retrieve current values from UI controls
    lo = round(get(h.slid(1), 'Value'));
    up = round(get(h.slid(2), 'Value'));
    set(h.txt(1), 'String',sprintf('LoDiff: %3d',lo));
    set(h.txt(2), 'String',sprintf('UpDiff: %3d',up));
    drawnow;

    % update stored parameters
    p = getappdata(h.fig, 'params');
    p.loDiff = lo;
    p.upDiff = up;
    setappdata(h.fig, 'params', p);
end

function updateModes(h)
    %UPDATEMODES  Helper function to show current parameters in editbox

    % stored parameters
    p = getappdata(h.fig, 'params');
    set(h.txt(3), 'String',sprintf('c=%d, m=%d, fill=%c, cn=%d', ...
        p.isColor, p.useMask, p.ffillMode, p.connectivity));
end

function resetImages(h)
    %RESETIMAGES  Helper function to reset images

    % color
    set(h.img(1), 'CData',h.img0);

    % gray
    gray = cv.cvtColor(h.img0, 'RGB2GRAY');
    set(h.img(2), 'CData',gray)

    % mask
    mask = get(h.img(3), 'CData');
    mask(:) = 0;
    set(h.img(3), 'CData',mask);

    % seed point
    set(h.lin, 'XData',NaN, 'YData',NaN);
end

function stackAxes(h, idx1, idx2)
    %STACKAXES  Helper function to switch which axis to show

    set(h.img(idx1), 'Visible','on');
    set(h.img(idx2), 'Visible','off');
    if ~mexopencv.isOctave()
        %HACK: UISTACK not implemented in Octave
        uistack(h.ax(idx1), 'top');
    end

    % re-parent seed point line inside top axis
    set(h.lin, 'Parent',h.ax(idx1));
end

function usage(~,~)
    %USAGE  Display help dialog

    helpdlg({
        'Click on the left image to set flood-fill seed point.'
        ''
        'Hot keys:'
        'ESC - quit the program'
        'h - this help dialog'
        'c - switch color/grayscale mode'
        'm - switch mask mode'
        'r - restore the original image'
        's - use null-range floodfill'
        'f - use gradient floodfill with fixed(absolute) range'
        'g - use gradient floodfill with floating(relative) range'
        '4 - use 4-connectivity mode'
        '8 - use 8-connectivity mode'
    });
end

function h = buildGUI(img)
    %BUILDGUI  Creates the UI

    % parameters
    sz = size(img);
    mask = zeros(sz(1:2)+2, 'uint8');
    sz(2) = max(sz(2), 300);  % minimum figure width
    gray = cv.cvtColor(img, 'RGB2GRAY');
    lo = 20;
    up = 20;

    % build the user interface (no resizing to keep it simple)
    h = struct();
    h.img0 = img;
    h.fig = figure('Name','Flood-Fill Demo', ...
        'NumberTitle','off', 'Menubar','none', 'Resize','off', ...
        'Position',[200 200 sz(2)*2 sz(1)+59]);
    if ~mexopencv.isOctave()
        %HACK: not implemented in Octave
        movegui(h.fig, 'center');
    end
    h.ax(1) = axes('Parent',h.fig, 'Units','pixels', 'Position',[1 60 sz(2) sz(1)]);
    h.ax(2) = axes('Parent',h.fig, 'Units','pixels', 'Position',[1 60 sz(2) sz(1)]);
    h.ax(3) = axes('Parent',h.fig, 'Units','pixels', 'Position',[sz(2)+1 60 sz(2) sz(1)]);
    if ~mexopencv.isOctave()
        h.img(1) = imshow(img, 'Parent',h.ax(1));
        h.img(2) = imshow(gray, 'Parent',h.ax(2));
        h.img(3) = imshow(mask, 'Parent',h.ax(3));
    else
        %HACK: https://savannah.gnu.org/bugs/index.php?45473
        axes(h.ax(1));
        h.img(1) = imshow(img);
        axes(h.ax(2));
        h.img(2) = imshow(gray);
        axes(h.ax(3));
        h.img(3) = imshow(mask);
    end
    h.txt(3) = uicontrol('Parent',h.fig, 'Style','text', 'FontSize',8, ...
        'Position',[5 5 120 20], 'String','', 'Enable','off');
    h.but = uicontrol('Parent',h.fig, 'Style','pushbutton', ...
        'Position',[40 30 60 20], 'String','Help', 'Callback',@usage);
    h.txt(1) = uicontrol('Parent',h.fig, 'Style','text', 'FontSize',11, ...
        'Position',[125 5 80 20], 'String',sprintf('LoDiff: %3d',lo));
    h.txt(2) = uicontrol('Parent',h.fig, 'Style','text', 'FontSize',11, ...
        'Position',[125 30 80 20], 'String',sprintf('UpDiff: %3d',up));
    h.slid(1) = uicontrol('Parent',h.fig, 'Style','slider', 'Value',lo, ...
        'Min',0, 'Max',255, 'SliderStep',[1 10]./(255-0), ...
        'Position',[205 5 sz(2)-210 20]);
    h.slid(2) = uicontrol('Parent',h.fig, 'Style','slider', 'Value',up, ...
        'Min',0, 'Max',255, 'SliderStep',[1 10]./(255-0), ...
        'Position',[205 30 sz(2)-210 20]);
    h.lin = line(NaN, NaN, 'Parent',h.ax(1), ...
        'LineStyle','none', 'Marker','.', 'MarkerSize',10, 'Color','r');

    %HACK: WindowKeyPressFcn not implemented in Octave
    % http://savannah.gnu.org/bugs/?44910
    if mexopencv.isOctave()
        % create GUI buttons to interact instead of keyboard pressing
        keys = 'qrcmsfg48';
        labels = {'Quit', 'Reset', 'Color/Grayscale', 'Mask', ...
            'null', 'fixed', 'floating', '4-conn', '8-conn'};
        for k=1:numel(keys)
            uicontrol('Parent',h.fig, 'Style','pushbutton', ...
                'Position',[sz(2)+20*k 30 20 20], 'String',keys(k), ...
                'TooltipString',labels{k}, ...
                'Callback',@(o,~) onType(o,struct('Key',keys(k)),h));
        end
    end

    % initialize interactive parameters
    p = struct();
    p.ffillMode = 'f';
    p.loDiff = lo;
    p.upDiff = up;
    p.connectivity = 4;
    p.isColor = true;
    p.useMask = false;
    p.newMaskVal = 255;
    setappdata(h.fig, 'params', p);

    % hook event handlers, and trigger default start
    opts = {'Interruptible','off', 'BusyAction','cancel'};
    set(h.slid, 'Callback',{@onChange,h}, opts{:});
    set(h.fig, 'WindowKeyPressFcn',{@onType,h}, opts{:});
    set(h.img(1:2), 'ButtonDownFcn',{@onClick,h}, opts{:});
    stackAxes(h, 1, 2);
    onChange([],[],h);
    updateModes(h);
end