function h_group = im_pix_line(h_parent,x_init,y_init)
%IM_PIX_LINE Create draggable and resizable line pixel by pixel.
% H = IM_PIX_LINE(HPARENT, X, Y) creates a draggable line on
% the object specified by HPARENT. The function returns H, a handle to
% the line, which is an hggroup object. HPARENT specifies the hggroups's
% parent, which is typically an axes object, but can also be any other
% object that can be the parent of an hggroup. X and Y specify the
% initial end point positions of the line in the form
% X = [X1 X2], Y =[Y1 Y2].
%
% A draggable line can be dragged interactively using the mouse.
%
% The draggable line has a context menu associated with it that allows you to
% copy the current end point positions to the clipboard in the form
% [X1 Y1; X2 Y2] and change the color used to display the line.
%
% API Function Syntaxes
% ---------------------
% A draggable line contains a structure of function handles, called
% an API, that can be used to manipulate it. To retrieve this
% structure from the draggable line, use the IPTGETAPI function.
%
% API = IPTGETAPI(H)
%
% Functions in the API, listed in the order of the structure fields, include:
%
% setPosition
%
% Sets the end point positions of the line.
%
% api.setPosition(X,Y)
% api.setPosition([X1 Y1; X2 Y2])
%
% getPosition
%
% Returns the end point positions of the line.
%
% pos = api.getPosition()
%
% where pos is a 2-by-2 array [X1 Y1; X2 Y2].
%
% delete
%
% Deletes the draggable line associated with the API.
%
% api.delete()
%
% setColor
%
% Sets the color used to draw the draggable line.
%
% api.setColor(new_color)
%
% where new_color is a three-element vector specifying an RGB
% value.
%
% addNewPositionCallback
%
% Adds the function handle FCN to the list of new-position callback
% functions.
%
% id = api.addNewPositionCallback(fcn)
%
% Whenever the draggable line changes its position, each
% function in the list is called with the syntax:
%
% fcn(pos)
%
% where pos is a 2-by-2 array [X1 Y1; X2 Y2].
%
% The return value, id, is used only with removeNewPositionCallback.
%
% removeNewPositionCallback
%
% Removes the corresponding function from the new-position callback
% list.
%
% api.removeNewPositionCallback(id)
%
% where id is the identifier returned by api.addNewPositionCallback.
%
% setDragConstraintFcn
%
% Sets the drag constraint function to be the specified function
% handle, fcn.
%
% api.setDragConstraintFcn(fcn)
%
% Whenever the draggable line is moved or resized because of a mouse drag,
% the constraint function is called using the syntax:
%
% constrained_position = fcn(new_position)
%
% where new_position is a 2-by-2 array [X1 Y1; X2 Y2].
%
% This allows a client, for example, to control where the line may be
% moved and how the line may be resized.
%
% getDragConstraintFcn
%
% Returns a function handle to the current drag constraint function.
%
% fcn = api.getDragConstraintFcn()
%
% Remarks
% -------
% If you use IM_PIX_LINE with an axis that contains an image object,
% and do not specify a drag constraint function, users can drag
% the point outside the extent of the image and lose the point.
% When used with an axis created by the PLOT function, the axis
% limits automatically expand to accommodate the movement of
% the point.
%
% Examples
% --------
% % Use a custom color for displaying the line.
% figure, imshow('pout.tif');
% h = im_pix_line(gca,[10 100], [100 100]);
% api = iptgetapi(h);
% api.setColor([0 1 0]);
% % Now, explore the context menu of the draggable line by right
% % clicking on the line.
% % Try to zoom in and out, you will find the line is drawed pixel
% % by pixel
%
% % Use addNewPositionCallback API function.
% figure, imshow('pout.tif');
% h = im_pix_line(gca,[10 100], [100 100]);
% api = iptgetapi(h);
% id = api.addNewPositionCallback(@(pos) title(mat2str(pos,3)));
% % Now move the mouse, note that the 2-by-2 position vector of the
% % line is displayed in the title above the image. Finally, remove
% % the callback using removeNewPositionCallback
% api.removeNewPositionCallback(id);
%
% % Use makeConstrainToRectFcn to prevent dragging out line outside
% % extent of image.
% figure, imshow('pout.tif');
% h = im_pix_line(gca,[10 100], [100 100]);
% api = iptgetapi(h);
% % IMPORTANT: use 'imline' here since makeConstrainToRectFcn can
% % only recognize one of the imline, imrect, impoint !!!
% fcn = makeConstrainToRectFcn('imline',get(gca,'XLim'),get(gca,'YLim'));
% api.setDragConstraintFcn(fcn);
%
% See also IMPOINT, IMRECT, IPTGETAPI, makeConstrainToRectFcn.
% Copyright 2004-2005 The MathWorks, Inc.
% $Revision: 1.1.6.3 $ $Date: 2005/12/12 23:22:09 $
%
% adopted by gauss.1982@gmali.com
% 15:52 2006-12-15 $
%
iptchecknargin(3, 3, nargin, mfilename);
if ~ishandle(h_parent)
error('Images:im_pix_line:invalidHandle', ...
'HPARENT must be a valid graphics handle.');
end
if strcmp(get(h_parent, 'type'), 'axes')
h_axes = h_parent;
else
h_axes = ancestor(h_parent, 'axes');
if isempty(h_axes)
error('Images:im_pix_line:noAxesAncestor', ...
'HPARENT must be a descendent of an axes object.');
end
end
try
h_group = hggroup('ButtonDownFcn', @startDrag, 'Parent', h_parent);
catch
error('Images:im_pix_line:failureToParent', ...
'HPARENT must be able to have an hggroup object as a child.');
end
%Returns API used to draw line graphic.
draw_api = pixLineSymbol(h_group);
% Initialize position variable for managing end point positions of line.
% This position variable is created to allow for abstraction of drag
% behavior in future. Position is in the form [X1 Y1; X2 Y2]
position = [reshape(x_init,2,1),reshape(y_init,2,1)];
% constraint_function is used by dragMotion() to give a client the
% opportunity to constraint where the draggable line can be
% dragged.
drag_constraint_function = identityFcn;
% new_position_callback_functions is used by sendNewPosition() to
% notify interested clients whenever the rectangle position changes.
new_position_callback_functions = makeList;
% Pattern for set associated with callbacks that get called as a
% result of the set.
insideSetPosition = false;
% Size of SelectionHighlight markers on end of line is 5 pixels. This
% constant is used throughout im_pix_line
marker_end_size = 5;
h_fig = iptancestor(h_axes, 'figure');
cmenu = uicontextmenu('Parent', h_fig);
%In order for IMDISTLINE to be draggable in IMTOOL, the HitTest property
%of the line objects created by LineSymbol() must be set to 'on'.
%this requires that the context menu be associated with the line objects
%rather than the h_group.
set(findobj(h_group,'Type','line'), 'UIContextMenu', cmenu);
uimenu(cmenu, ...
'Label', 'Copy Position', ...
'Callback', @copyPosition');
set_color_menu = uimenu(cmenu, ...
'Label', 'Set Line Color');
color_choices = getColorChoices;
for k = 1:numel(color_choices)
uimenu(set_color_menu, 'Label', color_choices(k).Label, ...
'Callback', @(varargin) setColor(color_choices(k).Color));
end
setColor(color_choices(1).Color);
api.setPosition = @setPosition;
api.getPosition = @getPosition;
api.delete = @deleteLine;
api.setColor = @setColor;
api.addNewPositionCallback = @addNewPositionCallback;
api.removeNewPositionCallback = @removeNewPositionCallback;
api.getDragConstraintFcn = @getDragConstraintFcn;
api.setDragConstraintFcn = @setDragConstraintFcn;
setappdata(h_group, 'API', api);
updateView(position);
% Create update function that knows how to get the position it needs when it
% will be called from HG contexts where it may not have access to the position
% otherwise.
update_fcn = @(varargin) updateView(api.getPosition());
% Install pointer manager in figure.
iptPointerManager(h_fig);
% Store pointer behavior in the hggroup object and all its children.
enterFcn = @(f,cp) set(f, 'Pointer', 'fleur');
iptSetPointerBehavior(findobj(h_group), enterFcn);
updateAncestorListeners(h_group,update_fcn);
%----------------------------
function setPosition(varargin)
% Validate inputs before caling break recursion pattern.
iptchecknargin(1, 2, nargin, sprintf('%s/setPosition',mfilename));
% Pattern to break recursion
if insideSetPosition
return
else
insideSetPosition = true;
end
switch nargin
case 1
position = varargin{1};
case 2
%Ensure that position vector always remains
%in the form [X1 Y1; X2 Y2] independent of whether data is
%entered as row or column vectors
position = [reshape(varargin{1},2,1),...
reshape(varargin{2},2,1)];
end
updateView(position);
sendNewPosition;
% Pattern to break recursion
insideSetPosition = false;
end
%-------------------------
function pos = getPosition
pos = position;
end
%---------------------------------------
function id = addNewPositionCallback(fun)
id = new_position_callback_functions.appendItem(fun);
end
%------------------------------------
function removeNewPositionCallback(id)
new_position_callback_functions.removeItem(id);
end
%--------------------------------
function setDragConstraintFcn(fun)
drag_constraint_function = fun;
end
%---------------------------------
function fh = getDragConstraintFcn
fh = drag_constraint_function;
end
%--------------------------------
function deleteLine(src, varargin) %#ok varargin needed by HG caller
if ishandle(h_group)
delete(h_group);
end
end
%---------------------------
function updateView(position)
draw_api.updateView(position);
end
%----------------------
function setColor(color)
draw_api.setColor(color)
end
%------------------------------
function copyPosition(varargin) %#ok varargin needed by HG caller
clipboard('copy', position);
end
%--------------------------------
function sendNewPosition(varargin) %#ok varargin needed by HG caller
list = new_position_callback_functions.getList();
for k = 1:numel(list)
fun = list{k};
fun(position);
end
end
%-------------------------------
function startDrag(varargin) %#ok varargin needed by HG caller
% Calculate scale of current axes. CurrentPoint property of axes
% is reported in axes units. Convert marker size to axes units to
% determine whether an end point has been selected.
[dx_per_pixel,dy_per_pixel]=getAxesScale(h_axes);
if strcmp(get(h_fig, 'SelectionType'), 'normal')
% Get the mouse location in data space.
[start_click_x,start_click_y] = getCurrentPoint(h_axes);
% Disable figure's pointer manager.
iptPointerManager(h_fig, 'disable');
initial_pos_x = position(:,1);
initial_pos_y = position(:,2);
start_position = position;
end_point_index = ...
abs(initial_pos_x-start_click_x)< marker_end_size*dx_per_pixel...
& abs(initial_pos_y-start_click_y) < marker_end_size*dy_per_pixel;
if ( any(end_point_index) )
%move end point
drag_motion_callback_id = iptaddcallback(h_fig, ...
'WindowButtonMotionFcn', ...
@endPointMotion);
drag_up_callback_id = iptaddcallback(h_fig, ...
'WindowButtonUpFcn', ...
@stopEndPointMotion);
else
%translate entire line
drag_motion_callback_id = iptaddcallback(h_fig, ...
'WindowButtonMotionFcn', ...
@dragMotion);
drag_up_callback_id = iptaddcallback(h_fig, ...
'WindowButtonUpFcn', ...
@stopDrag);
end
end
%---------------------------
function dragMotion(varargin) %#ok varargin needed by HG caller
if ~ishandle(h_axes)
return;
end
[new_x new_y] = getCurrentPoint(h_axes);
delta_x = new_x - start_click_x;
delta_y = new_y - start_click_y;
candidate_position = start_position + ...
[repmat(delta_x,2,1) repmat(delta_y,2,1)];
new_position = drag_constraint_function(candidate_position);
setPosition(new_position);
end % end dragMotion
%-------------------------
function stopDrag(varargin) %#ok varargin needed by HG caller
dragMotion();
iptremovecallback(h_fig, 'WindowButtonMotionFcn', ...
drag_motion_callback_id);
iptremovecallback(h_fig, 'WindowButtonUpFcn', ...
drag_up_callback_id);
% Enable figure's pointer manager.
iptPointerManager(h_fig, 'enable');
end % end stopDrag
%-------------------------------
function endPointMotion(varargin) %#ok varargin needed by HG caller
ax = ancestor(h_group, 'axes');
if ~ishandle(ax)
return;
end
[new_x new_y] = getCurrentPoint(h_axes);
delta_x = new_x - start_click_x;
delta_y = new_y - start_click_y;
if (all(end_point_index))
%if end points overlap, arbitrarily choose one of the end
%points to drag
end_point_index = logical([1 0]);
end
fixed_point = start_position(~end_point_index,:);
moving_point = start_position(end_point_index,:);
candidate_position(end_point_index,:) =...
moving_point + [delta_x delta_y];
candidate_position(~end_point_index,:) = fixed_point;
new_position=drag_constraint_function(candidate_position);
setPosition(new_position);
end % end endPointMotion
%-----------------------------------
function stopEndPointMotion(varargin) %#ok varargin needed by HG caller
endPointMotion();
iptremovecallback(h_fig, 'WindowButtonMotionFcn', ...
drag_motion_callback_id);
iptremovecallback(h_fig, 'WindowButtonUpFcn', ...
drag_up_callback_id);
% Enable figure's pointer manager.
iptPointerManager(h_fig, 'enable');
end % end stopEndPointMotion
end % end startDrag
end %end im_pix_line