Matlab 虽然自带的 imshow 也支持鼠标滚轮放大缩小,鼠标左键拖动放大的图片调整视野,但是一旦把 menubar 给关了,就丢失了 zoom 和 pan 的功能,最离谱的是——zoom 和 pan 还只能开一个,开了 zoom 就不能开 pan。所以打算自己撸一个同时支持鼠标滚轮放大图片和左键拖动图片的功能。

实现方案

具体实现功能

  • 鼠标滚轮放大/缩小图片
  • 鼠标左键长按拖拽图片
  • 鼠标左键双击恢复图片

鼠标滚轮放大图片

比较简单,监听 Figure 的鼠标滚轮事件 WindowScrollWheelFcn,根据滚动值 event.VerticalScrollCount 来判断滚动方向进行放大或缩小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function im_zoom(self,~,event)

currentPosition = self.ax.CurrentPoint;
x = currentPosition(1,1);
y = currentPosition(1,2);
if x >= self.ax.XLim(1) && x <= self.ax.XLim(2) && y >= self.ax.YLim(1) && y <= self.ax.YLim(2)
if event.VerticalScrollCount > 0
scale = 1.1;
else
scale = 1/1.1;
end
xlim_range = get(self.ax, 'xlim');
ylim_range = get(self.ax, 'ylim');
% 进行放大缩小
set(self.fig,'Pointer','crosshair'); % change mouse point
self.ax.XLim = (xlim_range - x) * scale + x; % 能保证鼠标滚动时,即使放大缩小,鼠标所在的位置还在原来的位置,保持不变
self.ax.YLim = (ylim_range - y) * scale + y;
end
end

鼠标拖拽图片

会复杂一点,

  1. 鼠标左键点击,ax.UserData.status 设置为’axes_paning’(默认情况为’idle’),并记录当前鼠标位置

    1
    2
    3
    4
    5
    function im_pan_start(self)
    self.ax.UserData.status = 'axes_paning';
    self.ax.UserData.previous_point = self.ax.CurrentPoint;
    set(self.fig,'Pointer','fleur'); % change mouse point
    end
  2. 监听鼠标移动事件,根据鼠标移动的位置,来更新 ax 的 XLim 和 YLim,实现平移效果为了只有鼠标按住左键才能拖动,只有 ax.UserData.status 为’axes_paning’时,才进行平移。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function fig_motion(self,~,~)
    currentPosition = self.ax.CurrentPoint;
    x = currentPosition(1,1);
    y = currentPosition(1,2);
    if x >= self.ax.XLim(1) && x <= self.ax.XLim(2) && y >= self.ax.YLim(1) && y <= self.ax.YLim(2)
    switch self.ax.UserData.status
    case "axes_paning"
    self.im_panning()
    otherwise
    return
    end
    end
    end
  3. 当鼠标左键抬起后,将 ax.UserData.status 设置为’idle’,使得 fig_motion 功能失效

    1
    2
    3
    4
    function im_pan_stop(self)
    self.ax.UserData.status = 'idle';
    set(self.fig,'Pointer','arrow'); % recover mouse point
    end

鼠标左键双击恢复图片

  • 载入图片时,记录 ax 原始的 XLim 和 YLim

    1
    2
    self.ax.XLim = self.ax.UserData.origin_xlim;
    self.ax.YLim = self.ax.UserData.origin_ylim;
  • 左键双击时,恢复为原始的 XLim 和 YLim

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function fig_click(self,src,~)
    set(self.fig,'Pointer','arrow');
    switch src.SelectionType
    case 'open' % Double Click
    % Double Click to restore origin xlim and ylim
    self.im_zoom_restore()
    end
    end

    function im_zoom_restore(self)
    self.ax.XLim = self.ax.UserData.origin_xlim;
    self.ax.YLim = self.ax.UserData.origin_ylim;
    end

全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
classdef FigureManualZoom < handle
properties
fig;
ax;
end


methods
% Class Constructor
function self = FigureManualZoom()
self.fig = figure("Name",'Test',"NumberTitle","off",'Menubar','none','Position',[10,10,408,408]);
self.set_fig_center(self.fig);
self.fig.WindowButtonDownFcn = @self.fig_click;
self.fig.WindowScrollWheelFcn = @self.im_zoom;
self.fig.WindowButtonMotionFcn = @self.fig_motion;
self.fig.WindowButtonUpFcn = @self.fig_click_done;
self.ax = axes('Parent',self.fig);
im = imread("cellpose_test.tif");
im_size = size(im);
imshow(im,[],'parent',self.ax,'border','tight','initialmagnification','fit');
self.ax.UserData.status = 'idle';
self.ax.UserData.previous_point = [];
self.ax.UserData.origin_xlim = [0 im_size(2)];
self.ax.UserData.origin_ylim = [0 im_size(2)];
self.ax.XLim = self.ax.UserData.origin_xlim;
self.ax.YLim = self.ax.UserData.origin_ylim;
end
end

% Custom Methods
methods
% Put fig in the center of current screen
function set_fig_center(~, fig)
%SET_FIG_CENTER:

% Get the screen size
screenSize = get(0, 'ScreenSize');

% Get the size of the Figure
figSize = get(fig, 'Position');

% Calculate the position for centering the Figure
centeredPosition = [(screenSize(3) - figSize(3)) / 2, (screenSize(4) - figSize(4)) / 2, figSize(3:4)];

% Set the position of the Figure
set(fig, 'Position', centeredPosition);
end

% Listening for mouse click events
function fig_click(self,src,~)
set(self.fig,'Pointer','arrow');
switch src.SelectionType
case 'normal' % Left Click
self.im_pan_start();
case 'open' % Double Click
% Double Click to restore origin xlim and ylim
self.im_zoom_restore()
case 'alt' % Ctrl + Left Click / Right Click
return
case 'extend' % Shift + Left Click
return
otherwise
return
end
end

% Listening for mouse click completion events
function fig_click_done(self,~,~)
self.im_pan_stop();
end

% Listening for mouse movement events
function fig_motion(self,~,~)
currentPosition = self.ax.CurrentPoint;
x = currentPosition(1,1);
y = currentPosition(1,2);
if x >= self.ax.XLim(1) && x <= self.ax.XLim(2) && y >= self.ax.YLim(1) && y <= self.ax.YLim(2)
switch self.ax.UserData.status
case "axes_paning"
self.im_panning()
otherwise
return
end
end
end

function im_zoom_restore(self)
self.ax.XLim = self.ax.UserData.origin_xlim;
self.ax.YLim = self.ax.UserData.origin_ylim;
end
function im_pan_start(self)
self.ax.UserData.status = 'axes_paning';
self.ax.UserData.previous_point = self.ax.CurrentPoint;
set(self.fig,'Pointer','fleur'); % change mouse point
end
function im_pan_stop(self)
self.ax.UserData.status = 'idle';
set(self.fig,'Pointer','arrow'); % recover mouse point
end


% Drag the image with the left mouse button
function im_panning(self)

% get mouse position in UIaxes
current_position = self.ax.CurrentPoint;

% get current location (in pixels)
% get current XY-limits
xlim_range = get(self.ax, 'xlim');
ylim_range = get(self.ax, 'ylim');
% find change in position
delta_points = current_position - self.ax.UserData.previous_point;

% Adjust limits
set(self.ax, 'Xlim', xlim_range - delta_points(1));
set(self.ax, 'Ylim', ylim_range - delta_points(3));

% save new position
self.ax.UserData.previous_point = get(self.ax, 'CurrentPoint');
end

% Mouse wheel zooms in and out of images
function im_zoom(self,~,event)

currentPosition = self.ax.CurrentPoint;
x = currentPosition(1,1);
y = currentPosition(1,2);
if x >= self.ax.XLim(1) && x <= self.ax.XLim(2) && y >= self.ax.YLim(1) && y <= self.ax.YLim(2)
if event.VerticalScrollCount > 0
scale = 1.1;
else
scale = 1/1.1;
end
xlim_range = get(self.ax, 'xlim');
ylim_range = get(self.ax, 'ylim');
% 进行放大缩小
set(self.fig,'Pointer','crosshair'); % change mouse point
self.ax.XLim = (xlim_range - x) * scale + x; % 能保证鼠标滚动时,即使放大缩小,鼠标所在的位置还在原来的位置,保持不变
self.ax.YLim = (ylim_range - y) * scale + y;
end
end
end
end

Ref