MATLABでお絵描きツールを作ってみた(2) (ボタンで各種設定)

# 前回のおさらい

[前回](https://qiita.com/sanmojih/items/5ac9ed1510da863d42f2)は、簡単なお絵描きツールとして、マウス操作をキャプチャするfigureのコールバック関数を中心に紹介しました。

# 今回の内容

せっかくなので、「消去」「画像保存」「線の色変更」「線の太さ変更」ができるように、お絵描きツールを拡張します。
ポイントとなる技術は、ボタンの作成と、各種ダイアログの使用です。

# 共通変数の追加
前準備として、線の色と線の幅の設定を保存するための変数を追加します。
ここでは、コールバック関数をネスト関数とすることで、共通に呼び出せるようにします。

```matlab
    LineColor   = [0,0.4470,0.7410]; % Lineの色
    LineWidth   = 1;                 % Lineの線幅
```

線を描画するところも以下のように変更し、色と線幅を指定するようにします。

```matlab
curLineH = line(axH,curLineX,curLineY,'Color',LineColor,'LineWidth',LineWidth);
```

# ボタンの配置

axes 表示域を少し小さくして、ウィンドウの下にボタンを配置します。
figure上のボタンは、uicontrol で Style をpushbutton に指定して作成します。
なお、uicontrol のStyle のデフォルト設定が、pushbutton なので、Style指定は省略することもできます。

## axes の更新

前回のコードを以下のように少し修正します。
Position を変更して、figureの下部分に、ボタンを表示する領域を確保します。
また、画像保存するときに、枠表示が邪魔になるので、XAxis, YAxisのColorの指定を'none' にします。

```matlab:
%% Axesの表示
axH               = axes(figH);    % axH: 軸オブジェクト
axH.XTick         = [];            % X軸目盛をオフ
axH.YTick         = [];            % Y軸目盛をオフ
axH.Units         = 'normalized';  % Positionの単位指定
axH.Position      = [0,0.1,1,0.9]; % Axesの位置指定 (下10%の領域はボタン用に残す)
axH.XLim          = [0,1];         % X軸範囲を指定
axH.YLim          = [0,1];         % Y軸範囲を指定
axH.XAxis.Color   ='none';         % 枠を無色に指定
axH.YAxis.Color   ='none';         % 枠を無色に指定
```

## ボタンの作成

例えば、「消去」用のボタンは、uicontrol を使って、以下のように設定します。
ボタンに表示する文字は、Stringで指定し、またボタン押下時のコールバック関数は、Callback に指定します。

```matlab
% 消去ボタン
btnClearH = uicontrol(figH);              % btnClearH :「消去」のボタンオブジェクト
btnClearH.Units    = 'normalized';        % Position の単位指定
btnClearH.Position = [0,0,0.1,0.1];       % ボタンの位置指定
btnClearH.String   = '消去';              % 表示文字列の指定
btnClearH.Callback = @Callback_btnClearH; % コールバック関数の指定

```

# コールバック関数の指定

コールバック関数には、ボタン押下時の処理を記載します。

## 「消去」ボタンのコールバック

findobj で axes 上の Lineを探索し、deleteですべて消去するようにします。

```matlab
    function Callback_btnClearH(~,~)
        %% 消去ボタンのコールバック
        delete(findobj(axH,'Type','Line'));  % Axes上にあるLineをすべて消去
    end
```


## 「保存」ボタンのコールバック

画像としてファイルに保存するため、まず、ファイル名を取得します。
便利なダイアログがすでにあり、uiputfile を使ってファイル名を指定させます。
ファイル名が指定されれば、print を使ってファイルに保存します。

```matlab
    function Callback_btnSaveH(~,~)
        %% 保存ボタンのコールバック        
        [FileName,PathName] = uiputfile('*.png','保存ファイル名'); % ファイル名の指定
        if ischar(FileName)
            % キャンセルでない場合
            print(figH,'-dpng','-noui',fullfile(PathName,FileName));
        end
    end
```

ファイル指定のダイアログは、次のようなものが立ち上がります。

![uiputfile1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482917/2e8ceebd-d84d-d777-b2e2-35b4e7d1c4da.png)


## 「線色」のコールバック

線の色を変更するために、色を指定します。
色の指定にも、便利なダイアログがあり、uisetcolor を使って指定します。

```matlab
    function Callback_btnLineColorH(~,~)
       %% 線色のコールバック
       % ダイアログで色の指定
        RGB = uisetcolor(LineColor);
        if length(RGB)==3
            % キャンセルでない場合
            LineColor = RGB;
        end
    end
```

色の指定は、次のようなダイアログが立ち上がります。

![uisetcolor2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482917/ad4bce8e-1115-7273-bc04-0e99316b6bd5.png)


## 「線幅」のコールバック

線の幅（太さ）を指定します。
ここでは、簡単のため、inputdlg を使って入力します。
inputdlg の結果は文字列になるため、数値に変換します。

```matlab
    function Callback_btnLineWidthH(~,~)
       %% 線幅のコールバック
       % ダイアログで線幅の指定
        WidthStr = inputdlg('線幅','',1,{num2str(LineWidth)});
        if ~isempty(WidthStr)
            % キャンセルでない場合
            
            % 文字列から数値に変換
            Width = str2double(WidthStr);
            if ~isnan(Width)
                % 数値に変換できたとき線幅を変更
                LineWidth = Width;
            end
        end
    end
```

線の指定では、次のようなダイアログが立ち上がります。

![inputdlg2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482917/c2ac088b-a882-3ec8-11f9-e8a2e511552c.png)


# 完成したプログラム

実行しやすいようにプログラム全体を表示します。

```matlab:simple_draw2.m
function simple_draw2()

%% 共通変数の定義
ButtonState = false;             % マウスボタンの押下状態
curLineH    = gobjects();        % 現在のLineのオブジェクト
curLineX    = [];                % 現在のLineのX座標
curLineY    = [];                % 現在のLineのY座標
LineColor   = [0,0.4470,0.7410]; % Lineの色
LineWidth   = 1;                 % Lineの線幅

%% Figureの表示
figH                       = figure();                     % figH: Figureオブジェクト
figH.MenuBar               = 'none';                       % メニューをオフ
figH.WindowButtonDownFcn   = @WindowButtonDownFcn_figH;    % マウスボタン押下時のコールバック関数を指定
figH.WindowButtonMotionFcn = @WindowButtonMotionFcn_figH;  % マウスボタン移動時のコールバック関数を指定
figH.WindowButtonUpFcn     = @WindowButtonUpFcn_figH;      % マウスボタンリリース時のコールバック関数を指定

%% Axesの表示
axH               = axes(figH);    % axH: 軸オブジェクト
axH.XTick         = [];            % X軸目盛をオフ
axH.YTick         = [];            % Y軸目盛をオフ
axH.Units         = 'normalized';  % Positionの単位指定
axH.Position      = [0,0.1,1,0.9]; % Axesの位置指定 (下10%の領域はボタン用に残す)
axH.XLim          = [0,1];         % X軸範囲を指定
axH.YLim          = [0,1];         % Y軸範囲を指定
axH.XAxis.Color   ='none';         % 枠を無色に指定
axH.YAxis.Color   ='none';         % 枠を無色に指定

%% ボタン
% 消去ボタン
btnClearH = uicontrol(figH);              % btnClearH :「消去」のボタンオブジェクト
btnClearH.Units    = 'normalized';        % Position の単位指定
btnClearH.Position = [0,0,0.1,0.1];       % ボタンの位置指定
btnClearH.String   = '消去';              % 表示文字列の指定
btnClearH.Callback = @Callback_btnClearH; % コールバック関数の指定

% 保存ボタン
btnSaveH = uicontrol(figH);              % btnSaveH :「保存」のボタンオブジェクト
btnSaveH.Units    = 'normalized';        % Position の単位指定
btnSaveH.Position = [0.1,0,0.1,0.1];     % ボタンの位置指定
btnSaveH.String   = '保存';              % 表示文字列の指定
btnSaveH.Callback = @Callback_btnSaveH;  % コールバック関数の指定

% 線色ボタン
btnLineColorH = uicontrol(figH);                   % btnLineColorH :「線色」のボタンオブジェクト
btnLineColorH.Units    = 'normalized';             % Position の単位指定
btnLineColorH.Position = [0.2,0,0.1,0.1];          % ボタンの位置指定
btnLineColorH.String   = '線色';                   % 表示文字列の指定
btnLineColorH.Callback = @Callback_btnLineColorH;  % コールバック関数の指定

% 線幅ボタン
btnLineWidthH = uicontrol(figH);                   % btnLineWidthH :「線幅」のボタンオブジェクト
btnLineWidthH.Units    = 'normalized';             % Position の単位指定
btnLineWidthH.Position = [0.3,0,0.1,0.1];          % ボタンの位置指定
btnLineWidthH.String   = '線幅';                   % 表示文字列の指定
btnLineWidthH.Callback = @Callback_btnLineWidthH;  % コールバック関数の指定

    %% コールバック関数の定義
    function WindowButtonDownFcn_figH(~,~)
        %% マウスボタン押下時のコールバック        
        ButtonState = true; % 押下状態を保存

        % マウスの位置の取得
        curLineX = axH.CurrentPoint(1,1);
        curLineY = axH.CurrentPoint(1,2);

        % 線の描画
        curLineH = line(axH,curLineX, curLineY,'Color',LineColor,'LineWidth',LineWidth);
    end

    function WindowButtonMotionFcn_figH(~,~)
        %% マウスボタン移動時のコールバック
        if ButtonState
            % マウスボタンを押下時には、Lineの描画を更新

            % 現在のマウスの位置
            X = axH.CurrentPoint(1,1);
            Y = axH.CurrentPoint(1,2);
            
            % 線の座標値を更新
            curLineX = [curLineX;X];
            curLineY = [curLineY;Y];

            % 描画の更新
            curLineH.XData = curLineX;
            curLineH.YData = curLineY;
        end
    end

    function WindowButtonUpFcn_figH(~,~)
        %% マウスボタンリリース時のコールバック
        ButtonState = false;  % ボタン押下状態を解除
    end

    function Callback_btnClearH(~,~)
        %% 消去ボタンのコールバック
        delete(findobj(axH,'Type','Line'));  % Axes上にあるLineをすべて消去
    end
    
    function Callback_btnSaveH(~,~)
        %% 保存ボタンのコールバック        
        [FileName,PathName] = uiputfile('*.png','保存ファイル名'); % ファイル名の指定
        if ischar(FileName)
            % キャンセルでない場合
            print(figH,'-dpng','-noui',fullfile(PathName,FileName));
        end
    end

    function Callback_btnLineColorH(~,~)
       %% 線色のコールバック
       % ダイアログで色の指定
        RGB = uisetcolor(LineColor);
        if length(RGB)==3
            % キャンセルでない場合
            LineColor = RGB;
        end
    end

    function Callback_btnLineWidthH(~,~)
       %% 線幅のコールバック
       % ダイアログで線幅の指定
        WidthStr = inputdlg('線幅','',1,{num2str(LineWidth)});
        if ~isempty(WidthStr)
            % キャンセルでない場合
            
            % 文字列から数値に変換
            Width = str2double(WidthStr);
            if ~isnan(Width)
                % 数値に変換できたとき線幅を変更
                LineWidth = Width;
            end
        end
    end

end
```

# 実行例

起動するとボタン付きの figureが表示されます。

![figure2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482917/b5f85e9f-9eb4-3685-428f-d9afae67c06a.png)


実際に絵を描いてみます。（相変わらず絵心がなくてすみません。。。）


![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482917/7589d1ac-d3d2-c31e-b79b-003105666022.png)


## 終わりに

この例では、uicontrol を使って、figure上にボタンを配置してお絵描きツールを拡張しました。
uiputfile, uisetcolor, inputdlg といったダイアログを使用することで、各ボタンの操作も簡単に記述できました。
他にも、背景色の変更やグリッドの表示、等、いろんな機能を追加することもできると思います。
よくある子供向けのお絵描きアプリには、書いた絵が動くようなものも多いので、そのような実装も可能でしょう。以上を参考に様々なアイデアを実装してみてください！