ぬの部屋(仮)
nu-no-he-ya
  •  123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
         12
    3456789
    10111213141516
    17181920212223
    2425262728  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
       1234
    567891011
    12131415161718
    19202122232425
    26272829   
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28      
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
    1234567
    891011121314
    15161718192021
    22232425262728
           
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
         12
    3456789
    10111213141516
    17181920212223
    242526272829 
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
        123
    45678910
    11121314151617
    18192021222324
    25262728   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    15161718192021
    293031    
           
         12
    3456789
    10111213141516
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
          1
    2345678
    9101112131415
    16171819202122
    232425262728 
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
  • wxWidgetsで画像を表示するパネルを作成

    マウスの右・左・ホイールで回転、移動、スケーリングする。

    ImagePanel.hpp

    #pragma once
    
    #ifndef WX_PRECOMP
    #include <wx/wx.h>
    #endif
    #include <wx/dcgraph.h>
    #include <wx/dcbuffer.h>
    
    #include <wx/geometry.h>
    
    #include <cmath>
    #include <wx/gdicmn.h>
    
    
    //! @brief 画像を回転させる
    //! @param transform 回転させる画像のアフィン変換行列
    //! @param imageCoordPos 回転の中心となる画像座標系の座標
    //! @param rad 回転角度(ラジアン)
    void RotateAroundPoint(wxAffineMatrix2D& transform, const wxPoint2DDouble& imageCoordPos, const double rad);
    
    
    //! @brief 画像をクライアント座標系で移動させる
    //! @param transform 移動させる画像のアフィン変換行列
    //! @param diff 移動量(パネルのクライアント座標系)
    void TranslateInScreenSpace(wxAffineMatrix2D& transform, const wxPoint& diff);
    
    
    //! @brief クライアント座標を元画像の座標系に変換
    //! @param clientPos クライアント座標
    //! @param transform 画像のアフィン変換行列
    //! @return 元画像の座標系の座標
    wxPoint2DDouble ClientToImage(const wxPoint& clientPos, const wxAffineMatrix2D& transform);
    
    //! @brief 画像を拡大・縮小する
    //! @param transform 拡大・縮小する画像のアフィン変換行列
    //! @param scalefactor 拡大・縮小率
    //! @param clientpos 拡大・縮小の中心座標(クライアント座標系)
    //! @details clientposに表示されているピクセルが移動しないように拡大・縮小する
    //! @return なし
    void Zoom(wxAffineMatrix2D& transform, double scalefactor, const wxPoint clientpos);
    
    //! @brief ウィンドウサイズの変化から、拡大・縮小率を計算する
    //! @param oldSize リサイズ前のウィンドウサイズ
    //! @param newSize リサイズ後のウィンドウサイズ
    //! @return 拡大・縮小率
    double CalcResizeScaleFactor(const wxSize& oldSize, const wxSize& newSize);
    
    //! @brief マウスドラッグによる拡大・縮小率を計算する
    //! @param oldpos ドラッグ開始時のマウス座標
    //! @param newpos ドラッグ中のマウス座標
    //! @return 拡大・縮小率
    double CalcDragScaleFactor(const wxAffineMatrix2D& transform, const wxPoint& oldpos, const wxPoint& newpos, const int pixelratio);
    
    
    //! @brief ウィンドウサイズが変化したときに、現在画面中央に表示されているピクセルが、リサイズ後も中央に表示されるようにする
    //! @param oldSize リサイズ前のウィンドウサイズ
    //! @param newSize リサイズ後のウィンドウサイズ
    //! @return なし
    void ResizeKeepCenter(wxAffineMatrix2D& transform, const wxSize& oldSize, const wxSize& newSize);
    
    //! @brief 画像をパネルにフィットさせるためのアフィン変換行列を計算
    //! @param transform アフィン変換行列
    //! @param imageSize 元画像のサイズ
    //! @param panelSize パネルのサイズ
    //! @return なし
    void FitImage(wxAffineMatrix2D& transform, const wxSize& imageSize, const wxSize& panelSize);
    
    //! @brief 3点の座標から回転角を計算する
    //! @param a 1つ目の座標(始点)
    //! @param b 2つ目の座標(回転中心)
    //! @param c 3つ目の座標(終点)
    //! @details cを中心に、aからbへ回転した時の回転角を計算する
    //! @return 回転角(ラジアン)
    double CalculateAngle(const wxPoint& a, const wxPoint& b, const wxPoint& c);
    

    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    
    // マウスによる移動量を角度で計算するクラス
    class MouseDrag {
        bool _during;    // ドラッグ中かどうか
        wxPoint _center; // 角度計算の中心座標
        wxPoint _from;   // 開始地点のマウス座標
        wxPoint _to;     // 現在のマウス座標
    public:
        MouseDrag(const wxPoint center) : _during(false), _center(center) {}
        MouseDrag() : _during(false), _center(0, 0) {}
    
        //! @brief ドラッグの開始地点
        wxPoint GetFrom()const { return _from; }
    
        //! @brief ドラッグの現在地点
        wxPoint GetTo()const { return _to; }
    
        //! @brief ドラッグ開始
        void Start(const wxPoint newPos) {
            _from = newPos;
            _during = true;
        }
        //! @brief ドラッグ終了
        void Stop() { _during = false; }
    
        //! @brief  現在回転中かどうか
        bool During() { return _during; }
    
        //! @brief  マウスの移動量を更新
        void Update(const wxPoint& newPos) {_to = newPos;}
    
        //! @brief マウスの移動量から角度を計算
        double Angle() const{return CalculateAngle(_to, _center, _from);}
    
        //! @brief マウスの移動量を取得
        wxPoint Difference()const {return _to - _from;}
    };
    

    class ImagePanel : public wxPanel {
        enum class DragMode {
            NONE,
            MOVE,
            ROTATE,
            ZOOM
        };
        wxAffineMatrix2D _transform;         //!< アフィン変換行列
        wxAffineMatrix2D _transform_back;    //!< 移動・回転前のアフィン変換行列のバックアップ
        wxSize _winsize_back;                //!< ウィンドウサイズが変更されるたびに更新されるウィンドウサイズのバックアップ
        // ウィンドウサイズの変化に関しては、変更開始イベントを拾えないため、変化するたびに前回からの差分に基づいて計算する
    
        DragMode _drag_mode;                 //!< ドラッグモード
        MouseDrag _mov_dragging;             //!< マウスドラッグ間の移動量を計算・管理
        std::weak_ptr<wxBitmap> _imageBitmap;//!< 画像データ
    
    public:
        ImagePanel(wxWindow* parent);
    
        //! @brief イベントのバインド
        void EventBind();
    
    private:
    
        //! @brief ペイントイベントハンドラ
        void OnPaint(wxPaintEvent& event);
        
        //! @brief イベントハンドラ(移動開始イベントにバインドする用)
        void OnEventMoveStart(wxMouseEvent& event);
        
        //! @brief イベントハンドラ(移動終了イベントにバインドする用)
        void OnEventMoveStop(wxMouseEvent& event);
        
        //! @brief イベントハンドラ(回転開始イベントにバインドする用)
        void OnEventRotateStart(wxMouseEvent& event);
    
        //! @brief イベントハンドラ(回転終了イベントにバインドする用)
        void OnEventRotateStop(wxMouseEvent& event);
        
        //! @brief イベントハンドラ(ドラッグでスケーリング開始イベントにバインドする用)
        void OnEventScaleStart(wxMouseEvent& event);
    
        //! @brief イベントハンドラ(ドラッグでスケーリング終了イベントにバインドする用)
        void OnEventScaleStop(wxMouseEvent& event);
    
        //! @brief マウス移動イベントハンドラ
        void OnMouseMove(wxMouseEvent& event);
        
        //! @brief イベントハンドラ(ホイール回転でスケーリングイベントにバインドする用)
        void OnEventScaleChange(wxMouseEvent& event);
    
        //! @brief ウィンドウサイズ変更イベントハンドラ
        void OnResize(wxSizeEvent& event);
    
    public:
        //! @brief 表示する画像をセット
        //! @param bitmap 表示する画像データ
        //! return なし
        void SetImage(const std::shared_ptr<wxBitmap>& bitmap);
    
        //! @brief 画像が画面に収まるように調節する
        void FitImageToPanel();
    
    private:
    
        //! @brief クライアント領域の中央の座標を取得
        //! @return クライアント領域のサイズの1/2
        wxPoint GetClientCenter();
    
        //! @brief ドラッグしてズーム処理の開始
        //! @param pos ドラッグ開始時のマウス座標
        //! @return なし
        void ZoomStart(const wxPoint& pos);
    
        //! @brief ドラッグしてズーム処理の終了
        //! @return なし
        void ZoomStop();
    
        //! @brief ドラッグ中のズーム処理
        //! @param pos ドラッグ中のマウス座標
        //! @return 画面更新が必要かどうか
        bool ZoomDuring(const wxPoint& pos);
    
        //! @brief ドラッグして移動処理の開始
        //! @param pos ドラッグ開始時のマウス座標
        //! @return なし
        void MoveStart(const wxPoint& pos);
    
        //! @brief ドラッグして移動処理の終了
        //! @return なし
        void MoveStop();
    
        //! @brief ドラッグして移動中の処理
        //! @param pos ドラッグ中のマウス座標
        //! @return 画面更新が必要かどうか
        bool MoveDuring(const wxPoint& pos);
    
        //! @brief ドラッグして回転処理の開始
        //! @param pos ドラッグ開始時のマウス座標
        //! @return なし
        void RotateStart(const wxPoint& pos);
    
        //! @brief ドラッグして回転処理の終了
        //! @return なし
        void RotateStop();
    
        //! @brief ドラッグして回転中の処理
        //! @param pos ドラッグ中のマウス座標
        //! @return 画面更新が必要かどうか
        bool RotateDuring(const wxPoint& pos);
    
    };
    

    ImagePanel.cpp

    #include "ImagePanel.hpp"
    
    
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    
    void RotateAroundPoint(wxAffineMatrix2D& transform, const wxPoint2DDouble& imageCoordPos, const double rad) {
        // 基準点 (px, py) を原点に移動
        transform.Translate(imageCoordPos.m_x, imageCoordPos.m_y);
        // 回転を適用 (角度はラジアン単位)
        transform.Rotate(rad);
    
        // 元の位置に戻す
        transform.Translate(-imageCoordPos.m_x, -imageCoordPos.m_y);
    
    }
    
    void TranslateInScreenSpace(wxAffineMatrix2D& transform, const wxPoint& diff) {
        // アフィン変換行列の現在の回転・スケーリングを取得
        wxMatrix2D mat2D;
        wxPoint2DDouble translation;
        transform.Get(&mat2D, &translation);
    
        // 回転を取り除くために逆回転行列を作成
        double rad = std::atan2(mat2D.m_12, mat2D.m_11); // 現在の回転角を計算(ラジアン)
        wxAffineMatrix2D inverseRotation;
        inverseRotation.Rotate(-rad);
    
        // スケーリング値を計算
        double scaleX = std::sqrt(mat2D.m_11 * mat2D.m_11 + mat2D.m_21 * mat2D.m_21);
        double scaleY = std::sqrt(mat2D.m_12 * mat2D.m_12 + mat2D.m_22 * mat2D.m_22);
    
    
        // 移動量 (dx, dy) を回転の影響を受けない座標系に変換
        wxPoint2DDouble adjustedMove = inverseRotation.TransformPoint(wxPoint2DDouble(diff.x, diff.y));
    
        // 変換行列に移動を適用
        transform.Translate(adjustedMove.m_x / scaleX, adjustedMove.m_y / scaleX);
    }
    
    wxPoint2DDouble ClientToImage(const wxPoint& clientPos, const wxAffineMatrix2D& transform) {
        wxAffineMatrix2D inverse = transform;
        inverse.Invert();
        wxPoint2DDouble imagePos = inverse.TransformPoint(wxPoint2DDouble(clientPos));
        return imagePos;
    }
    
    void Zoom(wxAffineMatrix2D& transform, double scalefactor, const wxPoint clientpos) {
        // クライアント座標を元画像の座標系に変換
        wxPoint2DDouble imagePos = ClientToImage(clientpos, transform);
    
        // スケーリングの中心をマウス座標に固定
        transform.Translate(imagePos.m_x, imagePos.m_y);
        transform.Scale(scalefactor, scalefactor);
        transform.Translate(-imagePos.m_x, -imagePos.m_y);
    
    }
    
    double CalcResizeScaleFactor(const wxSize& oldSize, const wxSize& newSize) {
        double diffx = newSize.GetWidth() - oldSize.GetWidth();
        double diffy = newSize.GetHeight() - oldSize.GetHeight();
        double scalex = newSize.GetWidth() / (double)oldSize.GetWidth();
        double scaley = newSize.GetHeight() / (double)oldSize.GetHeight();
        double scalefactor = 1.0;
    
        if ((int)diffy != 0)
            scalefactor = scaley;
    
    
        return scalefactor;
    }
    double CalcDragScaleFactor(const wxAffineMatrix2D& transform, const wxPoint& oldpos, const wxPoint& newpos, const int pixelratio) {
    
        // 原点からの距離を計算
        double olddist = std::sqrt(oldpos.x * oldpos.x + oldpos.y * oldpos.y);
        double newdist = std::sqrt(newpos.x * newpos.x + newpos.y * newpos.y);
    
    
    
        double scalefactor = 1.0;
        if (olddist < newdist) {
            // 拡大
            scalefactor = 1.0 + (newdist - olddist) / pixelratio;
        }
        else {
            // 縮小
            scalefactor = 1.0 - (olddist - newdist) / pixelratio;
        }
    
        return scalefactor;
    
    }
    
    void ResizeKeepCenter(wxAffineMatrix2D& transform, const wxSize& oldSize, const wxSize& newSize) {
        wxPoint oldwndCenter = wxPoint(oldSize.GetWidth() / 2, oldSize.GetHeight() / 2);
        wxPoint newwndCenter = wxPoint(newSize.GetWidth() / 2, newSize.GetHeight() / 2);
    
        // リサイズ前の画面中央に表示されていた画像の座標を取得
        wxPoint2DDouble oldImageCenter = ClientToImage(oldwndCenter, transform);
    
        // 画面サイズの変化に応じた新しい中央座標を取得
        wxPoint2DDouble newImageCenter = ClientToImage(newwndCenter, transform);
    
        // 変位を計算し、元の画像位置を新しい中央に移動
        wxPoint2DDouble shift = oldImageCenter - newImageCenter;
        transform.Translate(-shift.m_x, -shift.m_y);
    
    }
    
    void FitImage(wxAffineMatrix2D& transform, const wxSize& imageSize, const wxSize& panelSize) {
        double scaleX = static_cast<double>(panelSize.GetWidth()) / imageSize.GetWidth();
        double scaleY = static_cast<double>(panelSize.GetHeight()) / imageSize.GetHeight();
        double scale = std::min(scaleX, scaleY);
    
        // 中心を計算
        wxPoint offset(
            (panelSize.GetWidth() - imageSize.GetWidth() * scale) / 2,
            (panelSize.GetHeight() - imageSize.GetHeight() * scale) / 2);
    
        // アフィン変換を設定
        transform = wxAffineMatrix2D();
        transform.Translate(offset.x, offset.y);
        transform.Scale(scale, scale);
    
    }
    
    double CalculateAngle(const wxPoint& a, const wxPoint& b, const wxPoint& c) {
        wxRealPoint ab(b.x - a.x, b.y - a.y);
        wxRealPoint cb(b.x - c.x, b.y - c.y);
    
        // 内積を計算
        double dotProduct = ab.x * cb.x + ab.y * cb.y;
    
        // ベクトルの大きさを計算
        double magnitudeAB = std::sqrt(ab.x * ab.x + ab.y * ab.y);
        double magnitudeCB = std::sqrt(cb.x * cb.x + cb.y * cb.y);
    
        // コサイン
        double cosTheta = dotProduct / (magnitudeAB * magnitudeCB);
        cosTheta = std::min(1.0, std::max(-1.0, cosTheta));
    
        // 角度を計算(ラジアン)
        double angleRad = std::acos(cosTheta);
    
        // 外積を使って角度の符号を決定
        double cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
        if (cross < 0.0)
            angleRad = -angleRad;
    
    
        return angleRad;// angleRad * 180.0 / M_PI;
    }
    

    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    
    
    ImagePanel::ImagePanel(wxWindow* parent)
        : wxPanel(parent) {
        SetBackgroundStyle(wxBG_STYLE_PAINT);
        SetBackgroundColour(wxColour(0, 0, 0));
    
        _transform = wxAffineMatrix2D();
    
        _drag_mode = DragMode::NONE;
    
        EventBind();
    }
    
    void ImagePanel::OnPaint(wxPaintEvent& event) {
        wxAutoBufferedPaintDC dc(this);
        dc.Clear();
    
        std::shared_ptr<wxBitmap> pbitmap = _imageBitmap.lock();
        if (!pbitmap) return;
    
        wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
        if (gc) {
    
            wxGraphicsMatrix matrix = gc->CreateMatrix(_transform);
    
            gc->SetTransform(matrix);
            gc->DrawBitmap(*pbitmap, 0, 0, pbitmap->GetWidth(), pbitmap->GetHeight());
            delete gc;
        }
    }
    
    void ImagePanel::OnEventMoveStart(wxMouseEvent& event) {
        MoveStart(event.GetPosition());
        CaptureMouse();
    }
    
    void ImagePanel::OnEventMoveStop(wxMouseEvent& event) {
        MoveStop();
        if (HasCapture()) ReleaseMouse();
    }
    
    void ImagePanel::OnEventRotateStart(wxMouseEvent& event) {
        RotateStart(event.GetPosition());
        CaptureMouse();
    }
    
    void ImagePanel::OnEventRotateStop(wxMouseEvent& event) {
        RotateStop();
        if (HasCapture())
            ReleaseMouse();
    }
    
    void ImagePanel::OnEventScaleStart(wxMouseEvent& event) {
        ZoomStart(event.GetPosition());
        CaptureMouse();
    }
    
    void ImagePanel::OnEventScaleStop(wxMouseEvent& event) {
        ZoomStop();
        if (HasCapture())
            ReleaseMouse();
    }
    
    void ImagePanel::OnMouseMove(wxMouseEvent& event) {
    
        if (_mov_dragging.During()) {
    
            switch (_drag_mode) {
            case DragMode::MOVE:
                if (MoveDuring(event.GetPosition())) {
                    Refresh();
                }
                break;
            case DragMode::ROTATE:
                if (RotateDuring(event.GetPosition())) {
                    Refresh();
                }
                break;
            case DragMode::ZOOM:
                if (ZoomDuring(event.GetPosition())) {
                    Refresh();
                }
                break;
            }
    
        }
    }
    
    void ImagePanel::OnEventScaleChange(wxMouseEvent& event) {
        int wheelRotation = event.GetWheelRotation();
    
        // ホイールの回転量によって拡大・縮小率を変更
        double scaleFactor = (wheelRotation > 0) ? 1.1 : 0.9;
    
        // マウスのクライアント座標を取得
        wxPoint clientPos = event.GetPosition();
    
        Zoom(_transform, scaleFactor, clientPos);
    
    
        Refresh();
    }
    
    void ImagePanel::OnResize(wxSizeEvent& event) {
    
        if (GetClientSize().GetWidth() == 0 || GetClientSize().GetHeight() == 0) return;
    
        if (_imageBitmap.lock()) {
    
            wxSize oldSize = _winsize_back;
            wxSize newSize = GetClientSize();
            ResizeKeepCenter(_transform, oldSize, newSize);
            double scalefactor = CalcResizeScaleFactor(oldSize, newSize);
    
            if ((scalefactor > 0.001) && !isnan(scalefactor) && !isinf(scalefactor))
                Zoom(_transform, scalefactor, GetClientCenter());
    
        }
    
        // 旧ウィンドウサイズを記録
        _winsize_back = GetClientSize();
    
        Refresh();
    }
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    void ImagePanel::EventBind() {
        Bind(wxEVT_PAINT, &ImagePanel::OnPaint, this);
    
        // 移動に割り当て
        Bind(wxEVT_LEFT_DOWN, &ImagePanel::OnEventMoveStart, this);
        Bind(wxEVT_LEFT_UP, &ImagePanel::OnEventMoveStop, this);
    
        // 回転に割り当て
        Bind(wxEVT_RIGHT_DOWN, &ImagePanel::OnEventRotateStart, this);
        Bind(wxEVT_RIGHT_UP, &ImagePanel::OnEventRotateStop, this);
        // ホイールドラッグによる拡大・縮小
        Bind(wxEVT_MIDDLE_DOWN, &ImagePanel::OnEventScaleStart, this);
        Bind(wxEVT_MIDDLE_UP, &ImagePanel::OnEventScaleStop, this);
        /*
        // 拡大・縮小に割り当て
        Bind(wxEVT_MOUSEWHEEL, &ImagePanel::OnEventScaleChange, this);
        */
    
        Bind(wxEVT_MOTION, &ImagePanel::OnMouseMove, this);
    
        Bind(wxEVT_SIZE, &ImagePanel::OnResize, this);
    
    }
    
    void ImagePanel::SetImage(const std::shared_ptr<wxBitmap>& bitmap) {
        _imageBitmap = bitmap;
        Refresh();
    }
    
    void ImagePanel::FitImageToPanel() {
        std::shared_ptr<wxBitmap> pbitmap = _imageBitmap.lock();
        if (!pbitmap) return;
    
        // スケール計算
        wxSize panelSize = GetClientSize();
        wxSize imageSize = pbitmap->GetSize();
    
        FitImage(_transform, imageSize, panelSize);
    
        _winsize_back = GetClientSize();
    
        Refresh();
    }
    
    wxPoint ImagePanel::GetClientCenter() {
        wxSize panelSize = GetClientSize();
        return wxPoint(panelSize.GetWidth() / 2, panelSize.GetHeight() / 2);
    }
    
    void ImagePanel::ZoomStart(const wxPoint& pos) {
        _drag_mode = DragMode::ZOOM;
        _transform_back = _transform;
        _mov_dragging.Start(pos);
    }
    
    void ImagePanel::ZoomStop() {
        _mov_dragging.Stop();
    }
    
    bool ImagePanel::ZoomDuring(const wxPoint& pos) {
    
        _mov_dragging.Update(pos);
        wxPoint oldpos = _mov_dragging.GetFrom();
        wxPoint newpos = _mov_dragging.GetTo();
    
        wxPoint diff = _mov_dragging.Difference();
        if (diff.x == 0 && diff.y == 0) {
            return false;
        }
        wxPoint winoldpos = ClientToScreen(oldpos);
        wxPoint winnewpos = ClientToScreen(newpos);
    
    
        double scalefactor = CalcDragScaleFactor(_transform, winoldpos, winnewpos, GetClientSize().GetWidth());
    
        if (!isnan(scalefactor) && !isinf(scalefactor)) {
            _transform = _transform_back;
            Zoom(_transform, scalefactor, oldpos);
        }
        else {
            return false;
        }
    
        return true;
    
    }
    
    void ImagePanel::MoveStart(const wxPoint& pos) {
        _drag_mode = DragMode::MOVE;
        _transform_back = _transform;
        _mov_dragging.Start(pos);
    }
    
    void ImagePanel::MoveStop() {
        _mov_dragging.Stop();
    }
    
    bool ImagePanel::MoveDuring(const wxPoint& pos) {
        _mov_dragging.Update(pos);
        wxPoint diff = _mov_dragging.Difference();
    
        if (diff.x == 0 && diff.y == 0)
            return false;
    
        _transform = _transform_back;
        // 移動量を画面座標系で適用
        TranslateInScreenSpace(_transform, diff);
    
        return true;
    }
    
    void ImagePanel::RotateStart(const wxPoint& pos) {
        _drag_mode = DragMode::ROTATE;
        _transform_back = _transform;
    
        // クライアント領域の中心座標を取得
        wxSize panelSize = GetClientSize();
    
        _mov_dragging = MouseDrag(GetClientCenter());
        _mov_dragging.Start(pos);
    }
    
    void ImagePanel::RotateStop() {
        _mov_dragging.Stop();
    }
    
    bool ImagePanel::RotateDuring(const wxPoint& pos) {
        _mov_dragging.Update(pos);
        double rad = _mov_dragging.Angle();
    
        if (isnan(rad) || isinf(rad))
            return false;
    
        _transform = _transform_back;
        auto imgpos = ClientToImage(GetClientCenter(), _transform);
        RotateAroundPoint(_transform, imgpos, rad);
    
        return true;
    }
    

    使用例

    // https://docs.wxwidgets.org/3.0/overview_helloworld.html
    
    // プリプロセッサに以下二つを追加
    // __WXMSW__
    // WXUSINGDLL
    
    // サブシステムをWindowsに設定(WinMainで呼び出すので)
    // Windows (/SUBSYSTEM:WINDOWS)
    
    #ifndef WX_PRECOMP
    #include <wx/wx.h>
    #endif
    
    #include <wx/gdicmn.h> // wxPointに必要
    #include <wx/frame.h>  // wxFrameに必要
    
    /////////////////////////////////////
    /////////////////////////////////////
    /////////////////////////////////////
    
    #include <string>
    
    #include "ImagePanel.hpp"
    
    // ウィンドウ作成
    class MyFrame : public wxFrame
    {
        ImagePanel* _imagePanel;// 画像表示パネル
        std::shared_ptr<wxBitmap> _bitmap;
    public:
    
        void PostCreate() {
    
    
            //
            wxImage::AddHandler(new wxPNGHandler);
            _bitmap = std::make_shared<wxBitmap>();
            wxImage image;
            if (image.LoadFile(R"(C:\temp\test.png)", wxBITMAP_TYPE_PNG)) {// 画像読み込み
                *_bitmap = wxBitmap(image);
            }
    
    
            this->Layout(); // レイアウトの更新
    
            _imagePanel->SetImage(_bitmap);// パネルに画像を指定
            _imagePanel->FitImageToPanel();
    
        }
    
    
        MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
            : wxFrame(NULL, wxID_ANY, title, pos, size)
        
        {
            // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行
            // コンストラクタはウィンドウ生成イベント扱い
            CallAfter(&MyFrame::PostCreate);
    
            // ImagePanel作成
            _imagePanel = new ImagePanel(this);
            // 画面いっぱいに貼り付ける
            wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
            sizer->Add(_imagePanel, 1, wxEXPAND);
            this->SetSizer(sizer);
    
        }
    
    
    private:
    };
    
    /////////////////////////////////////
    /////////////////////////////////////
    /////////////////////////////////////
    
    // wxWidgetsのアプリケーション作成
    class MyApp : public wxApp {
    public:
    
        virtual bool OnInit() {
            MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340));
            frame->Show(true);
    
            return true;
        }
    
    };
    
    /////////////////////////////////////
    /////////////////////////////////////
    /////////////////////////////////////
    
    // WinMainをマクロで定義
    wxIMPLEMENT_APP(MyApp);