diff --git a/libs/kimath/include/geometry/geometry_utils.h b/libs/kimath/include/geometry/geometry_utils.h index 4963aeb6f9..a9e5d7bedd 100644 --- a/libs/kimath/include/geometry/geometry_utils.h +++ b/libs/kimath/include/geometry/geometry_utils.h @@ -174,6 +174,19 @@ VECTOR2<ret_type> GetClampedCoords( const VECTOR2<in_type>& aCoords, pad_type aP } +/** + * Check if both coordinates of a vector are within the limits of the integer type. + */ +template <typename T> +inline bool IsVec2SafeXY( const VECTOR2<T>& aVec ) +{ + constexpr T min = std::numeric_limits<int>::min(); + constexpr T max = std::numeric_limits<int>::max(); + + return aVec.x > min && aVec.x < max && aVec.y > min && aVec.y < max; +} + + /** * Test if any part of a line falls within the bounds of a rectangle. * diff --git a/libs/kimath/include/math/box2.h b/libs/kimath/include/math/box2.h index 7c32a0c237..00f4d5ab7b 100644 --- a/libs/kimath/include/math/box2.h +++ b/libs/kimath/include/math/box2.h @@ -72,6 +72,11 @@ public: return BOX2( aCorner1, aCorner2 - aCorner1 ); } + static constexpr BOX2<Vec> ByCenter( const Vec& aCenter, const SizeVec& aSize ) + { + return BOX2( aCenter - aSize / 2, aSize ); + } + constexpr void SetMaximum() { if constexpr( std::is_floating_point<coord_type>() ) @@ -916,6 +921,7 @@ private: /* Default specializations */ typedef BOX2<VECTOR2I> BOX2I; typedef BOX2<VECTOR2D> BOX2D; +typedef BOX2<VECTOR2L> BOX2L; typedef std::optional<BOX2I> OPT_BOX2I; @@ -935,6 +941,21 @@ inline constexpr BOX2I BOX2ISafe( const BOX2D& aInput ) } +/** + * Check if a BOX2 is safe for use with BOX2D + * (probably BOX2D or BOX2L) + */ +template <typename Vec> +inline constexpr bool IsBOX2Safe( const BOX2<Vec>& aInput ) +{ + constexpr double high = std::numeric_limits<int>::max(); + constexpr double low = -std::numeric_limits<int>::max(); + + return ( aInput.GetLeft() >= low && aInput.GetTop() >= low && + aInput.GetRight() <= high && aInput.GetBottom() <= high ); +} + + inline constexpr BOX2I BOX2ISafe( const VECTOR2D& aPos, const VECTOR2D& aSize ) { constexpr double high = std::numeric_limits<int>::max(); diff --git a/pcbnew/pcb_reference_image.cpp b/pcbnew/pcb_reference_image.cpp index bd45c88bef..33b0d62023 100644 --- a/pcbnew/pcb_reference_image.cpp +++ b/pcbnew/pcb_reference_image.cpp @@ -36,6 +36,7 @@ #include <core/mirror.h> #include <board.h> #include <trigo.h> +#include <geometry/geometry_utils.h> #include <geometry/shape_rect.h> #include <wx/mstream.h> @@ -165,32 +166,65 @@ double PCB_REFERENCE_IMAGE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const const BOX2I PCB_REFERENCE_IMAGE::GetBoundingBox() const { // Bitmaps are center origin, BOX2Is need top-left origin - VECTOR2I size = m_bitmapBase->GetSize(); - VECTOR2I topLeft = { m_pos.x - size.x / 2, m_pos.y - size.y / 2 }; + const VECTOR2I size = m_bitmapBase->GetSize(); + const VECTOR2I topLeft{ m_pos.x - size.x / 2, m_pos.y - size.y / 2 }; - return BOX2I( topLeft, size ); + return BOX2I{ topLeft, size }; } std::shared_ptr<SHAPE> PCB_REFERENCE_IMAGE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const { - BOX2I box = GetBoundingBox(); + const BOX2I box = GetBoundingBox(); return std::make_shared<SHAPE_RECT>( box.GetPosition(), box.GetWidth(), box.GetHeight() ); } +void PCB_REFERENCE_IMAGE::SetPosition( const VECTOR2I& aPos ) +{ + const BOX2D newBox = BOX2D::ByCenter( aPos, m_bitmapBase->GetSize() ); + + if( !IsBOX2Safe( newBox ) ) + return; + + m_pos = aPos; +} + + +void PCB_REFERENCE_IMAGE::Move( const VECTOR2I& aMoveVector ) +{ + // Defer to SetPosition to check the new position overflow + SetPosition( m_pos + aMoveVector ); +} + + void PCB_REFERENCE_IMAGE::SetImageScale( double aScale ) { + if( aScale < 0 ) + return; + const double ratio = aScale / m_bitmapBase->GetScale(); + const VECTOR2D currentOrigin = m_pos + m_transformOriginOffset; + const VECTOR2D newOffset = m_transformOriginOffset * ratio; + const VECTOR2D newCenter = currentOrigin - newOffset; + const VECTOR2D newSize = m_bitmapBase->GetSize() * ratio; + + // The span of the image is limited to the size of the coordinate system + if( !IsVec2SafeXY( newSize ) ) + return; + + const BOX2D newBox = BOX2D::ByCenter( newCenter, newSize ); + + // Any overflow, just reject the call + if( !IsBOX2Safe( newBox ) ) + return; + m_bitmapBase->SetScale( aScale ); - - const VECTOR2I currentOrigin = m_pos + m_transformOriginOffset; - const VECTOR2I newOffset = m_transformOriginOffset * ratio; - - SetTransformOriginOffset( newOffset ); - SetPosition( currentOrigin - newOffset ); + SetTransformOriginOffset( KiROUND( newOffset ) ); + // Don't need to recheck the box, we just did that + m_pos = KiROUND( newCenter ); } @@ -202,7 +236,15 @@ const VECTOR2I PCB_REFERENCE_IMAGE::GetSize() const void PCB_REFERENCE_IMAGE::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) { - MIRROR( m_pos, aCentre, aFlipDirection ); + VECTOR2I newPos = m_pos; + MIRROR( newPos, aCentre, aFlipDirection ); + + const BOX2D newBox = BOX2D::ByCenter( newPos, m_bitmapBase->GetSize() ); + + if( !IsBOX2Safe( newBox ) ) + return; + + m_pos = newPos; m_bitmapBase->Mirror( aFlipDirection ); } diff --git a/pcbnew/pcb_reference_image.h b/pcbnew/pcb_reference_image.h index d4dab320d8..cb0e8bd42b 100644 --- a/pcbnew/pcb_reference_image.h +++ b/pcbnew/pcb_reference_image.h @@ -72,6 +72,9 @@ public: * * The image is scaled such that the position of the image's * transform origin is unchanged. + * + * If the scale is negaive or the image would overflow the + * the coordinate system, nothing is updated. */ void SetImageScale( double aScale ); @@ -119,7 +122,7 @@ public: */ bool ReadImageFile( wxMemoryBuffer& aBuf ); - void Move( const VECTOR2I& aMoveVector ) override { m_pos += aMoveVector; } + void Move( const VECTOR2I& aMoveVector ) override; void Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) override; void Rotate( const VECTOR2I& aCenter, const EDA_ANGLE& aAngle ) override; @@ -133,8 +136,17 @@ public: void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override; + /** + * Get the position of the image (this is the center of the image). + */ VECTOR2I GetPosition() const override { return m_pos; } - void SetPosition( const VECTOR2I& aPosition ) override { m_pos = aPosition; } + + /** + * Set the position of the image. + * + * If this results in the image overflowing the coordinate system, nothing is updated. + */ + void SetPosition( const VECTOR2I& aPosition ) override; /** * Get the center of scaling, etc, relative to the image center (GetPosition()). diff --git a/qa/tests/libs/kimath/math/test_box2.cpp b/qa/tests/libs/kimath/math/test_box2.cpp index a2193981e0..30034aec2f 100644 --- a/qa/tests/libs/kimath/math/test_box2.cpp +++ b/qa/tests/libs/kimath/math/test_box2.cpp @@ -104,6 +104,14 @@ BOOST_AUTO_TEST_CASE( ByCorners ) BOOST_CHECK( boxByCorners == boxByPosSize ); } +BOOST_AUTO_TEST_CASE( ByCentre ) +{ + const BOX2I boxByCenter = BOX2I::ByCenter( VECTOR2I( 100, 100 ), VECTOR2I( 20, 20 ) ); + const BOX2I boxByPosSize = BOX2I( VECTOR2I( 90, 90 ), VECTOR2I( 20, 20 ) ); + + BOOST_CHECK( boxByCenter == boxByPosSize ); +} + BOOST_AUTO_TEST_CASE( test_closest_point_to, *boost::unit_test::tolerance( 0.000001 ) ) { BOX2D box( VECTOR2D( 1, 2 ), VECTOR2D( 3, 4 ) );